Files
netfelix-audio-fix/server/db/index.ts
Felix Förtsch d5f4afd26b
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m54s
split audio/subtitle concerns, remove docker-in-docker, add per-node path mapping
- install ffmpeg in dockerfile (fixes exit code 127)
- buildCommand() now audio-only remux, no subtitle extraction
- add unapprove endpoint + ui button for approved items
- add batch extract-all subtitles endpoint + ui button
- audio detail page shows only video+audio streams
- remove global movies_path/series_path config, add per-node path mapping
- remove docker-in-docker command building (buildDockerCommand, buildDockerExtractOnlyCommand)
- ssh execution translates /movies/ and /series/ to node-specific paths
- remove media paths section from setup page
- add unraid-template.xml

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 16:48:00 +01:00

111 lines
4.2 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',
};
/** 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 */ }
try { _db.exec("ALTER TABLE nodes ADD COLUMN movies_path TEXT NOT NULL DEFAULT ''"); } catch { /* already exists */ }
try { _db.exec("ALTER TABLE nodes ADD COLUMN series_path TEXT NOT NULL DEFAULT ''"); } 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;
}