fix: sync non-PTS moderation updates
This commit is contained in:
parent
cc76cd3679
commit
70e57b4d07
15 changed files with 331 additions and 378 deletions
|
|
@ -33,6 +33,10 @@ type usernameAvailabilityStore interface {
|
|||
CheckUsername(ctx context.Context, userID int64, username string) (bool, error)
|
||||
}
|
||||
|
||||
type moderationFlagAudienceStore interface {
|
||||
ModerationFlagAudience(ctx context.Context, userID int64, limit int) ([]int64, error)
|
||||
}
|
||||
|
||||
// Option 调整用户服务可选依赖。
|
||||
type Option func(*Service)
|
||||
|
||||
|
|
@ -176,27 +180,6 @@ func (s *Service) ByIDs(ctx context.Context, currentUserID int64, userIDs []int6
|
|||
return s.projectUsers(ctx, currentUserID, users)
|
||||
}
|
||||
|
||||
// ByIDsAuthoritative reloads an explicit profile-refresh target from the
|
||||
// durable store, then replaces the shared base cache before applying the
|
||||
// viewer projection. It is intentionally reserved for durable update
|
||||
// delivery; ordinary reads continue to use ByIDs.
|
||||
func (s *Service) ByIDsAuthoritative(ctx context.Context, currentUserID int64, userIDs []int64) ([]domain.User, error) {
|
||||
if currentUserID == 0 {
|
||||
return nil, ErrNotAuthorized
|
||||
}
|
||||
ids := uniqueUserIDs(userIDs, maxBatchUsers)
|
||||
if len(ids) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
s.dropCachedUsers(ctx, ids...)
|
||||
users, err := s.users.ByIDs(ctx, ids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.putCachedUsers(ctx, users...)
|
||||
return s.projectUsers(ctx, currentUserID, users)
|
||||
}
|
||||
|
||||
// ByIDsForViewers 跨多个 viewer 批量投影同一组 user(fan-out 模板化):base user 只加载一次,
|
||||
// 隐私/改名/头像投影经 userprojection.ForViewers 压成 O(owner) 查询。返回 map[viewerID][]User,
|
||||
// 每个切片与 ByIDs(viewer, ids) 字节等价——**唯一例外是 personal photo overlay**(ForViewers v1
|
||||
|
|
@ -421,6 +404,23 @@ func (s *Service) SetScamFake(ctx context.Context, userID int64, scam, fake bool
|
|||
return updated, nil
|
||||
}
|
||||
|
||||
// ModerationFlagAudience returns the bounded set of existing viewers that may
|
||||
// need an immediate updateUser after SCAM/FAKE changes. This is an online
|
||||
// accelerator only: it does not allocate PTS or create durable update events.
|
||||
func (s *Service) ModerationFlagAudience(ctx context.Context, userID int64, limit int) ([]int64, error) {
|
||||
if userID == 0 {
|
||||
return nil, ErrNotAuthorized
|
||||
}
|
||||
if limit <= 0 || limit > 4096 {
|
||||
limit = 4096
|
||||
}
|
||||
audience, ok := s.users.(moderationFlagAudienceStore)
|
||||
if !ok {
|
||||
return []int64{userID}, nil
|
||||
}
|
||||
return audience.ModerationFlagAudience(ctx, userID, limit)
|
||||
}
|
||||
|
||||
// SetSupport 设置/取消用户的 support 标记(官方客服账号)。写后刷新基础缓存。
|
||||
func (s *Service) SetSupport(ctx context.Context, userID int64, support bool) (domain.User, error) {
|
||||
if userID == 0 {
|
||||
|
|
|
|||
|
|
@ -259,49 +259,6 @@ func TestServiceUsesBaseCacheWithoutCachingViewerOverlay(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestServiceAuthoritativeUserReloadReplacesStaleBaseCache(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
base := memory.NewUserStore()
|
||||
viewer, err := base.Create(ctx, domain.User{AccessHash: 1, Phone: "15550000011", FirstName: "Viewer"})
|
||||
if err != nil {
|
||||
t.Fatalf("create viewer: %v", err)
|
||||
}
|
||||
target, err := base.Create(ctx, domain.User{AccessHash: 2, Phone: "15550000012", FirstName: "Target"})
|
||||
if err != nil {
|
||||
t.Fatalf("create target: %v", err)
|
||||
}
|
||||
store := &countingUserStore{UserStore: base}
|
||||
cache := newMemoryBaseUserCache()
|
||||
svc := NewService(store, WithBaseUserCache(cache))
|
||||
|
||||
primed, err := svc.ByIDs(ctx, viewer.ID, []int64{target.ID})
|
||||
if err != nil || len(primed) != 1 || primed[0].Scam {
|
||||
t.Fatalf("prime ByIDs users=%+v err=%v", primed, err)
|
||||
}
|
||||
if _, err := base.SetScamFake(ctx, target.ID, true, false); err != nil {
|
||||
t.Fatalf("commit moderation flags behind cache: %v", err)
|
||||
}
|
||||
stale, err := svc.ByIDs(ctx, viewer.ID, []int64{target.ID})
|
||||
if err != nil || len(stale) != 1 || stale[0].Scam {
|
||||
t.Fatalf("ordinary cached ByIDs users=%+v err=%v, want stale scam=false", stale, err)
|
||||
}
|
||||
|
||||
fresh, err := svc.ByIDsAuthoritative(ctx, viewer.ID, []int64{target.ID})
|
||||
if err != nil || len(fresh) != 1 || !fresh[0].Scam || fresh[0].Fake {
|
||||
t.Fatalf("authoritative ByIDs users=%+v err=%v, want scam=true fake=false", fresh, err)
|
||||
}
|
||||
if store.byIDsCalls != 2 {
|
||||
t.Fatalf("store ByIDs calls=%d, want prime + authoritative reload", store.byIDsCalls)
|
||||
}
|
||||
cached, err := svc.ByIDs(ctx, viewer.ID, []int64{target.ID})
|
||||
if err != nil || len(cached) != 1 || !cached[0].Scam || cached[0].Fake {
|
||||
t.Fatalf("replaced cache ByIDs users=%+v err=%v, want scam=true fake=false", cached, err)
|
||||
}
|
||||
if store.byIDsCalls != 2 {
|
||||
t.Fatalf("store ByIDs calls after cached read=%d, want unchanged", store.byIDsCalls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceRefreshesBaseCacheAfterProfileUpdate(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
base := memory.NewUserStore()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue