206 lines
6.8 KiB
Go
206 lines
6.8 KiB
Go
package userbadge
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"chatapp3-golang/internal/model"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type badgePictureRow struct {
|
|
BadgeID int64 `gorm:"column:badge_id"`
|
|
SourceType sql.NullString `gorm:"column:source_type"`
|
|
BadgeLevel sql.NullInt64 `gorm:"column:badge_level"`
|
|
Milestone sql.NullInt64 `gorm:"column:milestone"`
|
|
BadgeName sql.NullString `gorm:"column:badge_name"`
|
|
BadgeKey sql.NullString `gorm:"column:badge_key"`
|
|
SelectURL sql.NullString `gorm:"column:select_url"`
|
|
NotSelectURL sql.NullString `gorm:"column:not_select_url"`
|
|
AnimationURL sql.NullString `gorm:"column:animation_url"`
|
|
ExpireTime time.Time `gorm:"column:expire_time"`
|
|
}
|
|
|
|
// Current returns the current user's badge list split by long and short display type.
|
|
func (s *Service) Current(ctx context.Context, user AuthUser) (*CurrentBadgeListResponse, error) {
|
|
if s == nil || s.db == nil {
|
|
return nil, NewAppError(http.StatusServiceUnavailable, "database_unavailable", "database is unavailable")
|
|
}
|
|
sysOrigin, err := normalizeUser(user)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
now := s.now()
|
|
longBadges, vipBadgeID, err := s.loadLongBadges(ctx, sysOrigin, user.UserID, now)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
shortBadges, err := s.loadShortBadges(ctx, sysOrigin, user.UserID, vipBadgeID, now)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
badges := make([]CurrentBadgeItem, 0, len(longBadges)+len(shortBadges))
|
|
badges = append(badges, longBadges...)
|
|
badges = append(badges, shortBadges...)
|
|
|
|
return &CurrentBadgeListResponse{
|
|
UserID: user.UserID,
|
|
SysOrigin: sysOrigin,
|
|
Badges: badges,
|
|
LongBadges: longBadges,
|
|
ShortBadges: shortBadges,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) loadLongBadges(ctx context.Context, sysOrigin string, userID int64, now time.Time) ([]CurrentBadgeItem, int64, error) {
|
|
var state model.UserVipState
|
|
err := s.db.WithContext(ctx).
|
|
Where("sys_origin = ? AND user_id = ?", sysOrigin, userID).
|
|
First(&state).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return []CurrentBadgeItem{}, 0, nil
|
|
}
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
if !isActiveVIPState(state, now) || state.BadgeResourceID <= 0 {
|
|
return []CurrentBadgeItem{}, 0, nil
|
|
}
|
|
|
|
row, err := s.loadBadgePicture(ctx, sysOrigin, state.BadgeResourceID)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
item := CurrentBadgeItem{
|
|
ID: formatID(state.BadgeResourceID),
|
|
BadgeID: formatID(state.BadgeResourceID),
|
|
Type: badgeDisplayTypeLong,
|
|
SourceType: badgeSourceTypeVIP,
|
|
BadgeLevel: nullInt(row.BadgeLevel),
|
|
BadgeKey: nullString(row.BadgeKey),
|
|
Level: state.Level,
|
|
LevelCode: state.LevelCode,
|
|
DisplayName: state.DisplayName,
|
|
Use: true,
|
|
ExpireTime: formatMillis(state.ExpireAt),
|
|
ExpireAt: formatTime(state.ExpireAt),
|
|
}
|
|
item.BadgeName = firstNonEmpty(nullString(row.BadgeName), state.BadgeName, state.DisplayName)
|
|
item.Name = item.BadgeName
|
|
item.SelectURL = firstNonEmpty(nullString(row.SelectURL), state.BadgeURL)
|
|
item.NotSelectURL = nullString(row.NotSelectURL)
|
|
item.AnimationURL = nullString(row.AnimationURL)
|
|
item.URL = firstNonEmpty(item.SelectURL, state.BadgeURL)
|
|
if row.Milestone.Valid && row.Milestone.Int64 > 0 {
|
|
item.Milestone = formatID(row.Milestone.Int64)
|
|
}
|
|
if item.Name == "" && item.URL == "" {
|
|
return []CurrentBadgeItem{}, state.BadgeResourceID, nil
|
|
}
|
|
return []CurrentBadgeItem{item}, state.BadgeResourceID, nil
|
|
}
|
|
|
|
func (s *Service) loadShortBadges(ctx context.Context, sysOrigin string, userID int64, excludeBadgeID int64, now time.Time) ([]CurrentBadgeItem, error) {
|
|
rows := make([]badgePictureRow, 0)
|
|
query := s.db.WithContext(ctx).
|
|
Table("user_badge_backpack AS backpack").
|
|
Select(`
|
|
backpack.badge_id AS badge_id,
|
|
backpack.expire_time AS expire_time,
|
|
config.type AS source_type,
|
|
config.badge_level AS badge_level,
|
|
config.milestone AS milestone,
|
|
config.badge_name AS badge_name,
|
|
config.badge_key AS badge_key,
|
|
picture.select_url AS select_url,
|
|
picture.not_select_url AS not_select_url,
|
|
picture.animation_url AS animation_url
|
|
`).
|
|
Joins("JOIN sys_badge_config AS config ON config.id = backpack.badge_id").
|
|
Joins("JOIN sys_badge_picture_config AS picture ON picture.badge_config_id = config.id AND picture.sys_origin = ?", sysOrigin).
|
|
Where("backpack.user_id = ?", userID).
|
|
Where("backpack.is_use_props = ?", true).
|
|
Where("config.is_del = ?", false).
|
|
Where("config.type IN ?", []string{badgeSourceTypeActivity, badgeSourceTypeAchievement}).
|
|
Where("(backpack.expire_type = ? OR backpack.expire_time > ?)", expireTypePermanent, now).
|
|
Order("backpack.update_time DESC, backpack.id DESC")
|
|
if excludeBadgeID > 0 {
|
|
query = query.Where("backpack.badge_id <> ?", excludeBadgeID)
|
|
}
|
|
if err := query.Scan(&rows).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
items := make([]CurrentBadgeItem, 0, len(rows))
|
|
for _, row := range rows {
|
|
item := CurrentBadgeItem{
|
|
ID: formatID(row.BadgeID),
|
|
BadgeID: formatID(row.BadgeID),
|
|
Type: badgeDisplayTypeShort,
|
|
SourceType: nullString(row.SourceType),
|
|
Name: nullString(row.BadgeName),
|
|
BadgeName: nullString(row.BadgeName),
|
|
BadgeKey: nullString(row.BadgeKey),
|
|
BadgeLevel: nullInt(row.BadgeLevel),
|
|
URL: nullString(row.SelectURL),
|
|
SelectURL: nullString(row.SelectURL),
|
|
NotSelectURL: nullString(row.NotSelectURL),
|
|
AnimationURL: nullString(row.AnimationURL),
|
|
Use: true,
|
|
ExpireTime: formatMillis(row.ExpireTime),
|
|
ExpireAt: formatTime(row.ExpireTime),
|
|
}
|
|
if row.Milestone.Valid && row.Milestone.Int64 > 0 {
|
|
item.Milestone = formatID(row.Milestone.Int64)
|
|
}
|
|
items = append(items, item)
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func (s *Service) loadBadgePicture(ctx context.Context, sysOrigin string, badgeID int64) (badgePictureRow, error) {
|
|
var row badgePictureRow
|
|
err := s.db.WithContext(ctx).
|
|
Table("sys_badge_config AS config").
|
|
Select(`
|
|
config.id AS badge_id,
|
|
config.type AS source_type,
|
|
config.badge_level AS badge_level,
|
|
config.milestone AS milestone,
|
|
config.badge_name AS badge_name,
|
|
config.badge_key AS badge_key,
|
|
picture.select_url AS select_url,
|
|
picture.not_select_url AS not_select_url,
|
|
picture.animation_url AS animation_url
|
|
`).
|
|
Joins("LEFT JOIN sys_badge_picture_config AS picture ON picture.badge_config_id = config.id AND picture.sys_origin = ?", sysOrigin).
|
|
Where("config.id = ? AND config.is_del = ?", badgeID, false).
|
|
Scan(&row).Error
|
|
return row, err
|
|
}
|
|
|
|
func isActiveVIPState(state model.UserVipState, now time.Time) bool {
|
|
return strings.EqualFold(strings.TrimSpace(state.Status), statusActive) && state.Level > 0 && state.ExpireAt.After(now)
|
|
}
|
|
|
|
func nullString(value sql.NullString) string {
|
|
if !value.Valid {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(value.String)
|
|
}
|
|
|
|
func nullInt(value sql.NullInt64) int {
|
|
if !value.Valid {
|
|
return 0
|
|
}
|
|
return int(value.Int64)
|
|
}
|