merged from gramsrv upstream

This commit is contained in:
onysd 2026-09-01 12:06:31 +03:00
parent 79c64ee916
commit 21a0856587
651 changed files with 54774 additions and 4590 deletions

View file

@ -133,10 +133,17 @@ func (s *Service) AddContact(ctx context.Context, userID int64, input domain.Con
if input.FirstName == "" && input.LastName == "" {
return domain.Contact{}, ErrContactNameEmpty
}
// Android 的 contacts.addContact 会提交带 "+" 前缀的号码(TDesktop 传纯数字或空),
// 归一成纯数字。空串表示客户端只按 user id 添加联系人,必须原样保留;
// Android 的 contacts.addContact 会提交带 "+" 前缀的号码(TDesktop 传纯数字或空)。
// 可解析的完整号码写成与账号相同的 E.164 identity;无法解析的本地名片号码只
// 保留展示 digits,绝不能拿它做账号选择。空串表示客户端只按 user id 添加联系人,必须原样保留;
// TL 明确允许省略号码,服务端不得从 target 全局资料反向补出隐私号码。
input.Phone = digitsOnly(input.Phone)
if input.Phone != "" {
if canonical := domain.NormalizePhone(input.Phone); canonical != "" {
input.Phone = canonical
} else {
input.Phone = digitsOnly(input.Phone)
}
}
if s.users != nil {
_, found, err := s.users.ByID(ctx, input.ContactUserID)
if err != nil {
@ -222,7 +229,7 @@ func (s *Service) ImportContacts(ctx context.Context, userID int64, inputs []dom
phones := make([]string, 0, len(inputs))
seenPhones := make(map[string]struct{}, len(inputs))
for _, input := range inputs {
phone := normalizePhone(input.Phone)
phone := domain.NormalizePhone(input.Phone)
if phone == "" {
continue
}
@ -354,7 +361,7 @@ func (s *Service) Search(ctx context.Context, userID int64, query string, limit
}
phoneQuery := ""
if isPhoneSearchQuery(query) {
phoneQuery = normalizePhone(query)
phoneQuery = normalizePhoneQuery(query)
}
res, err := s.users.Search(ctx, userID, query, phoneQuery, limit)
if err != nil {
@ -686,7 +693,7 @@ func digitsOnly(phone string) string {
return b.String()
}
func normalizePhone(phone string) string {
func normalizePhoneQuery(phone string) string {
if !utf8.ValidString(phone) {
return ""
}

View file

@ -169,6 +169,36 @@ func TestImportContactsBatchesPhonesAndDedupesUpserts(t *testing.T) {
}
}
func TestImportContactsResolvesNationalTrunkVariantToCanonicalUser(t *testing.T) {
ctx := context.Background()
users := memory.NewUserStore()
contactsStore := memory.NewContactStore()
owner, err := users.Create(ctx, domain.User{Phone: "15551234567", FirstName: "Owner"})
if err != nil {
t.Fatalf("create owner: %v", err)
}
target, err := users.Create(ctx, domain.User{Phone: "989981679461", FirstName: "Iran"})
if err != nil {
t.Fatalf("create target: %v", err)
}
svc := NewService(contactsStore, users)
res, err := svc.ImportContacts(ctx, owner.ID, []domain.ContactInput{{
ClientID: 98,
Phone: "+98 0998 167 9461",
FirstName: "Saved",
}})
if err != nil {
t.Fatalf("ImportContacts: %v", err)
}
if len(res.Imported) != 1 || res.Imported[0].UserID != target.ID {
t.Fatalf("imported = %+v, want target %d", res.Imported, target.ID)
}
if len(res.Contacts) != 1 || res.Contacts[0].Phone != "989981679461" {
t.Fatalf("contacts = %+v, want one canonical contact", res.Contacts)
}
}
func TestAddContactWithoutPhoneDoesNotBackfillTargetPhone(t *testing.T) {
ctx := context.Background()
users := memory.NewUserStore()