feat: sync expose gramsrv account rating

Follow up PR #22 by projecting its composite rating through userFull while keeping profile reads cache-backed and strictly read-only.

Source-Commit: 00eea44c

Co-authored-by: Egor Egorov <business.egor.sg@gmail.com>
This commit is contained in:
iamxvbaba 2026-07-28 02:09:29 +08:00
parent fff8de783a
commit 74c9249091
9 changed files with 362 additions and 31 deletions

View file

@ -407,12 +407,58 @@ func (r *Router) buildUserFullProjection(ctx context.Context, currentUserID int6
full.SetBirthday(tgBirthday(u.Birthday))
}
}
r.applyAccountRatingToUserFull(ctx, currentUserID, u, &full)
// 个人频道account.updatePersonalChannel不在此落地它按 viewer 实时解析,作为缓存后的
// overlay 处理applyPersonalChannelToUserFull避免烤进 per-(viewer,target) 投影缓存以及
// build/chats 两次解析同一频道。
return full, nil
}
// applyAccountRatingToUserFull projects gramsrv's stored composite rating through
// the rating fields official clients already render. This is a gramsrv policy
// score, not a promise that its inputs or thresholds match Telegram's service.
//
// The projection is built inside the existing per-(viewer,target) UserFull
// cache. Therefore a cache miss adds at most one primary-key read and a cache hit
// adds none. Recompute and writes remain exclusively in the bounded background
// worker/admin paths.
func (r *Router) applyAccountRatingToUserFull(ctx context.Context, viewerUserID int64, target domain.User, full *tg.UserFull) {
targetUserID := target.ID
if r.deps.AccountRatings == nil || full == nil || !domain.RatableAccount(targetUserID, target.Bot) {
return
}
rating, err := r.deps.AccountRatings.Rating(ctx, targetUserID)
if err != nil {
// Missing, disabled and temporarily unavailable projections all preserve
// the legacy wire shape instead of failing the surrounding profile read.
return
}
full.SetStarsRating(tgAccountRatingLevel(rating.LevelSnapshot()))
if viewerUserID == 0 || viewerUserID != targetUserID {
return
}
pending, ok := rating.PendingLevel()
if !ok {
return
}
full.SetStarsMyPendingRating(tgAccountRatingLevel(pending))
full.SetStarsMyPendingRatingDate(int(rating.PendingDate.Unix()))
}
// tgAccountRatingLevel maps the local level snapshot onto starsRating#1b0e4f07.
// next_level_stars remains absent at the configured maximum local level.
func tgAccountRatingLevel(in domain.AccountRatingLevel) tg.StarsRating {
out := tg.StarsRating{
Level: in.Level,
CurrentLevelStars: in.CurrentLevelStars,
Stars: in.Stars,
}
if in.HasNextLevelStars {
out.SetNextLevelStars(in.NextLevelStars)
}
return out
}
// tgBirthday 把 domain 生日转 tg.BirthdayYear 可选0 表示不含年份)。
func tgBirthday(b domain.Birthday) tg.Birthday {
out := tg.Birthday{Day: b.Day, Month: b.Month}