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"` BackgroundKey string `gorm:"column:background_key"` CoverURL string `gorm:"column:cover_url"` AudioURL string `gorm:"column:audio_url"` BackgroundURL string `gorm:"column:background_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 "t_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 "t_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, BackgroundKey: t.BackgroundKey, CoverURL: t.CoverURL, AudioURL: t.AudioURL, BackgroundURL: t.BackgroundURL, 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, "background_key": t.BackgroundKey, "cover_url": t.CoverURL, "audio_url": t.AudioURL, "background_url": t.BackgroundURL, "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, BackgroundKey: row.BackgroundKey, CoverURL: row.CoverURL, AudioURL: row.AudioURL, BackgroundURL: row.BackgroundURL, RequireMember: row.RequireMember, SortOrder: row.SortOrder, Enabled: row.Enabled, CreatedAt: row.CreatedAt, UpdatedAt: row.UpdatedAt, } }