Bootstrap SQLite on server startup with embedded migrations and development seed data. Replace placeholder auth and library responses with database-backed services, bearer sessions, and repository-driven API handlers.
134 lines
4.4 KiB
Go
134 lines
4.4 KiB
Go
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
|
|
}
|