管理端按用户查询开启通讯录/相册/媒体拉取;服务端写入路径自动压图;新增 /v1/sign 与 /v1/energy。 Co-authored-by: Cursor <cursoragent@cursor.com>
197 lines
4.9 KiB
Go
197 lines
4.9 KiB
Go
package model
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
mysqldriver "github.com/go-sql-driver/mysql"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
|
|
"spark-server/internal/module/energy/entity"
|
|
"spark-server/internal/pkg/errs"
|
|
)
|
|
|
|
// Repo is MySQL persistence for wallets, sign records, and energy logs.
|
|
type Repo struct {
|
|
gdb *gorm.DB
|
|
}
|
|
|
|
func NewRepo(gdb *gorm.DB) *Repo { return &Repo{gdb: gdb} }
|
|
|
|
func (m *Repo) session(ctx context.Context) (*gorm.DB, error) {
|
|
if m == nil || m.gdb == nil {
|
|
return nil, errs.ErrAvailable
|
|
}
|
|
return m.gdb.WithContext(ctx), nil
|
|
}
|
|
|
|
func mapDBErr(err error) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return errs.ErrNotFound
|
|
}
|
|
var me *mysqldriver.MySQLError
|
|
if errors.As(err, &me) && me.Number == 1062 {
|
|
return errs.ErrDuplicate
|
|
}
|
|
return errs.ErrInner.WithError("energy-mysql", err)
|
|
}
|
|
|
|
func (m *Repo) GetWallet(ctx context.Context, userID int64) (*entity.Wallet, error) {
|
|
db, err := m.session(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var row WalletRow
|
|
if err := db.Where("user_id = ?", userID).Take(&row).Error; err != nil {
|
|
return nil, mapDBErr(err)
|
|
}
|
|
return row.Entity(), nil
|
|
}
|
|
|
|
func (m *Repo) EnsureWallet(ctx context.Context, userID int64) (*entity.Wallet, error) {
|
|
db, err := m.session(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
now := time.Now()
|
|
row := WalletRow{UserID: userID, Energy: 0, UpdatedAt: now}
|
|
err = db.Clauses(clause.OnConflict{DoNothing: true}).Create(&row).Error
|
|
if err != nil {
|
|
return nil, mapDBErr(err)
|
|
}
|
|
return m.GetWallet(ctx, userID)
|
|
}
|
|
|
|
func (m *Repo) AddEnergy(ctx context.Context, userID, delta int64, reason string, dayKey int, source string) (*entity.Wallet, *entity.EnergyLog, error) {
|
|
db, err := m.session(ctx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
var wallet *entity.Wallet
|
|
var logRow *entity.EnergyLog
|
|
err = db.Transaction(func(tx *gorm.DB) error {
|
|
now := time.Now()
|
|
var w WalletRow
|
|
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("user_id = ?", userID).Take(&w).Error; err != nil {
|
|
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return err
|
|
}
|
|
w = WalletRow{UserID: userID, Energy: 0, UpdatedAt: now}
|
|
if err := tx.Create(&w).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
w.Energy += delta
|
|
if w.Energy < 0 {
|
|
return errs.ErrInvalidArgument
|
|
}
|
|
w.UpdatedAt = now
|
|
if err := tx.Save(&w).Error; err != nil {
|
|
return err
|
|
}
|
|
lr := EnergyLogRow{
|
|
UserID: userID, Delta: delta, BalanceAfter: w.Energy, Reason: reason,
|
|
DayKey: dayKey, Source: source, CreatedAt: now,
|
|
}
|
|
if err := tx.Create(&lr).Error; err != nil {
|
|
return err
|
|
}
|
|
wallet = w.Entity()
|
|
logRow = lr.Entity()
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, nil, mapDBErr(err)
|
|
}
|
|
return wallet, logRow, nil
|
|
}
|
|
|
|
func (m *Repo) ListSignsInRange(ctx context.Context, userID int64, fromDay, toDay int) ([]*entity.SignRecord, error) {
|
|
db, err := m.session(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var rows []SignRow
|
|
if err := db.Where("user_id = ? AND day_key >= ? AND day_key <= ?", userID, fromDay, toDay).
|
|
Order("day_key ASC").Find(&rows).Error; err != nil {
|
|
return nil, mapDBErr(err)
|
|
}
|
|
out := make([]*entity.SignRecord, 0, len(rows))
|
|
for i := range rows {
|
|
out = append(out, rows[i].Entity())
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (m *Repo) GetSign(ctx context.Context, userID int64, dayKey int) (*entity.SignRecord, error) {
|
|
db, err := m.session(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var row SignRow
|
|
if err := db.Where("user_id = ? AND day_key = ?", userID, dayKey).Take(&row).Error; err != nil {
|
|
return nil, mapDBErr(err)
|
|
}
|
|
return row.Entity(), nil
|
|
}
|
|
|
|
func (m *Repo) CreateSign(ctx context.Context, rec *entity.SignRecord) error {
|
|
if rec == nil || rec.UserID <= 0 || rec.DayKey <= 0 {
|
|
return errs.ErrInvalidArgument
|
|
}
|
|
db, err := m.session(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
row := signFromEntity(rec)
|
|
if row.CreatedAt.IsZero() {
|
|
row.CreatedAt = time.Now()
|
|
}
|
|
return mapDBErr(db.Create(row).Error)
|
|
}
|
|
|
|
func (m *Repo) ListEnergyLogs(ctx context.Context, userID, cursor int64, limit int) ([]*entity.EnergyLog, error) {
|
|
db, err := m.session(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
limit = entity.ClampLimit(limit)
|
|
q := db.Where("user_id = ?", userID)
|
|
if cursor > 0 {
|
|
q = q.Where("id < ?", cursor)
|
|
}
|
|
var rows []EnergyLogRow
|
|
if err := q.Order("id DESC").Limit(limit).Find(&rows).Error; err != nil {
|
|
return nil, mapDBErr(err)
|
|
}
|
|
out := make([]*entity.EnergyLog, 0, len(rows))
|
|
for i := range rows {
|
|
out = append(out, rows[i].Entity())
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (m *Repo) ListRecentSigns(ctx context.Context, userID int64, limit int) ([]*entity.SignRecord, error) {
|
|
db, err := m.session(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if limit <= 0 {
|
|
limit = 40
|
|
}
|
|
var rows []SignRow
|
|
if err := db.Where("user_id = ?", userID).Order("day_key DESC").Limit(limit).Find(&rows).Error; err != nil {
|
|
return nil, mapDBErr(err)
|
|
}
|
|
out := make([]*entity.SignRecord, 0, len(rows))
|
|
for i := range rows {
|
|
out = append(out, rows[i].Entity())
|
|
}
|
|
return out, nil
|
|
}
|