Skip to content

Repository files, collaboration, releases and project activity.

ARCHFORGE HISTORY

Blame · src/database.js

Branch main · the Commit author is not necessarily a verified ArchForge account.

e59b7f7cc6archadmin2026-10-111 'use strict';
e59b7f7cc6archadmin2026-10-112 const fs=require('node:fs');const path=require('node:path');const {DatabaseSync}=require('node:sqlite');
e59b7f7cc6archadmin2026-10-113 function openDatabase(file){if(file!==':memory:')fs.mkdirSync(path.dirname(file),{recursive:true,mode:0o750});const db=new DatabaseSync(file); db.exec('PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000;');init(db);return db;}
e59b7f7cc6archadmin2026-10-114 function init(db){db.exec(`
e59b7f7cc6archadmin2026-10-115 CREATE TABLE IF NOT EXISTS admin(id INTEGER PRIMARY KEY CHECK(id=1), username TEXT NOT NULL, password_hash TEXT NOT NULL, totp_encrypted TEXT NOT NULL);
e59b7f7cc6archadmin2026-10-116 CREATE TABLE IF NOT EXISTS sessions(token_hash TEXT PRIMARY KEY, csrf TEXT NOT NULL, expires_at INTEGER NOT NULL, created_at INTEGER NOT NULL);
e59b7f7cc6archadmin2026-10-117 CREATE TABLE IF NOT EXISTS services(id TEXT PRIMARY KEY, label TEXT NOT NULL, description TEXT NOT NULL, configured INTEGER NOT NULL DEFAULT 1, category TEXT NOT NULL DEFAULT 'Core', current_state TEXT NOT NULL DEFAULT 'unknown', failures INTEGER NOT NULL DEFAULT 0, successes INTEGER NOT NULL DEFAULT 0, last_checked INTEGER, last_latency INTEGER);
e59b7f7cc6archadmin2026-10-118 CREATE TABLE IF NOT EXISTS samples(id INTEGER PRIMARY KEY AUTOINCREMENT, service_id TEXT NOT NULL REFERENCES services(id), checked_at INTEGER NOT NULL, success INTEGER NOT NULL, latency_ms INTEGER, warning INTEGER NOT NULL DEFAULT 0, detail TEXT NOT NULL DEFAULT '');
e59b7f7cc6archadmin2026-10-119 CREATE INDEX IF NOT EXISTS idx_samples_service_date ON samples(service_id,checked_at);
e59b7f7cc6archadmin2026-10-1110 CREATE TABLE IF NOT EXISTS incidents(id INTEGER PRIMARY KEY AUTOINCREMENT, service_id TEXT REFERENCES services(id), title TEXT NOT NULL, description TEXT NOT NULL, state TEXT NOT NULL CHECK(state IN ('investigating','identified','monitoring','resolved')), severity TEXT NOT NULL CHECK(severity IN ('degraded','outage')), automatic INTEGER NOT NULL DEFAULT 0, started_at INTEGER NOT NULL, resolved_at INTEGER);
e59b7f7cc6archadmin2026-10-1111 CREATE INDEX IF NOT EXISTS idx_incidents_active ON incidents(resolved_at, started_at);
e59b7f7cc6archadmin2026-10-1112 CREATE TABLE IF NOT EXISTS incident_updates(id INTEGER PRIMARY KEY AUTOINCREMENT, incident_id INTEGER NOT NULL REFERENCES incidents(id) ON DELETE CASCADE, state TEXT NOT NULL, body TEXT NOT NULL, created_at INTEGER NOT NULL);
e59b7f7cc6archadmin2026-10-1113 CREATE TABLE IF NOT EXISTS maintenance(id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, description TEXT NOT NULL, starts_at INTEGER NOT NULL, ends_at INTEGER NOT NULL, service_id TEXT REFERENCES services(id), created_at INTEGER NOT NULL);
e59b7f7cc6archadmin2026-10-1114 CREATE TABLE IF NOT EXISTS subscriptions(id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT NOT NULL UNIQUE COLLATE NOCASE, confirmed INTEGER NOT NULL DEFAULT 0, confirm_hash TEXT, confirm_until INTEGER, unsubscribe_hash TEXT NOT NULL, created_at INTEGER NOT NULL);
e59b7f7cc6archadmin2026-10-1115 CREATE TABLE IF NOT EXISTS messages(id INTEGER PRIMARY KEY AUTOINCREMENT, channel TEXT NOT NULL CHECK(channel IN ('discord','email')), destination TEXT NOT NULL, subject TEXT NOT NULL, body TEXT NOT NULL, created_at INTEGER NOT NULL, attempts INTEGER NOT NULL DEFAULT 0, next_at INTEGER NOT NULL, delivered_at INTEGER, last_error TEXT);
e59b7f7cc6archadmin2026-10-1116 CREATE INDEX IF NOT EXISTS idx_messages_queue ON messages(delivered_at,next_at);
e59b7f7cc6archadmin2026-10-1117 CREATE TABLE IF NOT EXISTS setting(key TEXT PRIMARY KEY,value TEXT NOT NULL);
e59b7f7cc6archadmin2026-10-1118 CREATE TABLE IF NOT EXISTS audit(id INTEGER PRIMARY KEY AUTOINCREMENT, event TEXT NOT NULL, created_at INTEGER NOT NULL);
2109dd33b8archadmin2026-10-1219 -- Canonical public brand labels stay correct even when an older process
2109dd33b8archadmin2026-10-1220 -- writes the original names into the same database. IDs stay stable.
2109dd33b8archadmin2026-10-1221 CREATE TRIGGER IF NOT EXISTS archforge_services_branding_insert
2109dd33b8archadmin2026-10-1222 AFTER INSERT ON services
2109dd33b8archadmin2026-10-1223 WHEN NEW.id IN ('git_ssh','git_http')
2109dd33b8archadmin2026-10-1224 BEGIN
2109dd33b8archadmin2026-10-1225  UPDATE services SET
2109dd33b8archadmin2026-10-1226  label=CASE NEW.id WHEN 'git_ssh' THEN 'ArchForge SSH' ELSE 'ArchForge HTTPS' END,
2109dd33b8archadmin2026-10-1227  category='ArchForge'
2109dd33b8archadmin2026-10-1228  WHERE id=NEW.id;
2109dd33b8archadmin2026-10-1229 END;
2109dd33b8archadmin2026-10-1230 CREATE TRIGGER IF NOT EXISTS archforge_services_branding_update
2109dd33b8archadmin2026-10-1231 AFTER UPDATE OF label,category ON services
2109dd33b8archadmin2026-10-1232 WHEN NEW.id IN ('git_ssh','git_http') AND
2109dd33b8archadmin2026-10-1233  (NEW.label != CASE NEW.id WHEN 'git_ssh' THEN 'ArchForge SSH' ELSE 'ArchForge HTTPS' END
2109dd33b8archadmin2026-10-1234  OR NEW.category != 'ArchForge')
2109dd33b8archadmin2026-10-1235 BEGIN
2109dd33b8archadmin2026-10-1236  UPDATE services SET
2109dd33b8archadmin2026-10-1237  label=CASE NEW.id WHEN 'git_ssh' THEN 'ArchForge SSH' ELSE 'ArchForge HTTPS' END,
2109dd33b8archadmin2026-10-1238  category='ArchForge'
2109dd33b8archadmin2026-10-1239  WHERE id=NEW.id;
2109dd33b8archadmin2026-10-1240 END;
e59b7f7cc6archadmin2026-10-1141 `);}
e59b7f7cc6archadmin2026-10-1142 function syncServices(db,services){const stmt=db.prepare('INSERT INTO services(id,label,description,configured,category) VALUES(?,?,?,?,?) ON CONFLICT(id) DO UPDATE SET label=excluded.label,description=excluded.description,configured=excluded.configured,category=excluded.category');for(const s of services)stmt.run(s.id,s.label,s.description,s.configured?1:0,s.category||'Core');}
e59b7f7cc6archadmin2026-10-1143 function rows(db,sql,...p){return db.prepare(sql).all(...p);}
e59b7f7cc6archadmin2026-10-1144 function one(db,sql,...p){return db.prepare(sql).get(...p);}
e59b7f7cc6archadmin2026-10-1145 function run(db,sql,...p){return db.prepare(sql).run(...p);}
e59b7f7cc6archadmin2026-10-1146 function transaction(db,fn){db.exec('BEGIN IMMEDIATE');try{const r=fn();db.exec('COMMIT');return r;}catch(e){db.exec('ROLLBACK');throw e;}}
e59b7f7cc6archadmin2026-10-1147 function now(){return Math.floor(Date.now()/1000);}
e59b7f7cc6archadmin2026-10-1148 module.exports={openDatabase,init,syncServices,rows,one,run,transaction,now};