hmm
This commit is contained in:
parent
f370a2fe68
commit
e335c65506
21 changed files with 33471 additions and 86 deletions
248
index.js
248
index.js
|
|
@ -1,22 +1,34 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const express = require('express');
|
||||
const session = require('express-session');
|
||||
const SQLiteStore = require('better-sqlite3-session-store')(session);
|
||||
const betterSqlite3 = require('better-sqlite3');
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const express = require("express");
|
||||
const session = require("express-session");
|
||||
const SQLiteStore = require("better-sqlite3-session-store")(session);
|
||||
const betterSqlite3 = require("better-sqlite3");
|
||||
const cheerio = require("cheerio");
|
||||
const { Eta } = require("eta");
|
||||
const bcrypt = require("bcrypt");
|
||||
const app = express();
|
||||
const bodyParser = require("body-parser");
|
||||
const PORT = 3000;
|
||||
|
||||
const db = betterSqlite3('prime.db');
|
||||
let viewpath = path.join(__dirname, "views");
|
||||
let eta = new Eta({ views: viewpath, cache: false, autoEscape: false });
|
||||
|
||||
app.use(session({
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(express.static("public"));
|
||||
|
||||
const db = betterSqlite3("prime.db");
|
||||
db.pragma("journal_mode = WAL");
|
||||
|
||||
app.use(
|
||||
session({
|
||||
store: new SQLiteStore({ client: db, expired: { clear: true } }),
|
||||
secret: 'your secret key',
|
||||
secret: "your secret key",
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
cookie: { secure: false }
|
||||
}));
|
||||
|
||||
cookie: { secure: false },
|
||||
})
|
||||
);
|
||||
|
||||
function applyMigrations() {
|
||||
db.exec(`
|
||||
|
|
@ -27,18 +39,20 @@ function applyMigrations() {
|
|||
);
|
||||
`);
|
||||
|
||||
const migrationsDir = path.join(__dirname, "/migrations");
|
||||
const migrationFiles = fs
|
||||
.readdirSync(migrationsDir)
|
||||
.filter((file) => file.endsWith(".sql"));
|
||||
|
||||
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);
|
||||
|
||||
migrationFiles.forEach(file => {
|
||||
const isApplied = db.prepare('SELECT filename FROM migrations WHERE filename = ?').get(file);
|
||||
|
||||
console.log(!isApplied, file)
|
||||
if (!isApplied) {
|
||||
const sql = fs.readFileSync(path.join(migrationsDir, file), 'utf-8');
|
||||
const sql = fs.readFileSync(path.join(migrationsDir, file), "utf-8");
|
||||
db.exec(sql);
|
||||
db.prepare('INSERT INTO migrations (filename) VALUES (?)').run(file);
|
||||
db.prepare("INSERT INTO migrations (filename) VALUES (?)").run(file);
|
||||
console.log(`Migration applied: ${file}`);
|
||||
}
|
||||
});
|
||||
|
|
@ -46,21 +60,203 @@ function applyMigrations() {
|
|||
|
||||
applyMigrations();
|
||||
|
||||
app.get('*', (req, res) => {
|
||||
// __ __ _______ _______ ______
|
||||
// | | | || || || _ |
|
||||
// | | | || _____|| ___|| | ||
|
||||
// | |_| || |_____ | |___ | |_||_
|
||||
// | ||_____ || ___|| __ |
|
||||
// | | _____| || |___ | | | |
|
||||
// |_______||_______||_______||___| |_|
|
||||
|
||||
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) => {
|
||||
try {
|
||||
const { username, password } = req.body;
|
||||
const hashedPassword = await bcrypt.hash(password, 10); // 10 is the saltRounds
|
||||
createUser(db, username, hashedPassword);
|
||||
res.redirect("/workshop");
|
||||
} catch (e) {
|
||||
res.render("auth/register.html", { error: e });
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/login", async (req, res) => {
|
||||
const { username, password } = req.body;
|
||||
const user = getUser(db, username);
|
||||
|
||||
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?",
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
app.get("/register", async (req, res) => {
|
||||
if (req.session.userId) {
|
||||
return res.redirect("/");
|
||||
}
|
||||
return res.render("auth/register", { error: null });
|
||||
});
|
||||
|
||||
app.get("/login", async (req, res) => {
|
||||
console.log(req.session.userId);
|
||||
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("/");
|
||||
});
|
||||
});
|
||||
|
||||
// _ _ _______ ______ ___ _ _______ __ __ _______ _______
|
||||
// | | _ | || || _ | | | | || || | | || || |
|
||||
// | || || || _ || | || | |_| || _____|| |_| || _ || _ |
|
||||
// | || | | || |_||_ | _|| |_____ | || | | || |_| |
|
||||
// | || |_| || __ || |_ |_____ || || |_| || ___|
|
||||
// | _ || || | | || _ | _____| || _ || || |
|
||||
// |__| |__||_______||___| |_||___| |_||_______||__| |__||_______||___|
|
||||
|
||||
function getStructures(db) {
|
||||
return db.prepare("SELECT * from structures").all();
|
||||
}
|
||||
|
||||
function getStructure(db, id) {
|
||||
return db.prepare("SELECT * from structures where ID = ?").run(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, userId, handler) {
|
||||
const stmt = db.prepare(
|
||||
"INSERT INTO routes (verb, path, structure_id, user_id, handler) VALUES (?, ?, ?, ?, ?)"
|
||||
);
|
||||
const info = stmt.run(verb, path, structureId, userId, handler);
|
||||
return info.lastInsertRowid; // Returns the route_id of the newly created route
|
||||
}
|
||||
|
||||
function getTemplateString(db, templateAlias, structId) {
|
||||
const stmt = db.prepare(`
|
||||
SELECT t.path
|
||||
FROM templates AS t
|
||||
JOIN structure_templates AS st ON t.id = st.template_id
|
||||
WHERE st.structure_id = ? AND st.alias = ?
|
||||
`);
|
||||
|
||||
const path = stmt.get(structId, templateAlias);
|
||||
return fs.readFileSync(path).toString();
|
||||
}
|
||||
|
||||
const { LRUCache } = require("lru-cache");
|
||||
// we'll figure out max size later
|
||||
const cache = new LRUCache({ max: 10000 });
|
||||
|
||||
function getTemplater(structId) {
|
||||
let etaInstance = cache.get(structId);
|
||||
|
||||
if (!etaInstance) {
|
||||
etaInstance = new Eta({});
|
||||
etaInstance.resolvePath = function (path, _) {
|
||||
return path;
|
||||
};
|
||||
etaInstance.readFile = function (templateAlias) {
|
||||
return getTemplateString(db, templateAlias, structId);
|
||||
};
|
||||
cache.set(structId, etaInstance);
|
||||
}
|
||||
|
||||
return etaInstance;
|
||||
}
|
||||
|
||||
function bootstrapTemplateWithHTMXetc(htmlString) {
|
||||
const $ = cheerio.load(htmlString);
|
||||
|
||||
if ($("html").length > 0) {
|
||||
console.log("waddup");
|
||||
let head = $("head");
|
||||
|
||||
// If <head> does not exist, prepend it to <html>
|
||||
if (head.length === 0) {
|
||||
$("html").prepend("<head></head>");
|
||||
head = $("head");
|
||||
}
|
||||
|
||||
head.append(`
|
||||
<script src="https://unpkg.com/htmx.org"></script>
|
||||
<script src="https://unpkg.com/hyperscript.org"></script>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
`);
|
||||
}
|
||||
|
||||
return $.html();
|
||||
}
|
||||
|
||||
app.post("/workshop", (req, res) => {
|
||||
let structId = createStructure(db, req.body.name);
|
||||
return res.redirect("/workshop/" + structId);
|
||||
});
|
||||
|
||||
app.get("/workshop", (req, res) => {
|
||||
return res.send(
|
||||
bootstrapTemplateWithHTMXetc(
|
||||
eta.render("workshop/index", {
|
||||
structures: getStructures(db),
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
app.get("/workshop/:structure_id", (req, res) => {
|
||||
return res.send(
|
||||
bootstrapTemplateWithHTMXetc(
|
||||
eta.render("workshop/editor", {
|
||||
structure: getStructure(db, req.params.structure_id),
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
app.all("*", (req, res) => {
|
||||
const path = req.path;
|
||||
const verb = req.method;
|
||||
|
||||
try {
|
||||
const stmt = db.prepare('SELECT * FROM routes WHERE path = ?');
|
||||
const route = stmt.get(path);
|
||||
const stmt = db.prepare("SELECT * FROM routes WHERE path = ? AND verb = ?");
|
||||
const route = stmt.get(path, verb);
|
||||
|
||||
if (route) {
|
||||
res.json({ success: true, message: 'Path found', data: route });
|
||||
return res.send(
|
||||
eval(route.handler)({ ...this, ...handlerContext(req, res) })
|
||||
);
|
||||
} else {
|
||||
res.status(404).json({ success: false, message: 'Path not found' });
|
||||
res.status(404).json({ success: false, message: "Path not found" });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ success: false, message: 'Internal server error' });
|
||||
res.status(500).json({ success: false, message: "Internal server error" });
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,51 +1,63 @@
|
|||
CREATE TABLE users (
|
||||
user_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
UNIQUE(username)
|
||||
);
|
||||
|
||||
CREATE TABLE structures (
|
||||
structure_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
user_id INTEGER,
|
||||
FOREIGN KEY(user_id) REFERENCES users(user_id)
|
||||
FOREIGN KEY(user_id) REFERENCES users(id),
|
||||
UNIQUE(name, user_id)
|
||||
);
|
||||
|
||||
CREATE TABLE dbs (
|
||||
db_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
belongs_to_struct INTEGER,
|
||||
FOREIGN KEY(belongs_to_struct) REFERENCES structures(structure_id)
|
||||
FOREIGN KEY(belongs_to_struct) REFERENCES structures(id),
|
||||
UNIQUE(name, belongs_to_struct)
|
||||
);
|
||||
|
||||
CREATE TABLE templates (
|
||||
template_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
content TEXT NOT NULL,
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
file_path TEXT NOT NULL,
|
||||
user_id INTEGER,
|
||||
belongs_to_struct INTEGER,
|
||||
FOREIGN KEY(user_id) REFERENCES users(user_id),
|
||||
FOREIGN KEY(belongs_to_struct) REFERENCES structures(structure_id)
|
||||
engine TEXT NOT NULL,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id),
|
||||
FOREIGN KEY(belongs_to_struct) REFERENCES structures(id),
|
||||
UNIQUE(name, belongs_to_struct)
|
||||
);
|
||||
|
||||
CREATE TABLE routes (
|
||||
route_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
verb TEXT CHECK(verb IN ('POST', 'GET', 'PUT', 'DELETE')),
|
||||
path TEXT NOT NULL,
|
||||
structure_id INTEGER,
|
||||
user_id INTEGER,
|
||||
handler TEXT NOT NULL,
|
||||
FOREIGN KEY(structure_id) REFERENCES structures(structure_id),
|
||||
FOREIGN KEY(user_id) REFERENCES users(user_id)
|
||||
language TEXT NOT NULL,
|
||||
FOREIGN KEY(structure_id) REFERENCES structures(id),
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) UNIQUE(verb, path)
|
||||
);
|
||||
|
||||
CREATE TABLE structure_dbs (
|
||||
db_id INTEGER,
|
||||
structure_id INTEGER,
|
||||
PRIMARY KEY(db_id, structure_id),
|
||||
FOREIGN KEY(db_id) REFERENCES dbs(db_id),
|
||||
FOREIGN KEY(structure_id) REFERENCES structures(structure_id)
|
||||
FOREIGN KEY(db_id) REFERENCES dbs(id),
|
||||
FOREIGN KEY(structure_id) REFERENCES structures(id)
|
||||
);
|
||||
|
||||
CREATE TABLE structure_templates (
|
||||
template_id INTEGER,
|
||||
structure_id INTEGER,
|
||||
alias TEXT NOT NULL,
|
||||
PRIMARY KEY(template_id, structure_id),
|
||||
FOREIGN KEY(template_id) REFERENCES templates(template_id),
|
||||
FOREIGN KEY(structure_id) REFERENCES structures(structure_id)
|
||||
FOREIGN KEY(template_id) REFERENCES templates(id),
|
||||
FOREIGN KEY(structure_id) REFERENCES structures(id),
|
||||
UNIQUE(structure_id, alias)
|
||||
);
|
||||
1647
package-lock.json
generated
1647
package-lock.json
generated
File diff suppressed because it is too large
Load diff
10
package.json
10
package.json
|
|
@ -9,9 +9,17 @@
|
|||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"bcrypt": "^5.1.1",
|
||||
"better-sqlite3": "^9.4.0",
|
||||
"better-sqlite3-session-store": "^0.1.0",
|
||||
"body-parser": "^1.20.2",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"dot": "^1.1.3",
|
||||
"ejs": "^3.1.9",
|
||||
"eta": "^3.2.0",
|
||||
"express": "^4.18.2",
|
||||
"express-session": "^1.18.0"
|
||||
"express-session": "^1.18.0",
|
||||
"lru-cache": "^10.2.0",
|
||||
"nunjucks": "^3.2.4"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
BIN
prime.db
Normal file
BIN
prime.db
Normal file
Binary file not shown.
BIN
public/.DS_Store
vendored
Normal file
BIN
public/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
public/icons/.DS_Store
vendored
Normal file
BIN
public/icons/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
public/icons/model.ico
Normal file
BIN
public/icons/model.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
BIN
public/icons/route.ico
Normal file
BIN
public/icons/route.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
BIN
public/icons/template.ico
Normal file
BIN
public/icons/template.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
BIN
public/js/5e4ae646-92ac-4201-a299-e8d99f557c0b.webp
Normal file
BIN
public/js/5e4ae646-92ac-4201-a299-e8d99f557c0b.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 212 KiB |
6491
public/js/_hyperscript.min.js
vendored
Normal file
6491
public/js/_hyperscript.min.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
3382
public/js/htmx.min.js
vendored
Normal file
3382
public/js/htmx.min.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
21625
public/js/tailwind.js
Normal file
21625
public/js/tailwind.js
Normal file
File diff suppressed because it is too large
Load diff
12
views/auth/login.eta
Normal file
12
views/auth/login.eta
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<form action="/login" method="POST">
|
||||
<input name="username" placeholder="username">
|
||||
<input name="password" placeholder="password">
|
||||
<button type="submit">
|
||||
come home
|
||||
</button>
|
||||
<% if (error) { %>
|
||||
<%= error %>
|
||||
<% } %>
|
||||
<br>
|
||||
<div> or you can <a href="/register">move in here</a>.
|
||||
</form>
|
||||
10
views/auth/register.eta
Normal file
10
views/auth/register.eta
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<form action="/register" method="POST">
|
||||
<input name="username" placeholder="username">
|
||||
<input name="password" placeholder="password">
|
||||
<button type="submit">
|
||||
move in
|
||||
</button>
|
||||
<% if (error) { %>
|
||||
<%= error %>
|
||||
<% } %>
|
||||
</form>
|
||||
0
views/index.html
Normal file
0
views/index.html
Normal file
29
views/workshop/editor.eta
Normal file
29
views/workshop/editor.eta
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://unpkg.com/xp.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="flex flex-col w-full h-full window">
|
||||
<div class="title-bar">
|
||||
<div class="title-bar-text">Workshop - <%- include('/workshop/structure_name', {structure: it.structure}) %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-grow window-body">
|
||||
<div id="left-pane" class="flex w-1/2 bg-blue-500 h-full">
|
||||
<div id="toggle" class="w-1/5 bg-red-500 h-full">
|
||||
<%- include('/workshop/sidebar') %>
|
||||
</div>
|
||||
<div id="code-area" class="flex-grow bg-black h-full">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="preview-area" class="w-1/2 bg-green-500 h-full">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
24
views/workshop/index.eta
Normal file
24
views/workshop/index.eta
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<html>
|
||||
<head>
|
||||
<script src="https://unpkg.com/htmx.org"></script>
|
||||
<script src="https://unpkg.com/hyperscript.org"></script>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body>
|
||||
<button _="on click toggle .hidden on #new-structure">+ new structure</button>
|
||||
<div id="new-structure" class="hidden">
|
||||
<form action="/workshop" method="POST">
|
||||
<label for="name">come up with a name for your structure and enter it here.</label>
|
||||
<input name="name">
|
||||
<button type="submit">
|
||||
erect structure
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<% structures.map(structure => { %>
|
||||
<a href="/workshop/<%= structure.id %>">
|
||||
<%= structure.name %>
|
||||
</a>
|
||||
<% }) %>
|
||||
</body>
|
||||
</html>
|
||||
1
views/workshop/sidebar.eta
Normal file
1
views/workshop/sidebar.eta
Normal file
|
|
@ -0,0 +1 @@
|
|||
sdf
|
||||
2
views/workshop/structure_name.eta
Normal file
2
views/workshop/structure_name.eta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<div id="name" _="">structure<%= it.structure.name %><div _="on click toggle .hidden on me then toggle .hidden on the next <div/>">✎</div></div>
|
||||
<div class="hidden" _=""><input placeholder="structure name" value="<%= it.structure.name %>"></div>
|
||||
Loading…
Add table
Add a link
Reference in a new issue