package db import ( "context" "database/sql" "fmt" "time" "golang.org/x/crypto/bcrypt" "github.com/benya/temporserv/internal/config" ) func Seed(ctx context.Context, database *sql.DB, cfg config.Config) error { if err := seedAdmin(ctx, database, cfg); err != nil { return err } if cfg.AppEnv == "development" { if err := seedLibrary(ctx, database); err != nil { return err } } return nil } func seedAdmin(ctx context.Context, database *sql.DB, cfg config.Config) error { var count int if err := database.QueryRowContext(ctx, "SELECT COUNT(*) FROM users").Scan(&count); err != nil { return fmt.Errorf("count users: %w", err) } if count > 0 { return nil } passwordHash, err := bcrypt.GenerateFromPassword([]byte(cfg.DefaultAdminPassword), bcrypt.DefaultCost) if err != nil { return fmt.Errorf("hash admin password: %w", err) } now := time.Now().UTC().Format(time.RFC3339) _, err = database.ExecContext( ctx, `INSERT INTO users (id, username, password_hash, is_admin, created_at, last_login_at) VALUES (?, ?, ?, 1, ?, ?)`, "user-admin", cfg.DefaultAdminUsername, string(passwordHash), now, now, ) if err != nil { return fmt.Errorf("insert admin user: %w", err) } return nil } func seedLibrary(ctx context.Context, database *sql.DB) error { var artistCount int if err := database.QueryRowContext(ctx, "SELECT COUNT(*) FROM artists").Scan(&artistCount); err != nil { return fmt.Errorf("count artists: %w", err) } if artistCount > 0 { return nil } now := time.Now().UTC().Format(time.RFC3339) statements := []struct { query string args []any }{ { query: `INSERT INTO artists (id, name, sort_name, cover_art_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`, args: []any{"artist-1", "Tycho", "Tycho", "", now, now}, }, { query: `INSERT INTO artists (id, name, sort_name, cover_art_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`, args: []any{"artist-2", "Bonobo", "Bonobo", "", now, now}, }, { query: `INSERT INTO artists (id, name, sort_name, cover_art_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`, args: []any{"artist-3", "Boards of Canada", "Boards of Canada", "", now, now}, }, { query: `INSERT INTO albums (id, artist_id, title, sort_title, year, genre, cover_art_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, args: []any{"album-1", "artist-1", "Awake", "Awake", 2014, "Electronic", "", now, now}, }, { query: `INSERT INTO albums (id, artist_id, title, sort_title, year, genre, cover_art_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, args: []any{"album-2", "artist-2", "Migration", "Migration", 2017, "Electronic", "", now, now}, }, { query: `INSERT INTO albums (id, artist_id, title, sort_title, year, genre, cover_art_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, args: []any{"album-3", "artist-3", "Tomorrow's Harvest", "Tomorrow's Harvest", 2013, "Electronic", "", now, now}, }, { query: `INSERT INTO tracks (id, album_id, artist_id, title, track_number, disc_number, duration_seconds, file_path, content_type, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, args: []any{"track-1", "album-1", "artist-1", "Awake", 1, 1, 224, "demo/awake.mp3", "audio/mpeg", now, now}, }, { query: `INSERT INTO tracks (id, album_id, artist_id, title, track_number, disc_number, duration_seconds, file_path, content_type, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, args: []any{"track-2", "album-2", "artist-2", "Migration", 1, 1, 301, "demo/migration.mp3", "audio/mpeg", now, now}, }, { query: `INSERT INTO tracks (id, album_id, artist_id, title, track_number, disc_number, duration_seconds, file_path, content_type, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, args: []any{"track-3", "album-3", "artist-3", "Reach for the Dead", 1, 1, 292, "demo/reach-for-the-dead.mp3", "audio/mpeg", now, now}, }, } tx, err := database.BeginTx(ctx, nil) if err != nil { return fmt.Errorf("begin seed transaction: %w", err) } for _, statement := range statements { if _, err := tx.ExecContext(ctx, statement.query, statement.args...); err != nil { _ = tx.Rollback() return fmt.Errorf("seed statement failed: %w", err) } } if err := tx.Commit(); err != nil { return fmt.Errorf("commit seed transaction: %w", err) } return nil }