'use strict'; const fs = require('fs'); const path = require('path'); const Database = require('better-sqlite3'); const DB_PATH = path.join(__dirname, 'dashboard.db'); const CONFIG_JSON = path.join(__dirname, 'config.json'); const db = new Database(DB_PATH); // Enable WAL for better concurrent read performance db.pragma('journal_mode = WAL'); // ── Schema ─────────────────────────────────────────────────────────────────── db.exec(` CREATE TABLE IF NOT EXISTS config ( key TEXT PRIMARY KEY, value TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS tests ( id TEXT PRIMARY KEY, test_id TEXT, parent_dir TEXT, filename TEXT, interference TEXT, device TEXT, rotation TEXT, test_point TEXT, station TEXT, band TEXT, channel TEXT, bandwidth TEXT, rssi TEXT, direction TEXT, completed INTEGER NOT NULL DEFAULT 0, completed_at TEXT, duration_seconds REAL ); CREATE INDEX IF NOT EXISTS idx_tests_test_id ON tests (test_id); CREATE INDEX IF NOT EXISTS idx_tests_device ON tests (device); `); // Add new columns to existing databases (idempotent) for (const colDef of ['tput_mbps REAL', 'dl_rssi_dbm REAL', 'ul_rssi_dbm REAL', 'tput_results TEXT']) { try { db.exec(`ALTER TABLE tests ADD COLUMN ${colDef}`); } catch { /* already exists */ } } // ── One-time migration from config.json ────────────────────────────────────── { const alreadyMigrated = db.prepare("SELECT COUNT(*) AS n FROM config").get().n > 0; if (!alreadyMigrated && fs.existsSync(CONFIG_JSON)) { try { const legacy = JSON.parse(fs.readFileSync(CONFIG_JSON, 'utf8')); const insert = db.prepare('INSERT OR IGNORE INTO config (key, value) VALUES (?, ?)'); const migrate = db.transaction((obj) => { for (const [k, v] of Object.entries(obj)) { if (v != null) insert.run(k, String(v)); } }); migrate(legacy); console.log('[db] Migrated config.json → SQLite'); } catch (e) { console.warn('[db] Could not migrate config.json:', e.message); } } } // ── Config ─────────────────────────────────────────────────────────────────── const _stmtGetConfig = db.prepare('SELECT value FROM config WHERE key = ?'); const _stmtSetConfig = db.prepare('INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)'); const _stmtDelConfig = db.prepare('DELETE FROM config WHERE key = ?'); function getConfig(key) { return _stmtGetConfig.get(key)?.value ?? null; } function setConfig(key, value) { _stmtSetConfig.run(key, value); } function delConfig(key) { _stmtDelConfig.run(key); } // ── Tests ───────────────────────────────────────────────────────────────────── const _stmtUpsertTest = db.prepare(` INSERT INTO tests (id, test_id, parent_dir, filename, interference, device, rotation, test_point, station, band, channel, bandwidth, rssi, direction) VALUES (@id, @test_id, @parent_dir, @filename, @interference, @device, @rotation, @test_point, @station, @band, @channel, @bandwidth, @rssi, @direction) ON CONFLICT(id) DO UPDATE SET test_id = excluded.test_id, parent_dir = excluded.parent_dir, filename = excluded.filename, interference = excluded.interference, device = excluded.device, rotation = excluded.rotation, test_point = excluded.test_point, station = excluded.station, band = excluded.band, channel = excluded.channel, bandwidth = excluded.bandwidth, rssi = excluded.rssi, direction = excluded.direction -- completed / completed_at / duration_seconds intentionally preserved `); const _stmtMarkCompleted = db.prepare(` UPDATE tests SET completed = 1, completed_at = ?, duration_seconds = ?, tput_results = ? WHERE test_id = ? AND (? IS NULL OR device = ?) `); const _stmtResetTest = db.prepare(` UPDATE tests SET completed = 0, completed_at = NULL, duration_seconds = NULL, tput_results = NULL WHERE test_id = ? AND (? IS NULL OR device = ?) `); const _stmtGetStation = db.prepare('SELECT station FROM tests WHERE test_id = ? AND device = ? LIMIT 1'); const _stmtClearTests = db.prepare('DELETE FROM tests'); const _stmtGetAllTests = db.prepare('SELECT * FROM tests'); const _stmtCountTests = db.prepare('SELECT COUNT(*) AS n FROM tests'); function upsertTest(test) { _stmtUpsertTest.run(test); } function markCompleted(test_id, device, completed_at, duration_seconds, tputResults = null) { const json = tputResults && tputResults.length > 0 ? JSON.stringify(tputResults) : null; _stmtMarkCompleted.run(completed_at, duration_seconds, json, test_id, device, device); } function getStationForTest(test_id, device) { return _stmtGetStation.get(test_id, device)?.station ?? null; } function resetByFileIdAndDevice(test_id, device) { _stmtResetTest.run(test_id, device, device); } function clearTests() { _stmtClearTests.run(); } function getAllTests() { return _stmtGetAllTests.all(); } function countTests() { return _stmtCountTests.get().n; } module.exports = { getConfig, setConfig, delConfig, upsertTest, markCompleted, resetByFileIdAndDevice, clearTests, getAllTests, countTests, getStationForTest, };