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
@@ -0,0 +1,71 @@
|
||||
// The read-only, unauthenticated projection of published incidents —
|
||||
// consumed by goonk's /failures page at build and runtime. Never touches
|
||||
// an incident until it has been explicitly published (see
|
||||
// routes/incidents.mjs's publish/unpublish action); an unpublished
|
||||
// incident is invisible here regardless of how much of it is filled in.
|
||||
import { publicIncident, publicDetail, publicMarkdown } from '../views.mjs';
|
||||
|
||||
const jsonHeaders = {
|
||||
'content-type': 'application/json; charset=utf-8',
|
||||
'access-control-allow-origin': '*',
|
||||
'cache-control': 'public, max-age=60',
|
||||
};
|
||||
|
||||
export function createPublicFailuresRoutes(db) {
|
||||
async function handle(req, res, path) {
|
||||
if (req.method !== 'GET') return false;
|
||||
|
||||
if (path === '/api/v1/public/failures') {
|
||||
const rows = db
|
||||
.prepare("SELECT * FROM incidents WHERE published_at<>'' ORDER BY detected_at DESC")
|
||||
.all();
|
||||
res.writeHead(200, jsonHeaders).end(
|
||||
JSON.stringify({
|
||||
schemaVersion: 1,
|
||||
generatedAt: new Date().toISOString(),
|
||||
failures: rows.map(publicIncident),
|
||||
}),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
const md = path.match(/^\/api\/v1\/public\/failures\/([^/]+)\.md$/);
|
||||
if (md) {
|
||||
const x = db
|
||||
.prepare("SELECT * FROM incidents WHERE public_slug=? AND published_at<>''")
|
||||
.get(decodeURIComponent(md[1]));
|
||||
if (!x) {
|
||||
res.writeHead(404).end('Not found');
|
||||
return true;
|
||||
}
|
||||
res
|
||||
.writeHead(200, { ...jsonHeaders, 'content-type': 'text/markdown; charset=utf-8' })
|
||||
.end(publicMarkdown(x));
|
||||
return true;
|
||||
}
|
||||
|
||||
const detail = path.match(/^\/api\/v1\/public\/failures\/([^/]+)$/);
|
||||
if (detail) {
|
||||
const x = db
|
||||
.prepare("SELECT * FROM incidents WHERE public_slug=? AND published_at<>''")
|
||||
.get(decodeURIComponent(detail[1]));
|
||||
if (!x) {
|
||||
res
|
||||
.writeHead(404, {
|
||||
'content-type': 'application/json; charset=utf-8',
|
||||
'access-control-allow-origin': '*',
|
||||
})
|
||||
.end('{"error":"not_found"}');
|
||||
return true;
|
||||
}
|
||||
res
|
||||
.writeHead(200, jsonHeaders)
|
||||
.end(JSON.stringify({ schemaVersion: 1, failure: publicDetail(x) }));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return { handle };
|
||||
}
|
||||
Reference in New Issue
Block a user