Files
TermorServer/internal/db/seed.go

161 lines
5.1 KiB
Go

package db
import (
"context"
"database/sql"
"fmt"
"io/fs"
"path/filepath"
"strings"
"time"
"golang.org/x/crypto/bcrypt"
"github.com/benya/temporserv/internal/auth"
"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" && !hasMediaFiles(cfg.MediaRoot) {
if err := seedLibrary(ctx, database); err != nil {
return err
}
}
return nil
}
func hasMediaFiles(root string) bool {
found := false
_ = filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() {
return nil
}
switch strings.ToLower(filepath.Ext(path)) {
case ".aac", ".flac", ".m4a", ".mp3", ".ogg", ".oga", ".opus", ".wav", ".wma":
found = true
return fs.SkipAll
}
return nil
})
return found
}
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)
}
subsonicSecret, err := auth.EncryptSubsonicSecret(cfg.DefaultAdminPassword, cfg.EncryptionKey)
if err != nil {
return fmt.Errorf("encrypt admin subsonic secret: %w", err)
}
now := time.Now().UTC().Format(time.RFC3339)
_, err = database.ExecContext(
ctx,
`INSERT INTO users (id, username, password_hash, subsonic_auth_secret, is_admin, created_at, last_login_at)
VALUES (?, ?, ?, ?, 1, ?, ?)`,
"user-admin",
cfg.DefaultAdminUsername,
string(passwordHash),
subsonicSecret,
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
}