package database import ( "database/sql" "time" "github.com/jmoiron/sqlx" "github.com/peterqiu0516/sub-store/internal/model" "github.com/peterqiu0516/sub-store/internal/util" ) type SourceRepo struct { db *sqlx.DB } func NewSourceRepo(db *sqlx.DB) *SourceRepo { return &SourceRepo{db: db} } type sourceRow struct { ID string `db:"id"` Name string `db:"name"` Alias string `db:"alias"` Type string `db:"type"` URL string `db:"url"` Content string `db:"content"` Enabled int `db:"enabled"` FiltersJSON string `db:"filters_json"` MetaJSON string `db:"meta_json"` CreatedAt int64 `db:"created_at"` UpdatedAt int64 `db:"updated_at"` } func (r *SourceRepo) List() ([]model.SourceRecord, error) { var rows []sourceRow if err := r.db.Select(&rows, `SELECT id, name, alias, type, url, content, enabled, filters_json, meta_json, created_at, updated_at FROM sources ORDER BY created_at ASC`); err != nil { return nil, err } result := make([]model.SourceRecord, 0, len(rows)) for _, row := range rows { result = append(result, sourceFromRow(row)) } return result, nil } func (r *SourceRepo) Get(id string) (*model.SourceRecord, error) { var row sourceRow if err := r.db.Get(&row, `SELECT id, name, alias, type, url, content, enabled, filters_json, meta_json, created_at, updated_at FROM sources WHERE id = ?`, id); err != nil { if err == sql.ErrNoRows { return nil, nil } return nil, err } rec := sourceFromRow(row) return &rec, nil } func (r *SourceRepo) Upsert(input model.SourceRecord) (model.SourceRecord, error) { now := time.Now().UnixMilli() id := input.ID if id == "" { id = util.ToId(input.Name) } existing, _ := r.Get(id) createdAt := now if existing != nil { createdAt = existing.CreatedAt } filters := input.Filters if filters == nil { filters = []model.FilterRule{} } meta := input.Meta if meta == nil { meta = map[string]any{} } rec := model.SourceRecord{ ID: id, Name: input.Name, Alias: input.Alias, Type: input.Type, URL: input.URL, Content: input.Content, Enabled: input.Enabled, Filters: filters, Meta: meta, CreatedAt: createdAt, UpdatedAt: now, } filtersJSON := marshalJSON(filters) metaJSON := marshalJSON(meta) enabled := boolToInt(rec.Enabled) sourceType := rec.Type if sourceType == "" { sourceType = "remote" } if sourceType != "local" { sourceType = "remote" } _, err := r.db.Exec( `INSERT INTO sources (id, name, alias, type, url, content, enabled, filters_json, meta_json, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET name = excluded.name, alias = excluded.alias, type = excluded.type, url = excluded.url, content = excluded.content, enabled = excluded.enabled, filters_json = excluded.filters_json, meta_json = excluded.meta_json, updated_at = excluded.updated_at`, rec.ID, rec.Name, rec.Alias, sourceType, rec.URL, rec.Content, enabled, filtersJSON, metaJSON, rec.CreatedAt, rec.UpdatedAt, ) if err != nil { return rec, err } return rec, nil } func (r *SourceRepo) Delete(id string) error { _, err := r.db.Exec("DELETE FROM sources WHERE id = ?", id) return err } func (r *SourceRepo) Sort(ids []string) error { now := time.Now().UnixMilli() tx, err := r.db.Beginx() if err != nil { return err } defer tx.Rollback() for i, id := range ids { _, err := tx.Exec("UPDATE sources SET created_at = ?, updated_at = ? WHERE id = ?", now+int64(i), now, id) if err != nil { return err } } return tx.Commit() } func sourceFromRow(row sourceRow) model.SourceRecord { sourceType := "remote" if row.Type == "local" { sourceType = "local" } return model.SourceRecord{ ID: row.ID, Name: row.Name, Alias: row.Alias, Type: sourceType, URL: row.URL, Content: row.Content, Enabled: row.Enabled != 0, Filters: model.UnmarshalFilterRules(row.FiltersJSON), Meta: model.UnmarshalMeta(row.MetaJSON), CreatedAt: row.CreatedAt, UpdatedAt: row.UpdatedAt, } }