bliss/index.js

801 lines
22 KiB
JavaScript
Raw Normal View History

2024-02-24 11:12:14 -05:00
const fs = require("fs");
2024-10-29 21:29:40 -04:00
const util = require("util");
2024-06-02 00:39:30 -04:00
const vm = require("node:vm");
2024-02-24 11:12:14 -05:00
const path = require("path");
const express = require("express");
const session = require("express-session");
2024-06-02 10:20:01 -04:00
const fileUpload = require("express-fileupload");
2024-02-24 11:12:14 -05:00
const SQLiteStore = require("better-sqlite3-session-store")(session);
const { Eta } = require("eta");
2024-06-02 00:39:30 -04:00
const { match } = require("path-to-regexp");
2024-02-24 11:12:14 -05:00
const bcrypt = require("bcrypt");
2024-06-02 00:39:30 -04:00
const cheerio = require("cheerio");
2024-02-03 13:22:50 -05:00
const app = express();
2024-06-08 18:29:31 -04:00
const _expressWs = require("express-ws")(app);
2024-02-24 11:12:14 -05:00
const bodyParser = require("body-parser");
2025-01-09 15:42:56 -05:00
const model = require("./db");
2024-02-03 13:22:50 -05:00
const PORT = 3000;
2025-01-09 15:42:56 -05:00
const db = model.db;
2024-10-29 21:29:40 -04:00
2024-02-24 11:12:14 -05:00
let viewpath = path.join(__dirname, "views");
2024-06-02 00:39:30 -04:00
let eta = new Eta({ views: viewpath, cache: false, autoEscape: true });
2024-06-08 11:42:27 -04:00
let routes = { GET: [], POST: [], PUT: [], DELETE: [] };
2024-02-03 13:22:50 -05:00
2024-02-24 11:12:14 -05:00
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static("public"));
2024-06-02 10:20:01 -04:00
app.use(fileUpload());
2024-02-24 11:12:14 -05:00
app.use(
session({
2024-02-03 13:22:50 -05:00
store: new SQLiteStore({ client: db, expired: { clear: true } }),
2024-02-24 11:12:14 -05:00
secret: "your secret key",
2024-02-03 13:22:50 -05:00
resave: false,
saveUninitialized: true,
2024-02-24 11:12:14 -05:00
cookie: { secure: false },
2025-01-09 15:42:56 -05:00
}),
2024-02-24 11:12:14 -05:00
);
2024-02-03 13:22:50 -05:00
2024-10-29 21:29:40 -04:00
function bootstrapContext(db, structureId, routeId, initContext) {
2024-06-08 18:29:31 -04:00
const allDbInstances = {};
function getDb(alias) {
return allDbInstances[alias];
}
// 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
2024-10-29 21:29:40 -04:00
const eta = model.getTemplater(structureId);
2024-06-08 18:29:31 -04:00
const libs = { eta, db: getDb };
2024-10-29 21:29:40 -04:00
let dbs = model.getDbsForStructure(db, structureId);
2024-06-08 18:29:31 -04:00
let context = vm.createContext({
...initContext,
require: function (str) {
return libs[str];
},
module: { exports: null },
2024-10-29 21:29:40 -04:00
console: {
log: function (...content) {
const s = content.reduce((acc, curr) => {
2025-01-09 15:42:56 -05:00
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);
},
},
2024-06-08 18:29:31 -04:00
});
for (let appDb of dbs) {
2024-10-29 21:29:40 -04:00
let dbInstance = model.getDbInstance(appDb.id);
2024-06-08 18:29:31 -04:00
context.sql = dbInstance;
vm.runInContext(appDb.library, context);
allDbInstances[appDb.alias] = {
library: context.module.exports,
sql: dbInstance,
};
context.module.exports = null;
}
return context;
}
function routeWithPrefix(route) {
const prefix = route.route_prefix;
return prefix ? path.join(prefix, route.path) : route.path;
}
function bootstrapWebsocketHandler(route) {
// todo only expose app when running the handler, should not be available to the handler itself
// need to move to runscript or whatever
try {
2024-10-29 21:29:40 -04:00
const context = bootstrapContext(db, route.structure_id, route.id, { app });
2024-06-08 18:29:31 -04:00
let result = vm.runInContext(
route.handler + `\n\napp.ws("${routeWithPrefix(route)}", handler)`,
2025-01-09 15:42:56 -05:00
context,
2024-06-08 18:29:31 -04:00
);
2024-10-29 21:29:40 -04:00
model.updateRoute(db, { ...route, error: null });
2024-06-08 18:29:31 -04:00
return result;
} catch (e) {
2024-10-29 21:29:40 -04:00
model.updateRoute(db, { ...route, error: e.stack });
2024-06-08 18:29:31 -04:00
}
}
2024-06-02 00:39:30 -04:00
function buildRoutes() {
2024-06-08 11:42:27 -04:00
let newRoutes = {
GET: [],
POST: [],
PUT: [],
DELETE: [],
};
2024-10-29 21:29:40 -04:00
for (let route of model.getAllRoutes(db)) {
2024-06-08 18:29:31 -04:00
const p = routeWithPrefix(route);
if (route.verb == "WS") {
bootstrapWebsocketHandler(route);
} else {
newRoutes[route.verb].push({
matcher: match(p, { decode: decodeURIComponent }),
id: route.id,
path: route.path,
});
}
2024-06-02 00:39:30 -04:00
}
2024-06-08 11:42:27 -04:00
routes = newRoutes;
2024-06-02 00:39:30 -04:00
}
2024-10-29 21:29:40 -04:00
model.applyMigrations();
2024-06-02 00:39:30 -04:00
buildRoutes();
2024-02-03 13:22:50 -05:00
2024-02-24 11:12:14 -05:00
// __ __ _______ _______ ______
// | | | || || || _ |
// | | | || _____|| ___|| | ||
// | |_| || |_____ | |___ | |_||_
// | ||_____ || ___|| __ |
// | | _____| || |___ | | | |
// |_______||_______||_______||___| |_|
app.post("/register", async (req, res) => {
try {
const { username, password } = req.body;
const hashedPassword = await bcrypt.hash(password, 10); // 10 is the saltRounds
2024-10-29 21:29:40 -04:00
userId = model.createUser(db, username, hashedPassword);
2024-06-02 00:39:30 -04:00
req.session.userId = userId;
2024-02-24 11:12:14 -05:00
res.redirect("/workshop");
} catch (e) {
res.render("auth/register.html", { error: e });
}
});
app.post("/login", async (req, res) => {
const { username, password } = req.body;
2024-10-29 21:29:40 -04:00
const user = model.getUser(db, username);
2024-02-24 11:12:14 -05:00
if (user && (await bcrypt.compare(password, user.password))) {
req.session.userId = user.id;
return res.redirect("/");
}
return res.send(
eta.render("auth/login", {
error: "are you sure you entered that right?",
2025-01-09 15:42:56 -05:00
}),
2024-02-24 11:12:14 -05:00
);
});
app.get("/register", async (req, res) => {
if (req.session.userId) {
return res.redirect("/");
}
2024-06-02 00:39:30 -04:00
return res.send(eta.render("auth/register", { error: null }));
2024-02-24 11:12:14 -05:00
});
app.get("/login", async (req, res) => {
if (req.session.userId) {
return res.redirect("/");
}
return res.send(eta.render("auth/login", { error: null }));
});
app.all("/logout", async (req, res) => {
return req.session.destroy(() => {
res.redirect("/");
});
});
// _ _ _______ ______ ___ _ _______ __ __ _______ _______
// | | _ | || || _ | | | | || || | | || || |
// | || || || _ || | || | |_| || _____|| |_| || _ || _ |
// | || | | || |_||_ | _|| |_____ | || | | || |_| |
// | || |_| || __ || |_ |_____ || || |_| || ___|
// | _ || || | | || _ | _____| || _ || || |
// |__| |__||_______||___| |_||___| |_||_______||__| |__||_______||___|
2024-10-29 21:29:40 -04:00
//
2024-06-04 03:01:19 -04:00
function bootstrapTemplateWithHTMXetc(
htmlString,
blissRoute,
blissClone,
blissCopy,
2024-06-08 15:30:28 -04:00
headInjection,
2025-01-09 15:42:56 -05:00
htmxRequest = false,
2024-06-04 03:01:19 -04:00
) {
2024-06-08 18:29:31 -04:00
if (
htmlString.toLowerCase().startsWith("<html>") ||
htmlString.toLowerCase().startsWith("<!doctype") ||
!htmxRequest
) {
2024-06-02 00:39:30 -04:00
const $ = cheerio.load(htmlString);
2024-02-24 11:12:14 -05:00
let head = $("head");
if (head.length === 0) {
$("html").prepend("<head></head>");
head = $("head");
}
2024-06-08 18:29:31 -04:00
head.attr("id", "head");
2024-06-08 15:30:28 -04:00
2024-02-24 11:12:14 -05:00
head.append(`
2024-06-02 00:39:30 -04:00
<script src="/js/hyperscript.js"></script>
<script src="/js/tailwind.js"></script>
<script src="/js/htmx.js"></script>
2024-06-08 18:29:31 -04:00
<script src="https://unpkg.com/htmx.org@1.9.12/dist/ext/ws.js"></script>
2024-06-02 00:39:30 -04:00
<script src="/js/bliss_inspector.js"></script>
2024-10-29 21:29:40 -04:00
<script>
tailwind.config = {
corePlugins: {
preflight: false
}
}
</script>
<style> body { margin: 0; }</style>
2024-06-08 15:30:28 -04:00
${headInjection || ""}
2024-02-24 11:12:14 -05:00
`);
2024-06-02 00:39:30 -04:00
if (blissRoute) {
2024-06-04 03:01:19 -04:00
$("body").attr("data-bliss-route", blissRoute);
$("body").attr("data-bliss-clone", blissClone);
if (blissCopy) $("body").attr("data-bliss-copy", blissCopy);
2024-06-02 00:39:30 -04:00
}
htmlString = $.html();
2024-06-08 15:30:28 -04:00
} else if (htmxRequest && blissRoute) {
2024-06-02 00:39:30 -04:00
const $ = cheerio.load(htmlString, null, false);
$.root().children().attr("data-bliss-route", blissRoute);
2024-06-04 03:01:19 -04:00
$.root().children().attr("data-bliss-clone", blissClone);
if (blissCopy) $.root().children().attr("data-bliss-copy", blissCopy);
2024-06-08 18:29:31 -04:00
htmlString = $.html();
2024-06-08 15:30:28 -04:00
if (htmxRequest) {
htmlString += `<div id="head" hx-oob-swap="before_end">${headInjection}</div>`;
}
2024-02-24 11:12:14 -05:00
}
2024-06-02 00:39:30 -04:00
return htmlString;
2024-02-24 11:12:14 -05:00
}
app.post("/workshop", (req, res) => {
2024-10-29 21:29:40 -04:00
let structId = model.createStructure(db, req.body.name);
2024-02-24 11:12:14 -05:00
return res.redirect("/workshop/" + structId);
});
app.get("/workshop", (req, res) => {
return res.send(
bootstrapTemplateWithHTMXetc(
eta.render("workshop/index", {
2024-10-29 21:29:40 -04:00
structures: model.getStructures(db),
2025-01-09 15:42:56 -05:00
}),
),
2024-02-24 11:12:14 -05:00
);
});
2024-06-02 00:39:30 -04:00
function smartRedirect(req, res, redirectUrl) {
if (req.headers["hx-request"]) {
res.set("HX-Redirect", redirectUrl);
res.send();
} else {
res.redirect(redirectUrl);
}
}
function sidebarStuff(db, structId) {
return {
2024-10-29 21:29:40 -04:00
structure: model.getStructure(db, structId),
routes: model.getRoutes(db, structId),
templates: model.getTemplates(db, structId),
dbs: model.getDbsForStructure(db, structId),
2024-06-02 00:39:30 -04:00
};
}
2024-02-24 11:12:14 -05:00
app.get("/workshop/:structure_id", (req, res) => {
return res.send(
bootstrapTemplateWithHTMXetc(
2025-01-09 15:42:56 -05:00
eta.render("workshop/editor", sidebarStuff(db, req.params.structure_id)),
),
2024-06-02 00:39:30 -04:00
);
});
2024-06-04 03:01:19 -04:00
app.post("/workshop/:structure_id/clone", (req, res) => {
let routePrefix = req.body.route_prefix;
routePrefix = routePrefix[0] == "/" ? routePrefix : "/" + routePrefix;
let newStructureId;
try {
2024-10-29 21:29:40 -04:00
newStructureId = model.cloneStructure(
2024-06-04 03:01:19 -04:00
req.params.structure_id,
req.body.name,
1,
// req.session.userId,
routePrefix,
2025-01-09 15:42:56 -05:00
req.body.clone_dbs,
2024-06-04 03:01:19 -04:00
);
buildRoutes();
} catch (e) {
return res.send(e.stack);
}
return smartRedirect(req, res, `/workshop/${newStructureId}`);
});
2024-06-02 00:39:30 -04:00
app.post("/workshop/:structure_id/db", (req, res) => {
2024-10-29 21:29:40 -04:00
const dbId = model.createDb(db, req.params.structure_id, req.body.name);
2024-06-02 00:39:30 -04:00
const redirectUrl = `/workshop/${req.params.structure_id}/db/${dbId}`;
return smartRedirect(req, res, redirectUrl);
});
app.post("/workshop/:structure_id/db/attach", (req, res) => {
2024-10-29 21:29:40 -04:00
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}`;
return smartRedirect(req, res, redirectUrl);
});
2024-06-02 00:39:30 -04:00
app.get("/workshop/:structure_id/db/:db_id", (req, res) => {
2024-10-29 21:29:40 -04:00
const structdb = model.getDbForStructure(
2024-06-02 00:39:30 -04:00
db,
req.params.structure_id,
2025-01-09 15:42:56 -05:00
req.params.db_id,
2024-06-02 00:39:30 -04:00
);
if (!structdb) {
return res.send("uh oh");
}
return res.send(
bootstrapTemplateWithHTMXetc(
eta.render("workshop/db_garden", {
db: structdb,
...sidebarStuff(db, req.params.structure_id),
2025-01-09 15:42:56 -05:00
}),
),
2024-06-02 00:39:30 -04:00
);
});
app.post("/workshop/:structure_id/route", (req, res) => {
2024-06-08 18:29:31 -04:00
let p = req.body.path;
let dummyHandler =
"// put your handler code here\n\nfunction handler(req, res) {\n res.send('henlo world') \n}";
2024-06-02 00:44:02 -04:00
2024-06-08 18:29:31 -04:00
if (req.body.verb == "WS") {
dummyHandler = `// put your websocket handler code here\n\nfunction handler(ws, req) {\n ws.on('message', function(msg) {\n ws.send(msg);\n })\n}`;
}
if (p[0] == "/") {
p = p.substring(1);
}
if (p[p.length - 1] == "/") {
p = p.substring(0, p.length - 1);
}
2024-10-29 21:29:40 -04:00
const route = model.createRoute(
2024-06-02 00:39:30 -04:00
db,
req.body.verb,
2024-06-08 18:29:31 -04:00
p[0] == "/" ? p.substring(1) : "/" + p,
2024-06-02 00:39:30 -04:00
req.params.structure_id,
2025-01-09 15:42:56 -05:00
dummyHandler,
2024-06-02 00:39:30 -04:00
);
2024-06-04 03:01:19 -04:00
// todo optimize by only adding new, don't just rebuild all
2024-06-02 00:39:30 -04:00
buildRoutes();
return smartRedirect(
req,
res,
2025-01-09 15:42:56 -05:00
`/workshop/${req.params.structure_id}/route/${route}`,
2024-06-02 00:39:30 -04:00
);
});
app.put("/workshop/:structure_id/route/:route_id", (req, res) => {
2024-10-29 21:29:40 -04:00
const route = model.getRoute(db, req.params.route_id);
model.updateRoute(db, { ...route, ...req.body });
2024-06-02 00:39:30 -04:00
return res.send("good");
});
app.put("/workshop/:structure_id/db/:db_id/library", (req, res) => {
try {
let dbId = req.params.db_id;
2024-10-29 21:29:40 -04:00
let appDb = model.getDb(db, dbId);
model.updateDb(db, { ...appDb, library: req.body.library });
appDb = model.getDb(db, dbId);
2024-06-02 00:39:30 -04:00
2024-10-29 21:29:40 -04:00
let dbInstance = model.getDbInstance(dbId);
2024-06-02 00:39:30 -04:00
let capturedOutput = [];
let context = vm.createContext({
module: { exports: null },
sql: dbInstance,
console: {
log: (...args) => capturedOutput.push(args.join(" ")),
},
});
let evaledCode = vm.runInContext(appDb.library, context);
2025-01-09 15:42:56 -05:00
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");
2024-06-02 00:39:30 -04:00
return res.send(`${stdout}\n\n> ${evaledCode}`.trim());
} catch (e) {
return res.send(`${e}\n\n${e.stack}`);
}
});
app.post("/workshop/:structure_id/db/:db_id/repl", (req, res) => {
try {
let dbId = req.params.db_id;
2024-10-29 21:29:40 -04:00
let appDb = model.getDb(db, dbId);
let dbInstance = model.getDbInstance(dbId);
2024-06-02 00:39:30 -04:00
let capturedOutput = [];
let context = vm.createContext({
module: { exports: null },
sql: dbInstance,
console: {
2025-01-09 15:42:56 -05:00
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(" "),
),
2024-06-02 00:39:30 -04:00
},
});
const libraryScript = new vm.Script(appDb.library);
libraryScript.runInContext(context);
context.library = context.module.exports;
const replScript = new vm.Script(req.body.code);
2024-10-29 21:29:40 -04:00
let evaledCode = JSON.stringify(replScript.runInContext(context));
2024-06-02 00:39:30 -04:00
let stdout = capturedOutput.join("\n");
return res.send(`${stdout}\n\n> ${evaledCode}`.trim());
} catch (e) {
return res.send(`${e}\n\n${e.stack}`);
}
});
2024-06-02 10:20:01 -04:00
app.post("/workshop/:structure_id/template", (req, res) => {
let name = req.body.name;
2024-10-29 21:29:40 -04:00
const template = model.createTemplate(
2024-06-02 10:20:01 -04:00
db,
req.params.structure_id,
name,
"<div>henlo <%= it.name %></div>",
2025-01-09 15:42:56 -05:00
"it = { name: 'templates!' };",
2024-06-02 10:20:01 -04:00
);
return smartRedirect(
req,
res,
2025-01-09 15:42:56 -05:00
`/workshop/${req.params.structure_id}/template/${template}`,
2024-06-02 10:20:01 -04:00
);
});
app.put("/workshop/:structure_id/template/:template_id", (req, res) => {
let id = req.params.template_id;
let content = req.body.content;
let test_object = req.body.test_object;
2025-01-09 15:42:56 -05:00
model.updateTemplate(db, {
...model.updateTemplate(db, id),
content,
test_object,
});
2024-06-02 10:20:01 -04:00
return res.send("good");
});
app.get("/workshop/:structure_id/template/:template_id", (req, res) => {
2024-10-29 21:29:40 -04:00
const template = model.getTemplate(db, req.params.template_id);
console.log("wassup", {
2025-01-09 15:42:56 -05:00
template: template,
...sidebarStuff(db, req.params.structure_id),
});
2024-06-02 10:20:01 -04:00
return res.send(
bootstrapTemplateWithHTMXetc(
eta.render("workshop/template", {
template: template,
...sidebarStuff(db, req.params.structure_id),
2025-01-09 15:42:56 -05:00
}),
),
2024-06-02 10:20:01 -04:00
);
});
app.get("/workshop/:structure_id/template/:template_id/preview", (req, res) => {
2024-10-29 21:29:40 -04:00
const template = model.getTemplate(db, req.params.template_id);
const struct = model.getStructure(db, req.params.structure_id);
const eta = model.getTemplater(req.params.structure_id);
2025-01-09 15:42:56 -05:00
console.log(template);
2024-06-02 10:20:01 -04:00
const context = vm.createContext({ it: null });
vm.runInContext(template.test_object, context);
2024-06-04 03:01:19 -04:00
context.it.route = function (url) {
return struct.route_prefix ? path.join(struct.route_prefix, url) : url;
};
2024-06-02 10:20:01 -04:00
return res.send(
bootstrapTemplateWithHTMXetc(
eta.render(template.name, context.it),
null,
2024-06-04 03:01:19 -04:00
null,
null,
2024-06-08 15:30:28 -04:00
struct.head_injection,
2025-01-09 15:42:56 -05:00
false,
),
2024-06-02 10:20:01 -04:00
);
});
2024-06-02 00:39:30 -04:00
app.get("/workshop/:structure_id/route/:id", (req, res) => {
2024-10-29 21:29:40 -04:00
const route = model.getRoute(db, req.params.id);
2025-01-09 15:42:56 -05:00
let routePrefix = model.getStructure(
db,
req.params.structure_id,
).route_prefix;
2024-06-08 11:42:27 -04:00
let previewUrl = routePrefix
? path.join(routePrefix || "", route.path)
: route.path;
2024-06-08 18:29:31 -04:00
2024-06-02 00:39:30 -04:00
return res.send(
bootstrapTemplateWithHTMXetc(
eta.render("workshop/route", {
route: route,
2024-06-04 03:01:19 -04:00
previewUrl:
2024-06-08 11:42:27 -04:00
route["verb"] == "GET" ? previewUrl : "/workshop/do-something-here",
2024-06-02 00:39:30 -04:00
...sidebarStuff(db, req.params.structure_id),
2025-01-09 15:42:56 -05:00
}),
),
2024-06-02 00:39:30 -04:00
);
});
2024-10-29 21:29:40 -04:00
app.get("/workshop/:structure_id/route/:route_id/logs", (req, res) => {
2025-01-09 15:42:56 -05:00
const since = req.query.since;
let logs = [];
2024-10-29 21:29:40 -04:00
if (since != undefined) {
2025-01-09 15:42:56 -05:00
logs = model.getNewLogsByRoute(db, req.params.route_id, since);
} else {
logs = model.getLogsByRoute(db, req.params.route_id);
2024-10-29 21:29:40 -04:00
}
2025-01-09 15:42:56 -05:00
const lastId = model.getMostRecentLogIdByRoute(db, req.params.route_id);
2024-10-29 21:29:40 -04:00
return res.send(
bootstrapTemplateWithHTMXetc(
eta.render("workshop/logs", {
logs: logs.reverse(),
structId: req.params.structure_id,
routeId: req.params.route_id,
since: lastId,
2025-01-09 15:42:56 -05:00
}),
),
2024-10-29 21:29:40 -04:00
);
});
2024-06-03 20:19:26 -04:00
// POST route to handle file upload
app.post("/workshop/:structure_id/files", (req, res) => {
if (!req.files || Object.keys(req.files).length === 0) {
return res.status(400).send("failed to upload that file");
}
const { structure_id } = req.params;
const file = req.files.file;
const name = file.name;
const [mime_type, mime_subtype] = file.mimetype.split("/");
const uploadPath = path.join(__dirname, "public", structure_id);
const storedPath = path.join(structure_id, file.name);
fs.mkdirSync(uploadPath, { recursive: true });
file.mv(path.join(uploadPath, name), (err) => {
try {
if (err) {
return res.status(500).send(err);
}
2024-10-29 21:29:40 -04:00
let id = model.createFile(
2024-06-03 20:19:26 -04:00
db,
structure_id,
name,
storedPath,
mime_type,
2025-01-09 15:42:56 -05:00
mime_subtype,
2024-06-03 20:19:26 -04:00
);
2024-10-29 21:29:40 -04:00
let file = model.getFile(db, id);
2024-06-03 20:19:26 -04:00
file.url = prefixUrlWithHost(req, file.path);
return res.send(eta.render("workshop/file_detail", { file: file }));
} catch (e) {
res.status(500);
return res.send(e);
}
});
});
function prefixUrlWithHost(req, path) {
return req.protocol + "://" + req.get("host") + "/" + path;
}
// POST route to handle file upload
app.get("/workshop/:structure_id/files", (req, res) => {
const { structure_id } = req.params;
2024-10-29 21:29:40 -04:00
const files = model.getFilesForStruct(db, structure_id);
2024-06-03 20:19:26 -04:00
files.map((it) => {
it.url = prefixUrlWithHost(req, it.path);
});
return res.send(
bootstrapTemplateWithHTMXetc(
eta.render("workshop/files", {
files,
...sidebarStuff(db, structure_id),
2025-01-09 15:42:56 -05:00
}),
),
2024-06-03 20:19:26 -04:00
);
2024-06-02 00:39:30 -04:00
});
2024-06-04 03:01:19 -04:00
app.get("/workshop/:structure_id/settings", (req, res) => {
return res.send(
bootstrapTemplateWithHTMXetc(
eta.render("workshop/settings", {
...sidebarStuff(db, req.params.structure_id),
2025-01-09 15:42:56 -05:00
}),
),
2024-06-04 03:01:19 -04:00
);
});
app.put("/workshop/:structure_id/settings", (req, res) => {
let structId = req.params.structure_id;
2024-10-29 21:29:40 -04:00
let struct = model.getStructure(db, structId);
2024-06-08 18:29:31 -04:00
let routePrefix = req.body.route_prefix;
if (routePrefix[0] != "/") {
routePrefix = "/" + routePrefix;
}
if (routePrefix[routePrefix.length - 1] == "/") {
routePrefix = routePrefix.substring(0, routePrefix.length - 1);
}
2024-10-29 21:29:40 -04:00
model.updateStruct(db, {
2024-06-08 15:30:28 -04:00
...struct,
2024-06-08 18:29:31 -04:00
route_prefix: routePrefix,
2024-06-08 15:30:28 -04:00
head_injection: req.body.head_injection,
});
2024-10-29 21:29:40 -04:00
struct = model.getStructure(db, structId);
2024-06-04 03:01:19 -04:00
res.send("success!");
});
2024-06-02 00:39:30 -04:00
app.get("/workshop/:structure_id/new_template_modal", (req, res) => {
return res.send(
bootstrapTemplateWithHTMXetc(
eta.render("workshop/new_template_modal", {
2024-10-29 21:29:40 -04:00
structure: model.getStructure(db, req.params.structure_id),
2025-01-09 15:42:56 -05:00
}),
),
2024-06-02 00:39:30 -04:00
);
});
app.get("/workshop/:structure_id/new_route_modal", (req, res) => {
return res.send(
bootstrapTemplateWithHTMXetc(
eta.render("workshop/new_route_modal", {
2024-10-29 21:29:40 -04:00
structure: model.getStructure(db, req.params.structure_id),
2025-01-09 15:42:56 -05:00
}),
),
2024-06-02 00:39:30 -04:00
);
});
app.get("/workshop/:structure_id/new_db_modal", (req, res) => {
return res.send(
bootstrapTemplateWithHTMXetc(
eta.render("workshop/new_db_modal", {
2024-10-29 21:29:40 -04:00
structure: model.getStructure(db, req.params.structure_id),
2025-01-09 15:42:56 -05:00
}),
),
2024-02-24 11:12:14 -05:00
);
});
2024-06-04 03:01:19 -04:00
app.get("/workshop/:structure_id/clone_modal", (req, res) => {
2024-10-29 21:29:40 -04:00
const structure = model.getStructure(db, req.params.structure_id);
const dbs = model.getDbsForStructure(db, req.params.structure_id);
2024-06-08 11:42:27 -04:00
2024-06-04 03:01:19 -04:00
return res.send(
bootstrapTemplateWithHTMXetc(
eta.render("workshop/clone_structure_modal", {
structure,
2024-06-08 11:42:27 -04:00
dbs,
2024-06-04 03:01:19 -04:00
defaultRoutePrefix: path.join(structure.route_prefix || "", "clone"),
2025-01-09 15:42:56 -05:00
}),
),
2024-06-04 03:01:19 -04:00
);
});
function embedHTML(url) {
return `<div hx-get="${url}" hx-trigger="load"></div>`;
}
2024-02-24 11:12:14 -05:00
app.all("*", (req, res) => {
2024-06-04 03:01:19 -04:00
const req_path = req.path;
2024-02-24 11:12:14 -05:00
const verb = req.method;
try {
2024-06-02 00:39:30 -04:00
let routeMatch = null;
2024-02-24 11:12:14 -05:00
2024-06-02 00:39:30 -04:00
for (let { matcher, id } of routes[verb]) {
2024-06-04 03:01:19 -04:00
let matchFromRoute = matcher(req_path);
2024-06-02 00:39:30 -04:00
if (matchFromRoute) {
routeMatch = { params: matchFromRoute.params, id: id };
}
}
if (routeMatch) {
req.params = routeMatch.params;
const route = db
.prepare("SELECT * FROM routes WHERE id = ?")
.get(routeMatch.id);
2024-10-29 21:29:40 -04:00
const structure = model.getStructure(db, route.structure_id);
2024-06-08 18:29:31 -04:00
const __urlPrefix = structure.route_prefix;
2024-06-02 00:39:30 -04:00
try {
2024-06-08 18:29:31 -04:00
// todo: only add req res to contexst when running the handler()
// which means moving to runscript instead of runincontext for that
2024-10-29 21:29:40 -04:00
let context = bootstrapContext(db, route.structure_id, route.id, {
2024-06-02 00:39:30 -04:00
req,
res,
2024-06-08 15:30:28 -04:00
eta,
2024-06-02 00:39:30 -04:00
});
2024-06-02 10:20:01 -04:00
res.render = (template, context) => {
2024-06-08 11:42:27 -04:00
context = context || {};
2024-06-04 03:01:19 -04:00
context.route = function (url) {
return __urlPrefix ? path.join(__urlPrefix, url) : url;
};
2024-10-29 21:29:40 -04:00
const eta = model.getTemplater(route.structure_id);
2024-06-08 15:30:28 -04:00
res.send(
2024-06-04 03:01:19 -04:00
bootstrapTemplateWithHTMXetc(
eta.render(template, context),
`/workshop/${route.structure_id}/route/${route.id}`,
`/workshop/${route.structure_id}/clone_modal/`,
2024-06-08 15:30:28 -04:00
verb == "GET" ? embedHTML(req.originalUrl) : null,
structure.head_injection,
2025-01-09 15:42:56 -05:00
req.headers["hx-request"],
),
2024-06-04 03:01:19 -04:00
);
2024-06-02 10:20:01 -04:00
};
2024-06-02 00:39:30 -04:00
return vm.runInContext(
(route.handler += `\n\nhandler(req, res)`),
2025-01-09 15:42:56 -05:00
context,
2024-06-02 00:39:30 -04:00
);
} catch (e) {
2024-10-29 21:29:40 -04:00
const error = e.stack ? `${e}\n\n${e.stack}` : `${e}`;
model.createLog(db, structure.id, route.id, error, true);
return res.send(error);
2024-06-02 00:39:30 -04:00
}
2024-02-24 11:12:14 -05:00
} else {
res.status(404).json({ success: false, message: "Path not found" });
2024-02-03 13:22:50 -05:00
}
2024-02-24 11:12:14 -05:00
} catch (error) {
console.error(error);
res.status(500).json({ success: false, message: "Internal server error" });
}
2024-02-03 13:22:50 -05:00
});
app.listen(PORT, () => {
2024-02-24 11:12:14 -05:00
console.log(`Server is running on http://localhost:${PORT}`);
2024-02-03 13:22:50 -05:00
});