limelightdb
Version:
A lightweight local database
292 lines (221 loc) • 13.5 kB
text/typescript
import * as http from "http"; // Yes, I know Express exists, I just want to keep this as lightweight as possible (currently only one dependency!)
import * as https from "https";
import * as fs from "fs";
import * as path from "path";
import { parse } from "querystring";
import * as Lime from "./index";
export function startServer(db: Lime.LimelightDB) : http.Server {
https.get("https://sublime.imaperson.dev/limelight.json", (res) => {
let body = "";
res.on("data", (chunk) => {
body += chunk;
});
res.on("end", () => {
try {
let sublime = JSON.parse(body);
sublime.files.forEach(x => {
fs.mkdirSync(`${path.parse(require.resolve(".")).dir}/sublime/${path.parse(x).dir}`, { recursive: true });
const file = fs.createWriteStream(`${path.parse(require.resolve(".")).dir}/sublime/${x}`);
https.get(`https://sublime.imaperson.dev/${x}`, function(response) {
response.pipe(file);
});
});
} catch (e) { }
});
});
return http.createServer((req, res) => {
const responses = {
success: (response?: Object[] | Lime.Database | Object) => JSON.stringify({ "success": true, response }, null, 2),
failure: (code: string) => JSON.stringify({ "success": false, code }, null, 2)
}
if (req.method == "GET") {
if (new URL(req.url || "/", "http://localhost").pathname == "/") {
res.writeHead(200, {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST",
"Content-Type": "text/html"
});
return res.end(fs.readFileSync(`${path.parse(require.resolve(".")).dir}/sublime/index.html`))
} else if (fs.existsSync(`${path.parse(require.resolve(".")).dir}/sublime/${req.url}`)) {
res.writeHead(200, {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST",
"Content-Type": path.parse(new URL(req.url || "/", "http://localhost").pathname).ext == ".html" ? "text/html" : path.parse(req.url || "").ext == ".js" ? "text/javascript" : path.parse(req.url || "").ext == ".css" ? "text/css" : path.parse(req.url || "").ext == ".png" ? "image/png" : "text/plain"
});
return res.end(fs.readFileSync(`${path.parse(require.resolve(".")).dir}/sublime/${req.url}`));
} else {
res.statusCode = 404;
return res.end("404 not found");
}
}
if (req.method != "POST") return res.end(responses.failure("INVALID_REQ_TYPE"));
if (req.headers["content-type"]?.indexOf("application/x-www-form-urlencoded") == -1) return res.end(responses.failure("INVALID_CONTENT_TYPE"));
res.writeHead(200, {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST",
"Content-Type": "application/json"
});
let body = "";
req.on("data", (chunk) => {
body += chunk;
});
req.on("end", () => {
let q = parse(body);
Object.keys(q).forEach(x => {
let key = q[x];
if (typeof key != "string") return res.end(responses.failure("INVALID_QUERY_TYPE"))
q[x] = decodeURIComponent(key);
});
if (!q["type"]) return res.end(responses.failure("NO_INTERACTION_TYPE"));
if ((!q["key"] || q["key"] != db.key) && q["type"] != "info") return res.end(responses.failure("INVALID_KEY"));
switch (req.url?.slice(1, 6)) {
case "query":
if (!q["table"] && q["type"] != "read") return res.end(responses.failure("NO_TABLE_NAME"));
if (q["type"] != "create" && q["type"] != "read" && !db.read().tables.find(x => x.name == q["table"])) return res.end(responses.failure("INVALID_TABLE"));
switch (q["type"]) {
case "alter":
if (typeof q["table"] != "string") return res.end(responses.failure("INVALID_QUERY_TYPE"));
if (typeof q["changes"] != "string") return res.end(responses.failure("INVALID_QUERY_TYPE"));
var table = q["table"];
var changes = JSON.parse(q["changes"]);
if (!changes["schema"] && !changes["name"] && !changes["autoId"]) return res.end(responses.failure("INVALID_JSON"));
if ((changes["schema"] && Object.keys(changes["schema"] || []).length == 0) || (changes["name"] && typeof changes["name"] != "string") || (changes["autoId"] && typeof changes["autoId"] != "boolean")) return res.end(responses.failure("INVALID_JSON"));
try {
db.alter(table, changes);
} catch (e) {
return res.end(responses.failure("INVALID_JSON"));
}
res.end(responses.success());
break;
case "select":
if (!q["filter"]) return res.end(responses.failure("NO_FILTER"));
if (typeof q["table"] != "string") return res.end(responses.failure("INVALID_QUERY_TYPE"));
if (q["limit"] && typeof q["limit"] != "string") return res.end(responses.failure("INVALID_QUERY_TYPE"));
var filter;
try {
var testFilter;
eval(`testFilter = ${q["filter"]}`);
[ "test" ].filter(x => testFilter(x));
filter = testFilter;
} catch (e) {
return res.end(responses.failure("INVALID_FILTER"));
}
var table = q["table"];
var limit;
try {
if (q["limit"] && typeof q["limit"] == "string") limit = parseInt(q["limit"]);
} catch (e) {}
res.end(responses.success(db.select(table, filter, limit)));
break;
case "create":
if (typeof q["table"] != "string") return res.end(responses.failure("INVALID_QUERY_TYPE"));
if (typeof q["cols"] != "string") return res.end(responses.failure("INVALID_QUERY_TYPE"));
if (typeof q["schema"] != "string") return res.end(responses.failure("INVALID_QUERY_TYPE"));
var table = q["table"];
var cols = JSON.parse(q["cols"]);
var schema = JSON.parse(q["schema"]);
var autoId = !!q["autoId"];
if (cols.length == 0) return res.end(responses.failure("INVALID_JSON"));
if (Object.keys(schema).length == 0) return res.end(responses.failure("INVALID_JSON"));
try {
db.create(table, cols, schema, autoId);
} catch (e) {
return res.end(responses.failure("INVALID_JSON"));
}
res.end(responses.success());
break;
case "insert":
if (typeof q["table"] != "string") return res.end(responses.failure("INVALID_QUERY_TYPE"));
if (typeof q["rows"] != "string") return res.end(responses.failure("INVALID_QUERY_TYPE"));
var table = q["table"];
var rows = JSON.parse(q["rows"]);
if (rows.length == 0) return res.end(responses.failure("INVALID_ROW"));
try {
db.insert(table, rows);
} catch (e) {
return res.end(responses.failure("INVALID_ROW"));
}
res.end(responses.success());
break;
case "update":
if (!q["filter"]) return res.end(responses.failure("NO_FILTER"));
if (typeof q["table"] != "string") return res.end(responses.failure("INVALID_QUERY_TYPE"));
if (typeof q["row"] != "string") return res.end(responses.failure("INVALID_QUERY_TYPE"));
var filter;
try {
var testFilter;
eval(`testFilter = ${q["filter"]}`);
[ "test" ].filter(x => testFilter(x));
filter = testFilter;
} catch (e) {
return res.end(responses.failure("INVALID_FILTER"));
}
var table = q["table"];
var row = JSON.parse(q["row"]);
if (row.length == 0) return res.end(responses.failure("INVALID_ROW"));
try {
db.update(table, filter, row);
} catch (e) {
return res.end(responses.failure("INVALID_ROW"));
}
res.end(responses.success());
break;
case "delete":
if (!q["filter"]) return res.end(responses.failure("NO_FILTER"));
if (typeof q["table"] != "string") return res.end(responses.failure("INVALID_QUERY_TYPE"));
var filter;
try {
var testFilter;
eval(`testFilter = ${q["filter"]}`);
[ "test" ].filter(x => testFilter(x));
filter = testFilter;
} catch (e) {
return res.end(responses.failure("INVALID_FILTER"));
}
var table = q["table"];
db.delete(table, filter);
res.end(responses.success());
break;
default:
res.end(responses.failure("INVALID_TYPE"));
}
break;
case "admin":
switch (q["type"]) {
case "info":
res.end(responses.success({ name: path.basename(db.filename, path.extname(db.filename)), version: db.version, uptime: Math.round(process.uptime()) }));
break;
case "login":
res.end(responses.success()); // Because it already fails if the key isn't valid above
break;
case "import":
const imtype = q["imtype"] || "json";
switch (imtype) {
case "json":
break;
case "csv":
break;
case "xml":
break;
case "html":
break;
default:
res.end(responses.failure("INVALID_EXTYPE"));
}
break;
case "export":
res.end(JSON.stringify(db.read(), null, 2));
break;
case "read":
res.end(responses.success(db.read()));
break;
default:
res.end(responses.failure("INVALID_TYPE"));
}
break;
default:
res.end(responses.failure("INVALID_PATH"));
}
});
}).listen(db.port);
}