fix(admin): resolve moderation, gift, and admin-panel bugs from QA

- scam/fake: enforce mutual exclusivity (scam precedence) at TL conversion, admin command, and UI toggles; both flags could render as neither
- scam/fake flicker: persist scam/fake in the Redis user base cache so cache hits no longer drop the flags
- getSavedStarGifts: hidden (unsaved) gifts are now force-excluded for non-owner viewers; same guard for get-by-ref
- convertStarGift: map already-upgraded/owner-invalid/unavailable to a clean client error instead of 500
- channel force-settings: send only changed fields (partial patch) and re-sync toggles after reload, so one setting no longer resets another
- give gifts: removed custom collectible numbers (auto sequential only); locked sender to the system account 777000
- give gifts UI: fixed picker card spacing/right gap; static (hover-play) Lottie previews on emoji + picker to stop render lag; fixed emoji ID field overflow
This commit is contained in:
epilepticseizureee 2026-07-23 06:24:53 +03:00
parent 313624eab2
commit 90792cdfab
22 changed files with 214 additions and 135 deletions

View file

@ -451,7 +451,9 @@ func tgChannel(viewerUserID int64, ch domain.Channel, self *domain.ChannelMember
Creator: ch.CreatorUserID == viewerUserID && viewerUserID != 0,
Verified: ch.Verified,
Scam: ch.Scam,
Fake: ch.Fake,
// scam and fake are mutually exclusive in Telegram; a peer flagged as
// both would render neither badge on clients. scam takes precedence.
Fake: ch.Fake && !ch.Scam,
Gigagroup: ch.Gigagroup,
Broadcast: ch.Broadcast,
Megagroup: ch.Megagroup,

View file

@ -53,7 +53,9 @@ func tgUser(u domain.User) *tg.User {
Phone: u.Phone,
Verified: u.Verified,
Scam: u.Scam,
Fake: u.Fake,
// scam and fake are mutually exclusive in Telegram; a peer flagged as
// both would render neither badge on clients. scam takes precedence.
Fake: u.Fake && !u.Scam,
Support: u.Support,
Contact: u.Contact,
MutualContact: u.Mutual,

View file

@ -523,10 +523,17 @@ func (r *Router) onPaymentsGetSavedStarGifts(ctx context.Context, req *tg.Paymen
if r.deps.Gifts == nil {
return emptySavedStarGifts(), nil
}
// Gifts hidden from the profile (unsaved) are visible only to the owner (or a
// channel admin). Never trust the client's exclude_unsaved flag for other
// viewers: force-exclude hidden gifts unless the requester manages the owner.
excludeUnsaved := req.ExcludeUnsaved
if r.ensureCanManageStarGiftOwner(ctx, userID, owner) != nil {
excludeUnsaved = true
}
collectionID, _ := req.GetCollectionID()
page, err := r.deps.Gifts.ListSavedFiltered(ctx, domain.SavedStarGiftFilter{
Owner: owner,
ExcludeUnsaved: req.ExcludeUnsaved,
ExcludeUnsaved: excludeUnsaved,
ExcludeSaved: req.ExcludeSaved,
ExcludeUnlimited: req.ExcludeUnlimited,
ExcludeUnique: req.ExcludeUnique,
@ -556,6 +563,17 @@ func (r *Router) onPaymentsGetSavedStarGift(ctx context.Context, refs []tg.Input
return emptySavedStarGifts(), nil
}
gifts := make([]domain.SavedStarGift, 0, len(refs))
// A gift hidden from the profile (unsaved) is visible only to the owner or a
// channel admin. Memoize the manage check per owner to avoid repeat lookups.
manageCache := make(map[domain.Peer]bool)
canManageOwner := func(owner domain.Peer) bool {
if v, ok := manageCache[owner]; ok {
return v
}
v := r.ensureCanManageStarGiftOwner(ctx, userID, owner) == nil
manageCache[owner] = v
return v
}
for _, ref := range refs {
dref, ok, err := r.starGiftRefFromInput(ctx, userID, ref)
if err != nil {
@ -569,6 +587,9 @@ func (r *Router) onPaymentsGetSavedStarGift(ctx context.Context, refs []tg.Input
return nil, internalErr()
}
if found && !g.Converted {
if g.Unsaved && !canManageOwner(g.Owner) {
continue
}
gifts = append(gifts, g)
}
}
@ -669,9 +690,14 @@ func (r *Router) onPaymentsConvertStarGift(ctx context.Context, ref tg.InputSave
})
if err != nil {
switch {
case errors.Is(err, domain.ErrStarGiftNotFound):
return false, starGiftInvalidErr()
case errors.Is(err, domain.ErrStarGiftAlreadyConverted):
case errors.Is(err, domain.ErrStarGiftNotFound),
errors.Is(err, domain.ErrStarGiftAlreadyConverted),
errors.Is(err, domain.ErrStarGiftAlreadyUpgraded),
errors.Is(err, domain.ErrStarGiftOwnerInvalid),
errors.Is(err, domain.ErrStarGiftUnavailable):
// These are known business conditions (e.g. converting an already
// upgraded/unique gift). Surface a clean client error instead of a
// 500 INTERNAL_SERVER_ERROR.
return false, starGiftInvalidErr()
default:
return false, internalErr()

View file

@ -90,7 +90,6 @@ func (r *Router) adminGrantUpgradedStarGift(ctx context.Context, senderID int64,
ModelAttributeID: grant.ModelAttributeID,
PatternAttributeID: grant.PatternAttributeID,
BackdropAttributeID: grant.BackdropAttributeID,
Num: grant.Num,
}); err != nil {
return err
}