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:
epilepticseizureee 2026-07-23 04:00:29 +03:00
parent 9e45da69ef
commit 313624eab2
63 changed files with 3650 additions and 71 deletions

View file

@ -496,7 +496,7 @@ func scanChannel(row rowScanner) (domain.Channel, error) {
func channelScanDest(ch *domain.Channel, rights, reactionPolicy *string, wallpaper **string) []any {
return []any{
&ch.ID, &ch.AccessHash, &ch.CreatorUserID, &ch.Title, &ch.About, &ch.Username, &ch.Verified,
&ch.ID, &ch.AccessHash, &ch.CreatorUserID, &ch.Title, &ch.About, &ch.Username, &ch.Verified, &ch.Scam, &ch.Fake, &ch.Gigagroup,
&ch.Broadcast, &ch.Megagroup, &ch.Forum, &ch.ForumTabs, &ch.Autotranslation, &ch.RestrictedSponsored, &ch.BroadcastMessagesAllowed, &ch.SendPaidMessagesStars, &ch.NoForwards, &ch.JoinToSend, &ch.JoinRequest, &ch.Signatures, &ch.PreHistoryHidden, &ch.ParticipantsHidden, &ch.AntiSpam, &ch.HasLink, &ch.LinkedChatID, &ch.LinkedCommunityID, &ch.Monoforum, &ch.LinkedMonoforumID, &ch.SlowmodeSeconds, &ch.BoostsUnrestrict, rights,
reactionPolicy, &ch.Color.HasColor, &ch.Color.Color, &ch.Color.BackgroundEmojiID, &ch.ProfileColor.HasColor, &ch.ProfileColor.Color, &ch.ProfileColor.BackgroundEmojiID, &ch.EmojiStatus.DocumentID, &ch.EmojiStatus.Until,
wallpaper, &ch.ParticipantsCount, &ch.AdminsCount, &ch.KickedCount, &ch.BannedCount, &ch.TopMessageID,

View file

@ -284,6 +284,177 @@ func (s *ChannelStore) SetChannelVerified(ctx context.Context, channelID int64,
return channel, nil
}
// SetChannelScamFake 设置/取消频道的 scam 与 fake 标记。
func (s *ChannelStore) SetChannelScamFake(ctx context.Context, channelID int64, scam, fake bool) (domain.Channel, error) {
if channelID == 0 {
return domain.Channel{}, domain.ErrChannelInvalid
}
channel, err := s.channelByID(ctx, s.db, channelID)
if err != nil {
return domain.Channel{}, err
}
if channel.Scam == scam && channel.Fake == fake {
return channel, nil
}
if _, err := s.db.Exec(ctx, `UPDATE channels SET scam = $2, fake = $3, updated_at = now() WHERE id = $1 AND NOT deleted`, channelID, scam, fake); err != nil {
return domain.Channel{}, fmt.Errorf("set channel scam/fake: %w", err)
}
if s.rowCache != nil {
s.rowCache.delete(channelID)
}
channel.Scam = scam
channel.Fake = fake
return channel, nil
}
// SetChannelAdminSettings applies an admin-direct moderation-settings patch
// (no membership/permission checks). nil fields are left unchanged.
func (s *ChannelStore) SetChannelAdminSettings(ctx context.Context, channelID int64, patch domain.ChannelAdminSettings) (domain.Channel, error) {
if channelID == 0 {
return domain.Channel{}, domain.ErrChannelInvalid
}
if patch.Empty() {
return s.channelByID(ctx, s.db, channelID)
}
sets := make([]string, 0, 7)
args := []any{channelID}
idx := 2
add := func(col string, val any) {
sets = append(sets, fmt.Sprintf("%s = $%d", col, idx))
args = append(args, val)
idx++
}
if patch.Gigagroup != nil {
add("gigagroup", *patch.Gigagroup)
}
if patch.AntiSpam != nil {
add("antispam", *patch.AntiSpam)
}
if patch.ParticipantsHidden != nil {
add("participants_hidden", *patch.ParticipantsHidden)
}
if patch.NoForwards != nil {
add("noforwards", *patch.NoForwards)
}
if patch.JoinToSend != nil {
add("join_to_send", *patch.JoinToSend)
}
if patch.JoinRequest != nil {
add("join_request", *patch.JoinRequest)
}
if patch.SlowmodeSeconds != nil {
add("slowmode_seconds", *patch.SlowmodeSeconds)
}
query := "UPDATE channels SET " + strings.Join(sets, ", ") + ", updated_at = now() WHERE id = $1 AND NOT deleted"
if _, err := s.db.Exec(ctx, query, args...); err != nil {
return domain.Channel{}, fmt.Errorf("set channel admin settings: %w", err)
}
if s.rowCache != nil {
s.rowCache.delete(channelID)
}
return s.channelByID(ctx, s.db, channelID)
}
// SetChannelUsernameAdmin force-sets or clears (empty) a channel username with
// no permission checks. Username uniqueness is still enforced by peer_usernames.
func (s *ChannelStore) SetChannelUsernameAdmin(ctx context.Context, channelID int64, username string) (domain.Channel, error) {
if channelID == 0 {
return domain.Channel{}, domain.ErrChannelInvalid
}
beginner, ok := s.db.(txBeginner)
if !ok {
return domain.Channel{}, fmt.Errorf("set channel username: db does not support transactions")
}
username = strings.TrimSpace(strings.TrimPrefix(username, "@"))
usernameLower := strings.ToLower(username)
tx, err := beginner.Begin(ctx)
if err != nil {
return domain.Channel{}, fmt.Errorf("begin set channel username: %w", err)
}
committed := false
defer func() {
if !committed {
_ = tx.Rollback(ctx)
}
}()
channel, err := s.channelByID(ctx, tx, channelID)
if err != nil {
return domain.Channel{}, err
}
if strings.EqualFold(channel.Username, username) {
return channel, nil
}
if err := replacePeerUsernameTx(ctx, tx, peerUsernameTypeChannel, channelID, usernameLower); err != nil {
return domain.Channel{}, err
}
if _, err := tx.Exec(ctx, `UPDATE channels SET username = NULLIF($2,''), updated_at = now() WHERE id = $1`, channelID, username); err != nil {
return domain.Channel{}, fmt.Errorf("set channel username: %w", err)
}
if err := markUserChannelMemberIndexPublicTx(ctx, tx, channelID, username != ""); err != nil {
return domain.Channel{}, err
}
if err := tx.Commit(ctx); err != nil {
return domain.Channel{}, fmt.Errorf("commit set channel username: %w", err)
}
committed = true
if s.rowCache != nil {
s.rowCache.delete(channelID)
}
channel.Username = username
return channel, nil
}
// SetChannelColorAdmin force-sets a channel name/profile color (no permission checks).
func (s *ChannelStore) SetChannelColorAdmin(ctx context.Context, channelID int64, forProfile bool, color domain.ChannelPeerColor) (domain.Channel, error) {
if channelID == 0 {
return domain.Channel{}, domain.ErrChannelInvalid
}
channel, err := s.channelByID(ctx, s.db, channelID)
if err != nil {
return domain.Channel{}, err
}
if forProfile {
if _, err := s.db.Exec(ctx, `UPDATE channels SET profile_color_set = $2, profile_color = $3, profile_color_background_emoji_id = $4, updated_at = now() WHERE id = $1`,
channelID, color.HasColor, color.Color, color.BackgroundEmojiID); err != nil {
return domain.Channel{}, fmt.Errorf("set channel profile color: %w", err)
}
channel.ProfileColor = color
} else {
if _, err := s.db.Exec(ctx, `UPDATE channels SET color_set = $2, color = $3, color_background_emoji_id = $4, updated_at = now() WHERE id = $1`,
channelID, color.HasColor, color.Color, color.BackgroundEmojiID); err != nil {
return domain.Channel{}, fmt.Errorf("set channel color: %w", err)
}
channel.Color = color
}
if s.rowCache != nil {
s.rowCache.delete(channelID)
}
return channel, nil
}
// SetChannelEmojiStatusAdmin force-sets or clears a channel emoji status (no permission checks).
func (s *ChannelStore) SetChannelEmojiStatusAdmin(ctx 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
}
channel, err := s.channelByID(ctx, s.db, channelID)
if err != nil {
return domain.Channel{}, err
}
if _, err := s.db.Exec(ctx, `UPDATE channels SET emoji_status_document_id = $2, emoji_status_until = $3, updated_at = now() WHERE id = $1`,
channelID, status.DocumentID, status.Until); err != nil {
return domain.Channel{}, fmt.Errorf("set channel emoji status: %w", err)
}
if s.rowCache != nil {
s.rowCache.delete(channelID)
}
channel.EmojiStatus = status
return channel, nil
}
func (s *ChannelStore) ResolvePublicChannelUsername(ctx context.Context, viewerUserID int64, username string) (domain.Channel, bool, error) {
_ = viewerUserID // zero is the anonymous public-web view; this query is viewer-independent.
usernameLower := strings.ToLower(strings.TrimSpace(strings.TrimPrefix(username, "@")))

View file

@ -113,7 +113,7 @@ func NewChannelStore(db sqlcgen.DBTX, opts ...ChannelStoreOption) *ChannelStore
return s
}
const channelColumns = `c.id, c.access_hash, c.creator_user_id, c.title, c.about, COALESCE(c.username, ''), c.verified,
const channelColumns = `c.id, c.access_hash, c.creator_user_id, c.title, c.about, COALESCE(c.username, ''), c.verified, c.scam, c.fake, c.gigagroup,
c.broadcast, c.megagroup, c.forum, c.forum_tabs, c.autotranslation, c.restricted_sponsored, c.broadcast_messages_allowed, c.send_paid_messages_stars, c.noforwards, c.join_to_send, c.join_request, c.signatures, c.pre_history_hidden, c.participants_hidden, c.antispam,
EXISTS (SELECT 1 FROM channel_invites ci WHERE ci.channel_id = c.id AND NOT ci.revoked) AS has_link,
c.linked_chat_id, c.linked_community_id, c.monoforum, c.linked_monoforum_id, c.slowmode_seconds, c.boosts_unrestrict, c.default_banned_rights::text,

View file

@ -153,6 +153,21 @@ SET verified = sqlc.arg(verified)::boolean,
WHERE id = sqlc.arg(id)::bigint AND deleted_at IS NULL
RETURNING *;
-- name: SetUserScamFake :one
UPDATE users
SET scam = sqlc.arg(scam)::boolean,
fake = sqlc.arg(fake)::boolean,
updated_at = now()
WHERE id = sqlc.arg(id)::bigint AND deleted_at IS NULL
RETURNING *;
-- name: SetUserSupport :one
UPDATE users
SET support = sqlc.arg(support)::boolean,
updated_at = now()
WHERE id = sqlc.arg(id)::bigint AND deleted_at IS NULL
RETURNING *;
-- name: SweepExpiredPremium :many
UPDATE users
SET premium_expires_at = NULL,

View file

@ -167,7 +167,7 @@ func (q *Queries) InsertBot(ctx context.Context, arg InsertBotParams) error {
const insertBotUser = `-- name: InsertBotUser :one
INSERT INTO users (access_hash, phone, first_name, last_name, username, country_code, is_bot, bot_info_version)
VALUES ($1, '', $2, '', $3, '', TRUE, 1)
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id, scam, fake
`
type InsertBotUserParams struct {
@ -216,6 +216,8 @@ func (q *Queries) InsertBotUser(ctx context.Context, arg InsertBotUserParams) (U
&i.EmojiStatusCollectibleID,
&i.EmojiStatusCollectible,
&i.LinkedCommunityID,
&i.Scam,
&i.Fake,
)
return i, err
}

View file

@ -2283,6 +2283,8 @@ type User struct {
EmojiStatusCollectibleID *int64
EmojiStatusCollectible []byte
LinkedCommunityID int64
Scam bool
Fake bool
}
type UserBusinessProfile struct {

View file

@ -14,7 +14,7 @@ import (
const createUser = `-- name: CreateUser :one
INSERT INTO users (access_hash, phone, first_name, last_name, username, country_code, premium_expires_at)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id, scam, fake
`
type CreateUserParams struct {
@ -75,12 +75,14 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, e
&i.EmojiStatusCollectibleID,
&i.EmojiStatusCollectible,
&i.LinkedCommunityID,
&i.Scam,
&i.Fake,
)
return i, err
}
const getUserByID = `-- name: GetUserByID :one
SELECT id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id FROM users WHERE id = $1
SELECT id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id, scam, fake FROM users WHERE id = $1
`
func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) {
@ -123,12 +125,14 @@ func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) {
&i.EmojiStatusCollectibleID,
&i.EmojiStatusCollectible,
&i.LinkedCommunityID,
&i.Scam,
&i.Fake,
)
return i, err
}
const getUserByPhone = `-- name: GetUserByPhone :one
SELECT id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id FROM users WHERE phone = $1 AND deleted_at IS NULL
SELECT id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id, scam, fake FROM users WHERE phone = $1 AND deleted_at IS NULL
`
func (q *Queries) GetUserByPhone(ctx context.Context, phone string) (User, error) {
@ -171,12 +175,14 @@ func (q *Queries) GetUserByPhone(ctx context.Context, phone string) (User, error
&i.EmojiStatusCollectibleID,
&i.EmojiStatusCollectible,
&i.LinkedCommunityID,
&i.Scam,
&i.Fake,
)
return i, err
}
const getUserByUsername = `-- name: GetUserByUsername :one
SELECT id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id FROM users WHERE lower(username) = lower($1) AND username <> '' AND deleted_at IS NULL
SELECT id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id, scam, fake FROM users WHERE lower(username) = lower($1) AND username <> '' AND deleted_at IS NULL
`
func (q *Queries) GetUserByUsername(ctx context.Context, lower string) (User, error) {
@ -219,12 +225,14 @@ func (q *Queries) GetUserByUsername(ctx context.Context, lower string) (User, er
&i.EmojiStatusCollectibleID,
&i.EmojiStatusCollectible,
&i.LinkedCommunityID,
&i.Scam,
&i.Fake,
)
return i, err
}
const getUsersByIDs = `-- name: GetUsersByIDs :many
SELECT id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id
SELECT id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id, scam, fake
FROM users
WHERE id = ANY($1::bigint[])
ORDER BY id
@ -276,6 +284,8 @@ func (q *Queries) GetUsersByIDs(ctx context.Context, ids []int64) ([]User, error
&i.EmojiStatusCollectibleID,
&i.EmojiStatusCollectible,
&i.LinkedCommunityID,
&i.Scam,
&i.Fake,
); err != nil {
return nil, err
}
@ -288,7 +298,7 @@ func (q *Queries) GetUsersByIDs(ctx context.Context, ids []int64) ([]User, error
}
const getUsersByPhones = `-- name: GetUsersByPhones :many
SELECT id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id
SELECT id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id, scam, fake
FROM users
WHERE phone = ANY($1::text[]) AND deleted_at IS NULL
ORDER BY id
@ -340,6 +350,8 @@ func (q *Queries) GetUsersByPhones(ctx context.Context, phones []string) ([]User
&i.EmojiStatusCollectibleID,
&i.EmojiStatusCollectible,
&i.LinkedCommunityID,
&i.Scam,
&i.Fake,
); err != nil {
return nil, err
}
@ -535,7 +547,7 @@ UPDATE users
SET premium_expires_at = $1::timestamptz,
updated_at = now()
WHERE id = $2::bigint AND deleted_at IS NULL
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id, scam, fake
`
type SetUserPremiumUntilParams struct {
@ -583,6 +595,128 @@ func (q *Queries) SetUserPremiumUntil(ctx context.Context, arg SetUserPremiumUnt
&i.EmojiStatusCollectibleID,
&i.EmojiStatusCollectible,
&i.LinkedCommunityID,
&i.Scam,
&i.Fake,
)
return i, err
}
const setUserScamFake = `-- name: SetUserScamFake :one
UPDATE users
SET scam = $1::boolean,
fake = $2::boolean,
updated_at = now()
WHERE id = $3::bigint AND deleted_at IS NULL
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id, scam, fake
`
type SetUserScamFakeParams struct {
Scam bool
Fake bool
ID int64
}
func (q *Queries) SetUserScamFake(ctx context.Context, arg SetUserScamFakeParams) (User, error) {
row := q.db.QueryRow(ctx, setUserScamFake, arg.Scam, arg.Fake, arg.ID)
var i User
err := row.Scan(
&i.ID,
&i.AccessHash,
&i.Phone,
&i.FirstName,
&i.LastName,
&i.Username,
&i.CountryCode,
&i.CreatedAt,
&i.UpdatedAt,
&i.Verified,
&i.Support,
&i.About,
&i.LastSeenAt,
&i.DefaultHistoryTtlPeriod,
&i.IsBot,
&i.BotInfoVersion,
&i.PremiumExpiresAt,
&i.EmojiStatusDocumentID,
&i.EmojiStatusUntil,
&i.ColorSet,
&i.Color,
&i.ColorBackgroundEmojiID,
&i.ProfileColorSet,
&i.ProfileColor,
&i.ProfileColorBackgroundEmojiID,
&i.BirthdayDay,
&i.BirthdayMonth,
&i.BirthdayYear,
&i.PersonalChannelID,
&i.DeletedAt,
&i.DeletionSource,
&i.DeletionReason,
&i.AccountDeleteAt,
&i.EmojiStatusCollectibleID,
&i.EmojiStatusCollectible,
&i.LinkedCommunityID,
&i.Scam,
&i.Fake,
)
return i, err
}
const setUserSupport = `-- name: SetUserSupport :one
UPDATE users
SET support = $1::boolean,
updated_at = now()
WHERE id = $2::bigint AND deleted_at IS NULL
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id, scam, fake
`
type SetUserSupportParams struct {
Support bool
ID int64
}
func (q *Queries) SetUserSupport(ctx context.Context, arg SetUserSupportParams) (User, error) {
row := q.db.QueryRow(ctx, setUserSupport, arg.Support, arg.ID)
var i User
err := row.Scan(
&i.ID,
&i.AccessHash,
&i.Phone,
&i.FirstName,
&i.LastName,
&i.Username,
&i.CountryCode,
&i.CreatedAt,
&i.UpdatedAt,
&i.Verified,
&i.Support,
&i.About,
&i.LastSeenAt,
&i.DefaultHistoryTtlPeriod,
&i.IsBot,
&i.BotInfoVersion,
&i.PremiumExpiresAt,
&i.EmojiStatusDocumentID,
&i.EmojiStatusUntil,
&i.ColorSet,
&i.Color,
&i.ColorBackgroundEmojiID,
&i.ProfileColorSet,
&i.ProfileColor,
&i.ProfileColorBackgroundEmojiID,
&i.BirthdayDay,
&i.BirthdayMonth,
&i.BirthdayYear,
&i.PersonalChannelID,
&i.DeletedAt,
&i.DeletionSource,
&i.DeletionReason,
&i.AccountDeleteAt,
&i.EmojiStatusCollectibleID,
&i.EmojiStatusCollectible,
&i.LinkedCommunityID,
&i.Scam,
&i.Fake,
)
return i, err
}
@ -592,7 +726,7 @@ UPDATE users
SET verified = $1::boolean,
updated_at = now()
WHERE id = $2::bigint AND deleted_at IS NULL
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id, scam, fake
`
type SetUserVerifiedParams struct {
@ -640,6 +774,8 @@ func (q *Queries) SetUserVerified(ctx context.Context, arg SetUserVerifiedParams
&i.EmojiStatusCollectibleID,
&i.EmojiStatusCollectible,
&i.LinkedCommunityID,
&i.Scam,
&i.Fake,
)
return i, err
}
@ -656,7 +792,7 @@ WHERE id IN (
ORDER BY premium_expires_at
LIMIT $2::int
)
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id, scam, fake
`
type SweepExpiredPremiumParams struct {
@ -710,6 +846,8 @@ func (q *Queries) SweepExpiredPremium(ctx context.Context, arg SweepExpiredPremi
&i.EmojiStatusCollectibleID,
&i.EmojiStatusCollectible,
&i.LinkedCommunityID,
&i.Scam,
&i.Fake,
); err != nil {
return nil, err
}
@ -728,7 +866,7 @@ SET birthday_day = $1::int,
birthday_year = $3::int,
updated_at = now()
WHERE id = $4::bigint AND deleted_at IS NULL
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id, scam, fake
`
type UpdateUserBirthdayParams struct {
@ -783,6 +921,8 @@ func (q *Queries) UpdateUserBirthday(ctx context.Context, arg UpdateUserBirthday
&i.EmojiStatusCollectibleID,
&i.EmojiStatusCollectible,
&i.LinkedCommunityID,
&i.Scam,
&i.Fake,
)
return i, err
}
@ -794,7 +934,7 @@ SET color_set = $1::boolean,
color_background_emoji_id = $3::bigint,
updated_at = now()
WHERE id = $4::bigint AND deleted_at IS NULL
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id, scam, fake
`
type UpdateUserColorParams struct {
@ -849,6 +989,8 @@ func (q *Queries) UpdateUserColor(ctx context.Context, arg UpdateUserColorParams
&i.EmojiStatusCollectibleID,
&i.EmojiStatusCollectible,
&i.LinkedCommunityID,
&i.Scam,
&i.Fake,
)
return i, err
}
@ -861,7 +1003,7 @@ SET emoji_status_document_id = $1::bigint,
emoji_status_collectible = $4::jsonb,
updated_at = now()
WHERE id = $5::bigint AND deleted_at IS NULL
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id, scam, fake
`
type UpdateUserEmojiStatusParams struct {
@ -918,6 +1060,8 @@ func (q *Queries) UpdateUserEmojiStatus(ctx context.Context, arg UpdateUserEmoji
&i.EmojiStatusCollectibleID,
&i.EmojiStatusCollectible,
&i.LinkedCommunityID,
&i.Scam,
&i.Fake,
)
return i, err
}
@ -944,7 +1088,7 @@ UPDATE users
SET personal_channel_id = $1::bigint,
updated_at = now()
WHERE id = $2::bigint AND deleted_at IS NULL
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id, scam, fake
`
type UpdateUserPersonalChannelParams struct {
@ -992,6 +1136,8 @@ func (q *Queries) UpdateUserPersonalChannel(ctx context.Context, arg UpdateUserP
&i.EmojiStatusCollectibleID,
&i.EmojiStatusCollectible,
&i.LinkedCommunityID,
&i.Scam,
&i.Fake,
)
return i, err
}
@ -1001,7 +1147,7 @@ UPDATE users
SET phone = $1::text,
updated_at = now()
WHERE id = $2::bigint AND deleted_at IS NULL
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id, scam, fake
`
type UpdateUserPhoneParams struct {
@ -1049,6 +1195,8 @@ func (q *Queries) UpdateUserPhone(ctx context.Context, arg UpdateUserPhoneParams
&i.EmojiStatusCollectibleID,
&i.EmojiStatusCollectible,
&i.LinkedCommunityID,
&i.Scam,
&i.Fake,
)
return i, err
}
@ -1060,7 +1208,7 @@ SET first_name = $2,
about = $4,
updated_at = now()
WHERE id = $1 AND deleted_at IS NULL
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id, scam, fake
`
type UpdateUserProfileParams struct {
@ -1115,6 +1263,8 @@ func (q *Queries) UpdateUserProfile(ctx context.Context, arg UpdateUserProfilePa
&i.EmojiStatusCollectibleID,
&i.EmojiStatusCollectible,
&i.LinkedCommunityID,
&i.Scam,
&i.Fake,
)
return i, err
}
@ -1126,7 +1276,7 @@ SET profile_color_set = $1::boolean,
profile_color_background_emoji_id = $3::bigint,
updated_at = now()
WHERE id = $4::bigint AND deleted_at IS NULL
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id, scam, fake
`
type UpdateUserProfileColorParams struct {
@ -1181,6 +1331,8 @@ func (q *Queries) UpdateUserProfileColor(ctx context.Context, arg UpdateUserProf
&i.EmojiStatusCollectibleID,
&i.EmojiStatusCollectible,
&i.LinkedCommunityID,
&i.Scam,
&i.Fake,
)
return i, err
}
@ -1190,7 +1342,7 @@ UPDATE users
SET username = $2,
updated_at = now()
WHERE id = $1 AND deleted_at IS NULL
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, deleted_at, deletion_source, deletion_reason, account_delete_at, emoji_status_collectible_id, emoji_status_collectible, linked_community_id, scam, fake
`
type UpdateUserUsernameParams struct {
@ -1238,6 +1390,8 @@ func (q *Queries) UpdateUserUsername(ctx context.Context, arg UpdateUserUsername
&i.EmojiStatusCollectibleID,
&i.EmojiStatusCollectible,
&i.LinkedCommunityID,
&i.Scam,
&i.Fake,
)
return i, err
}

View file

@ -150,20 +150,33 @@ WHERE collectible_revision_id=$1 AND crafted
if err != nil {
return err
}
modelID, err := chooseCollectibleAttribute(ctx, tx, "star_gift_collectible_models", revision.ID)
modelID, err := resolveCollectibleAttribute(ctx, tx, "star_gift_collectible_models", revision.ID, req.ModelAttributeID)
if err != nil {
return err
}
patternID, err := chooseCollectibleAttribute(ctx, tx, "star_gift_collectible_patterns", revision.ID)
patternID, err := resolveCollectibleAttribute(ctx, tx, "star_gift_collectible_patterns", revision.ID, req.PatternAttributeID)
if err != nil {
return err
}
backdropID, err := chooseCollectibleAttribute(ctx, tx, "star_gift_collectible_backdrops", revision.ID)
backdropID, err := resolveCollectibleAttribute(ctx, tx, "star_gift_collectible_backdrops", revision.ID, req.BackdropAttributeID)
if err != nil {
return err
}
num := revision.Issued + 1
if req.Num > 0 {
if req.Num > revision.SupplyTotal {
return domain.ErrStarGiftCollectibleInvalid
}
var numTaken bool
if err := tx.QueryRow(ctx, `SELECT EXISTS (SELECT 1 FROM unique_star_gifts WHERE gift_id=$1 AND num=$2)`, locked.GiftID, req.Num).Scan(&numTaken); err != nil {
return fmt.Errorf("check collectible number availability: %w", err)
}
if numTaken {
return domain.ErrStarGiftCollectibleNumberTaken
}
num = req.Num
}
var uniqueID int64
if err := tx.QueryRow(ctx, `SELECT nextval('unique_star_gift_id_seq')`).Scan(&uniqueID); err != nil {
return fmt.Errorf("allocate unique star gift id: %w", err)
@ -666,6 +679,30 @@ func debitStarGiftUpgrade(ctx context.Context, tx pgx.Tx, userID, amount int64,
return result, nil
}
// resolveCollectibleAttribute returns explicitID when it names a renderable
// attribute belonging to revisionID (admin-pinned choice), otherwise it falls
// back to the weighted random draw. Models excluded from the random pool
// (crafted) are also rejected for explicit selection to preserve invariants.
func resolveCollectibleAttribute(ctx context.Context, tx pgx.Tx, table string, revisionID, explicitID int64) (int64, error) {
if explicitID <= 0 {
return chooseCollectibleAttribute(ctx, tx, table, revisionID)
}
extra := ""
if table == "star_gift_collectible_models" {
extra = " AND NOT crafted"
}
var ok bool
if err := tx.QueryRow(ctx, fmt.Sprintf(`SELECT EXISTS (SELECT 1 FROM %s
WHERE id=$1 AND collectible_revision_id=$2 AND rarity_kind='permille' AND rarity_permille > 0%s)`, table, extra),
explicitID, revisionID).Scan(&ok); err != nil {
return 0, fmt.Errorf("validate collectible attribute: %w", err)
}
if !ok {
return 0, domain.ErrStarGiftCollectibleInvalid
}
return explicitID, nil
}
func chooseCollectibleAttribute(ctx context.Context, tx pgx.Tx, table string, revisionID int64) (int64, error) {
extra := ""
if table == "star_gift_collectible_models" {

View file

@ -321,6 +321,37 @@ func (s *UserStore) SetVerified(ctx context.Context, userID int64, verified bool
return userFromModel(row), nil
}
// SetSupport 设置/取消用户的 support 标记(官方客服账号)。
func (s *UserStore) SetSupport(ctx context.Context, userID int64, support bool) (domain.User, error) {
row, err := s.q.SetUserSupport(ctx, sqlcgen.SetUserSupportParams{
ID: userID,
Support: support,
})
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return domain.User{}, domain.ErrUserNotFound
}
return domain.User{}, fmt.Errorf("set user support: %w", err)
}
return userFromModel(row), nil
}
// SetScamFake 设置/取消用户的 scam 与 fake 标记bot 复用同一路径)。
func (s *UserStore) SetScamFake(ctx context.Context, userID int64, scam, fake bool) (domain.User, error) {
row, err := s.q.SetUserScamFake(ctx, sqlcgen.SetUserScamFakeParams{
ID: userID,
Scam: scam,
Fake: fake,
})
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return domain.User{}, domain.ErrUserNotFound
}
return domain.User{}, fmt.Errorf("set user scam/fake: %w", err)
}
return userFromModel(row), nil
}
// SweepExpiredPremium 清空到期会员行并返回清理后的用户。
func (s *UserStore) SweepExpiredPremium(ctx context.Context, now int64, limit int) ([]domain.User, error) {
if limit <= 0 {
@ -547,6 +578,8 @@ func userFromModel(r sqlcgen.User) domain.User {
Username: r.Username,
CountryCode: r.CountryCode,
Verified: r.Verified,
Scam: r.Scam,
Fake: r.Fake,
Support: r.Support,
Bot: r.IsBot,
BotInfoVersion: int(r.BotInfoVersion),