735 lines
21 KiB
Go
735 lines
21 KiB
Go
package managercenter
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"chatapp3-golang/internal/integration"
|
|
"chatapp3-golang/internal/model"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type teamManagerInfoRow struct {
|
|
ID int64 `gorm:"column:id;primaryKey"`
|
|
UserID int64 `gorm:"column:user_id"`
|
|
Origin string `gorm:"column:sys_origin"`
|
|
}
|
|
|
|
func (teamManagerInfoRow) TableName() string {
|
|
return "team_manager_base_info"
|
|
}
|
|
|
|
type userBaseInfoRow struct {
|
|
ID int64 `gorm:"column:id;primaryKey"`
|
|
Account string `gorm:"column:account"`
|
|
UserAvatar string `gorm:"column:user_avatar"`
|
|
UserNickname string `gorm:"column:user_nickname"`
|
|
CountryCode string `gorm:"column:country_code"`
|
|
CountryName string `gorm:"column:country_name"`
|
|
OriginSys string `gorm:"column:origin_sys"`
|
|
AccountStatus string `gorm:"column:account_status"`
|
|
IsDel bool `gorm:"column:is_del"`
|
|
}
|
|
|
|
func (userBaseInfoRow) TableName() string {
|
|
return "user_base_info"
|
|
}
|
|
|
|
type propsSourceRecordRow struct {
|
|
ID int64 `gorm:"column:id;primaryKey"`
|
|
Type string `gorm:"column:type"`
|
|
Code string `gorm:"column:code"`
|
|
Name string `gorm:"column:name"`
|
|
Cover string `gorm:"column:cover"`
|
|
SourceURL string `gorm:"column:source_url"`
|
|
Expand string `gorm:"column:expand"`
|
|
AdminFree bool `gorm:"column:admin_free"`
|
|
IsDel bool `gorm:"column:is_del"`
|
|
}
|
|
|
|
func (propsSourceRecordRow) TableName() string {
|
|
return "props_source_record"
|
|
}
|
|
|
|
type propsNobleVIPAbilityRow struct {
|
|
ID int64 `gorm:"column:id;primaryKey"`
|
|
VIPLevel int `gorm:"column:vip_level"`
|
|
AvatarFrameID int64 `gorm:"column:avatar_frame_id"`
|
|
CarID int64 `gorm:"column:car_id"`
|
|
ChatBubbleID int64 `gorm:"column:chat_bubble_id"`
|
|
DataCardID int64 `gorm:"column:data_card_id"`
|
|
}
|
|
|
|
func (propsNobleVIPAbilityRow) TableName() string {
|
|
return "props_noble_vip_ability"
|
|
}
|
|
|
|
func (s *Service) GetProfile(ctx context.Context, user AuthUser) (*ProfileResponse, error) {
|
|
if err := s.ensureManager(ctx, user); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
view, err := s.loadUserViewByID(ctx, user.UserID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &ProfileResponse{Manager: view}, nil
|
|
}
|
|
|
|
func (s *Service) SearchUser(ctx context.Context, user AuthUser, account string) (*UserSearchResponse, error) {
|
|
if err := s.ensureManager(ctx, user); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
account = strings.TrimSpace(account)
|
|
if account == "" {
|
|
return nil, badRequest("account_required", "account is required")
|
|
}
|
|
|
|
view, err := s.findUserView(ctx, account)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &UserSearchResponse{User: view}, nil
|
|
}
|
|
|
|
func (s *Service) ListProps(ctx context.Context, user AuthUser, propsType string) (*PropsListResponse, error) {
|
|
if err := s.ensureManager(ctx, user); err != nil {
|
|
return nil, err
|
|
}
|
|
if s.db == nil {
|
|
return nil, serviceUnavailable("database_unavailable", "database is unavailable")
|
|
}
|
|
|
|
propsType = normalizePropsType(propsType)
|
|
if propsType == propsTypeNobleVIP {
|
|
return s.listVIPProps(ctx, user)
|
|
}
|
|
|
|
query := s.db.WithContext(ctx).
|
|
Table("props_source_record").
|
|
Select("id, type, code, name, cover, source_url, expand, admin_free, is_del").
|
|
Where("is_del = ? AND admin_free = ?", false, true)
|
|
if propsType != "" {
|
|
query = query.Where("type = ?", propsType)
|
|
}
|
|
|
|
var rows []propsSourceRecordRow
|
|
if err := query.Order("type ASC, id ASC").Find(&rows).Error; err != nil {
|
|
return nil, serverError("props_query_failed", err.Error())
|
|
}
|
|
|
|
vipLevels, err := s.loadVIPLevels(ctx, rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
records := make([]PropsView, 0, len(rows))
|
|
for _, row := range rows {
|
|
view, ok := propsRowToView(row, vipLevels)
|
|
if !ok {
|
|
continue
|
|
}
|
|
records = append(records, view)
|
|
}
|
|
|
|
return &PropsListResponse{
|
|
Type: propsType,
|
|
Records: records,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) SendProps(ctx context.Context, user AuthUser, req SendPropsRequest) (*SendPropsResponse, error) {
|
|
if err := s.ensureManager(ctx, user); err != nil {
|
|
return nil, err
|
|
}
|
|
if req.AcceptUserID.Int64() <= 0 {
|
|
return nil, badRequest("accept_user_required", "acceptUserId is required")
|
|
}
|
|
if req.PropsID.Int64() <= 0 {
|
|
return nil, badRequest("props_required", "propsId is required")
|
|
}
|
|
if s.java == nil {
|
|
return nil, serviceUnavailable("java_gateway_unavailable", "java gateway is unavailable")
|
|
}
|
|
|
|
target, err := s.loadUserRowByID(ctx, req.AcceptUserID.Int64())
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, notFound("user_not_found", "user not found")
|
|
}
|
|
return nil, serverError("user_query_failed", err.Error())
|
|
}
|
|
|
|
prop, err := s.loadGiftableProps(ctx, req.PropsID.Int64())
|
|
if err != nil {
|
|
if isAppErrorCode(err, "prop_not_giftable") {
|
|
if resp, vipErr := s.sendVIPGift(ctx, user, target, req.PropsID.Int64()); vipErr != nil {
|
|
return nil, vipErr
|
|
} else if resp != nil {
|
|
return resp, nil
|
|
}
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
days := propsGiftDays(prop.Type)
|
|
if prop.Type == propsTypeBadge {
|
|
if err := s.java.ActivateTemporaryBadge(ctx, req.AcceptUserID.Int64(), prop.ID, days); err != nil {
|
|
return nil, serverError("send_badge_failed", err.Error())
|
|
}
|
|
return &SendPropsResponse{
|
|
Success: true,
|
|
AcceptUserID: req.AcceptUserID,
|
|
PropsID: req.PropsID,
|
|
Type: prop.Type,
|
|
Days: days,
|
|
}, nil
|
|
}
|
|
|
|
grants := []propsGrant{{
|
|
propsID: prop.ID,
|
|
typ: prop.Type,
|
|
days: days,
|
|
}}
|
|
if prop.Type == propsTypeNobleVIP {
|
|
if _, err := s.loadGiftableNobleVIPAbility(ctx, prop.ID); err != nil {
|
|
return nil, err
|
|
}
|
|
ability, err := s.java.GetNobleVIPAbility(ctx, prop.ID)
|
|
if err != nil {
|
|
return nil, serverError("vip_ability_query_failed", err.Error())
|
|
}
|
|
grants = append(grants, nobleVIPAccessoryGrants(ability)...)
|
|
}
|
|
|
|
for _, grant := range grants {
|
|
if err := s.java.GivePropsBackpack(ctx, integration.GivePropsBackpackRequest{
|
|
AcceptUserID: req.AcceptUserID.Int64(),
|
|
PropsID: grant.propsID,
|
|
Type: grant.typ,
|
|
Origin: adminFreeOrigin,
|
|
OriginDesc: adminFreeOriginDesc,
|
|
Days: grant.days,
|
|
UseProps: true,
|
|
AllowGive: false,
|
|
OpUser: user.UserID,
|
|
}); err != nil {
|
|
return nil, serverError("send_props_failed", err.Error())
|
|
}
|
|
}
|
|
|
|
if prop.Type == propsTypeNobleVIP {
|
|
if err := s.switchMaxNobleVIP(ctx, req.AcceptUserID.Int64()); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return &SendPropsResponse{
|
|
Success: true,
|
|
AcceptUserID: req.AcceptUserID,
|
|
PropsID: req.PropsID,
|
|
Type: prop.Type,
|
|
Days: days,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) BanUser(ctx context.Context, user AuthUser, req BanUserRequest) (*BanUserResponse, error) {
|
|
if err := s.ensureManager(ctx, user); err != nil {
|
|
return nil, err
|
|
}
|
|
if req.UserID.Int64() <= 0 {
|
|
return nil, badRequest("user_required", "userId is required")
|
|
}
|
|
if s.java == nil {
|
|
return nil, serviceUnavailable("java_gateway_unavailable", "java gateway is unavailable")
|
|
}
|
|
|
|
target, err := s.loadUserRowByID(ctx, req.UserID.Int64())
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, notFound("user_not_found", "user not found")
|
|
}
|
|
return nil, serverError("user_query_failed", err.Error())
|
|
}
|
|
if isUnbannableAccountStatus(target.AccountStatus) {
|
|
return nil, badRequest("user_already_banned", "user is already banned")
|
|
}
|
|
|
|
hasRecharge, err := s.userHasRecharge(ctx, target.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if hasRecharge {
|
|
return nil, badRequest("user_recharged", "only users without recharge can be banned")
|
|
}
|
|
|
|
description := buildBanDescription(user.UserID, req.Reason)
|
|
if err := s.java.PunishAccount(ctx, integration.PunishAccountRequest{
|
|
BeOptUserID: target.ID,
|
|
OptUserID: user.UserID,
|
|
Status: accountStatusArchive,
|
|
Description: description,
|
|
}); err != nil {
|
|
return nil, serverError("ban_user_failed", err.Error())
|
|
}
|
|
|
|
return &BanUserResponse{
|
|
Success: true,
|
|
UserID: req.UserID,
|
|
AccountStatus: accountStatusArchive,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) UnbanUser(ctx context.Context, user AuthUser, req UnbanUserRequest) (*UnbanUserResponse, error) {
|
|
if err := s.ensureManager(ctx, user); err != nil {
|
|
return nil, err
|
|
}
|
|
if req.UserID.Int64() <= 0 {
|
|
return nil, badRequest("user_required", "userId is required")
|
|
}
|
|
if s.java == nil {
|
|
return nil, serviceUnavailable("java_gateway_unavailable", "java gateway is unavailable")
|
|
}
|
|
|
|
target, err := s.loadUserRowByID(ctx, req.UserID.Int64())
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, notFound("user_not_found", "user not found")
|
|
}
|
|
return nil, serverError("user_query_failed", err.Error())
|
|
}
|
|
if !isUnbannableAccountStatus(target.AccountStatus) {
|
|
return nil, badRequest("user_not_banned", "only banned or frozen users can be unbanned")
|
|
}
|
|
|
|
description := buildUnbanDescription(user.UserID, req.Reason)
|
|
if err := s.java.UnblockAccount(ctx, integration.UnblockAccountRequest{
|
|
BeOptUserID: target.ID,
|
|
OptUserID: user.UserID,
|
|
Description: description,
|
|
}); err != nil {
|
|
return nil, serverError("unban_user_failed", err.Error())
|
|
}
|
|
|
|
return &UnbanUserResponse{
|
|
Success: true,
|
|
UserID: req.UserID,
|
|
AccountStatus: accountStatusNormal,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) ensureManager(ctx context.Context, user AuthUser) error {
|
|
if user.UserID <= 0 {
|
|
return unauthorized("invalid_user", "authenticated user is required")
|
|
}
|
|
if s.db == nil {
|
|
return serviceUnavailable("database_unavailable", "database is unavailable")
|
|
}
|
|
|
|
var count int64
|
|
err := s.db.WithContext(ctx).
|
|
Table("team_manager_base_info").
|
|
Where("user_id = ?", user.UserID).
|
|
Count(&count).Error
|
|
if err != nil {
|
|
return serverError("manager_query_failed", err.Error())
|
|
}
|
|
if count == 0 {
|
|
return forbidden("not_manager", "user is not manager")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) findUserView(ctx context.Context, account string) (UserView, error) {
|
|
if userID, err := strconv.ParseInt(account, 10, 64); err == nil && userID > 0 {
|
|
if row, err := s.loadUserRowByID(ctx, userID); err == nil {
|
|
return s.userRowToView(ctx, row)
|
|
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return UserView{}, serverError("user_query_failed", err.Error())
|
|
}
|
|
}
|
|
|
|
if s.java != nil {
|
|
profile, err := s.java.GetUserProfileByAccountOne(ctx, account)
|
|
if err == nil && int64(profile.ID) > 0 {
|
|
if row, rowErr := s.loadUserRowByID(ctx, int64(profile.ID)); rowErr == nil {
|
|
view, viewErr := s.userRowToView(ctx, row)
|
|
if viewErr != nil {
|
|
return UserView{}, viewErr
|
|
}
|
|
overlayJavaProfile(&view, profile)
|
|
return view, nil
|
|
} else if !errors.Is(rowErr, gorm.ErrRecordNotFound) {
|
|
return UserView{}, serverError("user_query_failed", rowErr.Error())
|
|
}
|
|
return s.javaProfileToView(ctx, profile)
|
|
}
|
|
}
|
|
|
|
row, err := s.loadUserRowByAccount(ctx, account)
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return UserView{}, notFound("user_not_found", "user not found")
|
|
}
|
|
return UserView{}, serverError("user_query_failed", err.Error())
|
|
}
|
|
return s.userRowToView(ctx, row)
|
|
}
|
|
|
|
func (s *Service) loadUserViewByID(ctx context.Context, userID int64) (UserView, error) {
|
|
row, err := s.loadUserRowByID(ctx, userID)
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return UserView{}, notFound("user_not_found", "user not found")
|
|
}
|
|
return UserView{}, serverError("user_query_failed", err.Error())
|
|
}
|
|
return s.userRowToView(ctx, row)
|
|
}
|
|
|
|
func (s *Service) loadUserRowByID(ctx context.Context, userID int64) (userBaseInfoRow, error) {
|
|
var row userBaseInfoRow
|
|
err := s.db.WithContext(ctx).
|
|
Table("user_base_info").
|
|
Where("id = ? AND is_del = ?", userID, false).
|
|
First(&row).Error
|
|
return row, err
|
|
}
|
|
|
|
func (s *Service) loadUserRowByAccount(ctx context.Context, account string) (userBaseInfoRow, error) {
|
|
var row userBaseInfoRow
|
|
err := s.db.WithContext(ctx).
|
|
Table("user_base_info").
|
|
Where("account = ? AND is_del = ?", account, false).
|
|
First(&row).Error
|
|
return row, err
|
|
}
|
|
|
|
func (s *Service) userRowToView(ctx context.Context, row userBaseInfoRow) (UserView, error) {
|
|
hasRecharge, canBan := s.userRechargeEligibility(ctx, row.ID)
|
|
canBan = canBan && !isUnbannableAccountStatus(row.AccountStatus)
|
|
return UserView{
|
|
UserID: ID(row.ID),
|
|
Account: row.Account,
|
|
UserAvatar: row.UserAvatar,
|
|
UserNickname: row.UserNickname,
|
|
CountryCode: row.CountryCode,
|
|
CountryName: row.CountryName,
|
|
OriginSys: row.OriginSys,
|
|
AccountStatus: row.AccountStatus,
|
|
HasRecharge: hasRecharge,
|
|
CanBan: canBan,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) javaProfileToView(ctx context.Context, profile integration.UserProfile) (UserView, error) {
|
|
hasRecharge, canBan := s.userRechargeEligibility(ctx, int64(profile.ID))
|
|
canBan = canBan && !isUnbannableAccountStatus(profile.AccountStatus)
|
|
return UserView{
|
|
UserID: ID(profile.ID),
|
|
Account: profile.Account,
|
|
UserAvatar: profile.UserAvatar,
|
|
UserNickname: profile.UserNickname,
|
|
CountryCode: profile.CountryCode,
|
|
CountryName: profile.CountryName,
|
|
OriginSys: profile.OriginSys,
|
|
AccountStatus: profile.AccountStatus,
|
|
HasRecharge: hasRecharge,
|
|
CanBan: canBan,
|
|
}, nil
|
|
}
|
|
|
|
func overlayJavaProfile(view *UserView, profile integration.UserProfile) {
|
|
if profile.Account != "" {
|
|
view.Account = profile.Account
|
|
}
|
|
if profile.UserAvatar != "" {
|
|
view.UserAvatar = profile.UserAvatar
|
|
}
|
|
if profile.UserNickname != "" {
|
|
view.UserNickname = profile.UserNickname
|
|
}
|
|
if profile.CountryCode != "" {
|
|
view.CountryCode = profile.CountryCode
|
|
}
|
|
if profile.CountryName != "" {
|
|
view.CountryName = profile.CountryName
|
|
}
|
|
if profile.OriginSys != "" {
|
|
view.OriginSys = profile.OriginSys
|
|
}
|
|
if profile.AccountStatus != "" {
|
|
view.AccountStatus = profile.AccountStatus
|
|
if isUnbannableAccountStatus(profile.AccountStatus) {
|
|
view.CanBan = false
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Service) userHasRecharge(ctx context.Context, userID int64) (bool, error) {
|
|
if userID <= 0 {
|
|
return false, nil
|
|
}
|
|
if s.java == nil {
|
|
return false, nil
|
|
}
|
|
|
|
recharges, err := s.java.MapUserTotalRecharge(ctx, []int64{userID})
|
|
if err != nil {
|
|
return false, serverError("recharge_query_failed", err.Error())
|
|
}
|
|
for _, item := range recharges[userID] {
|
|
raw := strings.TrimSpace(string(item.Amount))
|
|
if raw == "" {
|
|
continue
|
|
}
|
|
amount, err := strconv.ParseFloat(raw, 64)
|
|
if err != nil {
|
|
return false, serverError("recharge_query_failed", err.Error())
|
|
}
|
|
if amount > 0 {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func (s *Service) userRechargeEligibility(ctx context.Context, userID int64) (bool, bool) {
|
|
hasRecharge, err := s.userHasRecharge(ctx, userID)
|
|
if err != nil {
|
|
return false, false
|
|
}
|
|
return hasRecharge, !hasRecharge
|
|
}
|
|
|
|
func (s *Service) loadGiftableProps(ctx context.Context, propsID int64) (propsSourceRecordRow, error) {
|
|
var row propsSourceRecordRow
|
|
err := s.db.WithContext(ctx).
|
|
Table("props_source_record").
|
|
Select("id, type, code, name, cover, source_url, expand, admin_free, is_del").
|
|
Where("id = ? AND is_del = ? AND admin_free = ?", propsID, false, true).
|
|
First(&row).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return propsSourceRecordRow{}, badRequest("prop_not_giftable", "props is not admin-free giftable")
|
|
}
|
|
if err != nil {
|
|
return propsSourceRecordRow{}, serverError("props_query_failed", err.Error())
|
|
}
|
|
return row, nil
|
|
}
|
|
|
|
func (s *Service) loadGiftableNobleVIPAbility(ctx context.Context, propsID int64) (propsNobleVIPAbilityRow, error) {
|
|
var row propsNobleVIPAbilityRow
|
|
err := s.db.WithContext(ctx).
|
|
Table("props_noble_vip_ability").
|
|
Where("id = ?", propsID).
|
|
First(&row).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return propsNobleVIPAbilityRow{}, badRequest("vip_not_giftable", "noble VIP ability is not giftable")
|
|
}
|
|
if err != nil {
|
|
return propsNobleVIPAbilityRow{}, serverError("vip_ability_query_failed", err.Error())
|
|
}
|
|
if row.VIPLevel > maxGiftableVIPLevel {
|
|
return propsNobleVIPAbilityRow{}, badRequest("vip_not_giftable", "only noble VIP level 1-3 can be gifted")
|
|
}
|
|
return row, nil
|
|
}
|
|
|
|
func (s *Service) loadVIPLevels(ctx context.Context, rows []propsSourceRecordRow) (map[int64]int, error) {
|
|
ids := make([]int64, 0)
|
|
for _, row := range rows {
|
|
if row.Type == propsTypeNobleVIP {
|
|
ids = append(ids, row.ID)
|
|
}
|
|
}
|
|
if len(ids) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
var abilities []propsNobleVIPAbilityRow
|
|
if err := s.db.WithContext(ctx).
|
|
Table("props_noble_vip_ability").
|
|
Select("id, vip_level").
|
|
Where("id IN ?", ids).
|
|
Find(&abilities).Error; err != nil {
|
|
return nil, serverError("vip_ability_query_failed", err.Error())
|
|
}
|
|
|
|
levels := make(map[int64]int, len(abilities))
|
|
for _, ability := range abilities {
|
|
levels[ability.ID] = ability.VIPLevel
|
|
}
|
|
return levels, nil
|
|
}
|
|
|
|
type propsGrant struct {
|
|
propsID int64
|
|
typ string
|
|
days int
|
|
}
|
|
|
|
func nobleVIPAccessoryGrants(ability integration.PropsNobleVIPAbility) []propsGrant {
|
|
grants := make([]propsGrant, 0, 4)
|
|
appendIfPositive := func(propsID int64, typ string) {
|
|
if propsID <= 0 {
|
|
return
|
|
}
|
|
grants = append(grants, propsGrant{propsID: propsID, typ: typ, days: defaultVIPGiftDays})
|
|
}
|
|
appendIfPositive(int64(ability.CarID), propsTypeRide)
|
|
appendIfPositive(int64(ability.AvatarFrameID), propsTypeAvatarFrame)
|
|
appendIfPositive(int64(ability.ChatBubbleID), propsTypeChatBubble)
|
|
appendIfPositive(int64(ability.DataCardID), propsTypeDataCard)
|
|
return grants
|
|
}
|
|
|
|
func (s *Service) switchMaxNobleVIP(ctx context.Context, userID int64) error {
|
|
ability, err := s.java.GetUserMaxNobleVIPAbility(ctx, userID)
|
|
if err != nil {
|
|
return serverError("vip_ability_query_failed", err.Error())
|
|
}
|
|
|
|
ids := []int64{
|
|
int64(ability.ID),
|
|
int64(ability.AvatarFrameID),
|
|
int64(ability.DataCardID),
|
|
int64(ability.CarID),
|
|
int64(ability.ChatBubbleID),
|
|
}
|
|
for _, propsID := range compactPositiveIDs(ids) {
|
|
if err := s.java.SwitchUseProps(ctx, userID, propsID); err != nil {
|
|
return serverError("switch_props_failed", err.Error())
|
|
}
|
|
}
|
|
if err := s.java.RemoveUserProfileCacheAll(ctx, userID); err != nil {
|
|
return serverError("remove_profile_cache_failed", err.Error())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func compactPositiveIDs(ids []int64) []int64 {
|
|
seen := make(map[int64]struct{}, len(ids))
|
|
out := make([]int64, 0, len(ids))
|
|
for _, id := range ids {
|
|
if id <= 0 {
|
|
continue
|
|
}
|
|
if _, exists := seen[id]; exists {
|
|
continue
|
|
}
|
|
seen[id] = struct{}{}
|
|
out = append(out, id)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func propsRowToView(row propsSourceRecordRow, vipLevels map[int64]int) (PropsView, bool) {
|
|
level := 0
|
|
if row.Type == propsTypeNobleVIP {
|
|
var exists bool
|
|
level, exists = vipLevels[row.ID]
|
|
if !exists || level > 3 {
|
|
return PropsView{}, false
|
|
}
|
|
}
|
|
return PropsView{
|
|
ID: ID(row.ID),
|
|
Type: row.Type,
|
|
Code: row.Code,
|
|
Name: row.Name,
|
|
Cover: row.Cover,
|
|
SourceURL: row.SourceURL,
|
|
Expand: row.Expand,
|
|
Days: propsGiftDays(row.Type),
|
|
VIPLevel: level,
|
|
}, true
|
|
}
|
|
|
|
func propsGiftDays(propsType string) int {
|
|
if propsType == propsTypeNobleVIP {
|
|
return defaultVIPGiftDays
|
|
}
|
|
return defaultGiftDays
|
|
}
|
|
|
|
func normalizePropsType(propsType string) string {
|
|
return strings.ToUpper(strings.TrimSpace(propsType))
|
|
}
|
|
|
|
func buildBanDescription(managerID int64, reason string) string {
|
|
reason = strings.TrimSpace(reason)
|
|
if len([]rune(reason)) > 200 {
|
|
runes := []rune(reason)
|
|
reason = string(runes[:200])
|
|
}
|
|
if reason == "" {
|
|
return fmt.Sprintf("manager center ban non-recharged user, manager=%d", managerID)
|
|
}
|
|
return fmt.Sprintf("manager center ban non-recharged user, manager=%d, reason=%s", managerID, reason)
|
|
}
|
|
|
|
func buildUnbanDescription(managerID int64, reason string) string {
|
|
reason = strings.TrimSpace(reason)
|
|
if len([]rune(reason)) > 200 {
|
|
runes := []rune(reason)
|
|
reason = string(runes[:200])
|
|
}
|
|
if reason == "" {
|
|
return fmt.Sprintf("manager center unban user, manager=%d", managerID)
|
|
}
|
|
return fmt.Sprintf("manager center unban user, manager=%d, reason=%s", managerID, reason)
|
|
}
|
|
|
|
func isUnbannableAccountStatus(status string) bool {
|
|
switch strings.ToUpper(strings.TrimSpace(status)) {
|
|
case accountStatusFreeze, accountStatusArchive, accountStatusArchiveDevice:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func isAppErrorCode(err error, code string) bool {
|
|
var appErr *AppError
|
|
return errors.As(err, &appErr) && appErr.Code == code
|
|
}
|
|
|
|
func normalizeSysOrigin(sysOrigin string) string {
|
|
sysOrigin = strings.ToUpper(strings.TrimSpace(sysOrigin))
|
|
if sysOrigin == "" {
|
|
return "LIKEI"
|
|
}
|
|
return sysOrigin
|
|
}
|
|
|
|
func vipPropsViewFromConfig(row model.VipLevelConfig) PropsView {
|
|
days := row.DurationDays
|
|
if days <= 0 {
|
|
days = defaultVIPGiftDays
|
|
}
|
|
name := strings.TrimSpace(row.DisplayName)
|
|
if name == "" {
|
|
name = fmt.Sprintf("VIP %d", row.Level)
|
|
}
|
|
code := strings.TrimSpace(row.LevelCode)
|
|
if code == "" {
|
|
code = fmt.Sprintf("VIP%d", row.Level)
|
|
}
|
|
return PropsView{
|
|
ID: ID(row.ID),
|
|
Type: propsTypeNobleVIP,
|
|
Code: code,
|
|
Name: name,
|
|
Cover: row.BadgeURL,
|
|
SourceURL: row.BadgeURL,
|
|
Days: days,
|
|
VIPLevel: row.Level,
|
|
}
|
|
}
|