1
0

feat: config loading and sqlite schema

This commit is contained in:
2026-08-29 14:55:42 +02:00
parent 01ad6fbb40
commit 5545aa31fb
5 changed files with 186 additions and 0 deletions

View File

@@ -1,3 +1,7 @@
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import path from 'node:path'
import crypto from 'node:crypto'
export interface Config {
dataDir: string
artworkDir: string
@@ -5,3 +9,30 @@ export interface Config {
port: number
sessionSecret: string
}
export function loadConfig(env: Record<string, string | undefined> = process.env): Config {
const dataDir = env.DATA_DIR ?? path.resolve('data')
mkdirSync(dataDir, { recursive: true })
const artworkDir = path.join(dataDir, 'artwork-cache')
mkdirSync(artworkDir, { recursive: true })
return {
dataDir,
artworkDir,
dbPath: path.join(dataDir, 'record-shop.db'),
port: Number(env.PORT ?? 3000),
sessionSecret: getOrCreateSecret(dataDir),
}
}
function getOrCreateSecret(dataDir: string): string {
const secretPath = path.join(dataDir, 'session-secret')
try {
const existing = readFileSync(secretPath, 'utf8').trim()
if (existing) return existing
} catch {
// first boot — create below
}
const secret = crypto.randomBytes(32).toString('hex')
writeFileSync(secretPath, secret, { mode: 0o600 })
return secret
}

View File

@@ -6,5 +6,75 @@ export function openDatabase(path: string): DB {
const db = new Database(path)
db.pragma('journal_mode = WAL')
db.pragma('foreign_keys = ON')
migrate(db)
return db
}
export function migrate(db: DB): void {
db.exec(`
CREATE TABLE IF NOT EXISTS app_meta (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
is_admin INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS sessions (
token TEXT PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
expires_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS settings (
user_id INTEGER PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
discogs_token TEXT,
subsonic_url TEXT,
subsonic_username TEXT,
subsonic_password TEXT
);
CREATE TABLE IF NOT EXISTS collection_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
discogs_release_id INTEGER NOT NULL,
title TEXT NOT NULL,
artist TEXT NOT NULL,
year INTEGER,
formats TEXT NOT NULL DEFAULT '[]',
genres TEXT NOT NULL DEFAULT '[]',
labels TEXT NOT NULL DEFAULT '[]',
tracklist TEXT NOT NULL DEFAULT '[]',
catno TEXT,
country TEXT,
cover_url TEXT,
local_artwork_path TEXT,
barcodes TEXT NOT NULL DEFAULT '[]',
rip_override INTEGER,
date_added TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE (user_id, discogs_release_id)
);
CREATE TABLE IF NOT EXISTS digital_albums (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
subsonic_id TEXT NOT NULL,
title TEXT NOT NULL,
artist TEXT NOT NULL,
UNIQUE (user_id, subsonic_id)
);
CREATE TABLE IF NOT EXISTS match_links (
user_id INTEGER NOT NULL,
item_id INTEGER NOT NULL REFERENCES collection_items(id) ON DELETE CASCADE,
album_id INTEGER NOT NULL REFERENCES digital_albums(id) ON DELETE CASCADE,
PRIMARY KEY (user_id, item_id)
);
`)
}