- SESSION_SECRET and PASSWORD_HASH moved out of config.json into env vars - data.ts: AppConfig no longer holds secrets; loadConfig/loadData seed from defaults/ on first run if data/ files are missing - auth.ts: requireAuth/authRequest read SESSION_SECRET from process.env directly - login/+server.ts: reads PASSWORD_HASH and SESSION_SECRET from process.env - defaults/config.json: ships with image (no secrets) - defaults/units.json: ships with image as initial unit data - package.json: add dotenv dep; start/serve load .env via -r dotenv/config - Dockerfile: copy defaults/ into image; data/ is PVC-only - .env.example: documents required env vars for local dev - Remove k8s/ — managed externally
73 lines
2.8 KiB
TypeScript
73 lines
2.8 KiB
TypeScript
import { readFileSync, writeFileSync, renameSync, existsSync, mkdirSync, copyFileSync } from 'fs';
|
|
import { resolve } from 'path';
|
|
import type { UnitsData } from '$lib/types';
|
|
|
|
// Path is relative to project root (where the server runs from)
|
|
const DATA_DIR = resolve('data');
|
|
const DEFAULTS_DIR = resolve('defaults');
|
|
const UNITS_PATH = resolve(DATA_DIR, 'units.json');
|
|
const CONFIG_PATH = resolve(DATA_DIR, 'config.json');
|
|
const DEFAULT_UNITS_PATH = resolve(DEFAULTS_DIR, 'units.json');
|
|
const DEFAULT_CONFIG_PATH = resolve(DEFAULTS_DIR, 'config.json');
|
|
|
|
export interface AppConfig {
|
|
adminPath: string;
|
|
/** Label shown next to the YOLO mode toggle. Defaults to "YOLO mode". */
|
|
yoloLabel?: string;
|
|
/** Description shown inline after the label. Defaults to "(cross-group conversions)". */
|
|
yoloDescription?: string;
|
|
/**
|
|
* Controls when the YOLO mode toggle is visible to users.
|
|
* 'auto' — show only when more than one group is present (default)
|
|
* 'never' — always hidden
|
|
*/
|
|
yoloVisibility?: 'auto' | 'never';
|
|
}
|
|
|
|
/** Ensure data/ directory exists and seed missing files from defaults/. */
|
|
function ensureDataDir(): void {
|
|
mkdirSync(DATA_DIR, { recursive: true });
|
|
if (!existsSync(UNITS_PATH) && existsSync(DEFAULT_UNITS_PATH)) {
|
|
copyFileSync(DEFAULT_UNITS_PATH, UNITS_PATH);
|
|
console.log('[humor-units] First run: seeded data/units.json from defaults/units.json');
|
|
}
|
|
if (!existsSync(CONFIG_PATH) && existsSync(DEFAULT_CONFIG_PATH)) {
|
|
copyFileSync(DEFAULT_CONFIG_PATH, CONFIG_PATH);
|
|
console.log('[humor-units] First run: seeded data/config.json from defaults/config.json');
|
|
}
|
|
}
|
|
|
|
/** Read units.json synchronously. Seeds from defaults on first run. */
|
|
export function loadData(): UnitsData {
|
|
ensureDataDir();
|
|
if (!existsSync(UNITS_PATH)) {
|
|
throw new Error(`[humor-units] units.json not found at ${UNITS_PATH} and no default available.`);
|
|
}
|
|
const raw = readFileSync(UNITS_PATH, 'utf8');
|
|
return JSON.parse(raw) as UnitsData;
|
|
}
|
|
|
|
/** Write full UnitsData atomically (write to temp, rename). */
|
|
export function saveData(data: UnitsData): void {
|
|
const tmp = UNITS_PATH + '.tmp';
|
|
writeFileSync(tmp, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
renameSync(tmp, UNITS_PATH);
|
|
}
|
|
|
|
/** Read config.json synchronously. Seeds from defaults on first run. */
|
|
export function loadConfig(): AppConfig {
|
|
ensureDataDir();
|
|
if (!existsSync(CONFIG_PATH)) {
|
|
throw new Error(`[humor-units] config.json not found at ${CONFIG_PATH} and no default available.`);
|
|
}
|
|
const raw = readFileSync(CONFIG_PATH, 'utf8');
|
|
return JSON.parse(raw) as AppConfig;
|
|
}
|
|
|
|
/** Write config.json atomically. */
|
|
export function saveConfig(config: AppConfig): void {
|
|
const tmp = CONFIG_PATH + '.tmp';
|
|
writeFileSync(tmp, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
renameSync(tmp, CONFIG_PATH);
|
|
}
|