package asset import ( "context" "gorm.io/gorm" "gorm.io/gorm/clause" biz "kra/internal/biz/asset" "kra/internal/data/ledger" "time" ) type Provider interface{ DB() *gorm.DB } type repo struct{ data Provider } func NewRepo(data Provider) biz.Repo { return &repo{data: data} } func (r *repo) db(ctx context.Context) *gorm.DB { return r.data.DB().WithContext(ctx) } type row struct { UserID uint `gorm:"column:user_id"` Username string Nickname string Avatar string Enable int CreatedAt time.Time DiamondBalance, TotalRechargeCents, TotalSpentDiamonds int64 LastLoginAt *time.Time } func query(db *gorm.DB, f biz.Filter) *gorm.DB { q := db.Table("sys_users").Joins("LEFT JOIN live_wallets ON live_wallets.user_id = sys_users.id").Where("sys_users.deleted_at IS NULL") if f.Keyword != "" { v := "%" + f.Keyword + "%" q = q.Where("sys_users.username LIKE ? OR sys_users.nick_name LIKE ?", v, v) } if f.UserID > 0 { q = q.Where("sys_users.id = ?", f.UserID) } if f.Enabled != nil { if *f.Enabled { q = q.Where("sys_users.enable = 1") } else { q = q.Where("sys_users.enable <> 1") } } if len(f.CreatedAt) == 2 { q = q.Where("sys_users.created_at BETWEEN ? AND ?", f.CreatedAt[0], f.CreatedAt[1]) } return q } func (r *repo) List(ctx context.Context, f biz.Filter, o biz.ListOptions) ([]*biz.User, int64, error) { q := query(r.db(ctx), f) var total int64 if err := q.Count(&total).Error; err != nil { return nil, 0, err } var rows []row sel := "sys_users.id AS user_id,sys_users.username,sys_users.nick_name AS nickname,sys_users.header_img AS avatar,sys_users.enable,sys_users.created_at,COALESCE(live_wallets.diamond_balance,0) AS diamond_balance,COALESCE(live_wallets.total_recharge_cents,0) AS total_recharge_cents,COALESCE(live_wallets.total_spent_diamonds,0) AS total_spent_diamonds,(SELECT MAX(created_at) FROM sys_login_logs WHERE user_id=sys_users.id AND status=?) AS last_login_at" if err := q.Select(sel, true).Order(userOrder(o.OrderBy)).Offset(o.Offset).Limit(o.Limit).Scan(&rows).Error; err != nil { return nil, 0, err } out := make([]*biz.User, 0, len(rows)) for i := range rows { out = append(out, toBiz(&rows[i])) } return out, total, nil } func (r *repo) Get(ctx context.Context, id uint) (*biz.User, error) { items, _, err := r.List(ctx, biz.Filter{UserID: id}, biz.ListOptions{Limit: 1}) if err != nil { return nil, err } if len(items) == 0 { return nil, biz.ErrNotFound } v := items[0] var orders []ledger.RechargeOrderPO if err := r.db(ctx).Where("user_id = ?", id).Order("created_at DESC").Limit(20).Find(&orders).Error; err != nil { return nil, err } for _, x := range orders { status := "pending" if x.CreditedAt != nil { status = "paid" } v.RecentOrders = append(v.RecentOrders, biz.OrderRecord{OrderNo: x.OrderNo, Diamonds: x.Diamonds, AmountCents: x.AmountCents, Status: status, CreatedAt: x.CreatedAt}) } var sends []ledger.GiftSendPO if err := r.db(ctx).Where("user_id = ?", id).Order("created_at DESC").Limit(20).Find(&sends).Error; err != nil { return nil, err } for _, x := range sends { v.RecentSends = append(v.RecentSends, biz.SendRecord{SerialNo: x.SerialNo, RoomName: x.RoomName, GiftName: x.GiftName, GiftIcon: x.GiftIcon, DiamondTotal: x.DiamondTotal, CreatedAt: x.CreatedAt}) } return v, nil } func (r *repo) SetEnabled(ctx context.Context, id uint, enabled bool) error { value := 2 if enabled { value = 1 } res := r.db(ctx).Table("sys_users").Where("id = ? AND deleted_at IS NULL", id).Update("enable", value) if res.Error != nil { return res.Error } if res.RowsAffected == 0 { return biz.ErrNotFound } return nil } func (r *repo) Adjust(ctx context.Context, v *biz.Adjustment) (*biz.User, error) { err := r.db(ctx).Transaction(func(tx *gorm.DB) error { wallet := ledger.WalletPO{UserID: v.UserID} if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("user_id = ?", v.UserID).FirstOrCreate(&wallet).Error; err != nil { return err } balance := wallet.DiamondBalance if v.Type == "decrease" { if balance < v.Amount { return biz.ErrInsufficient } balance -= v.Amount } else { balance += v.Amount } if err := tx.Model(&wallet).Update("diamond_balance", balance).Error; err != nil { return err } return tx.Create(&ledger.BalanceAdjustmentPO{UserID: v.UserID, OperatorUserID: v.OperatorUserID, Type: v.Type, Amount: v.Amount, Reason: v.Reason, BalanceAfter: balance}).Error }) if err != nil { return nil, err } return r.Get(ctx, v.UserID) } func toBiz(v *row) *biz.User { return &biz.User{UserID: v.UserID, Username: v.Username, Nickname: v.Nickname, Avatar: v.Avatar, Enabled: v.Enable == 1, DiamondBalance: v.DiamondBalance, TotalRechargeCents: v.TotalRechargeCents, TotalSpentDiamonds: v.TotalSpentDiamonds, CreatedAt: v.CreatedAt, LastLoginAt: v.LastLoginAt} } func userOrder(v string) string { m := map[string]string{"id_asc": "sys_users.id ASC", "id_desc": "sys_users.id DESC", "balance_asc": "diamond_balance ASC", "balance_desc": "diamond_balance DESC", "recharge_asc": "total_recharge_cents ASC", "recharge_desc": "total_recharge_cents DESC", "spent_asc": "total_spent_diamonds ASC", "spent_desc": "total_spent_diamonds DESC", "created_asc": "sys_users.created_at ASC", "created_desc": "sys_users.created_at DESC", "created_at_asc": "sys_users.created_at ASC", "created_at_desc": "sys_users.created_at DESC", "last_login_asc": "last_login_at ASC", "last_login_desc": "last_login_at DESC"} if x := m[v]; x != "" { return x } return "sys_users.id ASC" }