100 lines
3.5 KiB
Go
100 lines
3.5 KiB
Go
package rpc
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/iamxvbaba/td/tg"
|
|
"go.uber.org/zap"
|
|
|
|
"telesrv/internal/domain"
|
|
)
|
|
|
|
// tgSelfUserWithReadModels is the single-object response-boundary projection
|
|
// used by authorization results and self-profile updates. tgSelfUser itself is
|
|
// intentionally a pure domain -> TL conversion because many list paths call it
|
|
// in loops; the read-model pass belongs here, where it stays one query per
|
|
// response.
|
|
func (r *Router) tgSelfUserWithReadModels(ctx context.Context, u domain.User) *tg.User {
|
|
self := r.tgSelfUser(u)
|
|
users := []tg.UserClass{self}
|
|
r.applyPeerReadModels(ctx, u.ID, users, nil)
|
|
return self
|
|
}
|
|
|
|
// pushUpdatesReadySelfProfile repairs the current session's cached self user
|
|
// when it first becomes eligible for proactive updates. Telegram's updateUser
|
|
// contract requires the complete User to be carried by the outer updates.users
|
|
// vector; TDLib applies that vector before invalidating userFull for updateUser.
|
|
//
|
|
// This is an ephemeral cache-convergence update: it allocates no PTS, writes no
|
|
// durable update event and targets only the physical session that just became
|
|
// ready. A username-registry read failure suppresses the refresh instead of
|
|
// replacing a possibly richer client cache with the legacy scalar-only shape.
|
|
func (r *Router) pushUpdatesReadySelfProfile(ctx context.Context, userID int64) {
|
|
updates, err := r.updatesReadySelfProfile(ctx, userID)
|
|
if err != nil {
|
|
r.log.Warn("build updates-ready self profile",
|
|
zap.Int64("user_id", userID),
|
|
zap.Error(err))
|
|
return
|
|
}
|
|
if updates == nil {
|
|
return
|
|
}
|
|
r.pushCurrentSessionMessage(ctx, "push updates-ready self profile", updates)
|
|
}
|
|
|
|
func (r *Router) updatesReadySelfProfile(ctx context.Context, userID int64) (*tg.Updates, error) {
|
|
if userID == 0 || r.deps.Users == nil {
|
|
return nil, nil
|
|
}
|
|
u, err := r.deps.Users.Self(ctx, userID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("load self user: %w", err)
|
|
}
|
|
if u.ID != userID || u.Deleted {
|
|
return nil, fmt.Errorf("invalid self user: requested %d, got %d deleted=%v", userID, u.ID, u.Deleted)
|
|
}
|
|
|
|
self := r.tgSelfUser(u)
|
|
users := []tg.UserClass{self}
|
|
if r.deps.Usernames != nil {
|
|
peer := domain.Peer{Type: domain.PeerTypeUser, ID: userID}
|
|
list, err := r.deps.Usernames.PeerUsernames(ctx, peer)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("load self usernames: %w", err)
|
|
}
|
|
if len(list) != 0 {
|
|
applyUsernamesFromRegistry(users, nil, map[domain.Peer][]domain.Username{peer: list})
|
|
}
|
|
}
|
|
// These read models are optional projections. Their existing response-boundary
|
|
// contract degrades independently; the username registry above is handled
|
|
// strictly because losing that vector is the cache corruption fixed here.
|
|
r.applyStoryMaxIDsToPeerObjects(ctx, userID, users, nil)
|
|
r.applyBotVerificationIconsToPeerObjects(ctx, users, nil)
|
|
|
|
usernames := tgUsernames(u.Username)
|
|
if vector, ok := self.GetUsernames(); ok && len(vector) != 0 {
|
|
usernames = vector
|
|
}
|
|
return &tg.Updates{
|
|
Updates: []tg.UpdateClass{
|
|
// updateUserName is the authoritative basic-name/username cache write.
|
|
// TDLib handles it by calling on_update_user_usernames directly.
|
|
&tg.UpdateUserName{
|
|
UserID: userID,
|
|
FirstName: u.FirstName,
|
|
LastName: u.LastName,
|
|
Usernames: usernames,
|
|
},
|
|
// updateUser additionally invalidates userFull while the complete basic
|
|
// User remains bundled in the outer users vector.
|
|
&tg.UpdateUser{UserID: userID},
|
|
},
|
|
Users: users,
|
|
Date: int(r.clock.Now().Unix()),
|
|
Seq: 0,
|
|
}, nil
|
|
}
|