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); const stmt = db.prepare( "INSERT INTO routes (verb, path, structure_id, handler) VALUES (?, ?, ?, ?)" ); const info = stmt.run(verb, path, structureId, handler); const routeId = info.lastInsertRowid; // Get the newly created route ID if (verb !== 'GET') { createScaffoldPage(db, routeId); } return routeId; // Returns the route_id of the newly created route } function getRoutes(db, structureId) { const query = ` SELECT r.*, s.route_prefix, sp.id AS scaffold_page_id, sp.content AS scaffold_page_content, sp2.id AS scaffold_params_id, sp2.query_params, sp2.url_params FROM routes r LEFT JOIN scaffold_pages sp ON r.id = sp.route_id LEFT JOIN scaffold_params sp2 ON r.id = sp2.route_id LEFT JOIN structures s ON r.structure_id = s.id WHERE r.structure_id = ? ORDER BY sp.created_at DESC, sp2.created_at DESC `; return db.prepare(query).all(structureId); } function getRoute(db, routeId) { const query = ` SELECT r.*, s.route_prefix, sp.id AS scaffold_page_id, sp.content AS scaffold_page_content, sp2.id AS scaffold_params_id, sp2.query_params, sp2.url_params FROM routes r LEFT JOIN scaffold_pages sp ON r.id = sp.route_id LEFT JOIN scaffold_params sp2 ON r.id = sp2.route_id LEFT JOIN structures s ON r.structure_id = s.id WHERE r.id = ? ORDER BY sp.created_at DESC, sp2.created_at DESC LIMIT 1 `; return db.prepare(query).get(routeId); } function defaultEndpointForRoute(db, 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); } // Clone templates const cloneTemplates = db.prepare(` INSERT INTO templates (name, content, structure_id, test_object, engine) SELECT name, content, ?, test_object, engine FROM templates WHERE structure_id = ?; `); cloneTemplates.run(newStructId, structId); // Clone routes const cloneRoutes = db.prepare(` INSERT INTO routes (verb, path, structure_id, handler) SELECT verb, path, ?, handler FROM routes WHERE structure_id = ?; `); cloneRoutes.run(newStructId, structId); // Clone scaffold pages const cloneScaffoldPages = db.prepare(` INSERT INTO scaffold_pages (route_id, content, created_at) SELECT newRoutes.id, sp.content, sp.created_at FROM scaffold_pages sp JOIN routes oldRoutes ON sp.route_id = oldRoutes.id JOIN routes newRoutes ON oldRoutes.path = newRoutes.path AND oldRoutes.verb = newRoutes.verb WHERE oldRoutes.structure_id = ? AND newRoutes.structure_id = ?; `); cloneScaffoldPages.run(structId, newStructId); // Clone scaffold params const cloneScaffoldParams = db.prepare(` INSERT INTO scaffold_params (route_id, query_params, url_params, created_at) SELECT newRoutes.id, sp.query_params, sp.url_params, sp.created_at FROM scaffold_params sp JOIN routes oldRoutes ON sp.route_id = oldRoutes.id JOIN routes newRoutes ON oldRoutes.path = newRoutes.path AND oldRoutes.verb = newRoutes.verb WHERE oldRoutes.structure_id = ? AND newRoutes.structure_id = ?; `); cloneScaffoldParams.run(structId, newStructId); return newStructId; }); return transaction(); } function createLog(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 MAX(id) AS id FROM logs WHERE route_id = ?") .get(routeId); return result ? result.id : 0; } function buildScaffoldUrl(endpoint, urlParams, queryString) { let populatedUrl = endpoint.replace(/:([^/]+)/g, () => urlParams.shift() || ''); return queryString ? `${populatedUrl}?${queryString}` : populatedUrl; } function createScaffoldPage(db, routeId, content=null) { if (!content) { const route = getRoute(db, routeId) const endpoint = buildScaffoldUrl(route.path, route.url_params, route.query_params); content = getDefaultScaffoldContentByVerb(endpoint, route.verb); console.log("gabga", content, route, endpoint) } console.log(content, "content") const stmt = db.prepare( "INSERT INTO scaffold_pages (route_id, content) VALUES (?, ?)" ); const info = stmt.run(routeId, content); return info.lastInsertRowid; // Returns the scaffold_page id of the newly created scaffold page } function getLatestScaffoldPage(db, routeId) { const stmt = db.prepare( "SELECT * FROM scaffold_pages WHERE route_id = ? ORDER BY created_at DESC LIMIT 1" ); return stmt.get(routeId); } function updateScaffoldPage(db, scaffoldPage) { const fields = ["content"]; const values = fields.map((field) => scaffoldPage[field]); const placeholders = fields.map((field) => `${field} = ?`).join(", "); console.log(scaffoldPage, "gabababab") const sql = `UPDATE scaffold_pages SET ${placeholders} WHERE id = ?`; values.push(scaffoldPage.id); // Add scaffoldPageId to the end for the WHERE clause const info = db.prepare(sql).run(...values); console.log(info.changes) return info.changes > 0; // Returns true if a row was updated, false otherwise } function generatePostForm(endpoint) { return `