feat: add logs
This commit is contained in:
parent
c40bdcbbed
commit
305bf8b571
11 changed files with 690 additions and 468 deletions
465
db.js
Normal file
465
db.js
Normal file
|
|
@ -0,0 +1,465 @@
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const betterSqlite3 = require("better-sqlite3");
|
||||||
|
const { LRUCache } = require("lru-cache");
|
||||||
|
const { Eta } = require("eta");
|
||||||
|
|
||||||
|
const db = betterSqlite3("./dbs/0.sqlite");
|
||||||
|
|
||||||
|
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(db, username, hashedPassword) {
|
||||||
|
return db
|
||||||
|
.prepare("INSERT INTO users (username, password) VALUES (?, ?)")
|
||||||
|
.run(username, hashedPassword).lastInsertRowid;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUser(db, username) {
|
||||||
|
return db.prepare("SELECT * from users where username = ?").get(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// _ _ _______ ______ ___ _ _______ __ __ _______ _______
|
||||||
|
// | | _ | || || _ | | | | || || | | || || |
|
||||||
|
// | || || || _ || | || | |_| || _____|| |_| || _ || _ |
|
||||||
|
// | || | | || |_||_ | _|| |_____ | || | | || |_| |
|
||||||
|
// | || |_| || __ || |_ |_____ || || |_| || ___|
|
||||||
|
// | _ || || | | || _ | _____| || _ || || |
|
||||||
|
// |__| |__||_______||___| |_||___| |_||_______||__| |__||_______||___|
|
||||||
|
|
||||||
|
|
||||||
|
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(db) {
|
||||||
|
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 });
|
||||||
|
|
||||||
|
function getTemplater(structId) {
|
||||||
|
let etaInstance = templateCache.get(structId);
|
||||||
|
|
||||||
|
if (!etaInstance) {
|
||||||
|
etaInstance = new Eta({});
|
||||||
|
etaInstance.resolvePath = function (path, _) {
|
||||||
|
return path;
|
||||||
|
};
|
||||||
|
etaInstance.readFile = function (templateAlias) {
|
||||||
|
return getTemplateContentByName(db, structId, templateAlias).content;
|
||||||
|
};
|
||||||
|
templateCache.set(structId, etaInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
return etaInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function getStructures(db) {
|
||||||
|
return db.prepare("SELECT * from structures").all();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStructure(db, id) {
|
||||||
|
return db.prepare("SELECT * from structures where ID = ?").get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createStructure(db, name, userId) {
|
||||||
|
const stmt = db.prepare(
|
||||||
|
"INSERT INTO structures (name, user_id) VALUES (?, ?)"
|
||||||
|
);
|
||||||
|
const info = stmt.run(name, userId);
|
||||||
|
return info.lastInsertRowid; // Returns the structure_id of the newly created structure
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRoute(db, verb, path, structureId, handler) {
|
||||||
|
path = encodeURI(path)
|
||||||
|
// add default handler here
|
||||||
|
const stmt = db.prepare(
|
||||||
|
"INSERT INTO routes (verb, path, structure_id, handler) VALUES (?, ?, ?, ?)"
|
||||||
|
);
|
||||||
|
const info = stmt.run(verb, path, structureId, handler);
|
||||||
|
return info.lastInsertRowid; // Returns the route_id of the newly created route
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRoutes(db, structureId) {
|
||||||
|
return db
|
||||||
|
.prepare("SELECT * from routes where structure_id = ?")
|
||||||
|
.all(structureId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRoute(db, routeId) {
|
||||||
|
return db.prepare("SELECT * from routes where id = ?").get(routeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateRoute(db, route) {
|
||||||
|
const fields = [
|
||||||
|
"verb",
|
||||||
|
"path",
|
||||||
|
"structure_id",
|
||||||
|
"handler",
|
||||||
|
"updated_at",
|
||||||
|
"error",
|
||||||
|
];
|
||||||
|
const values = fields.map((field) => route[field]);
|
||||||
|
const placeholders = fields.map((field) => `${field} = ?`).join(", ");
|
||||||
|
|
||||||
|
const sql = `UPDATE routes SET ${placeholders} WHERE id = ?`;
|
||||||
|
values.push(route.id); // Add routeId to the end for the WHERE clause
|
||||||
|
db.prepare(sql).run(...values);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDb(db, appDb) {
|
||||||
|
const fields = ["name", "library"];
|
||||||
|
const values = fields.map((field) => appDb[field]);
|
||||||
|
const placeholders = fields.map((field) => `${field} = ?`).join(", ");
|
||||||
|
|
||||||
|
const sql = `UPDATE dbs SET ${placeholders} WHERE id = ?`;
|
||||||
|
values.push(appDb.id); // Add routeId to the end for the WHERE clause
|
||||||
|
db.prepare(sql).run(...values);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStruct(db, struct) {
|
||||||
|
const fields = ["name", "route_prefix", "head_injection"];
|
||||||
|
const values = fields.map((field) => struct[field]);
|
||||||
|
const placeholders = fields.map((field) => `${field} = ?`).join(", ");
|
||||||
|
|
||||||
|
const sql = `UPDATE structures SET ${placeholders} WHERE id = ?`;
|
||||||
|
values.push(struct.id); // Add routeId to the end for the WHERE clause
|
||||||
|
db.prepare(sql).run(...values);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTemplate(db, template) {
|
||||||
|
const fields = ["content", "name", "test_object"];
|
||||||
|
const values = fields.map((field) => template[field]);
|
||||||
|
const placeholders = fields.map((field) => `${field} = ?`).join(", ");
|
||||||
|
|
||||||
|
const sql = `UPDATE templates SET ${placeholders} WHERE id = ?`;
|
||||||
|
values.push(template.id);
|
||||||
|
db.prepare(sql).run(...values);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTemplates(db, structureId) {
|
||||||
|
return db
|
||||||
|
.prepare("SELECT * from templates where structure_id = ?")
|
||||||
|
.all(structureId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTemplate(db, templateId) {
|
||||||
|
return db.prepare("SELECT * from templates where id = ?").get(templateId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTemplateContentByName(db, structId, name) {
|
||||||
|
return db
|
||||||
|
.prepare(
|
||||||
|
"SELECT content from templates where structure_id = ? AND name = ?"
|
||||||
|
)
|
||||||
|
.get(structId, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTemplate(db, structureId, name, content, testObjectString) {
|
||||||
|
return db
|
||||||
|
.prepare(
|
||||||
|
"INSERT INTO templates (structure_id, name, content, test_object) VALUES (?, ?, ?, ?)"
|
||||||
|
)
|
||||||
|
.run(structureId, name, content, testObjectString).lastInsertRowid;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDbsForStructure(db, 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 = ?
|
||||||
|
ORDER BY structure_dbs.created_at, is_aliased ASC;
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.all(structureId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDb(db, dbId) {
|
||||||
|
return db
|
||||||
|
.prepare(
|
||||||
|
`SELECT *
|
||||||
|
FROM dbs
|
||||||
|
WHERE id = ?;
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.get(dbId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDbForStructure(db, structureId, dbId) {
|
||||||
|
return db
|
||||||
|
.prepare(
|
||||||
|
`SELECT *
|
||||||
|
FROM structure_dbs
|
||||||
|
INNER JOIN dbs ON structure_dbs.db_id = dbs.id
|
||||||
|
WHERE structure_dbs.structure_id = ?
|
||||||
|
AND structure_dbs.db_id = ?;
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.get(structureId, dbId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDb(db, 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;
|
||||||
|
});
|
||||||
|
|
||||||
|
return transaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachDb(db, structId, dbId, alias) {
|
||||||
|
const insertStructureDbStmt = db.prepare(`
|
||||||
|
INSERT INTO structure_dbs (db_id, structure_id, alias)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
`);
|
||||||
|
insertStructureDbStmt.run(dbId, structId, alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFilesForStruct(db, structureId) {
|
||||||
|
let test = db
|
||||||
|
.prepare("SELECT * FROM files WHERE structure_id = ? ORDER BY id DESC")
|
||||||
|
.all(structureId);
|
||||||
|
|
||||||
|
return test;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFile(db, fileId) {
|
||||||
|
return db.prepare("SELECT * FROM files WHERE id = ?").get(fileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFile(db, 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(() => {
|
||||||
|
const cloneStructure = db.prepare(`
|
||||||
|
INSERT INTO structures (name, user_id, route_prefix, cloned_from)
|
||||||
|
VALUES (?, ?, ?, ?);
|
||||||
|
`);
|
||||||
|
|
||||||
|
const newStructId = cloneStructure.run(
|
||||||
|
newStructureName,
|
||||||
|
userId,
|
||||||
|
routePrefix,
|
||||||
|
structId
|
||||||
|
).lastInsertRowid;
|
||||||
|
|
||||||
|
const dbIds = db
|
||||||
|
.prepare(`select db_id from structure_dbs where structure_id = ?;`)
|
||||||
|
.all(structId);
|
||||||
|
|
||||||
|
const toClone = new Set(cloneDbs);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
return newStructId;
|
||||||
|
});
|
||||||
|
|
||||||
|
return transaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
function createLog(db, 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(db, routeId) {
|
||||||
|
return db.prepare("SELECT * FROM logs WHERE route_id = ? ORDER BY created_at DESC LIMIT 50").all(routeId).reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNewLogsByRoute(db, routeId, since) {
|
||||||
|
return db.prepare("SELECT * FROM logs WHERE route_id = ? AND id > ? ORDER BY created_at DESC").all(routeId, since);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMostRecentLogIdByRoute(db, routeId) {
|
||||||
|
const result = db
|
||||||
|
.prepare("SELECT id FROM logs WHERE route_id = ? ORDER BY created_at DESC LIMIT 1")
|
||||||
|
.get(routeId);
|
||||||
|
return result ? result.id : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
db,
|
||||||
|
applyMigrations,
|
||||||
|
getDbInstance,
|
||||||
|
getAllRoutes,
|
||||||
|
getUser,
|
||||||
|
createUser,
|
||||||
|
getStructures,
|
||||||
|
getStructure,
|
||||||
|
createStructure,
|
||||||
|
createRoute,
|
||||||
|
getRoutes,
|
||||||
|
getRoute,
|
||||||
|
updateRoute,
|
||||||
|
updateDb,
|
||||||
|
updateStruct,
|
||||||
|
updateTemplate,
|
||||||
|
getTemplater,
|
||||||
|
getTemplates,
|
||||||
|
getTemplate,
|
||||||
|
getTemplateContentByName,
|
||||||
|
createTemplate,
|
||||||
|
getDbsForStructure,
|
||||||
|
getDb,
|
||||||
|
getDbForStructure,
|
||||||
|
createDb,
|
||||||
|
attachDb,
|
||||||
|
getFilesForStruct,
|
||||||
|
getFile,
|
||||||
|
createFile,
|
||||||
|
cloneStructure,
|
||||||
|
createLog,
|
||||||
|
getLogsByRoute,
|
||||||
|
getNewLogsByRoute,
|
||||||
|
getMostRecentLogIdByRoute
|
||||||
|
}
|
||||||
586
index.js
586
index.js
|
|
@ -1,21 +1,24 @@
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
const util = require("util");
|
||||||
const vm = require("node:vm");
|
const vm = require("node:vm");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const express = require("express");
|
const express = require("express");
|
||||||
const session = require("express-session");
|
const session = require("express-session");
|
||||||
const fileUpload = require("express-fileupload");
|
const fileUpload = require("express-fileupload");
|
||||||
const SQLiteStore = require("better-sqlite3-session-store")(session);
|
const SQLiteStore = require("better-sqlite3-session-store")(session);
|
||||||
const betterSqlite3 = require("better-sqlite3");
|
|
||||||
const { Eta } = require("eta");
|
const { Eta } = require("eta");
|
||||||
const { match } = require("path-to-regexp");
|
const { match } = require("path-to-regexp");
|
||||||
const { LRUCache } = require("lru-cache");
|
|
||||||
const bcrypt = require("bcrypt");
|
const bcrypt = require("bcrypt");
|
||||||
const cheerio = require("cheerio");
|
const cheerio = require("cheerio");
|
||||||
|
const webPush = require('web-push');
|
||||||
const app = express();
|
const app = express();
|
||||||
const _expressWs = require("express-ws")(app);
|
const _expressWs = require("express-ws")(app);
|
||||||
const bodyParser = require("body-parser");
|
const bodyParser = require("body-parser");
|
||||||
|
const model = require("./db")
|
||||||
const PORT = 3000;
|
const PORT = 3000;
|
||||||
|
|
||||||
|
const db = model.db
|
||||||
|
|
||||||
let viewpath = path.join(__dirname, "views");
|
let viewpath = path.join(__dirname, "views");
|
||||||
let eta = new Eta({ views: viewpath, cache: false, autoEscape: true });
|
let eta = new Eta({ views: viewpath, cache: false, autoEscape: true });
|
||||||
|
|
||||||
|
|
@ -25,55 +28,15 @@ app.use(bodyParser.urlencoded({ extended: true }));
|
||||||
app.use(express.static("public"));
|
app.use(express.static("public"));
|
||||||
app.use(fileUpload());
|
app.use(fileUpload());
|
||||||
|
|
||||||
const db = betterSqlite3("./dbs/0.sqlite");
|
const publicVapidKey = 'BCW4o4mLShjgONRK21jwTT44ri49JZfrIeJ-W089TVatopdDh8UNujEptonafxM2ttbUPm4wbZF39GvY3dZWAO0';
|
||||||
|
const privateVapidKey = 'sXpmx2nRwvVWhGt7uBysZnmFStf9QjaafGd9Ereo4Gw';
|
||||||
|
|
||||||
db.pragma("journal_mode = WAL");
|
// Configure web-push with your VAPID details
|
||||||
|
webPush.setVapidDetails(
|
||||||
const dbCache = new LRUCache({ max: 25 });
|
'mailto:signups@sheepmail.net', // a mailto URL or URL
|
||||||
|
publicVapidKey,
|
||||||
function getDbInstance(dbId) {
|
privateVapidKey
|
||||||
let dbInstance = dbCache.get(dbId);
|
);
|
||||||
|
|
||||||
if (!dbInstance) {
|
|
||||||
dbInstance = betterSqlite3(`dbs/${dbId}.sqlite`);
|
|
||||||
dbInstance.pragma("journal_mode = WAL");
|
|
||||||
dbCache.set(dbId, dbInstance);
|
|
||||||
}
|
|
||||||
|
|
||||||
return dbInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
const templateCache = new LRUCache({ max: 100 });
|
|
||||||
|
|
||||||
function getTemplater(structId) {
|
|
||||||
let etaInstance = templateCache.get(structId);
|
|
||||||
|
|
||||||
if (!etaInstance) {
|
|
||||||
etaInstance = new Eta({});
|
|
||||||
etaInstance.resolvePath = function (path, _) {
|
|
||||||
return path;
|
|
||||||
};
|
|
||||||
etaInstance.readFile = function (templateAlias) {
|
|
||||||
return getTemplateContentByName(db, structId, templateAlias).content;
|
|
||||||
};
|
|
||||||
templateCache.set(structId, etaInstance);
|
|
||||||
}
|
|
||||||
|
|
||||||
return etaInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAllRoutes(db) {
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
session({
|
session({
|
||||||
|
|
@ -85,35 +48,7 @@ app.use(
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
function applyMigrations() {
|
function bootstrapContext(db, structureId, routeId, initContext) {
|
||||||
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 bootstrapContext(db, structureId, initContext) {
|
|
||||||
const allDbInstances = {};
|
const allDbInstances = {};
|
||||||
function getDb(alias) {
|
function getDb(alias) {
|
||||||
return allDbInstances[alias];
|
return allDbInstances[alias];
|
||||||
|
|
@ -121,20 +56,36 @@ function bootstrapContext(db, structureId, 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 = getTemplater(structureId);
|
const eta = model.getTemplater(structureId);
|
||||||
|
|
||||||
const libs = { eta, db: getDb };
|
const libs = { eta, db: getDb };
|
||||||
|
|
||||||
let dbs = getDbsForStructure(db, structureId);
|
let dbs = model.getDbsForStructure(db, structureId);
|
||||||
let context = vm.createContext({
|
let context = vm.createContext({
|
||||||
...initContext,
|
...initContext,
|
||||||
require: function (str) {
|
require: function (str) {
|
||||||
return libs[str];
|
return libs[str];
|
||||||
},
|
},
|
||||||
module: { exports: null },
|
module: { exports: null },
|
||||||
|
console: {
|
||||||
|
log: function (...content) {
|
||||||
|
const s = content.reduce((acc, curr) => {
|
||||||
|
console.log(curr)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
for (let appDb of dbs) {
|
for (let appDb of dbs) {
|
||||||
let dbInstance = getDbInstance(appDb.id);
|
let dbInstance = model.getDbInstance(appDb.id);
|
||||||
context.sql = dbInstance;
|
context.sql = dbInstance;
|
||||||
vm.runInContext(appDb.library, context);
|
vm.runInContext(appDb.library, context);
|
||||||
allDbInstances[appDb.alias] = {
|
allDbInstances[appDb.alias] = {
|
||||||
|
|
@ -156,15 +107,15 @@ 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
|
// need to move to runscript or whatever
|
||||||
try {
|
try {
|
||||||
const context = bootstrapContext(db, route.structure_id, { app });
|
const context = bootstrapContext(db, route.structure_id, route.id, { app });
|
||||||
let result = vm.runInContext(
|
let result = vm.runInContext(
|
||||||
route.handler + `\n\napp.ws("${routeWithPrefix(route)}", handler)`,
|
route.handler + `\n\napp.ws("${routeWithPrefix(route)}", handler)`,
|
||||||
context
|
context
|
||||||
);
|
);
|
||||||
updateRoute(db, { ...route, error: null });
|
model.updateRoute(db, { ...route, error: null });
|
||||||
return result;
|
return result;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
updateRoute(db, { ...route, error: e.stack });
|
model.updateRoute(db, { ...route, error: e.stack });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,7 +127,7 @@ function buildRoutes() {
|
||||||
DELETE: [],
|
DELETE: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
for (let route of getAllRoutes(db)) {
|
for (let route of model.getAllRoutes(db)) {
|
||||||
const p = routeWithPrefix(route);
|
const p = routeWithPrefix(route);
|
||||||
if (route.verb == "WS") {
|
if (route.verb == "WS") {
|
||||||
bootstrapWebsocketHandler(route);
|
bootstrapWebsocketHandler(route);
|
||||||
|
|
@ -191,7 +142,7 @@ function buildRoutes() {
|
||||||
routes = newRoutes;
|
routes = newRoutes;
|
||||||
}
|
}
|
||||||
|
|
||||||
applyMigrations();
|
model.applyMigrations();
|
||||||
buildRoutes();
|
buildRoutes();
|
||||||
|
|
||||||
// __ __ _______ _______ ______
|
// __ __ _______ _______ ______
|
||||||
|
|
@ -202,21 +153,12 @@ buildRoutes();
|
||||||
// | | _____| || |___ | | | |
|
// | | _____| || |___ | | | |
|
||||||
// |_______||_______||_______||___| |_|
|
// |_______||_______||_______||___| |_|
|
||||||
|
|
||||||
function createUser(db, username, hashedPassword) {
|
|
||||||
return db
|
|
||||||
.prepare("INSERT INTO users (username, password) VALUES (?, ?)")
|
|
||||||
.run(username, hashedPassword).lastInsertRowid;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getUser(db, username) {
|
|
||||||
return db.prepare("SELECT * from users where username = ?").get(username);
|
|
||||||
}
|
|
||||||
|
|
||||||
app.post("/register", async (req, res) => {
|
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 = createUser(db, username, hashedPassword);
|
userId = model.createUser(db, username, hashedPassword);
|
||||||
req.session.userId = userId;
|
req.session.userId = userId;
|
||||||
res.redirect("/workshop");
|
res.redirect("/workshop");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -226,7 +168,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 = getUser(db, username);
|
const user = model.getUser(db, 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;
|
||||||
|
|
@ -267,300 +209,7 @@ app.all("/logout", async (req, res) => {
|
||||||
// | || |_| || __ || |_ |_____ || || |_| || ___|
|
// | || |_| || __ || |_ |_____ || || |_| || ___|
|
||||||
// | _ || || | | || _ | _____| || _ || || |
|
// | _ || || | | || _ | _____| || _ || || |
|
||||||
// |__| |__||_______||___| |_||___| |_||_______||__| |__||_______||___|
|
// |__| |__||_______||___| |_||___| |_||_______||__| |__||_______||___|
|
||||||
|
//
|
||||||
function getStructures(db) {
|
|
||||||
return db.prepare("SELECT * from structures").all();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getStructure(db, id) {
|
|
||||||
return db.prepare("SELECT * from structures where ID = ?").get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createStructure(db, name, userId) {
|
|
||||||
const stmt = db.prepare(
|
|
||||||
"INSERT INTO structures (name, user_id) VALUES (?, ?)"
|
|
||||||
);
|
|
||||||
const info = stmt.run(name, userId);
|
|
||||||
return info.lastInsertRowid; // Returns the structure_id of the newly created structure
|
|
||||||
}
|
|
||||||
|
|
||||||
function createRoute(db, verb, path, structureId, handler) {
|
|
||||||
path = encodeURI(path)
|
|
||||||
// add default handler here
|
|
||||||
const stmt = db.prepare(
|
|
||||||
"INSERT INTO routes (verb, path, structure_id, handler) VALUES (?, ?, ?, ?)"
|
|
||||||
);
|
|
||||||
const info = stmt.run(verb, path, structureId, handler);
|
|
||||||
return info.lastInsertRowid; // Returns the route_id of the newly created route
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRoutes(db, structureId) {
|
|
||||||
return db
|
|
||||||
.prepare("SELECT * from routes where structure_id = ?")
|
|
||||||
.all(structureId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRoute(db, routeId) {
|
|
||||||
return db.prepare("SELECT * from routes where id = ?").get(routeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateRoute(db, route) {
|
|
||||||
const fields = [
|
|
||||||
"verb",
|
|
||||||
"path",
|
|
||||||
"structure_id",
|
|
||||||
"handler",
|
|
||||||
"updated_at",
|
|
||||||
"error",
|
|
||||||
];
|
|
||||||
const values = fields.map((field) => route[field]);
|
|
||||||
const placeholders = fields.map((field) => `${field} = ?`).join(", ");
|
|
||||||
|
|
||||||
const sql = `UPDATE routes SET ${placeholders} WHERE id = ?`;
|
|
||||||
values.push(route.id); // Add routeId to the end for the WHERE clause
|
|
||||||
db.prepare(sql).run(...values);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateDb(db, appDb) {
|
|
||||||
const fields = ["name", "library"];
|
|
||||||
const values = fields.map((field) => appDb[field]);
|
|
||||||
const placeholders = fields.map((field) => `${field} = ?`).join(", ");
|
|
||||||
|
|
||||||
const sql = `UPDATE dbs SET ${placeholders} WHERE id = ?`;
|
|
||||||
values.push(appDb.id); // Add routeId to the end for the WHERE clause
|
|
||||||
db.prepare(sql).run(...values);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateStruct(db, struct) {
|
|
||||||
const fields = ["name", "route_prefix", "head_injection"];
|
|
||||||
const values = fields.map((field) => struct[field]);
|
|
||||||
const placeholders = fields.map((field) => `${field} = ?`).join(", ");
|
|
||||||
|
|
||||||
const sql = `UPDATE structures SET ${placeholders} WHERE id = ?`;
|
|
||||||
values.push(struct.id); // Add routeId to the end for the WHERE clause
|
|
||||||
db.prepare(sql).run(...values);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateTemplate(db, template) {
|
|
||||||
const fields = ["content", "name", "test_object"];
|
|
||||||
const values = fields.map((field) => template[field]);
|
|
||||||
const placeholders = fields.map((field) => `${field} = ?`).join(", ");
|
|
||||||
|
|
||||||
const sql = `UPDATE templates SET ${placeholders} WHERE id = ?`;
|
|
||||||
values.push(template.id);
|
|
||||||
db.prepare(sql).run(...values);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTemplates(db, structureId) {
|
|
||||||
return db
|
|
||||||
.prepare("SELECT * from templates where structure_id = ?")
|
|
||||||
.all(structureId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTemplate(db, templateId) {
|
|
||||||
return db.prepare("SELECT * from templates where id = ?").get(templateId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTemplateContentByName(db, structId, name) {
|
|
||||||
return db
|
|
||||||
.prepare(
|
|
||||||
"SELECT content from templates where structure_id = ? AND name = ?"
|
|
||||||
)
|
|
||||||
.get(structId, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createTemplate(db, structureId, name, content, testObjectString) {
|
|
||||||
return db
|
|
||||||
.prepare(
|
|
||||||
"INSERT INTO templates (structure_id, name, content, test_object) VALUES (?, ?, ?, ?)"
|
|
||||||
)
|
|
||||||
.run(structureId, name, content, testObjectString).lastInsertRowid;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDbsForStructure(db, 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 = ?
|
|
||||||
ORDER BY structure_dbs.created_at, is_aliased ASC;
|
|
||||||
`
|
|
||||||
)
|
|
||||||
.all(structureId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDb(db, dbId) {
|
|
||||||
return db
|
|
||||||
.prepare(
|
|
||||||
`SELECT *
|
|
||||||
FROM dbs
|
|
||||||
WHERE id = ?;
|
|
||||||
`
|
|
||||||
)
|
|
||||||
.get(dbId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDbForStructure(db, structureId, dbId) {
|
|
||||||
return db
|
|
||||||
.prepare(
|
|
||||||
`SELECT *
|
|
||||||
FROM structure_dbs
|
|
||||||
INNER JOIN dbs ON structure_dbs.db_id = dbs.id
|
|
||||||
WHERE structure_dbs.structure_id = ?
|
|
||||||
AND structure_dbs.db_id = ?;
|
|
||||||
`
|
|
||||||
)
|
|
||||||
.get(structureId, dbId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDb(db, 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;
|
|
||||||
});
|
|
||||||
|
|
||||||
return transaction();
|
|
||||||
}
|
|
||||||
|
|
||||||
function attachDb(db, structId, dbId, alias) {
|
|
||||||
const insertStructureDbStmt = db.prepare(`
|
|
||||||
INSERT INTO structure_dbs (db_id, structure_id, alias)
|
|
||||||
VALUES (?, ?, ?)
|
|
||||||
`);
|
|
||||||
insertStructureDbStmt.run(dbId, structId, alias);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFilesForStruct(db, structureId) {
|
|
||||||
let test = db
|
|
||||||
.prepare("SELECT * FROM files WHERE structure_id = ? ORDER BY id DESC")
|
|
||||||
.all(structureId);
|
|
||||||
|
|
||||||
return test;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFile(db, fileId) {
|
|
||||||
return db.prepare("SELECT * FROM files WHERE id = ?").get(fileId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createFile(db, 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(() => {
|
|
||||||
const cloneStructure = db.prepare(`
|
|
||||||
INSERT INTO structures (name, user_id, route_prefix, cloned_from)
|
|
||||||
VALUES (?, ?, ?, ?);
|
|
||||||
`);
|
|
||||||
|
|
||||||
const newStructId = cloneStructure.run(
|
|
||||||
newStructureName,
|
|
||||||
userId,
|
|
||||||
routePrefix,
|
|
||||||
structId
|
|
||||||
).lastInsertRowid;
|
|
||||||
|
|
||||||
const dbIds = db
|
|
||||||
.prepare(`select db_id from structure_dbs where structure_id = ?;`)
|
|
||||||
.all(structId);
|
|
||||||
|
|
||||||
const toClone = new Set(cloneDbs);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
return newStructId;
|
|
||||||
});
|
|
||||||
|
|
||||||
return transaction();
|
|
||||||
}
|
|
||||||
|
|
||||||
function bootstrapTemplateWithHTMXetc(
|
function bootstrapTemplateWithHTMXetc(
|
||||||
htmlString,
|
htmlString,
|
||||||
blissRoute,
|
blissRoute,
|
||||||
|
|
@ -590,6 +239,14 @@ function bootstrapTemplateWithHTMXetc(
|
||||||
<script src="/js/htmx.js"></script>
|
<script src="/js/htmx.js"></script>
|
||||||
<script src="https://unpkg.com/htmx.org@1.9.12/dist/ext/ws.js"></script>
|
<script src="https://unpkg.com/htmx.org@1.9.12/dist/ext/ws.js"></script>
|
||||||
<script src="/js/bliss_inspector.js"></script>
|
<script src="/js/bliss_inspector.js"></script>
|
||||||
|
<script>
|
||||||
|
tailwind.config = {
|
||||||
|
corePlugins: {
|
||||||
|
preflight: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style> body { margin: 0; }</style>
|
||||||
${headInjection || ""}
|
${headInjection || ""}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
|
@ -616,7 +273,7 @@ function bootstrapTemplateWithHTMXetc(
|
||||||
}
|
}
|
||||||
|
|
||||||
app.post("/workshop", (req, res) => {
|
app.post("/workshop", (req, res) => {
|
||||||
let structId = createStructure(db, req.body.name);
|
let structId = model.createStructure(db, req.body.name);
|
||||||
return res.redirect("/workshop/" + structId);
|
return res.redirect("/workshop/" + structId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -624,7 +281,7 @@ app.get("/workshop", (req, res) => {
|
||||||
return res.send(
|
return res.send(
|
||||||
bootstrapTemplateWithHTMXetc(
|
bootstrapTemplateWithHTMXetc(
|
||||||
eta.render("workshop/index", {
|
eta.render("workshop/index", {
|
||||||
structures: getStructures(db),
|
structures: model.getStructures(db),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
@ -641,10 +298,10 @@ function smartRedirect(req, res, redirectUrl) {
|
||||||
|
|
||||||
function sidebarStuff(db, structId) {
|
function sidebarStuff(db, structId) {
|
||||||
return {
|
return {
|
||||||
structure: getStructure(db, structId),
|
structure: model.getStructure(db, structId),
|
||||||
routes: getRoutes(db, structId),
|
routes: model.getRoutes(db, structId),
|
||||||
templates: getTemplates(db, structId),
|
templates: model.getTemplates(db, structId),
|
||||||
dbs: getDbsForStructure(db, structId),
|
dbs: model.getDbsForStructure(db, structId),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -661,7 +318,7 @@ app.post("/workshop/:structure_id/clone", (req, res) => {
|
||||||
routePrefix = routePrefix[0] == "/" ? routePrefix : "/" + routePrefix;
|
routePrefix = routePrefix[0] == "/" ? routePrefix : "/" + routePrefix;
|
||||||
let newStructureId;
|
let newStructureId;
|
||||||
try {
|
try {
|
||||||
newStructureId = cloneStructure(
|
newStructureId = model.cloneStructure(
|
||||||
req.params.structure_id,
|
req.params.structure_id,
|
||||||
req.body.name,
|
req.body.name,
|
||||||
1,
|
1,
|
||||||
|
|
@ -677,19 +334,19 @@ 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 = createDb(db, req.params.structure_id, req.body.name);
|
const dbId = model.createDb(db, 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) => {
|
||||||
attachDb(db, req.params.structure_id, req.body.db_id, req.body.alias);
|
model.attachDb(db, 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 = getDbForStructure(
|
const structdb = model.getDbForStructure(
|
||||||
db,
|
db,
|
||||||
req.params.structure_id,
|
req.params.structure_id,
|
||||||
req.params.db_id
|
req.params.db_id
|
||||||
|
|
@ -723,7 +380,7 @@ app.post("/workshop/:structure_id/route", (req, res) => {
|
||||||
if (p[p.length - 1] == "/") {
|
if (p[p.length - 1] == "/") {
|
||||||
p = p.substring(0, p.length - 1);
|
p = p.substring(0, p.length - 1);
|
||||||
}
|
}
|
||||||
const route = createRoute(
|
const route = model.createRoute(
|
||||||
db,
|
db,
|
||||||
req.body.verb,
|
req.body.verb,
|
||||||
p[0] == "/" ? p.substring(1) : "/" + p,
|
p[0] == "/" ? p.substring(1) : "/" + p,
|
||||||
|
|
@ -740,19 +397,19 @@ 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 = getRoute(db, req.params.route_id);
|
const route = model.getRoute(db, req.params.route_id);
|
||||||
updateRoute(db, { ...route, ...req.body });
|
model.updateRoute(db, { ...route, ...req.body });
|
||||||
return res.send("good");
|
return res.send("good");
|
||||||
});
|
});
|
||||||
|
|
||||||
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 = getDb(db, dbId);
|
let appDb = model.getDb(db, dbId);
|
||||||
updateDb(db, { ...appDb, library: req.body.library });
|
model.updateDb(db, { ...appDb, library: req.body.library });
|
||||||
appDb = getDb(db, dbId);
|
appDb = model.getDb(db, dbId);
|
||||||
|
|
||||||
let dbInstance = 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 },
|
||||||
|
|
@ -762,7 +419,14 @@ app.put("/workshop/:structure_id/db/:db_id/library", (req, res) => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
let evaledCode = vm.runInContext(appDb.library, context);
|
let evaledCode = vm.runInContext(appDb.library, context);
|
||||||
let stdout = capturedOutput.join("\n");
|
let stdout = capturedOutput.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}`);
|
||||||
|
|
@ -772,21 +436,30 @@ 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 = getDb(db, dbId);
|
let appDb = model.getDb(db, dbId);
|
||||||
let dbInstance = 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) => capturedOutput.push(args.join(" ")),
|
log: (...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);
|
||||||
libraryScript.runInContext(context);
|
libraryScript.runInContext(context);
|
||||||
context.library = context.module.exports;
|
context.library = context.module.exports;
|
||||||
const replScript = new vm.Script(req.body.code);
|
const replScript = new vm.Script(req.body.code);
|
||||||
let evaledCode = replScript.runInContext(context);
|
let evaledCode = JSON.stringify(replScript.runInContext(context));
|
||||||
let stdout = capturedOutput.join("\n");
|
let stdout = capturedOutput.join("\n");
|
||||||
return res.send(`${stdout}\n\n> ${evaledCode}`.trim());
|
return res.send(`${stdout}\n\n> ${evaledCode}`.trim());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -797,7 +470,7 @@ app.post("/workshop/:structure_id/db/:db_id/repl", (req, res) => {
|
||||||
app.post("/workshop/:structure_id/template", (req, res) => {
|
app.post("/workshop/:structure_id/template", (req, res) => {
|
||||||
let name = req.body.name;
|
let name = req.body.name;
|
||||||
|
|
||||||
const template = createTemplate(
|
const template = model.createTemplate(
|
||||||
db,
|
db,
|
||||||
req.params.structure_id,
|
req.params.structure_id,
|
||||||
name,
|
name,
|
||||||
|
|
@ -817,13 +490,17 @@ 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;
|
||||||
|
|
||||||
updateTemplate(db, { ...getTemplate(db, id), content, test_object });
|
model.updateTemplate(db, { ...model.updateTemplate(db, id), content, test_object });
|
||||||
|
|
||||||
return res.send("good");
|
return res.send("good");
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get("/workshop/:structure_id/template/:template_id", (req, res) => {
|
app.get("/workshop/:structure_id/template/:template_id", (req, res) => {
|
||||||
const template = getTemplate(db, req.params.template_id);
|
const template = model.getTemplate(db, req.params.template_id);
|
||||||
|
|
||||||
|
console.log("wassup", {
|
||||||
|
template: template,
|
||||||
|
...sidebarStuff(db, req.params.structure_id),})
|
||||||
|
|
||||||
return res.send(
|
return res.send(
|
||||||
bootstrapTemplateWithHTMXetc(
|
bootstrapTemplateWithHTMXetc(
|
||||||
|
|
@ -836,9 +513,10 @@ app.get("/workshop/:structure_id/template/:template_id", (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get("/workshop/:structure_id/template/:template_id/preview", (req, res) => {
|
app.get("/workshop/:structure_id/template/:template_id/preview", (req, res) => {
|
||||||
const template = getTemplate(db, req.params.template_id);
|
const template = model.getTemplate(db, req.params.template_id);
|
||||||
const struct = getStructure(db, req.params.structure_id);
|
const struct = model.getStructure(db, req.params.structure_id);
|
||||||
const eta = getTemplater(req.params.structure_id);
|
const eta = model.getTemplater(req.params.structure_id);
|
||||||
|
console.log(template)
|
||||||
|
|
||||||
const context = vm.createContext({ it: null });
|
const context = vm.createContext({ it: null });
|
||||||
vm.runInContext(template.test_object, context);
|
vm.runInContext(template.test_object, context);
|
||||||
|
|
@ -860,19 +538,12 @@ 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 = getRoute(db, req.params.id);
|
const route = model.getRoute(db, req.params.id);
|
||||||
let routePrefix = getStructure(db, req.params.structure_id).route_prefix;
|
let routePrefix = model.getStructure(db, req.params.structure_id).route_prefix;
|
||||||
let previewUrl = routePrefix
|
let previewUrl = routePrefix
|
||||||
? path.join(routePrefix || "", route.path)
|
? path.join(routePrefix || "", route.path)
|
||||||
: route.path;
|
: route.path;
|
||||||
|
|
||||||
console.log(previewUrl, "baba");
|
|
||||||
// let previewUrl = prefixUrlWithHost(
|
|
||||||
// req,
|
|
||||||
// routePrefix ? path.join(routePrefix.substring(1), route.path) : route.path
|
|
||||||
// );
|
|
||||||
|
|
||||||
console.log(previewUrl);
|
|
||||||
return res.send(
|
return res.send(
|
||||||
bootstrapTemplateWithHTMXetc(
|
bootstrapTemplateWithHTMXetc(
|
||||||
eta.render("workshop/route", {
|
eta.render("workshop/route", {
|
||||||
|
|
@ -885,6 +556,31 @@ app.get("/workshop/:structure_id/route/:id", (req, res) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get("/workshop/:structure_id/route/:route_id/logs", (req, res) => {
|
||||||
|
const since = req.query.since
|
||||||
|
let logs = []
|
||||||
|
if (since != undefined) {
|
||||||
|
logs = model.getNewLogsByRoute(db, req.params.route_id, since)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logs = model.getLogsByRoute(db, req.params.route_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastId = model.getMostRecentLogIdByRoute(db, req.params.route_id)
|
||||||
|
|
||||||
|
return res.send(
|
||||||
|
bootstrapTemplateWithHTMXetc(
|
||||||
|
eta.render("workshop/logs", {
|
||||||
|
logs: logs.reverse(),
|
||||||
|
structId: req.params.structure_id,
|
||||||
|
routeId: req.params.route_id,
|
||||||
|
since: lastId,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// POST route to handle file upload
|
// POST route to handle file upload
|
||||||
app.post("/workshop/:structure_id/files", (req, res) => {
|
app.post("/workshop/:structure_id/files", (req, res) => {
|
||||||
if (!req.files || Object.keys(req.files).length === 0) {
|
if (!req.files || Object.keys(req.files).length === 0) {
|
||||||
|
|
@ -906,7 +602,7 @@ app.post("/workshop/:structure_id/files", (req, res) => {
|
||||||
return res.status(500).send(err);
|
return res.status(500).send(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
let id = createFile(
|
let id = model.createFile(
|
||||||
db,
|
db,
|
||||||
structure_id,
|
structure_id,
|
||||||
name,
|
name,
|
||||||
|
|
@ -915,7 +611,7 @@ app.post("/workshop/:structure_id/files", (req, res) => {
|
||||||
mime_subtype
|
mime_subtype
|
||||||
);
|
);
|
||||||
|
|
||||||
let file = getFile(db, id);
|
let file = model.getFile(db, id);
|
||||||
file.url = prefixUrlWithHost(req, file.path);
|
file.url = prefixUrlWithHost(req, file.path);
|
||||||
|
|
||||||
return res.send(eta.render("workshop/file_detail", { file: file }));
|
return res.send(eta.render("workshop/file_detail", { file: file }));
|
||||||
|
|
@ -934,7 +630,7 @@ 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 = getFilesForStruct(db, structure_id);
|
const files = model.getFilesForStruct(db, structure_id);
|
||||||
files.map((it) => {
|
files.map((it) => {
|
||||||
it.url = prefixUrlWithHost(req, it.path);
|
it.url = prefixUrlWithHost(req, it.path);
|
||||||
});
|
});
|
||||||
|
|
@ -961,7 +657,7 @@ app.get("/workshop/:structure_id/settings", (req, res) => {
|
||||||
|
|
||||||
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 = getStructure(db, structId);
|
let struct = model.getStructure(db, structId);
|
||||||
let routePrefix = req.body.route_prefix;
|
let routePrefix = req.body.route_prefix;
|
||||||
|
|
||||||
if (routePrefix[0] != "/") {
|
if (routePrefix[0] != "/") {
|
||||||
|
|
@ -971,12 +667,12 @@ app.put("/workshop/:structure_id/settings", (req, res) => {
|
||||||
routePrefix = routePrefix.substring(0, routePrefix.length - 1);
|
routePrefix = routePrefix.substring(0, routePrefix.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateStruct(db, {
|
model.updateStruct(db, {
|
||||||
...struct,
|
...struct,
|
||||||
route_prefix: routePrefix,
|
route_prefix: routePrefix,
|
||||||
head_injection: req.body.head_injection,
|
head_injection: req.body.head_injection,
|
||||||
});
|
});
|
||||||
struct = getStructure(db, structId);
|
struct = model.getStructure(db, structId);
|
||||||
|
|
||||||
res.send("success!");
|
res.send("success!");
|
||||||
});
|
});
|
||||||
|
|
@ -985,7 +681,7 @@ app.get("/workshop/:structure_id/new_template_modal", (req, res) => {
|
||||||
return res.send(
|
return res.send(
|
||||||
bootstrapTemplateWithHTMXetc(
|
bootstrapTemplateWithHTMXetc(
|
||||||
eta.render("workshop/new_template_modal", {
|
eta.render("workshop/new_template_modal", {
|
||||||
structure: getStructure(db, req.params.structure_id),
|
structure: model.getStructure(db, req.params.structure_id),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
@ -995,7 +691,7 @@ app.get("/workshop/:structure_id/new_route_modal", (req, res) => {
|
||||||
return res.send(
|
return res.send(
|
||||||
bootstrapTemplateWithHTMXetc(
|
bootstrapTemplateWithHTMXetc(
|
||||||
eta.render("workshop/new_route_modal", {
|
eta.render("workshop/new_route_modal", {
|
||||||
structure: getStructure(db, req.params.structure_id),
|
structure: model.getStructure(db, req.params.structure_id),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
@ -1005,15 +701,15 @@ app.get("/workshop/:structure_id/new_db_modal", (req, res) => {
|
||||||
return res.send(
|
return res.send(
|
||||||
bootstrapTemplateWithHTMXetc(
|
bootstrapTemplateWithHTMXetc(
|
||||||
eta.render("workshop/new_db_modal", {
|
eta.render("workshop/new_db_modal", {
|
||||||
structure: getStructure(db, req.params.structure_id),
|
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 = getStructure(db, req.params.structure_id);
|
const structure = model.getStructure(db, req.params.structure_id);
|
||||||
const dbs = getDbsForStructure(db, req.params.structure_id);
|
const dbs = model.getDbsForStructure(db, req.params.structure_id);
|
||||||
|
|
||||||
return res.send(
|
return res.send(
|
||||||
bootstrapTemplateWithHTMXetc(
|
bootstrapTemplateWithHTMXetc(
|
||||||
|
|
@ -1050,13 +746,13 @@ app.all("*", (req, res) => {
|
||||||
.prepare("SELECT * FROM routes WHERE id = ?")
|
.prepare("SELECT * FROM routes WHERE id = ?")
|
||||||
.get(routeMatch.id);
|
.get(routeMatch.id);
|
||||||
|
|
||||||
const structure = getStructure(db, route.structure_id);
|
const structure = model.getStructure(db, 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, {
|
let context = bootstrapContext(db, route.structure_id, route.id, {
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
eta,
|
eta,
|
||||||
|
|
@ -1067,7 +763,7 @@ app.all("*", (req, res) => {
|
||||||
context.route = function (url) {
|
context.route = function (url) {
|
||||||
return __urlPrefix ? path.join(__urlPrefix, url) : url;
|
return __urlPrefix ? path.join(__urlPrefix, url) : url;
|
||||||
};
|
};
|
||||||
const eta = getTemplater(route.structure_id);
|
const eta = model.getTemplater(route.structure_id);
|
||||||
res.send(
|
res.send(
|
||||||
bootstrapTemplateWithHTMXetc(
|
bootstrapTemplateWithHTMXetc(
|
||||||
eta.render(template, context),
|
eta.render(template, context),
|
||||||
|
|
@ -1084,7 +780,9 @@ app.all("*", (req, res) => {
|
||||||
context
|
context
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return res.send(`${e}\n\n${e.stack}`);
|
const error = e.stack ? `${e}\n\n${e.stack}` : `${e}`;
|
||||||
|
model.createLog(db, structure.id, route.id, error, true);
|
||||||
|
return res.send(error);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
res.status(404).json({ success: false, message: "Path not found" });
|
res.status(404).json({ success: false, message: "Path not found" });
|
||||||
|
|
|
||||||
18
migrations/001_add_logs.sql
Normal file
18
migrations/001_add_logs.sql
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
CREATE TABLE logs (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
error BOOLEAN NOT NULL,
|
||||||
|
structure_id INTEGER NOT NULL,
|
||||||
|
route_id INTEGER,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY(structure_id) REFERENCES structures(id),
|
||||||
|
FOREIGN KEY(route_id) REFERENCES routes(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Trigger to update `created_at` on insert
|
||||||
|
CREATE TRIGGER update_logs_created_at
|
||||||
|
AFTER INSERT ON logs
|
||||||
|
FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
UPDATE logs SET created_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||||
|
END;
|
||||||
24
package-lock.json
generated
24
package-lock.json
generated
|
|
@ -25,6 +25,9 @@
|
||||||
"lru-cache": "^10.2.0",
|
"lru-cache": "^10.2.0",
|
||||||
"nunjucks": "^3.2.4",
|
"nunjucks": "^3.2.4",
|
||||||
"path-to-regexp": "^6.2.2"
|
"path-to-regexp": "^6.2.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"prettier": "3.3.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mapbox/node-pre-gyp": {
|
"node_modules/@mapbox/node-pre-gyp": {
|
||||||
|
|
@ -1917,6 +1920,21 @@
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prettier": {
|
||||||
|
"version": "3.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
|
||||||
|
"integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"prettier": "bin/prettier.cjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/proxy-addr": {
|
"node_modules/proxy-addr": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||||
|
|
@ -3941,6 +3959,12 @@
|
||||||
"tunnel-agent": "^0.6.0"
|
"tunnel-agent": "^0.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"prettier": {
|
||||||
|
"version": "3.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
|
||||||
|
"integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"proxy-addr": {
|
"proxy-addr": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||||
|
|
|
||||||
|
|
@ -25,5 +25,8 @@
|
||||||
"lru-cache": "^10.2.0",
|
"lru-cache": "^10.2.0",
|
||||||
"nunjucks": "^3.2.4",
|
"nunjucks": "^3.2.4",
|
||||||
"path-to-regexp": "^6.2.2"
|
"path-to-regexp": "^6.2.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"prettier": "3.3.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
public/12/Screenshot 2023-11-25 at 6.41.35â¯PM.png
Normal file
BIN
public/12/Screenshot 2023-11-25 at 6.41.35â¯PM.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.5 MiB |
File diff suppressed because one or more lines are too long
|
|
@ -17,12 +17,12 @@
|
||||||
</div>
|
</div>
|
||||||
<div id="main-editor" class="flex flex-grow">
|
<div id="main-editor" class="flex flex-grow">
|
||||||
<div id="left-pane" class="w-1/2 flex-grow h-full flex flex-col">
|
<div id="left-pane" class="w-1/2 flex-grow h-full flex flex-col">
|
||||||
<h1>LIBRARY</h1>
|
<h1 class="text-lg">LIBRARY</h1>
|
||||||
<div id="library-editor" class="w-full flex-grow"><%= it.db.library %></div>
|
<div id="library-editor" class="w-full flex-grow"><%= it.db.library %></div>
|
||||||
<button id="save-library" type="submit">Save</button>
|
<button id="save-library" type="submit">Save</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="preview-area" class="w-1/2 h-full flex flex-col">
|
<div id="preview-area" class="w-1/2 h-full flex flex-col">
|
||||||
<h1>REPL</h1>
|
<h1 class="text-lg">REPL</h1>
|
||||||
<div id="repl-editor" class="w-full h-1/2">// evaluate code in here
|
<div id="repl-editor" class="w-full h-1/2">// evaluate code in here
|
||||||
</div>
|
</div>
|
||||||
<button id="send-repl">RUN</button>
|
<button id="send-repl">RUN</button>
|
||||||
|
|
@ -76,4 +76,4 @@
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
<%~ include('/workshop/sidebar', it) %>
|
<%~ include('/workshop/sidebar', it) %>
|
||||||
</div>
|
</div>
|
||||||
<div id="main-editor" class="flex flex-grow flex-col">
|
<div id="main-editor" class="flex flex-grow flex-col">
|
||||||
<h1>Files</h1>
|
<h1 class="text-lg">Files</h1>
|
||||||
<div>
|
<div>
|
||||||
<form hx-encoding='multipart/form-data' hx-post='/workshop/<%= it.structure.id %>/files' hx-target="#files-list" hx-swap="afterbegin" _='on htmx:xhr:progress(loaded, total) set #progress.value to (loaded/total)*100'>
|
<form hx-encoding='multipart/form-data' hx-post='/workshop/<%= it.structure.id %>/files' hx-target="#files-list" hx-swap="afterbegin" _='on htmx:xhr:progress(loaded, total) set #progress.value to (loaded/total)*100'>
|
||||||
<input type='file' name='file'>
|
<input type='file' name='file'>
|
||||||
|
|
@ -39,4 +39,4 @@
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
6
views/workshop/logs.eta
Normal file
6
views/workshop/logs.eta
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<div hx-swap="outerHTML" hx-get="/workshop/<%= it.structId %>/route/<%= it.routeId %>/logs?since=<%= it.since || 0 %>" hx-trigger="every 2s" class="hidden"></div>
|
||||||
|
<% it.logs.forEach((log) => { %>
|
||||||
|
<div class="<% if (log.error) { %>bg-red-800 text-white <% } %>">
|
||||||
|
<%= log.content %>
|
||||||
|
</div>
|
||||||
|
<% }) %>
|
||||||
|
|
@ -18,7 +18,13 @@
|
||||||
<div id="main-editor" class="flex flex-grow">
|
<div id="main-editor" class="flex flex-grow">
|
||||||
<div id="left-pane" class="w-1/2 flex-grow h-full flex flex-col">
|
<div id="left-pane" class="w-1/2 flex-grow h-full flex flex-col">
|
||||||
<div id="editor" class="w-full flex-grow"><%= it.route.handler %></div>
|
<div id="editor" class="w-full flex-grow"><%= it.route.handler %></div>
|
||||||
|
<div class="flex">
|
||||||
<button id="save-btn" type="submit">Save</button>
|
<button id="save-btn" type="submit">Save</button>
|
||||||
|
<button id="save-btn" type="submit" _="on click toggle .hidden on #logs">Logs</button>
|
||||||
|
</div>
|
||||||
|
<pre id="logs" class="max-h-[50vh] overflow-scroll hidden">
|
||||||
|
<ul hx-get="/workshop/<%= it.structure.id %>/route/<%= it.route.id %>/logs" hx-trigger="load" class="flex flex-col-reverse"></ul>
|
||||||
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
<div id="preview-area" class="w-1/2 h-full flex flex-col">
|
<div id="preview-area" class="w-1/2 h-full flex flex-col">
|
||||||
<form _="on submit halt the event then set #preview's contentWindow.location.href to #url's value">
|
<form _="on submit halt the event then set #preview's contentWindow.location.href to #url's value">
|
||||||
|
|
@ -38,24 +44,23 @@
|
||||||
editor.setTheme("ace/theme/monokai");
|
editor.setTheme("ace/theme/monokai");
|
||||||
editor.session.setMode("ace/mode/javascript");
|
editor.session.setMode("ace/mode/javascript");
|
||||||
|
|
||||||
document.getElementById('save-btn').onclick = function() {
|
document.getElementById('save-btn').onclick = function() {
|
||||||
fetch('/workshop/<%= it.structure.id %>/route/<%= it.route.id %>', {
|
fetch('/workshop/<%= it.structure.id %>/route/<%= it.route.id %>', {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
},
|
},
|
||||||
body: new URLSearchParams({
|
body: new URLSearchParams({
|
||||||
handler: editor.getValue()
|
handler: editor.getValue()
|
||||||
}).toString()
|
}).toString()
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
document.getElementById('preview').contentWindow.location.reload()
|
document.getElementById('preview').contentWindow.location.reload()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue