Split server.mjs into modules: db, views, routes
server.mjs mixed SQL schema, HTML-rendering functions, and route handling in one 476-line file. Split by concern: - src/db.mjs — schema + migrations (openDb()) - src/views.mjs — pure render functions: layout, incidentForm, publicMarkdown/Incident/Detail, no db or network access - src/style.mjs — the stylesheet, now genuinely shared instead of duplicated (receipts.mjs's /events page had its own near-copy, which is exactly what let their <nav> definitions drift out of sync a few commits ago) - src/http-utils.mjs — form()/redirect(), used by more than one route - src/routes/incidents.mjs — the private notebook + Git inbox + publish/unpublish, auth-gated once at the top instead of per-route - src/routes/public-failures.mjs — the read-only /api/v1/public/failures* projection server.mjs is now 79 lines of composition: open the db, build each route module, wire the dispatch order, listen. Also dropped the old POST /api/receipts endpoint and its `receipts` table — dead since the v1 events API replaced it, already flagged as "scheduled for removal" in the README. Verified behavior is unchanged, not just "looks the same": full route smoke test (401/200/404 in the right places, incident create→publish→ public-projection round trip, receipt ingestion), tests pass, and before/after screenshots of every page are pixel-identical. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
4b7b5a42d3
commit
0d825b4458
+79
@@ -0,0 +1,79 @@
|
||||
import { DatabaseSync } from 'node:sqlite';
|
||||
import { mkdirSync } from 'node:fs';
|
||||
|
||||
// Schema for the private incident notebook and the Git fix-candidate inbox.
|
||||
// Deployment-receipt tables live in receipts.mjs, next to the code that
|
||||
// actually owns that data — this file only owns the notebook's own shape.
|
||||
const SCHEMA = `
|
||||
PRAGMA journal_mode=WAL;
|
||||
PRAGMA foreign_keys=ON;
|
||||
CREATE TABLE IF NOT EXISTS incidents(
|
||||
id TEXT PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
project TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'investigating',
|
||||
severity TEXT NOT NULL DEFAULT 'annoyance',
|
||||
detected_at TEXT NOT NULL,
|
||||
symptom TEXT NOT NULL,
|
||||
impact TEXT DEFAULT '',
|
||||
root_cause TEXT DEFAULT '',
|
||||
confidence TEXT DEFAULT 'unknown',
|
||||
fix TEXT DEFAULT '',
|
||||
verification TEXT DEFAULT '',
|
||||
prevention TEXT DEFAULT '',
|
||||
remaining_risk TEXT DEFAULT '',
|
||||
lesson TEXT DEFAULT '',
|
||||
public_summary TEXT DEFAULT '',
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS timeline(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
incident_id TEXT NOT NULL REFERENCES incidents(id) ON DELETE CASCADE,
|
||||
at TEXT NOT NULL,
|
||||
kind TEXT NOT NULL,
|
||||
body TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS hypotheses(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
incident_id TEXT NOT NULL REFERENCES incidents(id) ON DELETE CASCADE,
|
||||
statement TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'untested',
|
||||
evidence TEXT DEFAULT ''
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS candidates(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
repo TEXT NOT NULL,
|
||||
sha TEXT NOT NULL,
|
||||
committed_at TEXT NOT NULL,
|
||||
subject TEXT NOT NULL,
|
||||
paths TEXT DEFAULT '',
|
||||
reason TEXT NOT NULL,
|
||||
state TEXT NOT NULL DEFAULT 'new',
|
||||
incident_id TEXT,
|
||||
UNIQUE(repo, sha)
|
||||
);`;
|
||||
|
||||
// Additive, best-effort migrations for columns that shipped after the
|
||||
// initial schema. SQLite has no "ADD COLUMN IF NOT EXISTS", so each one is
|
||||
// tried and a "duplicate column" failure is the expected steady state once
|
||||
// it's already applied everywhere.
|
||||
const MIGRATIONS = [
|
||||
"ALTER TABLE incidents ADD COLUMN published_at TEXT DEFAULT ''",
|
||||
"ALTER TABLE incidents ADD COLUMN public_slug TEXT DEFAULT ''",
|
||||
];
|
||||
|
||||
export function openDb(path = 'data/trace.db') {
|
||||
mkdirSync('data', { recursive: true });
|
||||
mkdirSync('data/exports', { recursive: true });
|
||||
const db = new DatabaseSync(path);
|
||||
db.exec(SCHEMA);
|
||||
for (const sql of MIGRATIONS) {
|
||||
try {
|
||||
db.exec(sql);
|
||||
} catch (error) {
|
||||
if (!String(error.message).includes('duplicate column')) throw error;
|
||||
}
|
||||
}
|
||||
return db;
|
||||
}
|
||||
Reference in New Issue
Block a user