feat: admin page with dynamic converter loading
Implements full admin interface for managing units and groups.
Migrates converter from static imports to server-side data loading.
- Switch adapter-static → adapter-node
- Add bcryptjs session auth with HMAC-signed cookies
- Add data/units.json and data/config.json data layer
- Add atomic file writes via temp-file rename
- Add public GET /api/units endpoint
- Add auth-gated admin CRUD API for units and groups
- Add two-panel admin UI with group tree and edit forms
- Add YOLO mode toggle for cross-group conversions
- Add visual group dividers in converter results grid
- Update ResultItem type for nullable convertedValue and isYolo flag
- Group deletion supports reassign/orphan with toBase recalculation
Rollback point: 067fd44
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
40
src/lib/server/data.ts
Normal file
40
src/lib/server/data.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { readFileSync, writeFileSync, renameSync } 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 UNITS_PATH = resolve(DATA_DIR, 'units.json');
|
||||
const CONFIG_PATH = resolve(DATA_DIR, 'config.json');
|
||||
|
||||
export interface AppConfig {
|
||||
adminPath: string;
|
||||
passwordHash: string;
|
||||
sessionSecret: string;
|
||||
}
|
||||
|
||||
/** Read units.json synchronously. Returns parsed UnitsData. */
|
||||
export function loadData(): UnitsData {
|
||||
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. */
|
||||
export function loadConfig(): AppConfig {
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user