admin: gift granting, collectible attribute/number control, and Layer 228 moderation tools
Admin console additions (Layer 228): - Give Gifts: dedicated tab with sorted Lottie/TGS gift picker + inline form; grant any catalog gift to a user/channel from 777000 (no charge) - Upgraded/collectible delivery: mint a unique gift with admin-selected model/pattern/backdrop and custom number, or random/auto (DB FK + UNIQUE(gift_id,num) enforce invariants) - SCAM/FAKE flags for users/channels (migration 0136) with configurable profile warning (TELESRV_SCAM_WARNING/TELESRV_FAKE_WARNING) - Support toggle, force channel settings incl. gigagroup (migration 0137), username management, cosmetic color/emoji-status - Emoji admin tab (custom emoji list + document IDs + Lottie/TGS preview) - Bot management; soft UI / dark theme Wired through Router -> admin.Service -> adminapi -> BFF -> React panel (en/zh/ru).
This commit is contained in:
parent
9e45da69ef
commit
313624eab2
63 changed files with 3650 additions and 71 deletions
|
|
@ -197,6 +197,110 @@ func (s *ChannelStore) SetChannelVerified(_ context.Context, channelID int64, ve
|
|||
return cloneChannel(channel), nil
|
||||
}
|
||||
|
||||
func (s *ChannelStore) SetChannelScamFake(_ context.Context, channelID int64, scam, fake bool) (domain.Channel, error) {
|
||||
if channelID == 0 {
|
||||
return domain.Channel{}, domain.ErrChannelInvalid
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
channel, ok := s.channels[channelID]
|
||||
if !ok || channel.Deleted {
|
||||
return domain.Channel{}, domain.ErrChannelInvalid
|
||||
}
|
||||
channel.Scam = scam
|
||||
channel.Fake = fake
|
||||
s.channels[channelID] = channel
|
||||
return cloneChannel(channel), nil
|
||||
}
|
||||
|
||||
func (s *ChannelStore) SetChannelAdminSettings(_ context.Context, channelID int64, patch domain.ChannelAdminSettings) (domain.Channel, error) {
|
||||
if channelID == 0 {
|
||||
return domain.Channel{}, domain.ErrChannelInvalid
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
channel, ok := s.channels[channelID]
|
||||
if !ok || channel.Deleted {
|
||||
return domain.Channel{}, domain.ErrChannelInvalid
|
||||
}
|
||||
if patch.Gigagroup != nil {
|
||||
channel.Gigagroup = *patch.Gigagroup
|
||||
}
|
||||
if patch.AntiSpam != nil {
|
||||
channel.AntiSpam = *patch.AntiSpam
|
||||
}
|
||||
if patch.ParticipantsHidden != nil {
|
||||
channel.ParticipantsHidden = *patch.ParticipantsHidden
|
||||
}
|
||||
if patch.NoForwards != nil {
|
||||
channel.NoForwards = *patch.NoForwards
|
||||
}
|
||||
if patch.JoinToSend != nil {
|
||||
channel.JoinToSend = *patch.JoinToSend
|
||||
}
|
||||
if patch.JoinRequest != nil {
|
||||
channel.JoinRequest = *patch.JoinRequest
|
||||
}
|
||||
if patch.SlowmodeSeconds != nil {
|
||||
channel.SlowmodeSeconds = *patch.SlowmodeSeconds
|
||||
}
|
||||
s.channels[channelID] = channel
|
||||
return cloneChannel(channel), nil
|
||||
}
|
||||
|
||||
func (s *ChannelStore) SetChannelUsernameAdmin(_ context.Context, channelID int64, username string) (domain.Channel, error) {
|
||||
if channelID == 0 {
|
||||
return domain.Channel{}, domain.ErrChannelInvalid
|
||||
}
|
||||
username = strings.TrimSpace(strings.TrimPrefix(username, "@"))
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
channel, ok := s.channels[channelID]
|
||||
if !ok || channel.Deleted {
|
||||
return domain.Channel{}, domain.ErrChannelInvalid
|
||||
}
|
||||
channel.Username = username
|
||||
s.channels[channelID] = channel
|
||||
return cloneChannel(channel), nil
|
||||
}
|
||||
|
||||
func (s *ChannelStore) SetChannelColorAdmin(_ context.Context, channelID int64, forProfile bool, color domain.ChannelPeerColor) (domain.Channel, error) {
|
||||
if channelID == 0 {
|
||||
return domain.Channel{}, domain.ErrChannelInvalid
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
channel, ok := s.channels[channelID]
|
||||
if !ok || channel.Deleted {
|
||||
return domain.Channel{}, domain.ErrChannelInvalid
|
||||
}
|
||||
if forProfile {
|
||||
channel.ProfileColor = color
|
||||
} else {
|
||||
channel.Color = color
|
||||
}
|
||||
s.channels[channelID] = channel
|
||||
return cloneChannel(channel), nil
|
||||
}
|
||||
|
||||
func (s *ChannelStore) SetChannelEmojiStatusAdmin(_ context.Context, channelID int64, status domain.ChannelEmojiStatus) (domain.Channel, error) {
|
||||
if channelID == 0 {
|
||||
return domain.Channel{}, domain.ErrChannelInvalid
|
||||
}
|
||||
if status.DocumentID == 0 {
|
||||
status.Until = 0
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
channel, ok := s.channels[channelID]
|
||||
if !ok || channel.Deleted {
|
||||
return domain.Channel{}, domain.ErrChannelInvalid
|
||||
}
|
||||
channel.EmojiStatus = status
|
||||
s.channels[channelID] = channel
|
||||
return cloneChannel(channel), nil
|
||||
}
|
||||
|
||||
func (s *ChannelStore) ResolvePublicChannelUsername(_ context.Context, viewerUserID int64, username string) (domain.Channel, bool, error) {
|
||||
_ = viewerUserID // zero is the anonymous public-web view; no membership state is projected.
|
||||
username = strings.ToLower(strings.TrimSpace(strings.TrimPrefix(username, "@")))
|
||||
|
|
|
|||
|
|
@ -283,6 +283,33 @@ func (s *UserStore) SetVerified(_ context.Context, userID int64, verified bool)
|
|||
return u, nil
|
||||
}
|
||||
|
||||
// SetSupport 设置/取消用户的 support 标记(与 postgres 语义一致)。
|
||||
func (s *UserStore) SetSupport(_ context.Context, userID int64, support bool) (domain.User, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
u, ok := s.byID[userID]
|
||||
if !ok || u.Deleted {
|
||||
return domain.User{}, domain.ErrUserNotFound
|
||||
}
|
||||
u.Support = support
|
||||
s.byID[userID] = u
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// SetScamFake 设置/取消用户的 scam 与 fake 标记(与 postgres 语义一致)。
|
||||
func (s *UserStore) SetScamFake(_ context.Context, userID int64, scam, fake bool) (domain.User, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
u, ok := s.byID[userID]
|
||||
if !ok || u.Deleted {
|
||||
return domain.User{}, domain.ErrUserNotFound
|
||||
}
|
||||
u.Scam = scam
|
||||
u.Fake = fake
|
||||
s.byID[userID] = u
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// SweepExpiredPremium 清空到期会员行并返回清理后的用户(与 postgres 语义一致)。
|
||||
func (s *UserStore) SweepExpiredPremium(_ context.Context, now int64, limit int) ([]domain.User, error) {
|
||||
if limit <= 0 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue