Files
netfelix-audio-fix/server/db/index.ts
Felix Förtsch 5ac44b7551 restructure to react spa + hono api, fix missing server/ and lib/
rewrite from monolithic hono jsx to react 19 spa with tanstack router
+ hono json api backend. add scan, review, execute, nodes, and setup
pages. multi-stage dockerfile (node for vite build, bun for runtime).

previously, server/ and src/shared/lib/ were silently excluded by
global gitignore patterns (/server/ from emacs, lib/ from python).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 22:57:40 +01:00

111 lines
4.0 KiB
TypeScript

import { Database } from 'bun:sqlite';
import { join } from 'node:path';
import { mkdirSync } from 'node:fs';
import { SCHEMA, DEFAULT_CONFIG } from './schema';
const dataDir = process.env.DATA_DIR ?? './data';
mkdirSync(dataDir, { recursive: true });
const isDev = process.env.NODE_ENV === 'development';
const dbPath = join(dataDir, isDev ? 'netfelix-dev.db' : 'netfelix.db');
// ─── Env-var → config key mapping ─────────────────────────────────────────────
const ENV_MAP: Record<string, string> = {
jellyfin_url: 'JELLYFIN_URL',
jellyfin_api_key: 'JELLYFIN_API_KEY',
jellyfin_user_id: 'JELLYFIN_USER_ID',
radarr_url: 'RADARR_URL',
radarr_api_key: 'RADARR_API_KEY',
radarr_enabled: 'RADARR_ENABLED',
sonarr_url: 'SONARR_URL',
sonarr_api_key: 'SONARR_API_KEY',
sonarr_enabled: 'SONARR_ENABLED',
subtitle_languages: 'SUBTITLE_LANGUAGES',
movies_path: 'MOVIES_PATH',
series_path: 'SERIES_PATH',
};
/** Read a config key from environment variables (returns null if not set). */
function envValue(key: string): string | null {
const envKey = ENV_MAP[key];
if (!envKey) return null;
const val = process.env[envKey];
if (!val) return null;
if (key.endsWith('_enabled')) return val === '1' || val.toLowerCase() === 'true' ? '1' : '0';
if (key === 'subtitle_languages') return JSON.stringify(val.split(',').map((s) => s.trim()));
if (key.endsWith('_url')) return val.replace(/\/$/, '');
return val;
}
/** True when minimum required Jellyfin env vars are present — skips the setup wizard. */
function isEnvConfigured(): boolean {
return !!(process.env.JELLYFIN_URL && process.env.JELLYFIN_API_KEY);
}
// ─── Database ──────────────────────────────────────────────────────────────────
let _db: Database | null = null;
export function getDb(): Database {
if (_db) return _db;
_db = new Database(dbPath, { create: true });
_db.exec(SCHEMA);
// Migrations for columns added after initial release
try { _db.exec('ALTER TABLE stream_decisions ADD COLUMN custom_title TEXT'); } catch { /* already exists */ }
try { _db.exec('ALTER TABLE review_plans ADD COLUMN subs_extracted INTEGER NOT NULL DEFAULT 0'); } catch { /* already exists */ }
seedDefaults(_db);
return _db;
}
function seedDefaults(db: Database): void {
const insert = db.prepare(
'INSERT OR IGNORE INTO config (key, value) VALUES (?, ?)'
);
for (const [key, value] of Object.entries(DEFAULT_CONFIG)) {
insert.run(key, value);
}
}
export function getConfig(key: string): string | null {
// Env vars take precedence over DB
const fromEnv = envValue(key);
if (fromEnv !== null) return fromEnv;
// Auto-complete setup when all required Jellyfin env vars are present
if (key === 'setup_complete' && isEnvConfigured()) return '1';
const row = getDb()
.prepare('SELECT value FROM config WHERE key = ?')
.get(key) as { value: string } | undefined;
return row?.value ?? null;
}
export function setConfig(key: string, value: string): void {
getDb()
.prepare('INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)')
.run(key, value);
}
/** Returns the set of config keys currently overridden by environment variables. */
export function getEnvLockedKeys(): Set<string> {
const locked = new Set<string>();
for (const key of Object.keys(ENV_MAP)) {
if (envValue(key) !== null) locked.add(key);
}
return locked;
}
export function getAllConfig(): Record<string, string> {
const rows = getDb()
.prepare('SELECT key, value FROM config')
.all() as { key: string; value: string }[];
const result = Object.fromEntries(rows.map((r) => [r.key, r.value ?? '']));
// Apply env overrides on top of DB values
for (const key of Object.keys(ENV_MAP)) {
const fromEnv = envValue(key);
if (fromEnv !== null) result[key] = fromEnv;
}
// Auto-complete setup when all required Jellyfin env vars are present
if (isEnvConfigured()) result.setup_complete = '1';
return result;
}