websocket support and route fixes
This commit is contained in:
parent
897dd38151
commit
c40bdcbbed
6 changed files with 218 additions and 84 deletions
227
index.js
227
index.js
|
|
@ -8,9 +8,11 @@ const SQLiteStore = require("better-sqlite3-session-store")(session);
|
||||||
const betterSqlite3 = require("better-sqlite3");
|
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 app = express();
|
const app = express();
|
||||||
|
const _expressWs = require("express-ws")(app);
|
||||||
const bodyParser = require("body-parser");
|
const bodyParser = require("body-parser");
|
||||||
const PORT = 3000;
|
const PORT = 3000;
|
||||||
|
|
||||||
|
|
@ -27,6 +29,39 @@ const db = betterSqlite3("./dbs/0.sqlite");
|
||||||
|
|
||||||
db.pragma("journal_mode = WAL");
|
db.pragma("journal_mode = WAL");
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
function getAllRoutes(db) {
|
||||||
return db
|
return db
|
||||||
.prepare(
|
.prepare(
|
||||||
|
|
@ -78,6 +113,61 @@ function applyMigrations() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function bootstrapContext(db, structureId, initContext) {
|
||||||
|
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
|
||||||
|
const eta = getTemplater(structureId);
|
||||||
|
|
||||||
|
const libs = { eta, db: getDb };
|
||||||
|
|
||||||
|
let dbs = getDbsForStructure(db, structureId);
|
||||||
|
let context = vm.createContext({
|
||||||
|
...initContext,
|
||||||
|
require: function (str) {
|
||||||
|
return libs[str];
|
||||||
|
},
|
||||||
|
module: { exports: null },
|
||||||
|
});
|
||||||
|
for (let appDb of dbs) {
|
||||||
|
let dbInstance = getDbInstance(appDb.id);
|
||||||
|
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 {
|
||||||
|
const context = bootstrapContext(db, route.structure_id, { app });
|
||||||
|
let result = vm.runInContext(
|
||||||
|
route.handler + `\n\napp.ws("${routeWithPrefix(route)}", handler)`,
|
||||||
|
context
|
||||||
|
);
|
||||||
|
updateRoute(db, { ...route, error: null });
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
updateRoute(db, { ...route, error: e.stack });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function buildRoutes() {
|
function buildRoutes() {
|
||||||
let newRoutes = {
|
let newRoutes = {
|
||||||
GET: [],
|
GET: [],
|
||||||
|
|
@ -87,14 +177,17 @@ function buildRoutes() {
|
||||||
};
|
};
|
||||||
|
|
||||||
for (let route of getAllRoutes(db)) {
|
for (let route of getAllRoutes(db)) {
|
||||||
const prefix = route.route_prefix;
|
const p = routeWithPrefix(route);
|
||||||
const p = prefix ? path.join(route.route_prefix, route.path) : route.path;
|
if (route.verb == "WS") {
|
||||||
|
bootstrapWebsocketHandler(route);
|
||||||
|
} else {
|
||||||
newRoutes[route.verb].push({
|
newRoutes[route.verb].push({
|
||||||
matcher: match(p, { decode: decodeURIComponent }),
|
matcher: match(p, { decode: decodeURIComponent }),
|
||||||
id: route.id,
|
id: route.id,
|
||||||
path: route.path,
|
path: route.path,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
routes = newRoutes;
|
routes = newRoutes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,6 +285,7 @@ function createStructure(db, name, userId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function createRoute(db, verb, path, structureId, handler) {
|
function createRoute(db, verb, path, structureId, handler) {
|
||||||
|
path = encodeURI(path)
|
||||||
// add default handler here
|
// add default handler here
|
||||||
const stmt = db.prepare(
|
const stmt = db.prepare(
|
||||||
"INSERT INTO routes (verb, path, structure_id, handler) VALUES (?, ?, ?, ?)"
|
"INSERT INTO routes (verb, path, structure_id, handler) VALUES (?, ?, ?, ?)"
|
||||||
|
|
@ -211,7 +305,14 @@ function getRoute(db, routeId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateRoute(db, route) {
|
function updateRoute(db, route) {
|
||||||
const fields = ["verb", "path", "structure_id", "handler", "updated_at"];
|
const fields = [
|
||||||
|
"verb",
|
||||||
|
"path",
|
||||||
|
"structure_id",
|
||||||
|
"handler",
|
||||||
|
"updated_at",
|
||||||
|
"error",
|
||||||
|
];
|
||||||
const values = fields.map((field) => route[field]);
|
const values = fields.map((field) => route[field]);
|
||||||
const placeholders = fields.map((field) => `${field} = ?`).join(", ");
|
const placeholders = fields.map((field) => `${field} = ?`).join(", ");
|
||||||
|
|
||||||
|
|
@ -232,7 +333,6 @@ function updateDb(db, appDb) {
|
||||||
|
|
||||||
function updateStruct(db, struct) {
|
function updateStruct(db, struct) {
|
||||||
const fields = ["name", "route_prefix", "head_injection"];
|
const fields = ["name", "route_prefix", "head_injection"];
|
||||||
console.log(struct, fields);
|
|
||||||
const values = fields.map((field) => struct[field]);
|
const values = fields.map((field) => struct[field]);
|
||||||
const placeholders = fields.map((field) => `${field} = ?`).join(", ");
|
const placeholders = fields.map((field) => `${field} = ?`).join(", ");
|
||||||
|
|
||||||
|
|
@ -358,8 +458,6 @@ function getFilesForStruct(db, structureId) {
|
||||||
.prepare("SELECT * FROM files WHERE structure_id = ? ORDER BY id DESC")
|
.prepare("SELECT * FROM files WHERE structure_id = ? ORDER BY id DESC")
|
||||||
.all(structureId);
|
.all(structureId);
|
||||||
|
|
||||||
console.log(db.prepare("PRAGMA table_info(files)").all());
|
|
||||||
|
|
||||||
return test;
|
return test;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -375,39 +473,6 @@ function createFile(db, structure_id, name, filePath, mime_type, mime_subtype) {
|
||||||
.run(structure_id, name, filePath, mime_type, mime_subtype).lastInsertRowid;
|
.run(structure_id, name, filePath, mime_type, mime_subtype).lastInsertRowid;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { LRUCache } = require("lru-cache");
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 cloneStructure(
|
function cloneStructure(
|
||||||
structId,
|
structId,
|
||||||
|
|
@ -436,8 +501,6 @@ function cloneStructure(
|
||||||
const toClone = new Set(cloneDbs);
|
const toClone = new Set(cloneDbs);
|
||||||
const toAlias = new Set();
|
const toAlias = new Set();
|
||||||
|
|
||||||
console.log(toClone, dbIds);
|
|
||||||
|
|
||||||
for (let { db_id } of dbIds) {
|
for (let { db_id } of dbIds) {
|
||||||
if (!toClone.has(db_id.toString())) {
|
if (!toClone.has(db_id.toString())) {
|
||||||
toAlias.add(db_id.toString());
|
toAlias.add(db_id.toString());
|
||||||
|
|
@ -504,10 +567,13 @@ function bootstrapTemplateWithHTMXetc(
|
||||||
blissClone,
|
blissClone,
|
||||||
blissCopy,
|
blissCopy,
|
||||||
headInjection,
|
headInjection,
|
||||||
htmxRequest=false
|
htmxRequest = false
|
||||||
) {
|
) {
|
||||||
if (htmlString.toLowerCase().startsWith("<html>") || htmlString.toLowerCase().startsWith("<!doctype") || !htmxRequest) {
|
if (
|
||||||
console.log("grow")
|
htmlString.toLowerCase().startsWith("<html>") ||
|
||||||
|
htmlString.toLowerCase().startsWith("<!doctype") ||
|
||||||
|
!htmxRequest
|
||||||
|
) {
|
||||||
const $ = cheerio.load(htmlString);
|
const $ = cheerio.load(htmlString);
|
||||||
let head = $("head");
|
let head = $("head");
|
||||||
|
|
||||||
|
|
@ -516,12 +582,13 @@ function bootstrapTemplateWithHTMXetc(
|
||||||
head = $("head");
|
head = $("head");
|
||||||
}
|
}
|
||||||
|
|
||||||
head.attr("id", "head")
|
head.attr("id", "head");
|
||||||
|
|
||||||
head.append(`
|
head.append(`
|
||||||
<script src="/js/hyperscript.js"></script>
|
<script src="/js/hyperscript.js"></script>
|
||||||
<script src="/js/tailwind.js"></script>
|
<script src="/js/tailwind.js"></script>
|
||||||
<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="/js/bliss_inspector.js"></script>
|
<script src="/js/bliss_inspector.js"></script>
|
||||||
${headInjection || ""}
|
${headInjection || ""}
|
||||||
`);
|
`);
|
||||||
|
|
@ -533,13 +600,12 @@ function bootstrapTemplateWithHTMXetc(
|
||||||
}
|
}
|
||||||
|
|
||||||
htmlString = $.html();
|
htmlString = $.html();
|
||||||
console.log(htmlString)
|
|
||||||
} else if (htmxRequest && blissRoute) {
|
} else if (htmxRequest && blissRoute) {
|
||||||
const $ = cheerio.load(htmlString, null, false);
|
const $ = cheerio.load(htmlString, null, false);
|
||||||
$.root().children().attr("data-bliss-route", blissRoute);
|
$.root().children().attr("data-bliss-route", blissRoute);
|
||||||
$.root().children().attr("data-bliss-clone", blissClone);
|
$.root().children().attr("data-bliss-clone", blissClone);
|
||||||
if (blissCopy) $.root().children().attr("data-bliss-copy", blissCopy);
|
if (blissCopy) $.root().children().attr("data-bliss-copy", blissCopy);
|
||||||
htmlString = $.html()
|
htmlString = $.html();
|
||||||
|
|
||||||
if (htmxRequest) {
|
if (htmxRequest) {
|
||||||
htmlString += `<div id="head" hx-oob-swap="before_end">${headInjection}</div>`;
|
htmlString += `<div id="head" hx-oob-swap="before_end">${headInjection}</div>`;
|
||||||
|
|
@ -642,14 +708,27 @@ app.get("/workshop/:structure_id/db/:db_id", (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/workshop/:structure_id/route", (req, res) => {
|
app.post("/workshop/:structure_id/route", (req, res) => {
|
||||||
let path = req.body.path;
|
let p = req.body.path;
|
||||||
|
|
||||||
|
let dummyHandler =
|
||||||
|
"// put your handler code here\n\nfunction handler(req, res) {\n res.send('henlo world') \n}";
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
const route = createRoute(
|
const route = createRoute(
|
||||||
db,
|
db,
|
||||||
req.body.verb,
|
req.body.verb,
|
||||||
path[0] == "/" ? path : "/" + path,
|
p[0] == "/" ? p.substring(1) : "/" + p,
|
||||||
req.params.structure_id,
|
req.params.structure_id,
|
||||||
"// put your handler code here\n\nfunction handler(req, res) {\n res.send('henlo world') \n}"
|
dummyHandler
|
||||||
);
|
);
|
||||||
// todo optimize by only adding new, don't just rebuild all
|
// todo optimize by only adding new, don't just rebuild all
|
||||||
buildRoutes();
|
buildRoutes();
|
||||||
|
|
@ -782,10 +861,18 @@ 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 = getRoute(db, req.params.id);
|
||||||
const routePrefix = getStructure(db, req.params.structure_id).route_prefix;
|
let routePrefix = 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", {
|
||||||
|
|
@ -873,12 +960,20 @@ app.get("/workshop/:structure_id/settings", (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
app.put("/workshop/:structure_id/settings", (req, res) => {
|
app.put("/workshop/:structure_id/settings", (req, res) => {
|
||||||
console.log(req.body)
|
|
||||||
let structId = req.params.structure_id;
|
let structId = req.params.structure_id;
|
||||||
let struct = getStructure(db, structId);
|
let struct = getStructure(db, structId);
|
||||||
|
let routePrefix = req.body.route_prefix;
|
||||||
|
|
||||||
|
if (routePrefix[0] != "/") {
|
||||||
|
routePrefix = "/" + routePrefix;
|
||||||
|
}
|
||||||
|
if (routePrefix[routePrefix.length - 1] == "/") {
|
||||||
|
routePrefix = routePrefix.substring(0, routePrefix.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
updateStruct(db, {
|
updateStruct(db, {
|
||||||
...struct,
|
...struct,
|
||||||
route_prefix: req.body.route_prefix,
|
route_prefix: routePrefix,
|
||||||
head_injection: req.body.head_injection,
|
head_injection: req.body.head_injection,
|
||||||
});
|
});
|
||||||
struct = getStructure(db, structId);
|
struct = getStructure(db, structId);
|
||||||
|
|
@ -919,7 +1014,6 @@ app.get("/workshop/:structure_id/new_db_modal", (req, res) => {
|
||||||
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 = getStructure(db, req.params.structure_id);
|
||||||
const dbs = getDbsForStructure(db, req.params.structure_id);
|
const dbs = getDbsForStructure(db, req.params.structure_id);
|
||||||
console.log(dbs);
|
|
||||||
|
|
||||||
return res.send(
|
return res.send(
|
||||||
bootstrapTemplateWithHTMXetc(
|
bootstrapTemplateWithHTMXetc(
|
||||||
|
|
@ -956,35 +1050,18 @@ app.all("*", (req, res) => {
|
||||||
.prepare("SELECT * FROM routes WHERE id = ?")
|
.prepare("SELECT * FROM routes WHERE id = ?")
|
||||||
.get(routeMatch.id);
|
.get(routeMatch.id);
|
||||||
|
|
||||||
let dbs = getDbsForStructure(db, route.structure_id);
|
|
||||||
const structure = getStructure(db, route.structure_id);
|
const structure = getStructure(db, route.structure_id);
|
||||||
const __urlPrefix = structure.route_prefix
|
const __urlPrefix = structure.route_prefix;
|
||||||
console.log(structure)
|
|
||||||
|
|
||||||
const allDbInstances = {};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let context = vm.createContext({
|
// todo: only add req res to contexst when running the handler()
|
||||||
|
// which means moving to runscript instead of runincontext for that
|
||||||
|
let context = bootstrapContext(db, route.structure_id, {
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
eta,
|
eta,
|
||||||
getDb: function (alias) {
|
|
||||||
return allDbInstances[alias];
|
|
||||||
},
|
|
||||||
module: { exports: null },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
for (let appDb of dbs) {
|
|
||||||
let dbInstance = getDbInstance(appDb.id);
|
|
||||||
context.sql = dbInstance;
|
|
||||||
vm.runInContext(appDb.library, context);
|
|
||||||
allDbInstances[appDb.alias] = {
|
|
||||||
library: context.module.exports,
|
|
||||||
sql: dbInstance,
|
|
||||||
};
|
|
||||||
context.module.exports = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.render = (template, context) => {
|
res.render = (template, context) => {
|
||||||
context = context || {};
|
context = context || {};
|
||||||
context.route = function (url) {
|
context.route = function (url) {
|
||||||
|
|
|
||||||
|
|
@ -46,10 +46,11 @@ CREATE TABLE templates (
|
||||||
|
|
||||||
CREATE TABLE routes (
|
CREATE TABLE routes (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
verb TEXT CHECK(verb IN ('POST', 'GET', 'PUT', 'DELETE')) NOT NULL,
|
verb TEXT CHECK(verb IN ('POST', 'GET', 'PUT', 'DELETE', 'WS')) NOT NULL,
|
||||||
path TEXT NOT NULL,
|
path TEXT NOT NULL,
|
||||||
structure_id INTEGER,
|
structure_id INTEGER,
|
||||||
handler TEXT NOT NULL,
|
handler TEXT NOT NULL,
|
||||||
|
error TEXT,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
FOREIGN KEY(structure_id) REFERENCES structures(id),
|
FOREIGN KEY(structure_id) REFERENCES structures(id),
|
||||||
|
|
|
||||||
51
package-lock.json
generated
51
package-lock.json
generated
|
|
@ -20,6 +20,7 @@
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-fileupload": "^1.5.0",
|
"express-fileupload": "^1.5.0",
|
||||||
"express-session": "^1.18.0",
|
"express-session": "^1.18.0",
|
||||||
|
"express-ws": "^5.0.2",
|
||||||
"jsdom": "^24.1.0",
|
"jsdom": "^24.1.0",
|
||||||
"lru-cache": "^10.2.0",
|
"lru-cache": "^10.2.0",
|
||||||
"nunjucks": "^3.2.4",
|
"nunjucks": "^3.2.4",
|
||||||
|
|
@ -883,6 +884,40 @@
|
||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
|
||||||
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="
|
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/express-ws": {
|
||||||
|
"version": "5.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/express-ws/-/express-ws-5.0.2.tgz",
|
||||||
|
"integrity": "sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"ws": "^7.4.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.5.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"express": "^4.0.0 || ^5.0.0-alpha.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/express-ws/node_modules/ws": {
|
||||||
|
"version": "7.5.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
|
||||||
|
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": "^5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/express/node_modules/body-parser": {
|
"node_modules/express/node_modules/body-parser": {
|
||||||
"version": "1.20.1",
|
"version": "1.20.1",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
|
||||||
|
|
@ -3210,6 +3245,22 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"express-ws": {
|
||||||
|
"version": "5.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/express-ws/-/express-ws-5.0.2.tgz",
|
||||||
|
"integrity": "sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ==",
|
||||||
|
"requires": {
|
||||||
|
"ws": "^7.4.6"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ws": {
|
||||||
|
"version": "7.5.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
|
||||||
|
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
|
||||||
|
"requires": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"file-uri-to-path": {
|
"file-uri-to-path": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-fileupload": "^1.5.0",
|
"express-fileupload": "^1.5.0",
|
||||||
"express-session": "^1.18.0",
|
"express-session": "^1.18.0",
|
||||||
|
"express-ws": "^5.0.2",
|
||||||
"jsdom": "^24.1.0",
|
"jsdom": "^24.1.0",
|
||||||
"lru-cache": "^10.2.0",
|
"lru-cache": "^10.2.0",
|
||||||
"nunjucks": "^3.2.4",
|
"nunjucks": "^3.2.4",
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
<form hx-post="/workshop/<%= it.structure.id %>/route" hx-target="#response">
|
<form hx-post="/workshop/<%= it.structure.id %>/route" hx-target="#response">
|
||||||
<div class="field-row-stacked" style="width: 300px">
|
<div class="field-row-stacked" style="width: 300px">
|
||||||
<label for="path">Path (e.g. /foo/bar)</label>
|
<label for="path">Path (e.g. /foo/bar)</label>
|
||||||
<div class="flex items-center"><%= it.structure.route_prefix %>/<input name="path" id="name" type="text" class="flex-grow" /></div>
|
<div class="flex items-center"><%= it.structure.route_prefix || "" %>/<input name="path" id="name" type="text" class="flex-grow" /></div>
|
||||||
</div>
|
</div>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>verb</legend>
|
<legend>verb</legend>
|
||||||
|
|
@ -30,6 +30,10 @@
|
||||||
<input id="delete" type="radio" name="verb" value="DELETE">
|
<input id="delete" type="radio" name="verb" value="DELETE">
|
||||||
<label for="delete">DELETE</label>
|
<label for="delete">DELETE</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field-row">
|
||||||
|
<input id="ws" type="radio" name="verb" value="WS">
|
||||||
|
<label for="ws">WS</label>
|
||||||
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<section class="field-row" style="justify-content: flex-end">
|
<section class="field-row" style="justify-content: flex-end">
|
||||||
<button type="submit">OK</button>
|
<button type="submit">OK</button>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a href="/workshop/<%= it.structure.id %>/route/<%= route.id %>">
|
<a href="/workshop/<%= it.structure.id %>/route/<%= route.id %>">
|
||||||
<%= route.verb %> -
|
<%= route.verb %> -
|
||||||
<% if (it.structure.route_prefix) { %><span class="text-gray-500"><%= it.structure.route_prefix %></span><% } %><span class="bold"><%= route.path %></span>
|
<% if (it.structure.route_prefix) { %><span class="text-gray-500"><%= it.structure.route_prefix %></span><% } %><span class="bold"><%= route.path %></span><% if (route.error) { %>⚠️<% } %>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<% }) %>
|
<% }) %>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue