import Database from "better-sqlite3"; const DB_PATH = process.env.DB_PATH || "/data/charges.db"; export interface Charge { id: number; start_time: string; end_time: string; duration_s: number; energy_wh: number; } export interface Totals { month_wh: number; year_wh: number; total_wh: number; install_date: string | null; } /** * Öffnet die DB read-only und gibt sie an `fn`. Es wird bewusst pro Aufruf eine * frische Verbindung geöffnet (und wieder geschlossen): Der Collector schreibt * aus einem anderen Prozess/Container im WAL-Modus, und eine langlebige * readonly-Verbindung bekommt diese Writes über den Docker-Desktop-FUSE-Mount * nicht zuverlässig mit (inkohärenter WAL-Index im Shared-Memory). Eine frische * Verbindung liest immer den aktuellen Stand. * * Gibt `fallback` zurück, wenn die Datei/das Schema noch nicht existiert * (Collector legt sie beim ersten Start an) — die Seite bleibt dann leer statt * zu crashen. */ function withDb(fallback: T, fn: (conn: Database.Database) => T): T { let conn: Database.Database | null = null; try { conn = new Database(DB_PATH, { readonly: true, fileMustExist: true }); const hasTable = conn .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='charges'") .get(); if (!hasTable) return fallback; return fn(conn); } catch { return fallback; } finally { conn?.close(); } } export function getCharges(): Charge[] { return withDb([], (conn) => conn .prepare( `SELECT id, start_time, end_time, duration_s, energy_wh FROM charges ORDER BY start_time DESC`, ) .all() as Charge[], ); } export function getTotals(): Totals { const empty: Totals = { month_wh: 0, year_wh: 0, total_wh: 0, install_date: null }; return withDb(empty, (conn) => { const month = conn .prepare( `SELECT COALESCE(SUM(energy_wh), 0) AS s FROM charges WHERE strftime('%Y-%m', start_time) = strftime('%Y-%m', 'now', 'localtime')`, ) .get() as { s: number }; const year = conn .prepare( `SELECT COALESCE(SUM(energy_wh), 0) AS s FROM charges WHERE strftime('%Y', start_time) = strftime('%Y', 'now', 'localtime')`, ) .get() as { s: number }; const total = conn .prepare(`SELECT COALESCE(SUM(energy_wh), 0) AS s FROM charges`) .get() as { s: number }; const install = conn .prepare(`SELECT value FROM meta WHERE key = 'install_date'`) .get() as { value: string } | undefined; return { month_wh: month.s, year_wh: year.s, total_wh: total.s, install_date: install?.value ?? null, }; }); }