chore: cleanup of code

This commit is contained in:
Your Name 2026-08-02 22:06:42 -04:00
parent 28a441d98a
commit ae51fc763c
3 changed files with 264 additions and 275 deletions

86
CLAUDE.md Normal file
View file

@ -0,0 +1,86 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Commands
```bash
node index.js # Start the server (port 3000)
npx prettier --write . # Format code
```
No build step. No test suite. Restart the server to pick up changes to `index.js` or `db.js`; changes to Eta templates in `views/` are picked up live (cache is disabled).
Requires a `.env` file with `VAPID_PUBLIC_KEY` and `VAPID_PRIVATE_KEY` for web push.
## What This Is
Bliss is a browser-based low-code platform where users build mini web apps ("Structures") entirely through a `/workshop` UI. Each Structure has routes, templates, and SQLite databases — all stored in the main app database and executed at runtime via Node's `vm` module.
## Architecture
### Two layers of routes
The Express app has two kinds of routes:
1. **Workshop routes** (hardcoded in `index.js`) — The `/workshop/*` editor UI for managing structures, routes, templates, databases, and files.
2. **User routes** (dynamic, loaded from DB) — Caught by the `app.all("*")` handler at the bottom of `index.js`. At request time it matches the path against the `routes` in-memory array (rebuilt by `buildRoutes()`), finds the matching route record, and executes the user's `handler` JS string inside a sandboxed VM context.
`buildRoutes()` must be called after any route is created/updated to refresh the in-memory matcher array.
### Sandboxed execution (`bootstrapContext`)
User route handlers run in `vm.createContext()` with a restricted API:
- `require('eta')` — Eta template instance scoped to the structure
- `require('db')` — getter for named database instances. `require('db')(alias)` returns `{ library, sql }`: `sql` is the raw `better-sqlite3` instance for that db, and `library` is `module.exports` from that db's own "library" script (arbitrary JS, edited via `/workshop/:structure_id/db/:db_id/library`, run once per request with `sql` bound in its own vm context — see `bootstrapContext` in `index.js`). Handlers typically call helper functions off `library` rather than writing raw SQL inline.
- `require('push')` — web-push library
- `require('files')``{ saveFile }` helper
- `console.log` — writes to the app's `logs` table (not stdout)
- `fetch`, `setTimeout`, `clearTimeout`
- `route(url)` — prefixes a URL with the structure's `route_prefix`
Handlers call `res.render(templateName, context)` or `ws.render(templateName, context)` to render Eta templates. These are injected onto `req`/`res` after the context is created (not available inside `bootstrapContext` itself).
WebSocket routes are bootstrapped once by `bootstrapWebsocketHandler` and stored in `wsRoutes`; subsequent saves only update the handler function without re-registering the ws path.
### Template rendering (`bootstrapTemplateWithHTMXetc`)
Every HTML response passes through this function, which uses Cheerio to inject HTMX, Tailwind, Hyperscript, and `bliss_inspector.js` into the `<head>`, and attaches `data-bliss-route` / `data-bliss-clone` / `data-bliss-copy` attributes to `<body>` (or to fragment children for HTMX partial swaps). These attributes drive the in-page editor overlay.
- `data-bliss-route` — link to the workshop editor page for whatever produced this element.
- `data-bliss-clone``hx-get` target for the clone-structure modal.
- `data-bliss-copy` — only set for GET routes, via `embedHTML(url)` (`index.js`): `<div hx-get="{url}" hx-trigger="load"></div>`. This is the actual page-nesting primitive — the inspector's 📋 button copies this snippet to the clipboard, and pasting it into any other template lazy-loads that route as a live child page via HTMX. This is how Structures compose: pages nesting pages nesting pages, each one an independently addressable, independently editable route.
### The inspector (`bliss_inspector.js`) and where it's headed
Currently the 🔍 overlay (injected into every rendered page, see above) only highlights `[data-bliss-route]` elements and offers links out to the editor, a copy-embed button, and a clone-structure modal — it does not let you edit anything in place yet.
The long-term goal is for the inspector to become a real-time, in-page structure editor: instead of jumping out to `/workshop/...`, you'd edit a nested page's route/template directly where it's embedded and see it update live. Two things that design needs and don't exist yet:
- Each embedded view needs to carry its **source** (which structure/route/template produced it) — `data-bliss-route` currently only gives a link out, not enough info to edit inline.
- Each embedded view needs to carry its **args** — the context/params it was rendered with (e.g. `res.render(template, context)`'s `context`, URL/query params) — so the in-place editor can show and modify what was actually passed in, not just the output.
Cross-fragment interaction (nested pages talking to each other) isn't a bespoke Bliss feature — it rides on standard HTMX/Hyperscript behavior since every nested page lives in the same DOM: `hx-swap-oob` for out-of-band updates (already used for head injection), and bubbling custom events (Hyperscript `send`/`trigger`, `hx-trigger="eventName from:body"`) for one nested page to notify siblings/ancestors.
### Database layer (`db.js`)
- Main app DB: `./dbs/0.sqlite` — stores structures, routes, templates, dbs metadata, files, logs.
- User databases: `./dbs/{id}.sqlite` — one SQLite file per user-created DB, opened on demand and cached via LRU (max 25 connections).
- Eta template instances are also LRU-cached per structure (max 100). The cache must be invalidated if template content changes — currently the cache is not explicitly invalidated on update, relying on the LRU eviction policy.
- Migrations in `migrations/` are applied sequentially on startup (tracked in a `migrations` table).
### Structure cloning
`cloneStructure` (in `db.js`) deep-copies a structure's routes, templates, scaffold pages, and optionally its databases (full copy vs. aliased reference). Aliased DBs share the same SQLite file across structures.
### Views vs. user templates
- `views/` — Eta templates for the workshop UI itself (not user content).
- User-created templates are stored as rows in the `templates` table and resolved at render time via `getTemplater()` / `readFile`.
### Scaffold pages
Non-GET routes (POST, PUT, DELETE, WS) get auto-generated scaffold pages — test forms or WebSocket test UIs — stored in `scaffold_pages`. These are served at `/workshop/:structure_id/route/:route_id/preview`.
### Frontend stack (workshop UI)
HTMX + Hyperscript + Tailwind (CDN, preflight disabled) + `bliss_inspector.js` (the in-page editor overlay). All served from `public/js/`.

108
db.js
View file

@ -44,13 +44,13 @@ function applyMigrations() {
// | | _____| || |___ | | | | // | | _____| || |___ | | | |
// |_______||_______||_______||___| |_| // |_______||_______||_______||___| |_|
function createUser(db, username, hashedPassword) { function createUser(username, hashedPassword) {
return db return db
.prepare("INSERT INTO users (username, password) VALUES (?, ?)") .prepare("INSERT INTO users (username, password) VALUES (?, ?)")
.run(username, hashedPassword).lastInsertRowid; .run(username, hashedPassword).lastInsertRowid;
} }
function getUser(db, username) { function getUser(username) {
return db.prepare("SELECT * from users where username = ?").get(username); return db.prepare("SELECT * from users where username = ?").get(username);
} }
@ -76,7 +76,7 @@ function getDbInstance(dbId) {
return dbInstance; return dbInstance;
} }
function getAllRoutes(db) { function getAllRoutes() {
return db return db
.prepare( .prepare(
` `
@ -100,7 +100,7 @@ function getTemplater(structId) {
return path; return path;
}; };
etaInstance.readFile = function (templateAlias) { etaInstance.readFile = function (templateAlias) {
return getTemplateContentByName(db, structId, templateAlias).content; return getTemplateContentByName(structId, templateAlias).content;
}; };
templateCache.set(structId, etaInstance); templateCache.set(structId, etaInstance);
} }
@ -108,15 +108,15 @@ function getTemplater(structId) {
return etaInstance; return etaInstance;
} }
function getStructures(db) { function getStructures() {
return db.prepare("SELECT * from structures").all(); return db.prepare("SELECT * from structures").all();
} }
function getStructure(db, id) { function getStructure(id) {
return db.prepare("SELECT * from structures where ID = ?").get(id); return db.prepare("SELECT * from structures where ID = ?").get(id);
} }
function createStructure(db, name, userId) { function createStructure(name, userId) {
const stmt = db.prepare( const stmt = db.prepare(
"INSERT INTO structures (name, user_id) VALUES (?, ?)", "INSERT INTO structures (name, user_id) VALUES (?, ?)",
); );
@ -124,7 +124,7 @@ function createStructure(db, name, userId) {
return info.lastInsertRowid; // Returns the structure_id of the newly created structure return info.lastInsertRowid; // Returns the structure_id of the newly created structure
} }
function createRoute(db, verb, path, structureId, handler) { function createRoute(verb, path, structureId, handler) {
path = encodeURI(path); path = encodeURI(path);
const stmt = db.prepare( const stmt = db.prepare(
@ -134,14 +134,13 @@ function createRoute(db, verb, path, structureId, handler) {
const routeId = info.lastInsertRowid; // Get the newly created route ID const routeId = info.lastInsertRowid; // Get the newly created route ID
if (verb !== 'GET') { if (verb !== 'GET') {
createScaffoldPage(db, routeId); createScaffoldPage(routeId);
} }
return routeId; // Returns the route_id of the newly created route return routeId; // Returns the route_id of the newly created route
} }
function getRoutes(db, structureId) { const ROUTE_SELECT = `
const query = `
SELECT SELECT
r.*, r.*,
s.route_prefix, s.route_prefix,
@ -154,40 +153,21 @@ function getRoutes(db, structureId) {
LEFT JOIN scaffold_pages sp ON r.id = sp.route_id LEFT JOIN scaffold_pages sp ON r.id = sp.route_id
LEFT JOIN scaffold_params sp2 ON r.id = sp2.route_id LEFT JOIN scaffold_params sp2 ON r.id = sp2.route_id
LEFT JOIN structures s ON r.structure_id = s.id LEFT JOIN structures s ON r.structure_id = s.id
WHERE r.structure_id = ? `;
ORDER BY sp.created_at DESC, sp2.created_at DESC
`;
return db.prepare(query).all(structureId); function getRoutes(structureId) {
return db
.prepare(`${ROUTE_SELECT} WHERE r.structure_id = ? ORDER BY sp.created_at DESC, sp2.created_at DESC`)
.all(structureId);
} }
function getRoute(db, routeId) { function getRoute(routeId) {
const query = ` return db
SELECT .prepare(`${ROUTE_SELECT} WHERE r.id = ? ORDER BY sp.created_at DESC, sp2.created_at DESC LIMIT 1`)
r.*, .get(routeId);
s.route_prefix,
sp.id AS scaffold_page_id,
sp.content AS scaffold_page_content,
sp2.id AS scaffold_params_id,
sp2.query_params,
sp2.url_params
FROM routes r
LEFT JOIN scaffold_pages sp ON r.id = sp.route_id
LEFT JOIN scaffold_params sp2 ON r.id = sp2.route_id
LEFT JOIN structures s ON r.structure_id = s.id
WHERE r.id = ?
ORDER BY sp.created_at DESC, sp2.created_at DESC
LIMIT 1
`;
return db.prepare(query).get(routeId);
} }
function defaultEndpointForRoute(db, routeId) { function updateRoute(route) {
}
function updateRoute(db, route) {
const fields = [ const fields = [
"verb", "verb",
"path", "path",
@ -204,7 +184,7 @@ function updateRoute(db, route) {
db.prepare(sql).run(...values); db.prepare(sql).run(...values);
} }
function updateDb(db, appDb) { function updateDb(appDb) {
const fields = ["name", "library"]; const fields = ["name", "library"];
const values = fields.map((field) => appDb[field]); const values = fields.map((field) => appDb[field]);
const placeholders = fields.map((field) => `${field} = ?`).join(", "); const placeholders = fields.map((field) => `${field} = ?`).join(", ");
@ -214,7 +194,7 @@ function updateDb(db, appDb) {
db.prepare(sql).run(...values); db.prepare(sql).run(...values);
} }
function updateStruct(db, struct) { function updateStruct(struct) {
const fields = ["name", "route_prefix", "head_injection"]; const fields = ["name", "route_prefix", "head_injection"];
const values = fields.map((field) => struct[field]); const values = fields.map((field) => struct[field]);
const placeholders = fields.map((field) => `${field} = ?`).join(", "); const placeholders = fields.map((field) => `${field} = ?`).join(", ");
@ -224,7 +204,7 @@ function updateStruct(db, struct) {
db.prepare(sql).run(...values); db.prepare(sql).run(...values);
} }
function updateTemplate(db, template) { function updateTemplate(template) {
const fields = ["content", "name", "test_object"]; const fields = ["content", "name", "test_object"];
const values = fields.map((field) => template[field]); const values = fields.map((field) => template[field]);
const placeholders = fields.map((field) => `${field} = ?`).join(", "); const placeholders = fields.map((field) => `${field} = ?`).join(", ");
@ -234,17 +214,17 @@ function updateTemplate(db, template) {
db.prepare(sql).run(...values); db.prepare(sql).run(...values);
} }
function getTemplates(db, structureId) { function getTemplates(structureId) {
return db return db
.prepare("SELECT * from templates where structure_id = ?") .prepare("SELECT * from templates where structure_id = ?")
.all(structureId); .all(structureId);
} }
function getTemplate(db, templateId) { function getTemplate(templateId) {
return db.prepare("SELECT * from templates where id = ?").get(templateId); return db.prepare("SELECT * from templates where id = ?").get(templateId);
} }
function getTemplateContentByName(db, structId, name) { function getTemplateContentByName(structId, name) {
return db return db
.prepare( .prepare(
"SELECT content from templates where structure_id = ? AND name = ?", "SELECT content from templates where structure_id = ? AND name = ?",
@ -252,7 +232,7 @@ function getTemplateContentByName(db, structId, name) {
.get(structId, name); .get(structId, name);
} }
function createTemplate(db, structureId, name, content, testObjectString) { function createTemplate(structureId, name, content, testObjectString) {
return db return db
.prepare( .prepare(
"INSERT INTO templates (structure_id, name, content, test_object) VALUES (?, ?, ?, ?)", "INSERT INTO templates (structure_id, name, content, test_object) VALUES (?, ?, ?, ?)",
@ -260,7 +240,7 @@ function createTemplate(db, structureId, name, content, testObjectString) {
.run(structureId, name, content, testObjectString).lastInsertRowid; .run(structureId, name, content, testObjectString).lastInsertRowid;
} }
function getDbsForStructure(db, structureId) { function getDbsForStructure(structureId) {
return db return db
.prepare( .prepare(
`SELECT `SELECT
@ -280,7 +260,7 @@ function getDbsForStructure(db, structureId) {
.all(structureId); .all(structureId);
} }
function getDb(db, dbId) { function getDb(dbId) {
return db return db
.prepare( .prepare(
`SELECT * `SELECT *
@ -291,7 +271,7 @@ function getDb(db, dbId) {
.get(dbId); .get(dbId);
} }
function getDbForStructure(db, structureId, dbId) { function getDbForStructure(structureId, dbId) {
return db return db
.prepare( .prepare(
`SELECT * `SELECT *
@ -304,7 +284,7 @@ function getDbForStructure(db, structureId, dbId) {
.get(structureId, dbId); .get(structureId, dbId);
} }
function createDb(db, structId, name) { function createDb(structId, name) {
const transaction = db.transaction(() => { const transaction = db.transaction(() => {
const insertDbStmt = db.prepare(` const insertDbStmt = db.prepare(`
INSERT INTO dbs (name, structure_id) INSERT INTO dbs (name, structure_id)
@ -328,7 +308,7 @@ function createDb(db, structId, name) {
return transaction(); return transaction();
} }
function attachDb(db, structId, dbId, alias) { function attachDb(structId, dbId, alias) {
const insertStructureDbStmt = db.prepare(` const insertStructureDbStmt = db.prepare(`
INSERT INTO structure_dbs (db_id, structure_id, alias) INSERT INTO structure_dbs (db_id, structure_id, alias)
VALUES (?, ?, ?) VALUES (?, ?, ?)
@ -336,7 +316,7 @@ function attachDb(db, structId, dbId, alias) {
insertStructureDbStmt.run(dbId, structId, alias); insertStructureDbStmt.run(dbId, structId, alias);
} }
function getFilesForStruct(db, structureId) { function getFilesForStruct(structureId) {
let test = db let test = db
.prepare("SELECT * FROM files WHERE structure_id = ? ORDER BY id DESC") .prepare("SELECT * FROM files WHERE structure_id = ? ORDER BY id DESC")
.all(structureId); .all(structureId);
@ -344,11 +324,11 @@ function getFilesForStruct(db, structureId) {
return test; return test;
} }
function getFile(db, fileId) { function getFile(fileId) {
return db.prepare("SELECT * FROM files WHERE id = ?").get(fileId); return db.prepare("SELECT * FROM files WHERE id = ?").get(fileId);
} }
function createFile(db, structure_id, name, filePath, mime_type, mime_subtype) { function createFile(structure_id, name, filePath, mime_type, mime_subtype) {
return db return db
.prepare( .prepare(
"INSERT INTO files (structure_id, name, path, mime_type, mime_subtype) VALUES (?, ?, ?, ?, ?)", "INSERT INTO files (structure_id, name, path, mime_type, mime_subtype) VALUES (?, ?, ?, ?, ?)",
@ -468,7 +448,7 @@ function cloneStructure(
return transaction(); return transaction();
} }
function createLog(db, structureId, routeId, content, error = false) { function createLog(structureId, routeId, content, error = false) {
const stmt = db.prepare( const stmt = db.prepare(
"INSERT INTO logs (error, structure_id, route_id, content) VALUES (?, ?, ?, ?)", "INSERT INTO logs (error, structure_id, route_id, content) VALUES (?, ?, ?, ?)",
); );
@ -476,7 +456,7 @@ function createLog(db, structureId, routeId, content, error = false) {
return info.lastInsertRowid; // Returns the log_id of the newly created log return info.lastInsertRowid; // Returns the log_id of the newly created log
} }
function getLogsByRoute(db, routeId) { function getLogsByRoute(routeId) {
return db return db
.prepare( .prepare(
"SELECT * FROM logs WHERE route_id = ? ORDER BY created_at DESC LIMIT 50", "SELECT * FROM logs WHERE route_id = ? ORDER BY created_at DESC LIMIT 50",
@ -485,7 +465,7 @@ function getLogsByRoute(db, routeId) {
.reverse(); .reverse();
} }
function getNewLogsByRoute(db, routeId, since) { function getNewLogsByRoute(routeId, since) {
return db return db
.prepare( .prepare(
"SELECT * FROM logs WHERE route_id = ? AND id > ? ORDER BY created_at DESC", "SELECT * FROM logs WHERE route_id = ? AND id > ? ORDER BY created_at DESC",
@ -493,7 +473,7 @@ function getNewLogsByRoute(db, routeId, since) {
.all(routeId, since); .all(routeId, since);
} }
function getMostRecentLogIdByRoute(db, routeId) { function getMostRecentLogIdByRoute(routeId) {
const result = db const result = db
.prepare("SELECT MAX(id) AS id FROM logs WHERE route_id = ?") .prepare("SELECT MAX(id) AS id FROM logs WHERE route_id = ?")
.get(routeId); .get(routeId);
@ -505,9 +485,9 @@ function buildScaffoldUrl(endpoint, urlParams, queryString) {
return queryString ? `${populatedUrl}?${queryString}` : populatedUrl; return queryString ? `${populatedUrl}?${queryString}` : populatedUrl;
} }
function createScaffoldPage(db, routeId, content=null) { function createScaffoldPage(routeId, content=null) {
if (!content) { if (!content) {
const route = getRoute(db, routeId) const route = getRoute(routeId)
const endpoint = buildScaffoldUrl(route.path, route.url_params, route.query_params); const endpoint = buildScaffoldUrl(route.path, route.url_params, route.query_params);
content = getDefaultScaffoldContentByVerb(endpoint, route.verb); content = getDefaultScaffoldContentByVerb(endpoint, route.verb);
console.log("gabga", content, route, endpoint) console.log("gabga", content, route, endpoint)
@ -520,14 +500,14 @@ function createScaffoldPage(db, routeId, content=null) {
return info.lastInsertRowid; // Returns the scaffold_page id of the newly created scaffold page return info.lastInsertRowid; // Returns the scaffold_page id of the newly created scaffold page
} }
function getLatestScaffoldPage(db, routeId) { function getLatestScaffoldPage(routeId) {
const stmt = db.prepare( const stmt = db.prepare(
"SELECT * FROM scaffold_pages WHERE route_id = ? ORDER BY created_at DESC LIMIT 1" "SELECT * FROM scaffold_pages WHERE route_id = ? ORDER BY created_at DESC LIMIT 1"
); );
return stmt.get(routeId); return stmt.get(routeId);
} }
function updateScaffoldPage(db, scaffoldPage) { function updateScaffoldPage(scaffoldPage) {
const fields = ["content"]; const fields = ["content"];
const values = fields.map((field) => scaffoldPage[field]); const values = fields.map((field) => scaffoldPage[field]);
const placeholders = fields.map((field) => `${field} = ?`).join(", "); const placeholders = fields.map((field) => `${field} = ?`).join(", ");
@ -582,7 +562,7 @@ function generateModifyForm(endpoint, verb) {
<body> <body>
<h1>Test ${verb} Request</h1> <h1>Test ${verb} Request</h1>
<form action="<%= it.route(${endpoint}) %>" method="${verb}"> <form action="<%= it.route("${endpoint}") %>" method="${verb}">
<!-- Add your form fields here to test ${verb.toLowerCase()} requests. --> <!-- Add your form fields here to test ${verb.toLowerCase()} requests. -->
<label for="testField">Test Field:</label> <label for="testField">Test Field:</label>
<input type="text" id="testField" name="testField" required> <input type="text" id="testField" name="testField" required>

297
index.js
View file

@ -28,6 +28,11 @@ let routes = { GET: [], POST: [], PUT: [], DELETE: [] };
const wsRoutes = {} const wsRoutes = {}
const wsConnections = {} const wsConnections = {}
const INSPECT_OPTS = { showHidden: false, depth: null, colors: false, compact: false };
function inspectArgs(args) {
return args.map((v) => util.inspect(v, INSPECT_OPTS)).join(" ");
}
app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json()); app.use(bodyParser.json());
app.use(express.static("public")); app.use(express.static("public"));
@ -64,7 +69,6 @@ async function saveFile(structureId, req, uploadedFile, asset=false) {
await uploadedFile.mv(path.join(uploadPath, name)); await uploadedFile.mv(path.join(uploadPath, name));
let id = model.createFile( let id = model.createFile(
db,
structureId, structureId,
name, name,
storedPath, storedPath,
@ -73,13 +77,13 @@ async function saveFile(structureId, req, uploadedFile, asset=false) {
asset, asset,
); );
let file = model.getFile(db, id); let file = model.getFile(id);
file.url = prefixUrlWithHost(req, file.path); file.url = prefixUrlWithHost(req, file.path);
return file return file
} }
function bootstrapContext(db, structureId, routeId, initContext) { function bootstrapContext(structureId, routeId, initContext) {
const allDbInstances = {}; const allDbInstances = {};
function getDb(alias) { function getDb(alias) {
return allDbInstances[alias]; return allDbInstances[alias];
@ -88,12 +92,12 @@ function bootstrapContext(db, structureId, routeId, initContext) {
// todo wrap template in data-bliss-edit-template thing since ws can't take us to the editor on a component basis?? maybe... // todo wrap template in data-bliss-edit-template thing since ws can't take us to the editor on a component basis?? maybe...
// for now just designing with component approach // for now just designing with component approach
const eta = model.getTemplater(structureId); const eta = model.getTemplater(structureId);
const structure = model.getStructure(db, structureId) const structure = model.getStructure(structureId)
const __urlPrefix = structure.route_prefix; const __urlPrefix = structure.route_prefix;
const libs = { eta, db: getDb, push: webPush, files: { saveFile: (...args) => saveFile(structureId, ...args) } }; const libs = { eta, db: getDb, push: webPush, files: { saveFile: (...args) => saveFile(structureId, ...args) } };
let dbs = model.getDbsForStructure(db, structureId); let dbs = model.getDbsForStructure(structureId);
let context = vm.createContext({ let context = vm.createContext({
...initContext, ...initContext,
require: function (str) { require: function (str) {
@ -102,21 +106,8 @@ function bootstrapContext(db, structureId, routeId, initContext) {
module: { exports: null }, module: { exports: null },
console: { console: {
log: function (...content) { log: function (...content) {
const s = content.reduce((acc, curr) => { content.forEach((c) => console.log(c));
console.log(curr); model.createLog(structureId, routeId, inspectArgs(content));
return (
acc +
(acc ? " " : "") +
`${util.inspect(curr, {
showHidden: false,
depth: null, // `null` lets you see the full depth of the object
colors: false, // Setting this to true uses ANSI color codes
compact: false,
})}`
);
}, "");
model.createLog(db, structureId, routeId, s);
}, },
}, },
vapidPublicKey: vapidPublicKey, vapidPublicKey: vapidPublicKey,
@ -124,7 +115,7 @@ function bootstrapContext(db, structureId, routeId, initContext) {
clearTimeout: clearTimeout, clearTimeout: clearTimeout,
setTimeout: setTimeout, setTimeout: setTimeout,
route: function (url) { route: function (url) {
return __urlPrefix ? path.join(__urlPrefix, url) : url; return withPrefix(__urlPrefix, url);
}, },
}); });
for (let appDb of dbs) { for (let appDb of dbs) {
@ -141,9 +132,12 @@ function bootstrapContext(db, structureId, routeId, initContext) {
return context; return context;
} }
function withPrefix(prefix, url) {
return prefix ? path.join(prefix, url) : url;
}
function routeWithPrefix(route) { function routeWithPrefix(route) {
const prefix = route.route_prefix; return withPrefix(route.route_prefix, route.path);
return prefix ? path.join(prefix, route.path) : route.path;
} }
@ -151,7 +145,7 @@ function bootstrapWebsocketHandler(route) {
// todo only expose app when running the handler, should not be available to the handler itself // todo only expose app when running the handler, should not be available to the handler itself
// need to move to runscript or whatever...or remove app because we can just call it using the handler? // need to move to runscript or whatever...or remove app because we can just call it using the handler?
try { try {
const context = bootstrapContext(db, route.structure_id, route.id, { app }); const context = bootstrapContext(route.structure_id, route.id, { app });
let handler = vm.runInContext(`${route.handler}\n\nhandler;`, context); let handler = vm.runInContext(`${route.handler}\n\nhandler;`, context);
if (!wsRoutes[routeWithPrefix(route)]) { if (!wsRoutes[routeWithPrefix(route)]) {
@ -167,17 +161,17 @@ function bootstrapWebsocketHandler(route) {
} }
catch (e) { catch (e) {
const error = e.stack ? `${e}\n\n${e.stack}` : `${e}`; const error = e.stack ? `${e}\n\n${e.stack}` : `${e}`;
model.createLog(db, route.structure_id, route.id, error, true); model.createLog(route.structure_id, route.id, error, true);
} }
}) })
} }
ws.render = (template, context) => { ws.render = (template, context) => {
context = context || {}; context = context || {};
context.route = function (url) {
return __urlPrefix ? path.join(__urlPrefix, url) : url;
};
const eta = model.getTemplater(route.structure_id); const eta = model.getTemplater(route.structure_id);
const structure = model.getStructure(db, route.structure_id); const structure = model.getStructure(route.structure_id);
context.route = function (url) {
return withPrefix(structure.route_prefix, url);
};
ws.send( ws.send(
bootstrapTemplateWithHTMXetc( bootstrapTemplateWithHTMXetc(
@ -202,14 +196,14 @@ function bootstrapWebsocketHandler(route) {
return handler(ws, req) return handler(ws, req)
} catch (e) { } catch (e) {
const error = e.stack ? `${e}\n\n${e.stack}` : `${e}`; const error = e.stack ? `${e}\n\n${e.stack}` : `${e}`;
model.createLog(db, route.structure_id, route.id, error, true); model.createLog(route.structure_id, route.id, error, true);
} }
} }
model.updateRoute(db, { ...route, error: null }); model.updateRoute({ ...route, error: null });
} catch (e) { } catch (e) {
const error = e.stack ? `${e}\n\n${e.stack}` : `${e}`; const error = e.stack ? `${e}\n\n${e.stack}` : `${e}`;
model.createLog(db, route.structure_id, route.id, error, true); model.createLog(route.structure_id, route.id, error, true);
model.updateRoute(db, { ...route, error: e.stack }); model.updateRoute({ ...route, error: e.stack });
} }
} }
@ -221,7 +215,7 @@ function buildRoutes() {
DELETE: [], DELETE: [],
}; };
for (let route of model.getAllRoutes(db)) { for (let route of model.getAllRoutes()) {
const p = routeWithPrefix(route); const p = routeWithPrefix(route);
if (route.verb == "WS") { if (route.verb == "WS") {
bootstrapWebsocketHandler(route); bootstrapWebsocketHandler(route);
@ -251,7 +245,7 @@ app.post("/register", async (req, res) => {
try { try {
const { username, password } = req.body; const { username, password } = req.body;
const hashedPassword = await bcrypt.hash(password, 10); // 10 is the saltRounds const hashedPassword = await bcrypt.hash(password, 10); // 10 is the saltRounds
userId = model.createUser(db, username, hashedPassword); const userId = model.createUser(username, hashedPassword);
req.session.userId = userId; req.session.userId = userId;
res.redirect("/workshop"); res.redirect("/workshop");
} catch (e) { } catch (e) {
@ -261,7 +255,7 @@ app.post("/register", async (req, res) => {
app.post("/login", async (req, res) => { app.post("/login", async (req, res) => {
const { username, password } = req.body; const { username, password } = req.body;
const user = model.getUser(db, username); const user = model.getUser(username);
if (user && (await bcrypt.compare(password, user.password))) { if (user && (await bcrypt.compare(password, user.password))) {
req.session.userId = user.id; req.session.userId = user.id;
@ -386,20 +380,20 @@ function bootstrapTemplateWithHTMXetc(
} }
app.post("/workshop", (req, res) => { app.post("/workshop", (req, res) => {
let structId = model.createStructure(db, req.body.name); let structId = model.createStructure(req.body.name);
return res.redirect("/workshop/" + structId); return res.redirect("/workshop/" + structId);
}); });
app.get("/workshop", (req, res) => { app.get("/workshop", (req, res) => {
return res.send( return renderWorkshop(res, "workshop/index", {
bootstrapTemplateWithHTMXetc( structures: model.getStructures(),
eta.render("workshop/index", { });
structures: model.getStructures(db),
}),
),
);
}); });
function renderWorkshop(res, template, context) {
res.send(bootstrapTemplateWithHTMXetc(eta.render(template, context)));
}
function smartRedirect(req, res, redirectUrl) { function smartRedirect(req, res, redirectUrl) {
if (req.headers["hx-request"]) { if (req.headers["hx-request"]) {
res.set("HX-Redirect", redirectUrl); res.set("HX-Redirect", redirectUrl);
@ -409,21 +403,17 @@ function smartRedirect(req, res, redirectUrl) {
} }
} }
function sidebarStuff(db, structId) { function sidebarStuff(structId) {
return { return {
structure: model.getStructure(db, structId), structure: model.getStructure(structId),
routes: model.getRoutes(db, structId), routes: model.getRoutes(structId),
templates: model.getTemplates(db, structId), templates: model.getTemplates(structId),
dbs: model.getDbsForStructure(db, structId), dbs: model.getDbsForStructure(structId),
}; };
} }
app.get("/workshop/:structure_id", (req, res) => { app.get("/workshop/:structure_id", (req, res) => {
return res.send( return renderWorkshop(res, "workshop/editor", sidebarStuff(req.params.structure_id));
bootstrapTemplateWithHTMXetc(
eta.render("workshop/editor", sidebarStuff(db, req.params.structure_id)),
),
);
}); });
app.post("/workshop/:structure_id/clone", (req, res) => { app.post("/workshop/:structure_id/clone", (req, res) => {
@ -447,34 +437,29 @@ app.post("/workshop/:structure_id/clone", (req, res) => {
}); });
app.post("/workshop/:structure_id/db", (req, res) => { app.post("/workshop/:structure_id/db", (req, res) => {
const dbId = model.createDb(db, req.params.structure_id, req.body.name); const dbId = model.createDb(req.params.structure_id, req.body.name);
const redirectUrl = `/workshop/${req.params.structure_id}/db/${dbId}`; const redirectUrl = `/workshop/${req.params.structure_id}/db/${dbId}`;
return smartRedirect(req, res, redirectUrl); return smartRedirect(req, res, redirectUrl);
}); });
app.post("/workshop/:structure_id/db/attach", (req, res) => { app.post("/workshop/:structure_id/db/attach", (req, res) => {
model.attachDb(db, req.params.structure_id, req.body.db_id, req.body.alias); model.attachDb(req.params.structure_id, req.body.db_id, req.body.alias);
const redirectUrl = `/workshop/${req.params.structure_id}/db/${req.body.db_id}`; const redirectUrl = `/workshop/${req.params.structure_id}/db/${req.body.db_id}`;
return smartRedirect(req, res, redirectUrl); return smartRedirect(req, res, redirectUrl);
}); });
app.get("/workshop/:structure_id/db/:db_id", (req, res) => { app.get("/workshop/:structure_id/db/:db_id", (req, res) => {
const structdb = model.getDbForStructure( const structdb = model.getDbForStructure(
db,
req.params.structure_id, req.params.structure_id,
req.params.db_id, req.params.db_id,
); );
if (!structdb) { if (!structdb) {
return res.send("uh oh"); return res.send("uh oh");
} }
return res.send( return renderWorkshop(res, "workshop/db_garden", {
bootstrapTemplateWithHTMXetc(
eta.render("workshop/db_garden", {
db: structdb, db: structdb,
...sidebarStuff(db, req.params.structure_id), ...sidebarStuff(req.params.structure_id),
}), });
),
);
}); });
app.post("/workshop/:structure_id/route", (req, res) => { app.post("/workshop/:structure_id/route", (req, res) => {
@ -494,7 +479,6 @@ app.post("/workshop/:structure_id/route", (req, res) => {
p = p.substring(0, p.length - 1); p = p.substring(0, p.length - 1);
} }
const route = model.createRoute( const route = model.createRoute(
db,
req.body.verb, req.body.verb,
p[0] == "/" ? p.substring(1) : "/" + p, p[0] == "/" ? p.substring(1) : "/" + p,
req.params.structure_id, req.params.structure_id,
@ -510,12 +494,12 @@ app.post("/workshop/:structure_id/route", (req, res) => {
}); });
app.put("/workshop/:structure_id/route/:route_id", (req, res) => { app.put("/workshop/:structure_id/route/:route_id", (req, res) => {
const route = model.getRoute(db, req.params.route_id); const route = model.getRoute(req.params.route_id);
model.updateRoute(db, { ...route, ...req.body }); model.updateRoute({ ...route, ...req.body });
if (req.body.scaffold_page) { if (req.body.scaffold_page) {
// TODO: make this send over scaffold page id too someday // TODO: make this send over scaffold page id too someday
const latestPage = model.getLatestScaffoldPage(db, req.params.route_id) const latestPage = model.getLatestScaffoldPage(req.params.route_id)
model.updateScaffoldPage(db, { ...latestPage, content: req.body.scaffold_page }) model.updateScaffoldPage({ ...latestPage, content: req.body.scaffold_page })
} }
if (route.verb == "WS") { if (route.verb == "WS") {
bootstrapWebsocketHandler({ ...route, ...req.body }) bootstrapWebsocketHandler({ ...route, ...req.body })
@ -526,9 +510,9 @@ app.put("/workshop/:structure_id/route/:route_id", (req, res) => {
app.put("/workshop/:structure_id/db/:db_id/library", (req, res) => { app.put("/workshop/:structure_id/db/:db_id/library", (req, res) => {
try { try {
let dbId = req.params.db_id; let dbId = req.params.db_id;
let appDb = model.getDb(db, dbId); let appDb = model.getDb(dbId);
model.updateDb(db, { ...appDb, library: req.body.library }); model.updateDb({ ...appDb, library: req.body.library });
appDb = model.getDb(db, dbId); appDb = model.getDb(dbId);
let dbInstance = model.getDbInstance(dbId); let dbInstance = model.getDbInstance(dbId);
let capturedOutput = []; let capturedOutput = [];
@ -536,20 +520,11 @@ app.put("/workshop/:structure_id/db/:db_id/library", (req, res) => {
module: { exports: null }, module: { exports: null },
sql: dbInstance, sql: dbInstance,
console: { console: {
log: (...args) => capturedOutput.push(args.join(" ")), log: (...args) => capturedOutput.push(inspectArgs(args)),
}, },
}); });
let evaledCode = vm.runInContext(appDb.library, context); let evaledCode = vm.runInContext(appDb.library, context);
let stdout = capturedOutput let stdout = capturedOutput.join("\n");
.map((v) => {
return util.inspect(v, {
showHidden: false,
depth: null, // `null` lets you see the full depth of the object
colors: false, // Setting this to true uses ANSI color codes
compact: false,
});
})
.join("\n");
return res.send(`${stdout}\n\n> ${evaledCode}`.trim()); return res.send(`${stdout}\n\n> ${evaledCode}`.trim());
} catch (e) { } catch (e) {
return res.send(`${e}\n\n${e.stack}`); return res.send(`${e}\n\n${e.stack}`);
@ -559,26 +534,14 @@ app.put("/workshop/:structure_id/db/:db_id/library", (req, res) => {
app.post("/workshop/:structure_id/db/:db_id/repl", (req, res) => { app.post("/workshop/:structure_id/db/:db_id/repl", (req, res) => {
try { try {
let dbId = req.params.db_id; let dbId = req.params.db_id;
let appDb = model.getDb(db, dbId); let appDb = model.getDb(dbId);
let dbInstance = model.getDbInstance(dbId); let dbInstance = model.getDbInstance(dbId);
let capturedOutput = []; let capturedOutput = [];
let context = vm.createContext({ let context = vm.createContext({
module: { exports: null }, module: { exports: null },
sql: dbInstance, sql: dbInstance,
console: { console: {
log: (...args) => log: (...args) => capturedOutput.push(inspectArgs(args)),
capturedOutput.push(
args
.map((v) => {
return util.inspect(v, {
showHidden: false,
depth: null, // `null` lets you see the full depth of the object
colors: false, // Setting this to true uses ANSI color codes
compact: false,
});
})
.join(" "),
),
}, },
}); });
const libraryScript = new vm.Script(appDb.library); const libraryScript = new vm.Script(appDb.library);
@ -597,7 +560,6 @@ app.post("/workshop/:structure_id/template", (req, res) => {
let name = req.body.name; let name = req.body.name;
const template = model.createTemplate( const template = model.createTemplate(
db,
req.params.structure_id, req.params.structure_id,
name, name,
"<div>henlo <%= it.name %></div>", "<div>henlo <%= it.name %></div>",
@ -616,8 +578,8 @@ app.put("/workshop/:structure_id/template/:template_id", (req, res) => {
let content = req.body.content; let content = req.body.content;
let test_object = req.body.test_object; let test_object = req.body.test_object;
model.updateTemplate(db, { model.updateTemplate({
...model.getTemplate(db, id), ...model.getTemplate(id),
content, content,
test_object, test_object,
}); });
@ -626,28 +588,24 @@ app.put("/workshop/:structure_id/template/:template_id", (req, res) => {
}); });
app.get("/workshop/:structure_id/template/:template_id", (req, res) => { app.get("/workshop/:structure_id/template/:template_id", (req, res) => {
const template = model.getTemplate(db, req.params.template_id); const template = model.getTemplate(req.params.template_id);
return res.send( return renderWorkshop(res, "workshop/template", {
bootstrapTemplateWithHTMXetc(
eta.render("workshop/template", {
template: template, template: template,
...sidebarStuff(db, req.params.structure_id), ...sidebarStuff(req.params.structure_id),
}), });
),
);
}); });
app.get("/workshop/:structure_id/template/:template_id/preview", (req, res) => { app.get("/workshop/:structure_id/template/:template_id/preview", (req, res) => {
const template = model.getTemplate(db, req.params.template_id); const template = model.getTemplate(req.params.template_id);
const struct = model.getStructure(db, req.params.structure_id); const struct = model.getStructure(req.params.structure_id);
const eta = model.getTemplater(req.params.structure_id); const eta = model.getTemplater(req.params.structure_id);
const context = vm.createContext({ it: null }); const context = vm.createContext({ it: null });
vm.runInContext(template.test_object, context); vm.runInContext(template.test_object, context);
context.it.route = function (url) { context.it.route = function (url) {
return struct.route_prefix ? path.join(struct.route_prefix, url) : url; return withPrefix(struct.route_prefix, url);
}; };
return res.send( return res.send(
@ -663,11 +621,8 @@ app.get("/workshop/:structure_id/template/:template_id/preview", (req, res) => {
}); });
app.get("/workshop/:structure_id/route/:id", (req, res) => { app.get("/workshop/:structure_id/route/:id", (req, res) => {
const route = model.getRoute(db, req.params.id); const route = model.getRoute(req.params.id);
let routePrefix = model.getStructure( let routePrefix = model.getStructure(req.params.structure_id).route_prefix;
db,
req.params.structure_id,
).route_prefix;
let previewUrl = null; let previewUrl = null;
if (route["verb"] == "GET" ) { if (route["verb"] == "GET" ) {
@ -679,26 +634,22 @@ app.get("/workshop/:structure_id/route/:id", (req, res) => {
previewUrl = `/workshop/${req.params.structure_id}/route/${req.params.id}/preview`; previewUrl = `/workshop/${req.params.structure_id}/route/${req.params.id}/preview`;
} }
template = route["verb"] == "GET" ? "workshop/route" : "workshop/cud_route"; const template = route["verb"] == "GET" ? "workshop/route" : "workshop/cud_route";
return res.send( return renderWorkshop(res, template, {
bootstrapTemplateWithHTMXetc(
eta.render(template, {
route: route, route: route,
previewUrl: previewUrl, previewUrl: previewUrl,
...sidebarStuff(db, req.params.structure_id), ...sidebarStuff(req.params.structure_id),
}), });
),
);
}); });
app.get("/workshop/:structure_id/route/:route_id/preview", (req, res) => { app.get("/workshop/:structure_id/route/:route_id/preview", (req, res) => {
const route = model.getRoute(db, req.params.route_id); const route = model.getRoute(req.params.route_id);
const struct = model.getStructure(db, req.params.structure_id); const struct = model.getStructure(req.params.structure_id);
const it = { const it = {
route: function (url) { route: function (url) {
return struct.route_prefix ? path.join(struct.route_prefix, url) : url; return withPrefix(struct.route_prefix, url);
} }
} }
@ -718,23 +669,19 @@ app.get("/workshop/:structure_id/route/:route_id/logs", (req, res) => {
const since = req.query.since; const since = req.query.since;
let logs = []; let logs = [];
if (since != undefined) { if (since != undefined) {
logs = model.getNewLogsByRoute(db, req.params.route_id, since); logs = model.getNewLogsByRoute(req.params.route_id, since);
} else { } else {
logs = model.getLogsByRoute(db, req.params.route_id); logs = model.getLogsByRoute(req.params.route_id);
} }
const lastId = model.getMostRecentLogIdByRoute(db, req.params.route_id); const lastId = model.getMostRecentLogIdByRoute(req.params.route_id);
return res.send( return renderWorkshop(res, "workshop/logs", {
bootstrapTemplateWithHTMXetc(
eta.render("workshop/logs", {
logs: logs, logs: logs,
structId: req.params.structure_id, structId: req.params.structure_id,
routeId: req.params.route_id, routeId: req.params.route_id,
since: lastId, since: lastId,
}), });
),
);
}); });
// POST route to handle file upload // POST route to handle file upload
@ -766,34 +713,26 @@ function prefixUrlWithHost(req, path) {
app.get("/workshop/:structure_id/files", (req, res) => { app.get("/workshop/:structure_id/files", (req, res) => {
const { structure_id } = req.params; const { structure_id } = req.params;
const files = model.getFilesForStruct(db, structure_id); const files = model.getFilesForStruct(structure_id);
files.map((it) => { files.map((it) => {
it.url = prefixUrlWithHost(req, it.path); it.url = prefixUrlWithHost(req, it.path);
}); });
return res.send( return renderWorkshop(res, "workshop/files", {
bootstrapTemplateWithHTMXetc(
eta.render("workshop/files", {
files, files,
...sidebarStuff(db, structure_id), ...sidebarStuff(structure_id),
}), });
),
);
}); });
app.get("/workshop/:structure_id/settings", (req, res) => { app.get("/workshop/:structure_id/settings", (req, res) => {
return res.send( return renderWorkshop(res, "workshop/settings", {
bootstrapTemplateWithHTMXetc( ...sidebarStuff(req.params.structure_id),
eta.render("workshop/settings", { });
...sidebarStuff(db, req.params.structure_id),
}),
),
);
}); });
app.put("/workshop/:structure_id/settings", (req, res) => { app.put("/workshop/:structure_id/settings", (req, res) => {
let structId = req.params.structure_id; let structId = req.params.structure_id;
let struct = model.getStructure(db, structId); let struct = model.getStructure(structId);
let routePrefix = req.body.route_prefix; let routePrefix = req.body.route_prefix;
if (routePrefix[0] != "/") { if (routePrefix[0] != "/") {
@ -803,59 +742,43 @@ app.put("/workshop/:structure_id/settings", (req, res) => {
routePrefix = routePrefix.substring(0, routePrefix.length - 1); routePrefix = routePrefix.substring(0, routePrefix.length - 1);
} }
model.updateStruct(db, { model.updateStruct({
...struct, ...struct,
route_prefix: routePrefix, route_prefix: routePrefix,
head_injection: req.body.head_injection, head_injection: req.body.head_injection,
}); });
struct = model.getStructure(db, structId); struct = model.getStructure(structId);
res.send("success!"); res.send("success!");
}); });
app.get("/workshop/:structure_id/new_template_modal", (req, res) => { app.get("/workshop/:structure_id/new_template_modal", (req, res) => {
return res.send( return renderWorkshop(res, "workshop/new_template_modal", {
bootstrapTemplateWithHTMXetc( structure: model.getStructure(req.params.structure_id),
eta.render("workshop/new_template_modal", { });
structure: model.getStructure(db, req.params.structure_id),
}),
),
);
}); });
app.get("/workshop/:structure_id/new_route_modal", (req, res) => { app.get("/workshop/:structure_id/new_route_modal", (req, res) => {
return res.send( return renderWorkshop(res, "workshop/new_route_modal", {
bootstrapTemplateWithHTMXetc( structure: model.getStructure(req.params.structure_id),
eta.render("workshop/new_route_modal", { });
structure: model.getStructure(db, req.params.structure_id),
}),
),
);
}); });
app.get("/workshop/:structure_id/new_db_modal", (req, res) => { app.get("/workshop/:structure_id/new_db_modal", (req, res) => {
return res.send( return renderWorkshop(res, "workshop/new_db_modal", {
bootstrapTemplateWithHTMXetc( structure: model.getStructure(req.params.structure_id),
eta.render("workshop/new_db_modal", { });
structure: model.getStructure(db, req.params.structure_id),
}),
),
);
}); });
app.get("/workshop/:structure_id/clone_modal", (req, res) => { app.get("/workshop/:structure_id/clone_modal", (req, res) => {
const structure = model.getStructure(db, req.params.structure_id); const structure = model.getStructure(req.params.structure_id);
const dbs = model.getDbsForStructure(db, req.params.structure_id); const dbs = model.getDbsForStructure(req.params.structure_id);
return res.send( return renderWorkshop(res, "workshop/clone_structure_modal", {
bootstrapTemplateWithHTMXetc(
eta.render("workshop/clone_structure_modal", {
structure, structure,
dbs, dbs,
defaultRoutePrefix: path.join(structure.route_prefix || "", "clone"), defaultRoutePrefix: path.join(structure.route_prefix || "", "clone"),
}), });
),
);
}); });
function embedHTML(url) { function embedHTML(url) {
@ -882,13 +805,13 @@ app.all("*", async (req, res) => {
.prepare("SELECT * FROM routes WHERE id = ?") .prepare("SELECT * FROM routes WHERE id = ?")
.get(routeMatch.id); .get(routeMatch.id);
const structure = model.getStructure(db, route.structure_id); const structure = model.getStructure(route.structure_id);
const __urlPrefix = structure.route_prefix; const __urlPrefix = structure.route_prefix;
try { try {
// todo: only add req res to contexst when running the handler() // todo: only add req res to contexst when running the handler()
// which means moving to runscript instead of runincontext for that // which means moving to runscript instead of runincontext for that
let context = bootstrapContext(db, route.structure_id, route.id, { let context = bootstrapContext(route.structure_id, route.id, {
req, req,
res, res,
eta, eta,
@ -897,7 +820,7 @@ app.all("*", async (req, res) => {
res.render = (template, context) => { res.render = (template, context) => {
context = context || {}; context = context || {};
context.route = function (url) { context.route = function (url) {
return __urlPrefix ? path.join(__urlPrefix, url) : url; return withPrefix(__urlPrefix, url);
}; };
const eta = model.getTemplater(route.structure_id); const eta = model.getTemplater(route.structure_id);
res.send( res.send(
@ -918,7 +841,7 @@ app.all("*", async (req, res) => {
const result = await executionScript.runInContext(context); const result = await executionScript.runInContext(context);
} catch (e) { } catch (e) {
const error = e.stack ? `${e}\n\n${e.stack}` : `${e}`; const error = e.stack ? `${e}\n\n${e.stack}` : `${e}`;
model.createLog(db, structure.id, route.id, error, true); model.createLog(structure.id, route.id, error, true);
return res.status(500).send(error); return res.status(500).send(error);
} }
} else { } else {