fixes for loading issues

This commit is contained in:
onysd 2026-07-31 22:33:08 +03:00
parent 7ddbcc9022
commit d7fd75e487
4 changed files with 60 additions and 48 deletions

View file

@ -424,6 +424,11 @@ type PrivacyService interface {
SetRules(ctx context.Context, ownerUserID int64, key domain.PrivacyKey, rules []domain.PrivacyRule) (domain.PrivacyRules, error) SetRules(ctx context.Context, ownerUserID int64, key domain.PrivacyKey, rules []domain.PrivacyRule) (domain.PrivacyRules, error)
AddAllowUser(ctx context.Context, ownerUserID int64, key domain.PrivacyKey, targetUserID int64) (domain.PrivacyRules, bool, error) AddAllowUser(ctx context.Context, ownerUserID int64, key domain.PrivacyKey, targetUserID int64) (domain.PrivacyRules, bool, error)
CanSee(ctx context.Context, ownerUserID, viewerUserID int64, key domain.PrivacyKey) (bool, error) CanSee(ctx context.Context, ownerUserID, viewerUserID int64, key domain.PrivacyKey) (bool, error)
// CanSeeBatch evaluates multiple keys for one (owner, viewer) pair with a
// single ListPrivacyRules + GetReverseContacts round trip, equivalent to
// one CanSee call per key. Used where a single projection (e.g.
// getFullUser) otherwise needs several keys checked at once.
CanSeeBatch(ctx context.Context, ownerUserIDs []int64, viewerUserID int64, keys []domain.PrivacyKey) (map[int64]map[domain.PrivacyKey]bool, error)
} }
// HelpService 抽象启动配置与国家区号目录。 // HelpService 抽象启动配置与国家区号目录。

View file

@ -102,6 +102,18 @@ func (p stubPrivacy) CanSee(_ context.Context, _, _ int64, key domain.PrivacyKey
return !p.deny[key], nil return !p.deny[key], nil
} }
func (p stubPrivacy) CanSeeBatch(_ context.Context, ownerUserIDs []int64, _ int64, keys []domain.PrivacyKey) (map[int64]map[domain.PrivacyKey]bool, error) {
out := make(map[int64]map[domain.PrivacyKey]bool, len(ownerUserIDs))
for _, owner := range ownerUserIDs {
m := make(map[domain.PrivacyKey]bool, len(keys))
for _, k := range keys {
m[k] = !p.deny[k]
}
out[owner] = m
}
return out, nil
}
type phoneFixture struct { type phoneFixture struct {
t *testing.T t *testing.T
ctx context.Context ctx context.Context

View file

@ -237,15 +237,40 @@ func applyContactNoteToUserFull(user domain.User, full *tg.UserFull) bool {
} }
func (r *Router) buildUserFullProjection(ctx context.Context, currentUserID int64, u domain.User) (tg.UserFull, error) { func (r *Router) buildUserFullProjection(ctx context.Context, currentUserID int64, u domain.User) (tg.UserFull, error) {
about := u.About // One CanSeeBatch call replaces what used to be up to six sequential
if r.deps.Privacy != nil && u.ID != currentUserID { // CanSee calls (about/phone_call/phone_p2p/profile_photo/saved_music/
allowed, err := r.deps.Privacy.CanSee(ctx, u.ID, currentUserID, domain.PrivacyKeyAbout) // birthday) below and in fillUserFullPhotos -- each CanSee cost two
// queries (rules + contacts), so this alone was ~12 of the ~15 db
// queries getFullUser used to make.
var privacyVisibility map[domain.PrivacyKey]bool
if r.deps.Privacy != nil && currentUserID != 0 && u.ID != 0 && u.ID != currentUserID {
batch, err := r.deps.Privacy.CanSeeBatch(ctx, []int64{u.ID}, currentUserID, []domain.PrivacyKey{
domain.PrivacyKeyAbout,
domain.PrivacyKeyPhoneCall,
domain.PrivacyKeyPhoneP2P,
domain.PrivacyKeyProfilePhoto,
domain.PrivacyKeySavedMusic,
domain.PrivacyKeyBirthday,
})
if err != nil { if err != nil {
return tg.UserFull{}, internalErr() return tg.UserFull{}, internalErr()
} }
if !allowed { privacyVisibility = batch[u.ID]
about = "" }
// canSee mirrors CanSee's own fail-open semantics: self is always
// visible, and an unset map means the batch call above was skipped
// (privacy service absent, or self) -- both cases default to visible,
// exactly like every "if r.deps.Privacy != nil" guard did individually.
canSee := func(key domain.PrivacyKey) bool {
if u.ID == currentUserID || privacyVisibility == nil {
return true
} }
return privacyVisibility[key]
}
about := u.About
if u.ID != currentUserID && !canSee(domain.PrivacyKeyAbout) {
about = ""
} }
// Surface the scam/fake warning to other viewers (never to the account // Surface the scam/fake warning to other viewers (never to the account
// itself), non-destructively over the projected About. // itself), non-destructively over the projected About.
@ -261,19 +286,8 @@ func (r *Router) buildUserFullProjection(ctx context.Context, currentUserID int6
// 通话入口:客户端不见 phone_calls_available=true 不显示通话按钮P1 前置项)。 // 通话入口:客户端不见 phone_calls_available=true 不显示通话按钮P1 前置项)。
// phone_calls_private 标记对端禁 P2Pp2p_allowed 真值在通话确认时另行计算)。 // phone_calls_private 标记对端禁 P2Pp2p_allowed 真值在通话确认时另行计算)。
if !u.Bot && u.ID != currentUserID { if !u.Bot && u.ID != currentUserID {
callsAllowed, p2pAllowed := true, true callsAllowed := canSee(domain.PrivacyKeyPhoneCall)
if r.deps.Privacy != nil { p2pAllowed := canSee(domain.PrivacyKeyPhoneP2P)
allowed, err := r.deps.Privacy.CanSee(ctx, u.ID, currentUserID, domain.PrivacyKeyPhoneCall)
if err != nil {
return tg.UserFull{}, internalErr()
}
callsAllowed = allowed
allowed, err = r.deps.Privacy.CanSee(ctx, u.ID, currentUserID, domain.PrivacyKeyPhoneP2P)
if err != nil {
return tg.UserFull{}, internalErr()
}
p2pAllowed = allowed
}
full.PhoneCallsAvailable = callsAllowed full.PhoneCallsAvailable = callsAllowed
full.VideoCallsAvailable = callsAllowed full.VideoCallsAvailable = callsAllowed
full.PhoneCallsPrivate = !p2pAllowed full.PhoneCallsPrivate = !p2pAllowed
@ -302,15 +316,11 @@ func (r *Router) buildUserFullProjection(ctx context.Context, currentUserID int6
break break
} }
} }
if err := r.fillUserFullPhotos(ctx, currentUserID, u.ID, &full); err != nil { if err := r.fillUserFullPhotos(ctx, currentUserID, u.ID, &full, canSee(domain.PrivacyKeyProfilePhoto)); err != nil {
return tg.UserFull{}, err return tg.UserFull{}, err
} }
if r.deps.Account != nil { if r.deps.Account != nil {
allowed, err := r.canSeeSavedMusic(ctx, currentUserID, u.ID) if canSee(domain.PrivacyKeySavedMusic) {
if err != nil {
return tg.UserFull{}, err
}
if allowed {
music, err := r.deps.Account.ListSavedMusic(ctx, u.ID, 0, 1) music, err := r.deps.Account.ListSavedMusic(ctx, u.ID, 0, 1)
if err != nil { if err != nil {
return tg.UserFull{}, internalErr() return tg.UserFull{}, internalErr()
@ -382,15 +392,7 @@ func (r *Router) buildUserFullProjection(ctx context.Context, currentUserID int6
// 生日account.updateBirthday落 userFull.birthday按 PrivacyKeyBirthday 对他人裁剪, // 生日account.updateBirthday落 userFull.birthday按 PrivacyKeyBirthday 对他人裁剪,
// 本人恒可见。 // 本人恒可见。
if u.Birthday.IsSet() { if u.Birthday.IsSet() {
birthdayVisible := true if canSee(domain.PrivacyKeyBirthday) {
if r.deps.Privacy != nil && u.ID != currentUserID {
allowed, err := r.deps.Privacy.CanSee(ctx, u.ID, currentUserID, domain.PrivacyKeyBirthday)
if err != nil {
return tg.UserFull{}, internalErr()
}
birthdayVisible = allowed
}
if birthdayVisible {
full.SetBirthday(tgBirthday(u.Birthday)) full.SetBirthday(tgBirthday(u.Birthday))
} }
} }
@ -570,7 +572,7 @@ func savedMusicDocumentIDs(docs []domain.Document) []int64 {
return ids return ids
} }
func (r *Router) fillUserFullPhotos(ctx context.Context, viewerUserID, ownerUserID int64, full *tg.UserFull) error { func (r *Router) fillUserFullPhotos(ctx context.Context, viewerUserID, ownerUserID int64, full *tg.UserFull, profileAllowed bool) error {
if r.deps.Files == nil || full == nil || ownerUserID == 0 { if r.deps.Files == nil || full == nil || ownerUserID == 0 {
return nil return nil
} }
@ -602,14 +604,6 @@ func (r *Router) fillUserFullPhotos(ctx context.Context, viewerUserID, ownerUser
} }
} }
} }
profileAllowed := true
if r.deps.Privacy != nil {
var err error
profileAllowed, err = r.deps.Privacy.CanSee(ctx, ownerUserID, viewerUserID, domain.PrivacyKeyProfilePhoto)
if err != nil {
return internalErr()
}
}
if profileAllowed { if profileAllowed {
if photo, found, err := r.deps.Files.CurrentProfilePhotoKind(ctx, domain.PeerTypeUser, ownerUserID, domain.ProfilePhotoKindProfile); err != nil { if photo, found, err := r.deps.Files.CurrentProfilePhotoKind(ctx, domain.PeerTypeUser, ownerUserID, domain.ProfilePhotoKindProfile); err != nil {
return internalErr() return internalErr()

View file

@ -30,15 +30,16 @@ ON CONFLICT (user_id) DO NOTHING`, userID)
func reserveUserPts(ctx context.Context, db sqlcgen.DBTX, userID int64, count int) (int, error) { func reserveUserPts(ctx context.Context, db sqlcgen.DBTX, userID int64, count int) (int, error) {
count = normalizePtsCount(count) count = normalizePtsCount(count)
if err := ensureUserUpdateWatermark(ctx, db, userID); err != nil { // Single upsert instead of ensure-insert-then-update: this runs on every
return 0, err // message send/update event, and the ensure step was a no-op round trip
} // for every user past their first-ever pts allocation.
var pts int var pts int
if err := db.QueryRow(ctx, ` if err := db.QueryRow(ctx, `
UPDATE user_update_watermarks INSERT INTO user_update_watermarks (user_id, contiguous_pts)
SET contiguous_pts = contiguous_pts + $2, VALUES ($1, $2)
ON CONFLICT (user_id) DO UPDATE
SET contiguous_pts = user_update_watermarks.contiguous_pts + $2,
updated_at = now() updated_at = now()
WHERE user_id = $1
RETURNING contiguous_pts`, userID, count).Scan(&pts); err != nil { RETURNING contiguous_pts`, userID, count).Scan(&pts); err != nil {
return 0, fmt.Errorf("reserve user pts: %w", err) return 0, fmt.Errorf("reserve user pts: %w", err)
} }