// plumbing.js — read-only JSON utility API. // // This is plumbing, not part of the product surface. The workshop UI renders // HTML; anything that wants structured data (the bliss CLI, scripts, tooling) // reads it here instead of scraping fragments. Read-only by design: all writes // still go through the real /workshop/* endpoints a human uses, so this stays a // thin mirror of model.* with no business logic of its own. const express = require("express"); const model = require("./db"); const router = express.Router(); // Wrap a handler so thrown errors come back as JSON instead of an HTML stack. function json(handler) { return (req, res) => { try { const body = handler(req); if (body === undefined || body === null) { return res.status(404).json({ error: "not found" }); } return res.json(body); } catch (e) { return res.status(500).json({ error: String(e), stack: e.stack }); } }; } // All structures. router.get( "/structures", json(() => model.getStructures()), ); // One structure with everything the sidebar shows, in one call. router.get( "/structures/:id", json((req) => { const structure = model.getStructure(req.params.id); if (!structure) return null; return { structure, routes: model.getRoutes(req.params.id), templates: model.getTemplates(req.params.id), dbs: model.getDbsForStructure(req.params.id), files: model.getFilesForStruct(req.params.id), }; }), ); // One route, including its handler source. router.get( "/structures/:id/routes/:routeId", json((req) => model.getRoute(req.params.routeId)), ); // One template, including content + test_object. router.get( "/structures/:id/templates/:templateId", json((req) => model.getTemplate(req.params.templateId)), ); // One db (scoped to the structure), including its library source. router.get( "/structures/:id/dbs/:dbId", json((req) => model.getDbForStructure(req.params.id, req.params.dbId)), ); // Files attached to a structure. router.get( "/structures/:id/files", json((req) => model.getFilesForStruct(req.params.id)), ); // Version history (newest first) for a route handler, template, or db library. // Each row is a summary; fetch /versions/:versionId for the full snapshot. router.get( "/structures/:id/routes/:routeId/versions", json((req) => model.getVersions("route", req.params.routeId)), ); router.get( "/structures/:id/templates/:templateId/versions", json((req) => model.getVersions("template", req.params.templateId)), ); router.get( "/structures/:id/dbs/:dbId/versions", json((req) => model.getVersions("db", req.params.dbId)), ); // One version, including its full snapshot (the versioned fields). router.get( "/versions/:versionId", json((req) => model.getVersion(req.params.versionId)), ); // Logs for a route. ?since= returns only newer rows (for polling). router.get( "/structures/:id/routes/:routeId/logs", json((req) => { const { since } = req.query; const logs = since !== undefined ? model.getNewLogsByRoute(req.params.routeId, since) : model.getLogsByRoute(req.params.routeId); return { logs, since: model.getMostRecentLogIdByRoute(req.params.routeId) }; }), ); module.exports = router;