const fs = require("fs"); const path = require("path"); const betterSqlite3 = require("better-sqlite3"); const { LRUCache } = require("lru-cache"); const { randomUUID } = require("node:crypto"); const { Eta } = require("eta"); const cheerio = require("cheerio"); const db = betterSqlite3("./dbs/0.sqlite"); const PRIME_DB_ID = 0; db.pragma("journal_mode = WAL"); function applyMigrations() { db.exec(` CREATE TABLE IF NOT EXISTS migrations ( id INTEGER PRIMARY KEY AUTOINCREMENT, filename TEXT NOT NULL, applied_at DATETIME DEFAULT CURRENT_TIMESTAMP ); `); const migrationsDir = path.join(__dirname, "/migrations"); const migrationFiles = fs .readdirSync(migrationsDir) .filter((file) => file.endsWith(".sql")); migrationFiles.forEach((file) => { const isApplied = db .prepare("SELECT filename FROM migrations WHERE filename = ?") .get(file); if (!isApplied) { const sql = fs.readFileSync(path.join(migrationsDir, file), "utf-8"); db.exec(sql); db.prepare("INSERT INTO migrations (filename) VALUES (?)").run(file); console.log(`Migration applied: ${file}`); } }); } // __ __ _______ _______ ______ // | | | || || || _ | // | | | || _____|| ___|| | || // | |_| || |_____ | |___ | |_||_ // | ||_____ || ___|| __ | // | | _____| || |___ | | | | // |_______||_______||_______||___| |_| function createUser(username, hashedPassword) { return db .prepare("INSERT INTO users (username, password) VALUES (?, ?)") .run(username, hashedPassword).lastInsertRowid; } function getUser(username) { return db.prepare("SELECT * from users where username = ?").get(username); } function getUserById(id) { return db.prepare("SELECT id, username FROM users WHERE id = ?").get(id); } // _ _ _______ ______ ___ _ _______ __ __ _______ _______ // | | _ | || || _ | | | | || || | | || || | // | || || || _ || | || | |_| || _____|| |_| || _ || _ | // | || | | || |_||_ | _|| |_____ | || | | || |_| | // | || |_| || __ || |_ |_____ || || |_| || ___| // | _ || || | | || _ | _____| || _ || || | // |__| |__||_______||___| |_||___| |_||_______||__| |__||_______||___| const dbCache = new LRUCache({ max: 25 }); function getDbInstance(dbId) { let dbInstance = dbCache.get(dbId); if (!dbInstance) { dbInstance = betterSqlite3(`dbs/${dbId}.sqlite`); dbInstance.pragma("journal_mode = WAL"); dbCache.set(dbId, dbInstance); } return dbInstance; } function getAllRoutes() { return db .prepare( ` SELECT routes.*, structures.route_prefix FROM routes JOIN structures ON routes.structure_id = structures.id ORDER BY routes.updated_at DESC; `, ) .all(); } const templateCache = new LRUCache({ max: 100 }); const templateContextCache = new LRUCache({ max: 2000, ttl: 30 * 60 * 1000 }); // The inspector needs the values that produced each rendered template, not a // fresh request's values. Contexts frequently contain helpers (such as // `route`) or cyclic request objects, neither of which belongs in HTML. Keep // the JSON-shaped portion and omit the rest rather than making a page fail to // render just because it cannot be inspected. function inspectableContext(data) { const seen = new WeakSet(); try { return JSON.parse( JSON.stringify(data ?? {}, (_key, value) => { if (typeof value === "function" || typeof value === "undefined") { return undefined; } if (typeof value === "object" && value !== null) { if (seen.has(value)) return undefined; seen.add(value); } return value; }), ); } catch (_) { return {}; } } function annotateTemplateHtml(html, template, data) { const context = inspectableContext(data); const contextId = randomUUID(); templateContextCache.set(contextId, { templateId: String(template.id), context, }); function annotate($, element) { const current = $(element).attr("data-bliss-templates"); let templates = []; if (current) { try { templates = JSON.parse(current); } catch (_) {} } if (!templates.some((item) => String(item.id) === String(template.id))) { templates.push({ id: String(template.id), name: template.name }); } $(element).attr("data-bliss-templates", JSON.stringify(templates)); const rawContextIds = $(element).attr("data-bliss-template-context-ids"); let contextIds = {}; if (rawContextIds) { try { contextIds = JSON.parse(rawContextIds); } catch (_) {} } contextIds[String(template.id)] = contextId; $(element).attr( "data-bliss-template-context-ids", JSON.stringify(contextIds), ); if (!$(element).attr("data-bliss-template-id")) { $(element).attr({ "data-bliss-template-id": String(template.id), "data-bliss-template-name": template.name, "data-bliss-template-context-id": contextId, }); } } const lower = html.trimStart().toLowerCase(); if (lower.startsWith(" anyone, no login (opt-in, with a warning) // public = 0, private = 0 -> any logged-in user (the default) // public = 0, private = 1 -> invited members only // // So login is required by default; making a structure public is the explicit // way back onto the open web. `private` layers a stricter members-only gate on // top: access is then exactly its member list (structure_members) — a flat set // of equals, no creator or owner. This is the single source of truth the // platform consults before serving a user route, mounting a db, or opening the // workshop — never the structure's own code. function isMember(structureId, userId) { return !!db .prepare( "SELECT 1 FROM structure_members WHERE structure_id = ? AND user_id = ?", ) .get(structureId, userId); } function canAccessStructure(userId, structureId) { const s = getStructure(structureId); if (!s) return false; if (s.public) return true; // open web, no login if (userId == null) return false; // everything else needs a login if (!s.private) return true; // any logged-in user return isMember(structureId, userId); // members-only } // Members of a structure, with their usernames, for the settings UI. function getMembers(structureId) { return db .prepare( `SELECT sv.user_id, u.username, sv.created_at FROM structure_members sv JOIN users u ON u.id = sv.user_id WHERE sv.structure_id = ? ORDER BY sv.created_at ASC`, ) .all(structureId); } function addMember(structureId, userId) { db.prepare( "INSERT OR IGNORE INTO structure_members (structure_id, user_id) VALUES (?, ?)", ).run(structureId, userId); } function removeMember(structureId, userId) { db.prepare( "DELETE FROM structure_members WHERE structure_id = ? AND user_id = ?", ).run(structureId, userId); } // Set a structure's privacy. Anyone may lock any structure down; the actor is // added to the member list so they don't shut themselves out. (Access is purely // the member set, so without this the person who flipped it private would lose // the very structure they just privatized.) function setStructurePrivacy(structureId, isPrivate, actorUserId) { const s = getStructure(structureId); if (!s) return; db.prepare("UPDATE structures SET private = ? WHERE id = ?").run( isPrivate ? 1 : 0, structureId, ); if (isPrivate && actorUserId != null) addMember(structureId, actorUserId); } // Put a structure on the open web (public = 1) or take it back off (login // required). Independent of the members-only `private` flag; public wins in // canAccessStructure, so a public structure is reachable regardless of private. function setStructurePublic(structureId, isPublic) { const s = getStructure(structureId); if (!s) return; db.prepare("UPDATE structures SET public = ? WHERE id = ?").run( isPublic ? 1 : 0, structureId, ); } // ---- kernel notifications ------------------------------------------------- // // Subscriptions are owned by the platform, not by structure databases. Each row // is tied to exactly one structure and one logged-in Bliss user; send-time code // re-checks canAccessStructure before handing anything to a push service. function upsertNotificationSubscription({ structureId, userId, endpoint, clientId, subscription, contentEncoding, }) { return db .prepare( `INSERT INTO notification_subscriptions (structure_id, user_id, endpoint, client_id, subscription, content_encoding, updated_at) VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) ON CONFLICT(structure_id, endpoint) DO UPDATE SET user_id = excluded.user_id, client_id = excluded.client_id, subscription = excluded.subscription, content_encoding = excluded.content_encoding, updated_at = CURRENT_TIMESTAMP`, ) .run( structureId, userId, endpoint, clientId == null ? null : String(clientId), JSON.stringify(subscription), contentEncoding === "aesgcm" ? "aesgcm" : "aes128gcm", ); } function deleteNotificationSubscription(structureId, endpoint) { return db .prepare( `DELETE FROM notification_subscriptions WHERE structure_id = ? AND endpoint = ?`, ) .run(structureId, endpoint); } function getNotificationSubscriptions(structureId, userIds = null) { if (userIds && userIds.length === 0) return []; const rows = userIds ? db .prepare( `SELECT * FROM notification_subscriptions WHERE structure_id = ? AND user_id IN (${userIds.map(() => "?").join(",")})`, ) .all(structureId, ...userIds) : db .prepare( `SELECT * FROM notification_subscriptions WHERE structure_id = ?`, ) .all(structureId); return rows.map((row) => ({ ...row, subscription: JSON.parse(row.subscription), })); } function getSubscribedNotificationUserIds(structureId) { return db .prepare( `SELECT DISTINCT user_id FROM notification_subscriptions WHERE structure_id = ? ORDER BY user_id`, ) .all(structureId) .map((row) => row.user_id); } function getStructureMemberUserIds(structureId) { return db .prepare( `SELECT user_id FROM structure_members WHERE structure_id = ? ORDER BY user_id`, ) .all(structureId) .map((row) => row.user_id); } function createRoute(verb, path, structureId, handler) { path = encodeURI(path); const stmt = db.prepare( "INSERT INTO routes (verb, path, structure_id, handler) VALUES (?, ?, ?, ?)", ); const info = stmt.run(verb, path, structureId, handler); const routeId = info.lastInsertRowid; // Get the newly created route ID if (verb !== "GET") { createScaffoldPage(routeId); } recordVersion("route", routeId, structureId, { verb, path, handler }); return routeId; // Returns the route_id of the newly created route } const ROUTE_SELECT = ` SELECT r.*, 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 `; 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(routeId) { return db .prepare( `${ROUTE_SELECT} WHERE r.id = ? ORDER BY sp.created_at DESC, sp2.created_at DESC LIMIT 1`, ) .get(routeId); } // Update `fields` of a row by id from a plain object. Returns the run info. function update(table, fields, obj) { const placeholders = fields.map((field) => `${field} = ?`).join(", "); const values = fields.map((field) => obj[field]); values.push(obj.id); return db .prepare(`UPDATE ${table} SET ${placeholders} WHERE id = ?`) .run(...values); } // ---- version history (append-only) -------------------------------------- // // Snapshot the code-bearing fields of a route/template/db every time they // change, so nothing a user (or the plumber) edits is ever truly lost. Deduped // against the latest snapshot for that entity, so no-op saves, error-flag-only // route writes, and server-restart WS re-bootstraps don't pile up noise. // snapshotFor() defines exactly which fields are versioned per type; keep the // key order stable so the dedup string compare is reliable. function snapshotFor(entityType, obj) { switch (entityType) { case "route": return { verb: obj.verb, path: obj.path, handler: obj.handler }; case "template": return { name: obj.name, content: obj.content, test_object: obj.test_object, }; case "db": return { name: obj.name, library: obj.library }; default: throw new Error(`unknown version entity type: ${entityType}`); } } function recordVersion(entityType, entityId, structureId, obj) { const json = JSON.stringify(snapshotFor(entityType, obj)); const last = db .prepare( "SELECT snapshot FROM versions WHERE entity_type = ? AND entity_id = ? ORDER BY id DESC LIMIT 1", ) .get(entityType, entityId); if (last && last.snapshot === json) return; // content unchanged — skip db.prepare( "INSERT INTO versions (entity_type, entity_id, structure_id, snapshot) VALUES (?, ?, ?, ?)", ).run(entityType, entityId, structureId, json); } // Version list for one entity (newest first). Omits the snapshot body to stay // scannable; fetch a single version to get its full content. function getVersions(entityType, entityId) { return db .prepare( `SELECT id, entity_type, entity_id, structure_id, created_at, length(snapshot) AS bytes FROM versions WHERE entity_type = ? AND entity_id = ? ORDER BY id DESC`, ) .all(entityType, entityId); } // One version with its full snapshot parsed back into fields. function getVersion(versionId) { const row = db.prepare("SELECT * FROM versions WHERE id = ?").get(versionId); if (!row) return null; return { ...row, snapshot: JSON.parse(row.snapshot) }; } function updateRoute(route) { update( "routes", ["verb", "path", "structure_id", "handler", "updated_at", "error"], route, ); recordVersion("route", route.id, route.structure_id, route); } function updateDb(appDb) { update("dbs", ["name", "library"], appDb); recordVersion("db", appDb.id, appDb.structure_id, appDb); } function updateStruct(struct) { update("structures", ["name", "route_prefix", "head_injection"], struct); } function updateTemplate(template) { update("templates", ["content", "name", "test_object"], template); templateCache.delete(template.structure_id); recordVersion("template", template.id, template.structure_id, template); } function getTemplates(structureId) { return db .prepare("SELECT * from templates where structure_id = ?") .all(structureId); } function getTemplate(templateId) { return db.prepare("SELECT * from templates where id = ?").get(templateId); } function getInspectableTemplateContext(contextId, templateId) { const entry = templateContextCache.get(contextId); if (!entry || entry.templateId !== String(templateId)) return null; return entry.context; } function getTemplateContentByName(structId, name) { return db .prepare( "SELECT content from templates where structure_id = ? AND name = ?", ) .get(structId, name); } function getTemplateByName(structId, name) { return db .prepare("SELECT * from templates where structure_id = ? AND name = ?") .get(structId, name); } function createTemplate(structureId, name, content, testObjectString) { const id = db .prepare( "INSERT INTO templates (structure_id, name, content, test_object) VALUES (?, ?, ?, ?)", ) .run(structureId, name, content, testObjectString).lastInsertRowid; recordVersion("template", id, structureId, { name, content, test_object: testObjectString, }); return id; } function getDbsForStructure(structureId) { return db .prepare( `SELECT *, CASE WHEN structure_dbs.structure_id != dbs.structure_id THEN 1 ELSE 0 END AS is_aliased, structure_dbs.structure_id as alias_struct_id, dbs.structure_id as db_struct_id FROM structure_dbs INNER JOIN dbs ON structure_dbs.db_id = dbs.id WHERE structure_dbs.structure_id = ? AND NOT (structure_dbs.db_id = ${PRIME_DB_ID} AND structure_dbs.structure_id != ${PRIME_DB_ID}) ORDER BY structure_dbs.created_at, is_aliased ASC; `, ) .all(structureId); } function getDb(dbId) { return db .prepare( `SELECT * FROM dbs WHERE id = ?; `, ) .get(dbId); } function getDbForStructure(structureId, dbId) { return db .prepare( `SELECT dbs.*, structure_dbs.alias, structure_dbs.structure_id AS alias_struct_id, dbs.structure_id AS db_struct_id FROM structure_dbs INNER JOIN dbs ON structure_dbs.db_id = dbs.id WHERE structure_dbs.structure_id = ? AND structure_dbs.db_id = ? AND NOT (structure_dbs.db_id = ${PRIME_DB_ID} AND structure_dbs.structure_id != ${PRIME_DB_ID}); `, ) .get(structureId, dbId); } function createDb(structId, name) { const transaction = db.transaction(() => { const insertDbStmt = db.prepare(` INSERT INTO dbs (name, structure_id) VALUES (?, ?) `); const result = insertDbStmt.run(name, structId); const dbId = result.lastInsertRowid; const insertStructureDbStmt = db.prepare(` INSERT INTO structure_dbs (db_id, structure_id, alias) VALUES (?, ?, ?) `); insertStructureDbStmt.run(dbId, structId, name); const newDbPath = path.join("dbs", `${dbId}.sqlite`); const newDb = betterSqlite3(newDbPath); newDb.close(); return dbId; }); const dbId = transaction(); recordVersion("db", dbId, structId, { name, library: "" }); return dbId; } function attachDb(structId, dbId, alias) { if (Number(dbId) === PRIME_DB_ID && Number(structId) !== PRIME_DB_ID) { throw new Error("prime db cannot be attached to other structures"); } const insertStructureDbStmt = db.prepare(` INSERT INTO structure_dbs (db_id, structure_id, alias) VALUES (?, ?, ?) `); insertStructureDbStmt.run(dbId, structId, alias); } function getFilesForStruct(structureId) { let test = db .prepare("SELECT * FROM files WHERE structure_id = ? ORDER BY id DESC") .all(structureId); return test; } function getFile(fileId) { return db.prepare("SELECT * FROM files WHERE id = ?").get(fileId); } function createFile(structure_id, name, filePath, mime_type, mime_subtype) { return db .prepare( "INSERT INTO files (structure_id, name, path, mime_type, mime_subtype) VALUES (?, ?, ?, ?, ?)", ) .run(structure_id, name, filePath, mime_type, mime_subtype).lastInsertRowid; } function cloneStructure( structId, newStructureName, userId, routePrefix = "", cloneDbs = [], ) { const transaction = db.transaction(() => { // Carry over the source's head_injection (viewport meta, PWA tags, favicons, // mobile-hardening CSS). Without this a clone renders unstyled/unscaled on // mobile even though its templates are identical. const source = getStructure(structId); const cloneStructure = db.prepare(` INSERT INTO structures (name, user_id, route_prefix, cloned_from, head_injection) VALUES (?, ?, ?, ?, ?); `); const newStructId = cloneStructure.run( newStructureName, userId, routePrefix, structId, source ? source.head_injection : null, ).lastInsertRowid; const dbIds = db .prepare(`SELECT db_id FROM structure_dbs WHERE structure_id = ? AND db_id != ${PRIME_DB_ID};`) .all(structId); const requestedCloneDbs = Array.isArray(cloneDbs) ? cloneDbs : cloneDbs ? [cloneDbs] : []; const toClone = new Set( requestedCloneDbs.filter((dbId) => Number(dbId) !== PRIME_DB_ID), ); const toAlias = new Set(); for (let { db_id } of dbIds) { if (!toClone.has(db_id.toString())) { toAlias.add(db_id.toString()); } } for (let db_id of toClone) { const newDb = db .prepare( ` INSERT INTO dbs (name, structure_id, library) SELECT name, ?, library FROM dbs WHERE id = ?; `, ) .run(newStructId, db_id).lastInsertRowid; db.prepare( ` INSERT INTO structure_dbs (db_id, structure_id, alias) SELECT ?, ?, alias FROM structure_dbs WHERE db_id = ? AND structure_id = ?; `, ).run(newDb, newStructId, db_id, structId); db.prepare(`SELECT id FROM dbs WHERE structure_id = ?`) .all(newStructId) .map((new_db) => { const srcPath = path.join(__dirname, "dbs", `${db_id}.sqlite`); const destPath = path.join(__dirname, "dbs", `${new_db.id}.sqlite`); fs.copyFileSync(srcPath, destPath); fs.copyFileSync(srcPath + "-shm", destPath + "-shm"); fs.copyFileSync(srcPath + "-wal", destPath + "-wal"); }); } for (let db_id of toAlias) { db.prepare( ` INSERT INTO structure_dbs (db_id, structure_id, alias) SELECT db_id, ?, alias FROM structure_dbs WHERE structure_id = ? AND db_id = ?; `, ).run(newStructId, structId, db_id); } // Clone templates const cloneTemplates = db.prepare(` INSERT INTO templates (name, content, structure_id, test_object, engine) SELECT name, content, ?, test_object, engine FROM templates WHERE structure_id = ?; `); cloneTemplates.run(newStructId, structId); // Clone routes const cloneRoutes = db.prepare(` INSERT INTO routes (verb, path, structure_id, handler) SELECT verb, path, ?, handler FROM routes WHERE structure_id = ?; `); cloneRoutes.run(newStructId, structId); // Clone scaffold pages const cloneScaffoldPages = db.prepare(` INSERT INTO scaffold_pages (route_id, content, created_at) SELECT newRoutes.id, sp.content, sp.created_at FROM scaffold_pages sp JOIN routes oldRoutes ON sp.route_id = oldRoutes.id JOIN routes newRoutes ON oldRoutes.path = newRoutes.path AND oldRoutes.verb = newRoutes.verb WHERE oldRoutes.structure_id = ? AND newRoutes.structure_id = ?; `); cloneScaffoldPages.run(structId, newStructId); // Clone scaffold params const cloneScaffoldParams = db.prepare(` INSERT INTO scaffold_params (route_id, query_params, url_params, created_at) SELECT newRoutes.id, sp.query_params, sp.url_params, sp.created_at FROM scaffold_params sp JOIN routes oldRoutes ON sp.route_id = oldRoutes.id JOIN routes newRoutes ON oldRoutes.path = newRoutes.path AND oldRoutes.verb = newRoutes.verb WHERE oldRoutes.structure_id = ? AND newRoutes.structure_id = ?; `); cloneScaffoldParams.run(structId, newStructId); return newStructId; }); return transaction(); } function createLog(structureId, routeId, content, error = false) { const stmt = db.prepare( "INSERT INTO logs (error, structure_id, route_id, content) VALUES (?, ?, ?, ?)", ); const info = stmt.run(error ? 1 : 0, structureId, routeId, content); return info.lastInsertRowid; // Returns the log_id of the newly created log } function getLogsByRoute(routeId) { return db .prepare( "SELECT * FROM logs WHERE route_id = ? ORDER BY created_at DESC LIMIT 50", ) .all(routeId) .reverse(); } function getNewLogsByRoute(routeId, since) { return db .prepare( "SELECT * FROM logs WHERE route_id = ? AND id > ? ORDER BY created_at DESC", ) .all(routeId, since); } function getMostRecentLogIdByRoute(routeId) { const result = db .prepare("SELECT MAX(id) AS id FROM logs WHERE route_id = ?") .get(routeId); return result ? result.id : 0; } function buildScaffoldUrl(endpoint, urlParams, queryString) { let populatedUrl = endpoint.replace( /:([^/]+)/g, () => urlParams.shift() || "", ); return queryString ? `${populatedUrl}?${queryString}` : populatedUrl; } function createScaffoldPage(routeId, content = null) { if (!content) { const route = getRoute(routeId); const endpoint = buildScaffoldUrl( route.path, route.url_params, route.query_params, ); content = getDefaultScaffoldContentByVerb(endpoint, route.verb); } const stmt = db.prepare( "INSERT INTO scaffold_pages (route_id, content) VALUES (?, ?)", ); const info = stmt.run(routeId, content); return info.lastInsertRowid; // Returns the scaffold_page id of the newly created scaffold page } function getLatestScaffoldPage(routeId) { const stmt = db.prepare( "SELECT * FROM scaffold_pages WHERE route_id = ? ORDER BY created_at DESC LIMIT 1", ); return stmt.get(routeId); } function updateScaffoldPage(scaffoldPage) { return update("scaffold_pages", ["content"], scaffoldPage).changes > 0; } function generatePostForm(endpoint) { return ` Test POST Request

Test POST Request

" method="POST">
`; } function generateModifyForm(endpoint, verb) { return ` Test ${verb} Request

Test ${verb} Request

" method="${verb}">
`; } function generateWSPage(endpoint) { return ` Test WebSocket

Test WebSocket Connection

`; } function getDefaultScaffoldContentByVerb(url, verb) { if (verb == "POST") { return generatePostForm(url); } else if (verb == "PUT" || verb == "DELETE") { return generateModifyForm(url, verb); } else if (verb == "WS") { return generateWSPage(url); } } module.exports = { db, applyMigrations, getDbInstance, getAllRoutes, getUser, getUserById, createUser, getStructures, getStructure, createStructure, canAccessStructure, isMember, getMembers, addMember, removeMember, setStructurePrivacy, setStructurePublic, upsertNotificationSubscription, deleteNotificationSubscription, getNotificationSubscriptions, getSubscribedNotificationUserIds, getStructureMemberUserIds, createRoute, getRoutes, getRoute, buildScaffoldUrl, updateRoute, updateDb, updateStruct, updateTemplate, getVersions, getVersion, getTemplater, getTemplates, getTemplate, getInspectableTemplateContext, getTemplateContentByName, getTemplateByName, createTemplate, getDbsForStructure, getDb, getDbForStructure, createDb, attachDb, getFilesForStruct, getFile, createFile, cloneStructure, createLog, getLogsByRoute, getNewLogsByRoute, getMostRecentLogIdByRoute, createScaffoldPage, getLatestScaffoldPage, updateScaffoldPage, };