business: fix contact projection and phone sharing
(cherry picked from commit c0a0e5b52240ed415d3b43ba77659821887bf50b)
This commit is contained in:
parent
d84fa6e126
commit
860e581d06
18 changed files with 703 additions and 36 deletions
|
|
@ -30,6 +30,7 @@ func (r *Router) registerContacts(d *tg.ServerDispatcher) {
|
|||
d.OnContactsGetStatuses(r.onContactsGetStatuses)
|
||||
d.OnContactsImportContacts(r.onContactsImportContacts)
|
||||
d.OnContactsAddContact(r.onContactsAddContact)
|
||||
d.OnContactsAcceptContact(r.onContactsAcceptContact)
|
||||
d.OnContactsDeleteContacts(r.onContactsDeleteContacts)
|
||||
d.OnContactsBlock(r.onContactsBlock)
|
||||
d.OnContactsUnblock(r.onContactsUnblock)
|
||||
|
|
@ -263,9 +264,19 @@ func (r *Router) onContactsImportContacts(ctx context.Context, input []tg.InputP
|
|||
}
|
||||
out.RetryContacts = append(out.RetryContacts, res.RetryContacts...)
|
||||
for _, contact := range res.Contacts {
|
||||
if err := r.recordPeerSettings(ctx, userID, domain.Peer{Type: domain.PeerTypeUser, ID: contact.User.ID}, domain.PeerSettings{ShareContact: true}); err != nil {
|
||||
peer := domain.Peer{Type: domain.PeerTypeUser, ID: contact.User.ID}
|
||||
settings, err := r.deps.Contacts.GetPeerSettings(ctx, userID, peer)
|
||||
if err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
if err := r.recordPeerSettings(ctx, userID, peer, settings); err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
if contact.Mutual {
|
||||
if err := r.recordAcceptedContactTargetUpdates(ctx, userID, contact.User.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := r.recordContactsReset(ctx, userID); err != nil {
|
||||
return nil, internalErr()
|
||||
|
|
@ -305,14 +316,85 @@ func (r *Router) onContactsAddContact(ctx context.Context, req *tg.ContactsAddCo
|
|||
if err != nil {
|
||||
return nil, contactErr(err)
|
||||
}
|
||||
updates := r.contactPeerSettingsUpdates(ctx, userID, contact.User, domain.PeerSettings{ShareContact: true}, true)
|
||||
peerUser := contact.User
|
||||
peerUser.Contact = true
|
||||
peerUser.Mutual = contact.Mutual || contact.User.Mutual
|
||||
if contact.Phone != "" {
|
||||
peerUser.Phone = contact.Phone
|
||||
}
|
||||
if contact.FirstName != "" || contact.LastName != "" {
|
||||
peerUser.FirstName = contact.FirstName
|
||||
peerUser.LastName = contact.LastName
|
||||
}
|
||||
peer := domain.Peer{Type: domain.PeerTypeUser, ID: contact.User.ID}
|
||||
settings, err := r.deps.Contacts.GetPeerSettings(ctx, userID, peer)
|
||||
if err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
updates := r.contactPeerSettingsUpdates(ctx, userID, peerUser, settings, true)
|
||||
updates.Updates = append(updates.Updates, &tg.UpdateContactsReset{})
|
||||
if err := r.recordPeerSettings(ctx, userID, domain.Peer{Type: domain.PeerTypeUser, ID: contact.User.ID}, domain.PeerSettings{ShareContact: true}); err != nil {
|
||||
if err := r.recordPeerSettings(ctx, userID, peer, settings); err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
if err := r.recordContactsReset(ctx, userID); err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
if contact.Mutual {
|
||||
if err := r.recordAcceptedContactTargetUpdates(ctx, userID, contact.User.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
r.pushUserUpdatesIfNoReliableDispatch(ctx, userID, updates)
|
||||
return updates, nil
|
||||
}
|
||||
|
||||
func (r *Router) onContactsAcceptContact(ctx context.Context, id tg.InputUserClass) (tg.UpdatesClass, error) {
|
||||
if r.deps.Contacts == nil {
|
||||
return &tg.Updates{Date: int(r.clock.Now().Unix())}, nil
|
||||
}
|
||||
userID, _, err := r.currentUserID(ctx)
|
||||
if err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
target, found, err := r.userFromInput(ctx, userID, id)
|
||||
if err != nil {
|
||||
return nil, contactErr(err)
|
||||
}
|
||||
if !found || target.ID == userID {
|
||||
return nil, contactIDInvalidErr()
|
||||
}
|
||||
contact, err := r.deps.Contacts.AcceptContact(ctx, userID, target.ID)
|
||||
if err != nil {
|
||||
return nil, contactErr(err)
|
||||
}
|
||||
peer := domain.Peer{Type: domain.PeerTypeUser, ID: target.ID}
|
||||
settings, err := r.deps.Contacts.GetPeerSettings(ctx, userID, peer)
|
||||
if err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
peerUser := contact.User
|
||||
peerUser.Contact = true
|
||||
peerUser.Mutual = contact.Mutual || contact.User.Mutual
|
||||
if contact.Phone != "" {
|
||||
peerUser.Phone = contact.Phone
|
||||
}
|
||||
if contact.FirstName != "" || contact.LastName != "" {
|
||||
peerUser.FirstName = contact.FirstName
|
||||
peerUser.LastName = contact.LastName
|
||||
}
|
||||
updates := r.contactPeerSettingsUpdates(ctx, userID, peerUser, settings, true)
|
||||
updates.Updates = append(updates.Updates, &tg.UpdateContactsReset{})
|
||||
if err := r.recordPeerSettings(ctx, userID, peer, settings); err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
if err := r.recordContactsReset(ctx, userID); err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
|
||||
if err := r.recordAcceptedContactTargetUpdates(ctx, userID, target.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.pushUserUpdatesIfNoReliableDispatch(ctx, userID, updates)
|
||||
return updates, nil
|
||||
}
|
||||
|
|
@ -562,6 +644,38 @@ func (r *Router) contactPeerSettingsUpdates(ctx context.Context, userID int64, p
|
|||
}
|
||||
}
|
||||
|
||||
func (r *Router) recordAcceptedContactTargetUpdates(ctx context.Context, userID, targetUserID int64) error {
|
||||
if targetUserID == 0 || targetUserID == userID {
|
||||
return nil
|
||||
}
|
||||
peer := domain.Peer{Type: domain.PeerTypeUser, ID: userID}
|
||||
settings, err := r.deps.Contacts.GetPeerSettings(ctx, targetUserID, peer)
|
||||
if err != nil {
|
||||
return internalErr()
|
||||
}
|
||||
var zeroAuthKeyID [8]byte
|
||||
if err := r.recordPeerSettingsForUser(ctx, zeroAuthKeyID, targetUserID, peer, settings, 0); err != nil {
|
||||
return internalErr()
|
||||
}
|
||||
if err := r.recordContactsResetForUser(ctx, zeroAuthKeyID, targetUserID, 0); err != nil {
|
||||
return internalErr()
|
||||
}
|
||||
peerUser := domain.User{ID: userID}
|
||||
if r.deps.Users != nil {
|
||||
u, found, err := r.deps.Users.ByID(ctx, targetUserID, userID)
|
||||
if err != nil {
|
||||
return internalErr()
|
||||
}
|
||||
if found {
|
||||
peerUser = u
|
||||
}
|
||||
}
|
||||
updates := r.contactPeerSettingsUpdates(ctx, targetUserID, peerUser, settings, true)
|
||||
updates.Updates = append(updates.Updates, &tg.UpdateContactsReset{})
|
||||
r.pushUserUpdatesIfNoReliableDispatch(ctx, targetUserID, updates)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Router) pushContactsReset(ctx context.Context, userID int64) {
|
||||
r.pushUserUpdatesIfNoReliableDispatch(ctx, userID, &tg.Updates{
|
||||
Updates: []tg.UpdateClass{&tg.UpdateContactsReset{}},
|
||||
|
|
@ -571,22 +685,30 @@ func (r *Router) pushContactsReset(ctx context.Context, userID int64) {
|
|||
}
|
||||
|
||||
func (r *Router) recordContactsReset(ctx context.Context, userID int64) error {
|
||||
authKeyID, _ := AuthKeyIDFrom(ctx)
|
||||
sessionID, _ := SessionIDFrom(ctx)
|
||||
return r.recordContactsResetForUser(ctx, authKeyID, userID, sessionID)
|
||||
}
|
||||
|
||||
func (r *Router) recordContactsResetForUser(ctx context.Context, authKeyID [8]byte, userID int64, excludeSessionID int64) error {
|
||||
if r.deps.Updates == nil || userID == 0 {
|
||||
return nil
|
||||
}
|
||||
authKeyID, _ := AuthKeyIDFrom(ctx)
|
||||
sessionID, _ := SessionIDFrom(ctx)
|
||||
_, _, err := r.deps.Updates.RecordContactsReset(ctx, authKeyID, userID, sessionID)
|
||||
_, _, err := r.deps.Updates.RecordContactsReset(ctx, authKeyID, userID, excludeSessionID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Router) recordPeerSettings(ctx context.Context, userID int64, peer domain.Peer, settings domain.PeerSettings) error {
|
||||
authKeyID, _ := AuthKeyIDFrom(ctx)
|
||||
sessionID, _ := SessionIDFrom(ctx)
|
||||
return r.recordPeerSettingsForUser(ctx, authKeyID, userID, peer, settings, sessionID)
|
||||
}
|
||||
|
||||
func (r *Router) recordPeerSettingsForUser(ctx context.Context, authKeyID [8]byte, userID int64, peer domain.Peer, settings domain.PeerSettings, excludeSessionID int64) error {
|
||||
if r.deps.Updates == nil || userID == 0 {
|
||||
return nil
|
||||
}
|
||||
authKeyID, _ := AuthKeyIDFrom(ctx)
|
||||
sessionID, _ := SessionIDFrom(ctx)
|
||||
_, _, err := r.deps.Updates.RecordPeerSettings(ctx, authKeyID, userID, peer, settings, sessionID)
|
||||
_, _, err := r.deps.Updates.RecordPeerSettings(ctx, authKeyID, userID, peer, settings, excludeSessionID)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -628,6 +750,8 @@ func contactErr(err error) error {
|
|||
return contactNameEmptyErr()
|
||||
case errors.Is(err, contacts.ErrContactIDInvalid):
|
||||
return contactIDInvalidErr()
|
||||
case errors.Is(err, contacts.ErrContactReqMissing):
|
||||
return contactReqMissingErr()
|
||||
default:
|
||||
return internalErr()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -231,7 +231,7 @@ func tgMessagesDialogs(viewerUserID int64, list domain.DialogList) tg.MessagesDi
|
|||
messages = append(messages, item)
|
||||
}
|
||||
}
|
||||
users := tgUsers(list.Users)
|
||||
users := tgUsersForViewer(viewerUserID, list.Users)
|
||||
chats := tgChannelsForDialogs(viewerUserID, list.Channels, list.Dialogs)
|
||||
if list.Count > len(dialogs) {
|
||||
return &tg.MessagesDialogsSlice{
|
||||
|
|
@ -250,14 +250,14 @@ func tgMessagesDialogs(viewerUserID int64, list domain.DialogList) tg.MessagesDi
|
|||
}
|
||||
}
|
||||
|
||||
func tgMessagesMessages(list domain.MessageList) tg.MessagesMessagesClass {
|
||||
func tgMessagesMessages(viewerUserID int64, list domain.MessageList) tg.MessagesMessagesClass {
|
||||
messages := make([]tg.MessageClass, 0, len(list.Messages))
|
||||
for _, msg := range list.Messages {
|
||||
if item := tgMessage(msg); item != nil {
|
||||
messages = append(messages, item)
|
||||
}
|
||||
}
|
||||
users := tgUsers(list.Users)
|
||||
users := tgUsersForViewer(viewerUserID, list.Users)
|
||||
if list.Count > len(messages) {
|
||||
return &tg.MessagesMessagesSlice{
|
||||
Count: list.Count,
|
||||
|
|
@ -279,6 +279,18 @@ func tgUsers(users []domain.User) []tg.UserClass {
|
|||
return out
|
||||
}
|
||||
|
||||
func tgUsersForViewer(viewerUserID int64, users []domain.User) []tg.UserClass {
|
||||
out := make([]tg.UserClass, 0, len(users))
|
||||
for _, u := range users {
|
||||
if viewerUserID != 0 && u.ID == viewerUserID {
|
||||
out = append(out, tgSelfUser(u))
|
||||
continue
|
||||
}
|
||||
out = append(out, tgUser(u))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func tgPeerDialogs(viewerUserID int64, list domain.DialogList, st domain.UpdateState) *tg.MessagesPeerDialogs {
|
||||
out := &tg.MessagesPeerDialogs{
|
||||
Dialogs: make([]tg.DialogClass, 0, len(list.Dialogs)),
|
||||
|
|
@ -303,7 +315,11 @@ func tgPeerDialogs(viewerUserID int64, list domain.DialogList, st domain.UpdateS
|
|||
}
|
||||
}
|
||||
for _, u := range list.Users {
|
||||
out.Users = append(out.Users, tgUser(u))
|
||||
if viewerUserID != 0 && u.ID == viewerUserID {
|
||||
out.Users = append(out.Users, tgSelfUser(u))
|
||||
} else {
|
||||
out.Users = append(out.Users, tgUser(u))
|
||||
}
|
||||
}
|
||||
out.Chats = append(out.Chats, tgChannelsForDialogs(viewerUserID, list.Channels, list.Dialogs)...)
|
||||
return out
|
||||
|
|
|
|||
31
internal/rpc/convert_test.go
Normal file
31
internal/rpc/convert_test.go
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gotd/td/tg"
|
||||
|
||||
"telesrv/internal/domain"
|
||||
)
|
||||
|
||||
func TestTGMessagesMessagesMarksViewerSelfAndKeepsProjectedPhone(t *testing.T) {
|
||||
const viewerID int64 = 1001
|
||||
res := tgMessagesMessages(viewerID, domain.MessageList{
|
||||
Users: []domain.User{
|
||||
{ID: viewerID, AccessHash: 11, Phone: "15550000001", FirstName: "Owner"},
|
||||
{ID: 1002, AccessHash: 22, Phone: "", FirstName: "Peer"},
|
||||
},
|
||||
})
|
||||
full, ok := res.(*tg.MessagesMessages)
|
||||
if !ok {
|
||||
t.Fatalf("result = %T, want *tg.MessagesMessages", res)
|
||||
}
|
||||
self, ok := full.Users[0].(*tg.User)
|
||||
if !ok || !self.Self || self.Phone != "15550000001" {
|
||||
t.Fatalf("self user = %+v ok=%v, want self with phone", full.Users[0], ok)
|
||||
}
|
||||
peer, ok := full.Users[1].(*tg.User)
|
||||
if !ok || peer.Self || peer.Phone != "" || peer.FirstName != "Peer" {
|
||||
t.Fatalf("peer user = %+v ok=%v, want projected non-self without phone", full.Users[1], ok)
|
||||
}
|
||||
}
|
||||
|
|
@ -133,6 +133,7 @@ type ContactsService interface {
|
|||
GetContacts(ctx context.Context, userID int64, hash int64) (domain.ContactList, bool, error)
|
||||
ContactIDs(ctx context.Context, userID int64, hash int64) ([]int, bool, error)
|
||||
AddContact(ctx context.Context, userID int64, input domain.ContactInput) (domain.Contact, error)
|
||||
AcceptContact(ctx context.Context, userID, contactUserID int64) (domain.Contact, error)
|
||||
ImportContacts(ctx context.Context, userID int64, inputs []domain.ContactInput) (domain.ImportContactsResult, error)
|
||||
Search(ctx context.Context, userID int64, query string, limit int) (domain.UserSearchResult, error)
|
||||
DeleteContacts(ctx context.Context, userID int64, contactUserIDs []int64) (int, error)
|
||||
|
|
|
|||
|
|
@ -126,6 +126,8 @@ func contactIDInvalidErr() error { return tgerr.New(400, "CONTACT_ID_INVALID") }
|
|||
|
||||
func contactNameEmptyErr() error { return tgerr.New(400, "CONTACT_NAME_EMPTY") }
|
||||
|
||||
func contactReqMissingErr() error { return tgerr.New(400, "CONTACT_REQ_MISSING") }
|
||||
|
||||
// messageEmptyErr 表示发送空文本。
|
||||
func messageEmptyErr() error { return tgerr.New(400, "MESSAGE_EMPTY") }
|
||||
|
||||
|
|
|
|||
|
|
@ -352,7 +352,7 @@ func (r *Router) registerMessages(d *tg.ServerDispatcher) {
|
|||
if filter.Hash != 0 && list.Hash == filter.Hash {
|
||||
return &tg.MessagesMessagesNotModified{Count: list.Count}, nil
|
||||
}
|
||||
return tgMessagesMessages(r.withMessageListPresence(list)), nil
|
||||
return tgMessagesMessages(userID, r.withMessageListPresence(list)), nil
|
||||
})
|
||||
d.OnMessagesReadHistory(func(ctx context.Context, req *tg.MessagesReadHistoryRequest) (*tg.MessagesAffectedMessages, error) {
|
||||
id, _ := AuthKeyIDFrom(ctx)
|
||||
|
|
@ -463,7 +463,7 @@ func (r *Router) registerMessages(d *tg.ServerDispatcher) {
|
|||
if filter.Hash != 0 && list.Hash == filter.Hash {
|
||||
return &tg.MessagesMessagesNotModified{Count: list.Count}, nil
|
||||
}
|
||||
return tgMessagesMessages(r.withMessageListPresence(list)), nil
|
||||
return tgMessagesMessages(userID, r.withMessageListPresence(list)), nil
|
||||
})
|
||||
d.OnMessagesSearchGlobal(r.onMessagesSearchGlobal)
|
||||
d.OnMessagesGetSearchResultsCalendar(r.onMessagesGetSearchResultsCalendar)
|
||||
|
|
@ -3792,7 +3792,7 @@ func (r *Router) onMessagesSearchGlobal(ctx context.Context, req *tg.MessagesSea
|
|||
}
|
||||
}
|
||||
if req.UsersOnly || r.deps.Channels == nil {
|
||||
return tgMessagesMessages(r.withMessageListPresence(limitMessageList(private, limit))), nil
|
||||
return tgMessagesMessages(userID, r.withMessageListPresence(limitMessageList(private, limit))), nil
|
||||
}
|
||||
channelHistory, err := r.deps.Channels.SearchJoinedMessages(ctx, userID, domain.ChannelGlobalSearchRequest{
|
||||
Query: query,
|
||||
|
|
|
|||
|
|
@ -8030,6 +8030,73 @@ func TestContactsStatusesAndContactsUsePresence(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestContactsAcceptContactReturnsSettingsAndReset(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
userStore := memory.NewUserStore()
|
||||
contactsStore := memory.NewContactStore()
|
||||
alice, err := userStore.Create(ctx, domain.User{AccessHash: 11, Phone: "1001", FirstName: "Alice", LastName: "A"})
|
||||
if err != nil {
|
||||
t.Fatalf("create alice: %v", err)
|
||||
}
|
||||
bob, err := userStore.Create(ctx, domain.User{AccessHash: 22, Phone: "1002", FirstName: "Bob", LastName: "B"})
|
||||
if err != nil {
|
||||
t.Fatalf("create bob: %v", err)
|
||||
}
|
||||
contactsSvc := appcontacts.NewService(contactsStore, userStore)
|
||||
if _, err := contactsSvc.AddContact(ctx, alice.ID, domain.ContactInput{
|
||||
ContactUserID: bob.ID,
|
||||
Phone: bob.Phone,
|
||||
FirstName: "Bobby",
|
||||
LastName: "Remark",
|
||||
}); err != nil {
|
||||
t.Fatalf("alice add bob: %v", err)
|
||||
}
|
||||
updatesSvc := &captureUpdates{state: domain.UpdateState{Pts: 10, Date: 1700000400}}
|
||||
r := New(Config{}, Deps{
|
||||
Contacts: contactsSvc,
|
||||
Users: appusers.NewService(userStore, appusers.WithContactStore(contactsStore)),
|
||||
Updates: updatesSvc,
|
||||
}, zaptest.NewLogger(t), fixedClock{now: time.Unix(1700000400, 0)})
|
||||
|
||||
out, err := r.onContactsAcceptContact(WithUserID(ctx, alice.ID), &tg.InputUser{UserID: bob.ID, AccessHash: bob.AccessHash})
|
||||
if err != nil {
|
||||
t.Fatalf("contacts.acceptContact: %v", err)
|
||||
}
|
||||
got, ok := out.(*tg.Updates)
|
||||
if !ok {
|
||||
t.Fatalf("updates = %T, want *tg.Updates", out)
|
||||
}
|
||||
if len(got.Updates) != 2 {
|
||||
t.Fatalf("updates = %+v, want peer settings + contacts reset", got.Updates)
|
||||
}
|
||||
settings, ok := got.Updates[0].(*tg.UpdatePeerSettings)
|
||||
if !ok {
|
||||
t.Fatalf("update[0] = %T, want UpdatePeerSettings", got.Updates[0])
|
||||
}
|
||||
if settings.Settings.ShareContact || settings.Settings.AddContact {
|
||||
t.Fatalf("peer settings = %+v, want share/add false", settings.Settings)
|
||||
}
|
||||
if _, ok := got.Updates[1].(*tg.UpdateContactsReset); !ok {
|
||||
t.Fatalf("update[1] = %T, want UpdateContactsReset", got.Updates[1])
|
||||
}
|
||||
if len(updatesSvc.events) != 4 {
|
||||
t.Fatalf("recorded events = %+v, want current peer/reset and target peer/reset", updatesSvc.events)
|
||||
}
|
||||
if updatesSvc.events[0].UserID != alice.ID || updatesSvc.events[0].Settings.ShareContact {
|
||||
t.Fatalf("current peer settings event = %+v, want alice share=false", updatesSvc.events[0])
|
||||
}
|
||||
if updatesSvc.events[2].UserID != bob.ID || updatesSvc.events[2].Settings.ShareContact {
|
||||
t.Fatalf("target peer settings event = %+v, want bob share=false", updatesSvc.events[2])
|
||||
}
|
||||
reverse, found, err := contactsStore.Get(ctx, bob.ID, alice.ID)
|
||||
if err != nil || !found {
|
||||
t.Fatalf("bob contact alice found=%v err=%v", found, err)
|
||||
}
|
||||
if reverse.Phone != alice.Phone || !reverse.Mutual {
|
||||
t.Fatalf("bob contact alice = %+v, want shared phone and mutual", reverse)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContactsStatusesUsesOnlineSessionFallback(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
alice := domain.User{ID: 1000000001, AccessHash: 11, FirstName: "Alice"}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue