Files
dickloads/src/lib/server/data.ts
Falkan 5480bb246c refactor: move sessionSecret+passwordHash to env vars, seed data/ from defaults/
- 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
2026-03-19 23:54:37 -04:00

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);
}