合并 origin/main 设备注册后,纳入本地 music 模块、seed 命令与 popadmin 相关路由/文档;music migration 编号为 0020 以避免与 0019_devices 冲突。 Co-authored-by: Cursor <cursoragent@cursor.com>
204 lines
5.1 KiB
Go
204 lines
5.1 KiB
Go
package model
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strconv"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
|
|
"spark-server/internal/module/music/entity"
|
|
)
|
|
|
|
type Repo struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NewRepo(db *gorm.DB) *Repo {
|
|
if db == nil {
|
|
return nil
|
|
}
|
|
return &Repo{db: db}
|
|
}
|
|
|
|
func (r *Repo) Ready() bool {
|
|
return r != nil && r.db != nil
|
|
}
|
|
|
|
type trackRow struct {
|
|
ID int64 `gorm:"column:id;primaryKey"`
|
|
Title string `gorm:"column:title"`
|
|
CoverKey string `gorm:"column:cover_key"`
|
|
AudioKey string `gorm:"column:audio_key"`
|
|
CoverURL string `gorm:"column:cover_url"`
|
|
AudioURL string `gorm:"column:audio_url"`
|
|
RequireMember bool `gorm:"column:require_member"`
|
|
SortOrder int `gorm:"column:sort_order"`
|
|
Enabled bool `gorm:"column:enabled"`
|
|
CreatedAt time.Time `gorm:"column:created_at"`
|
|
UpdatedAt time.Time `gorm:"column:updated_at"`
|
|
}
|
|
|
|
func (trackRow) TableName() string { return "music_tracks" }
|
|
|
|
type settingRow struct {
|
|
SettingKey string `gorm:"column:setting_key;primaryKey"`
|
|
SettingValue string `gorm:"column:setting_value"`
|
|
UpdatedAt time.Time `gorm:"column:updated_at"`
|
|
}
|
|
|
|
func (settingRow) TableName() string { return "music_settings" }
|
|
|
|
func (r *Repo) ListAll(ctx context.Context) ([]entity.Track, error) {
|
|
if !r.Ready() {
|
|
return nil, errors.New("music repo not ready")
|
|
}
|
|
var rows []trackRow
|
|
err := r.db.WithContext(ctx).Order("sort_order ASC, id ASC").Find(&rows).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([]entity.Track, 0, len(rows))
|
|
for _, row := range rows {
|
|
out = append(out, toEntity(row))
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (r *Repo) ListEnabled(ctx context.Context) ([]entity.Track, error) {
|
|
if !r.Ready() {
|
|
return nil, errors.New("music repo not ready")
|
|
}
|
|
var rows []trackRow
|
|
err := r.db.WithContext(ctx).Where("enabled = ?", true).Order("sort_order ASC, id ASC").Find(&rows).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([]entity.Track, 0, len(rows))
|
|
for _, row := range rows {
|
|
out = append(out, toEntity(row))
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (r *Repo) Get(ctx context.Context, id int64) (*entity.Track, error) {
|
|
if !r.Ready() {
|
|
return nil, errors.New("music repo not ready")
|
|
}
|
|
var row trackRow
|
|
err := r.db.WithContext(ctx).First(&row, id).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
t := toEntity(row)
|
|
return &t, nil
|
|
}
|
|
|
|
func (r *Repo) Insert(ctx context.Context, t *entity.Track) error {
|
|
if !r.Ready() {
|
|
return errors.New("music repo not ready")
|
|
}
|
|
now := time.Now().UTC()
|
|
row := trackRow{
|
|
Title: t.Title,
|
|
CoverKey: t.CoverKey,
|
|
AudioKey: t.AudioKey,
|
|
CoverURL: t.CoverURL,
|
|
AudioURL: t.AudioURL,
|
|
RequireMember: t.RequireMember,
|
|
SortOrder: t.SortOrder,
|
|
Enabled: t.Enabled,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
if err := r.db.WithContext(ctx).Create(&row).Error; err != nil {
|
|
return err
|
|
}
|
|
t.ID = row.ID
|
|
t.CreatedAt = row.CreatedAt
|
|
t.UpdatedAt = row.UpdatedAt
|
|
return nil
|
|
}
|
|
|
|
func (r *Repo) Update(ctx context.Context, t *entity.Track) error {
|
|
if !r.Ready() {
|
|
return errors.New("music repo not ready")
|
|
}
|
|
now := time.Now().UTC()
|
|
t.UpdatedAt = now
|
|
return r.db.WithContext(ctx).Model(&trackRow{}).Where("id = ?", t.ID).Updates(map[string]any{
|
|
"title": t.Title,
|
|
"cover_key": t.CoverKey,
|
|
"audio_key": t.AudioKey,
|
|
"cover_url": t.CoverURL,
|
|
"audio_url": t.AudioURL,
|
|
"require_member": t.RequireMember,
|
|
"sort_order": t.SortOrder,
|
|
"enabled": t.Enabled,
|
|
"updated_at": now,
|
|
}).Error
|
|
}
|
|
|
|
func (r *Repo) Delete(ctx context.Context, id int64) error {
|
|
if !r.Ready() {
|
|
return errors.New("music repo not ready")
|
|
}
|
|
return r.db.WithContext(ctx).Delete(&trackRow{}, id).Error
|
|
}
|
|
|
|
func (r *Repo) GetSetting(ctx context.Context, key string) (string, error) {
|
|
if !r.Ready() {
|
|
return "", errors.New("music repo not ready")
|
|
}
|
|
var row settingRow
|
|
err := r.db.WithContext(ctx).Where("setting_key = ?", key).First(&row).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return "", nil
|
|
}
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return row.SettingValue, nil
|
|
}
|
|
|
|
func (r *Repo) SetSetting(ctx context.Context, key, value string) error {
|
|
if !r.Ready() {
|
|
return errors.New("music repo not ready")
|
|
}
|
|
now := time.Now().UTC()
|
|
row := settingRow{SettingKey: key, SettingValue: value, UpdatedAt: now}
|
|
return r.db.WithContext(ctx).Save(&row).Error
|
|
}
|
|
|
|
func (r *Repo) MemberPreviewSeconds(ctx context.Context) (int, error) {
|
|
raw, err := r.GetSetting(ctx, entity.SettingMemberPreviewSeconds)
|
|
if err != nil {
|
|
return entity.DefaultPreviewSeconds, err
|
|
}
|
|
if raw == "" {
|
|
return entity.DefaultPreviewSeconds, nil
|
|
}
|
|
n, err := strconv.Atoi(raw)
|
|
if err != nil || n < 0 {
|
|
return entity.DefaultPreviewSeconds, nil
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
func toEntity(row trackRow) entity.Track {
|
|
return entity.Track{
|
|
ID: row.ID,
|
|
Title: row.Title,
|
|
CoverKey: row.CoverKey,
|
|
AudioKey: row.AudioKey,
|
|
CoverURL: row.CoverURL,
|
|
AudioURL: row.AudioURL,
|
|
RequireMember: row.RequireMember,
|
|
SortOrder: row.SortOrder,
|
|
Enabled: row.Enabled,
|
|
CreatedAt: row.CreatedAt,
|
|
UpdatedAt: row.UpdatedAt,
|
|
}
|
|
}
|