Files
NaviWatcher/internal/database/artist_settings.go
Vladimir Zagainov 44f3b0a2a7 feat: add Web UI artist detail, archive, and ignore actions
Implements Task 8: artist detail page (local albums + found-missing
with ignore buttons), ignored-releases archive with restore, and POST
handlers toggling ignore flags and ignore_singles. Adds ErrArtistNotFound
sentinel so callers can distinguish missing artists, and wires routes via
the enhanced ServeMux path wildcard.
2026-07-19 22:43:47 +03:00

132 lines
3.6 KiB
Go

package database
import (
"database/sql"
"errors"
"fmt"
)
// GetArtistSettings retrieves an artist_settings row by ID.
// Returns sql.ErrNoRows if the artist is not found.
func GetArtistSettings(db *DB, id string) (*ArtistSettings, error) {
var (
s ArtistSettings
mbid sql.NullString
)
err := db.Conn().QueryRow(
"SELECT id, name, mbid, ignore_singles, ignore_compilations, monitored FROM artist_settings WHERE id = ?",
id,
).Scan(&s.ID, &s.Name, &mbid, &s.IgnoreSingles, &s.IgnoreCompilations, &s.Monitored)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrArtistNotFound
}
return nil, err
}
s.MBID = mbid.String
return &s, nil
}
// SaveArtistSettings inserts or replaces an artist_settings row.
func SaveArtistSettings(db *DB, settings *ArtistSettings) error {
_, err := db.Conn().Exec(
"INSERT OR REPLACE INTO artist_settings (id, name, mbid, ignore_singles, ignore_compilations, monitored) VALUES (?, ?, ?, ?, ?, ?)",
settings.ID, settings.Name, settings.MBID, settings.IgnoreSingles, settings.IgnoreCompilations, settings.Monitored,
)
if err != nil {
return fmt.Errorf("save artist settings: %w", err)
}
return nil
}
// GetAllArtistSettings returns all rows from artist_settings.
func GetAllArtistSettings(db *DB) ([]ArtistSettings, error) {
rows, err := db.Conn().Query(
"SELECT id, name, mbid, ignore_singles, ignore_compilations, monitored FROM artist_settings",
)
if err != nil {
return nil, fmt.Errorf("query all artist settings: %w", err)
}
defer rows.Close()
var results []ArtistSettings
for rows.Next() {
var s ArtistSettings
if err := rows.Scan(&s.ID, &s.Name, &s.MBID, &s.IgnoreSingles, &s.IgnoreCompilations, &s.Monitored); err != nil {
return nil, fmt.Errorf("scan artist settings: %w", err)
}
results = append(results, s)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate artist settings: %w", err)
}
return results, nil
}
// UpdateArtistSettings updates specific fields of an artist_settings row by ID.
// The updates map keys must match column names: "name", "ignore_singles", "ignore_compilations", "monitored".
func UpdateArtistSettings(db *DB, id string, updates map[string]interface{}) error {
if len(updates) == 0 {
return fmt.Errorf("no updates provided")
}
// Build the query using a fixed set of allowed columns to avoid dynamic SQL.
const baseQuery = "UPDATE artist_settings SET"
var args []interface{}
setClause := ""
for col, val := range updates {
switch col {
case "name":
if setClause != "" {
setClause += ", "
}
setClause += "name = ?"
args = append(args, val)
case "mbid":
if setClause != "" {
setClause += ", "
}
setClause += "mbid = ?"
args = append(args, val)
case "ignore_singles":
if setClause != "" {
setClause += ", "
}
setClause += "ignore_singles = ?"
args = append(args, val)
case "ignore_compilations":
if setClause != "" {
setClause += ", "
}
setClause += "ignore_compilations = ?"
args = append(args, val)
case "monitored":
if setClause != "" {
setClause += ", "
}
setClause += "monitored = ?"
args = append(args, val)
default:
return fmt.Errorf("unknown column: %s", col)
}
}
args = append(args, id)
query := fmt.Sprintf("%s %s WHERE id = ?", baseQuery, setClause)
result, err := db.Conn().Exec(query, args...)
if err != nil {
return fmt.Errorf("update artist settings: %w", err)
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("rows affected: %w", err)
}
if rowsAffected == 0 {
return fmt.Errorf("artist not found: %s", id)
}
return nil
}