fix: sync contact note projection

This commit is contained in:
A 2026-07-21 15:48:33 +08:00
parent d69a34a4a8
commit 40743dfb09
8 changed files with 395 additions and 60 deletions

View file

@ -451,6 +451,9 @@ func cloneCachedUser(in domain.User) domain.User {
if in.PhotoStripped != nil {
in.PhotoStripped = append([]byte(nil), in.PhotoStripped...)
}
if in.ContactNoteEntities != nil {
in.ContactNoteEntities = append([]domain.MessageEntity(nil), in.ContactNoteEntities...)
}
return in
}

View file

@ -116,7 +116,13 @@ func (s *countingContactStore) SetPersonalPhoto(ctx context.Context, userID, con
func TestCachedContactStoreCachesProjectionReads(t *testing.T) {
ctx := context.Background()
base := memory.NewContactStore()
if _, err := base.Upsert(ctx, 1, domain.ContactInput{ContactUserID: 2, FirstName: "Alice", Phone: "111"}); err != nil {
if _, err := base.Upsert(ctx, 1, domain.ContactInput{
ContactUserID: 2,
FirstName: "Alice",
Phone: "111",
Note: "private note",
NoteEntities: []domain.MessageEntity{{Type: domain.MessageEntityBold, Offset: 0, Length: 7}},
}); err != nil {
t.Fatalf("upsert contact: %v", err)
}
counting := &countingContactStore{ContactStore: base}
@ -126,15 +132,16 @@ func TestCachedContactStoreCachesProjectionReads(t *testing.T) {
if err != nil {
t.Fatalf("get many first: %v", err)
}
if first[2].FirstName != "Alice" {
t.Fatalf("first contact = %+v, want Alice", first[2])
if first[2].FirstName != "Alice" || first[2].Note != "private note" || len(first[2].NoteEntities) != 1 {
t.Fatalf("first contact = %+v, want Alice with private note", first[2])
}
first[2].NoteEntities[0].Length = 99
second, err := cached.GetMany(ctx, 1, []int64{2, 3})
if err != nil {
t.Fatalf("get many second: %v", err)
}
if second[2].FirstName != "Alice" {
t.Fatalf("second contact = %+v, want Alice", second[2])
if second[2].FirstName != "Alice" || second[2].Note != "private note" || len(second[2].NoteEntities) != 1 || second[2].NoteEntities[0].Length != 7 {
t.Fatalf("second contact = %+v, want isolated cached Alice note", second[2])
}
if counting.listCalls != 1 {
t.Fatalf("ListByUser calls = %d, want 1 account snapshot load", counting.listCalls)

View file

@ -260,6 +260,9 @@ func cloneUsers(users []domain.User) []domain.User {
}
out := make([]domain.User, len(users))
copy(out, users)
for i := range out {
out[i].ContactNoteEntities = append([]domain.MessageEntity(nil), out[i].ContactNoteEntities...)
}
return out
}
@ -499,30 +502,7 @@ func projectOne(ctx context.Context, contacts store.ContactStore, viewerUserID i
if err != nil {
return domain.User{}, err
}
if !found {
user.Phone = ""
user.Contact = false
user.Mutual = false
user.CloseFriend = false
return user, nil
}
projected := user
projected.Contact = true
projected.Mutual = contact.Mutual || contact.User.Mutual
projected.CloseFriend = contact.CloseFriend || contact.User.CloseFriend
if contact.User.Phone != "" {
projected.Phone = contact.User.Phone
} else {
projected.Phone = contact.Phone
}
if contact.User.FirstName != "" || contact.User.LastName != "" {
projected.FirstName = contact.User.FirstName
projected.LastName = contact.User.LastName
} else if contact.FirstName != "" || contact.LastName != "" {
projected.FirstName = contact.FirstName
projected.LastName = contact.LastName
}
return projected, nil
return applyContactProjection(user, contact, found), nil
}
func uniqueUserIDs(users []domain.User) []int64 {
@ -575,11 +555,15 @@ func applyContactProjection(user domain.User, contact domain.Contact, found bool
user.Contact = false
user.Mutual = false
user.CloseFriend = false
user.ContactNote = ""
user.ContactNoteEntities = nil
return user
}
user.Contact = true
user.Mutual = contact.Mutual || contact.User.Mutual
user.CloseFriend = contact.CloseFriend || contact.User.CloseFriend
user.ContactNote = contact.Note
user.ContactNoteEntities = append([]domain.MessageEntity(nil), contact.NoteEntities...)
if contact.User.Phone != "" {
user.Phone = contact.User.Phone
} else {

View file

@ -21,6 +21,8 @@ func TestProjectorCombinesProfilePhotosAndViewerContacts(t *testing.T) {
Phone: "1111",
FirstName: "Alice",
LastName: "Contact",
Note: "private note",
NoteEntities: []domain.MessageEntity{{Type: domain.MessageEntityBold, Offset: 0, Length: 7}},
}); err != nil {
t.Fatalf("upsert contact: %v", err)
}
@ -47,12 +49,15 @@ func TestProjectorCombinesProfilePhotosAndViewerContacts(t *testing.T) {
if friend.FirstName != "Alice" || friend.LastName != "Contact" || friend.Phone != "1111" || !friend.Contact {
t.Fatalf("friend projection = %+v, want contact name/phone", friend)
}
if friend.ContactNote != "private note" || len(friend.ContactNoteEntities) != 1 || friend.ContactNoteEntities[0].Type != domain.MessageEntityBold {
t.Fatalf("friend contact note = %q %+v, want owner-scoped note", friend.ContactNote, friend.ContactNoteEntities)
}
if friend.PhotoID != 9001 || friend.PhotoDCID != 2 || string(friend.PhotoStripped) != string([]byte{1, 2}) {
t.Fatalf("friend photo = id %d dc %d stripped %v, want 9001/2/[1 2]", friend.PhotoID, friend.PhotoDCID, friend.PhotoStripped)
}
stranger := projectionUser(t, users, strangerID)
if stranger.Phone != "" || stranger.Contact {
t.Fatalf("stranger projection = %+v, want hidden phone and non-contact", stranger)
if stranger.Phone != "" || stranger.Contact || stranger.ContactNote != "" || len(stranger.ContactNoteEntities) != 0 {
t.Fatalf("stranger projection = %+v, want hidden phone and no contact note", stranger)
}
if stranger.PhotoID != 9002 || stranger.PhotoDCID != 3 {
t.Fatalf("stranger photo = id %d dc %d, want 9002/3", stranger.PhotoID, stranger.PhotoDCID)