working bundling
This commit is contained in:
@ -1,18 +1,130 @@
|
|||||||
import bundle from "./bundler.ts";
|
import * as esbuild from "npm:esbuild";
|
||||||
|
import { denoPlugins } from "jsr:@luca/esbuild-deno-loader";
|
||||||
|
import init, { minify } from "npm:@minify-html/wasm";
|
||||||
|
import { relative } from "jsr:@std/path@^1.0.8/relative";
|
||||||
|
|
||||||
const directoryToWatch = "./src/client_js"; // Update this to your directory
|
await esbuild.build({
|
||||||
|
plugins: [
|
||||||
|
...denoPlugins(),
|
||||||
|
],
|
||||||
|
entryPoints: ["../shared/utils/index.ts"],
|
||||||
|
outfile: "./public/js/shared.bundle.js",
|
||||||
|
bundle: true,
|
||||||
|
minify: true,
|
||||||
|
format: "esm",
|
||||||
|
});
|
||||||
|
esbuild.stop();
|
||||||
|
|
||||||
const watcher = Deno.watchFs(directoryToWatch);
|
const JS_FROM_DIR = "./src/js/";
|
||||||
|
const JS_TO_DIR = "./public/js/";
|
||||||
|
const JS_EXCLUDE = ".bundle.ts";
|
||||||
|
const watcherJS = Deno.watchFs(JS_FROM_DIR);
|
||||||
|
|
||||||
for await (const event of watcher) {
|
const cwd = Deno.cwd();
|
||||||
if (
|
|
||||||
event.kind === "modify" || event.kind === "create" ||
|
function getPublic(paths: string[]): string[] {
|
||||||
event.kind === "remove"
|
return paths.map((path) =>
|
||||||
|
path.replace(JS_FROM_DIR, JS_TO_DIR).replace(".ts", ".js")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
for await (const event of watcherJS) {
|
||||||
|
const paths = event.paths.filter((path) =>
|
||||||
|
!path.endsWith(JS_EXCLUDE) &&
|
||||||
|
(path.endsWith(".ts") || path.endsWith(".js"))
|
||||||
|
);
|
||||||
|
if (event.kind === "modify" || event.kind === "create") {
|
||||||
|
await esbuild.build({
|
||||||
|
plugins: [
|
||||||
|
...denoPlugins(),
|
||||||
|
],
|
||||||
|
entryPoints: paths,
|
||||||
|
outbase: JS_FROM_DIR,
|
||||||
|
outdir: JS_TO_DIR,
|
||||||
|
bundle: false,
|
||||||
|
minify: true,
|
||||||
|
format: "esm",
|
||||||
|
});
|
||||||
|
esbuild.stop();
|
||||||
|
|
||||||
|
for (
|
||||||
|
const path of getPublic(paths)
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
await bundle();
|
const content = await Deno.readTextFile(path);
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
const staticImportRegex =
|
||||||
|
/(import\s*[^'"]+from\s*['"])([^'"]+?)(\.ts)(['"])/g;
|
||||||
|
|
||||||
|
const dynamicImportRegex =
|
||||||
|
/(import\(\s*['"])([^'"]+?)(\.ts)(['"]\s*\))/g;
|
||||||
|
|
||||||
|
let updatedContent = content.replace(
|
||||||
|
staticImportRegex,
|
||||||
|
(_, p1, p2, p3, p4) => {
|
||||||
|
return `${p1}${p2}.js${p4}`;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
updatedContent = updatedContent.replace(
|
||||||
|
dynamicImportRegex,
|
||||||
|
(_, p1, p2, p3, p4) => {
|
||||||
|
return `${p1}${p2}.js${p4}`;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await Deno.writeTextFile(path, updatedContent);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
} else if (event.kind === "remove") {
|
||||||
|
try {
|
||||||
|
for (const path of getPublic(paths)) {
|
||||||
|
await Deno.remove(path);
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
const HTML_FROM_DIR = "./src/views/";
|
||||||
|
const HTML_TO_DIR = "./views/";
|
||||||
|
|
||||||
|
const watcherHTML = Deno.watchFs(HTML_FROM_DIR);
|
||||||
|
|
||||||
|
function minifyHTML(html: Uint8Array) {
|
||||||
|
const data = minify(html, {
|
||||||
|
keep_spaces_between_attributes: true,
|
||||||
|
keep_comments: true,
|
||||||
|
preserve_brace_template_syntax: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getHTMLToPath(path: string) {
|
||||||
|
return path.replace(HTML_FROM_DIR, HTML_TO_DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
for await (const event of watcherHTML) {
|
||||||
|
const paths = event.paths.filter((path) => path.endsWith(".html"));
|
||||||
|
if (event.kind === "modify" || event.kind === "create") {
|
||||||
|
try {
|
||||||
|
for (const path of paths) {
|
||||||
|
const html = await Deno.readFile(path);
|
||||||
|
|
||||||
|
const data = minifyHTML(html);
|
||||||
|
|
||||||
|
await Deno.writeFile(getHTMLToPath(path), data);
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
} else if (event.kind === "remove") {
|
||||||
|
try {
|
||||||
|
for (const path of paths) {
|
||||||
|
await Deno.remove(getHTMLToPath(path));
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|||||||
@ -1,76 +0,0 @@
|
|||||||
import * as esbuild from "npm:esbuild";
|
|
||||||
import * as fg from "npm:fast-glob";
|
|
||||||
import { denoPlugins } from "jsr:@luca/esbuild-deno-loader";
|
|
||||||
|
|
||||||
async function bundleSharedObject() {
|
|
||||||
await esbuild.build({
|
|
||||||
plugins: [
|
|
||||||
...denoPlugins(),
|
|
||||||
],
|
|
||||||
entryPoints: [
|
|
||||||
"../shared/utils/index.ts",
|
|
||||||
],
|
|
||||||
outfile: "./public/js/shared.bundle.js",
|
|
||||||
bundle: true,
|
|
||||||
format: "esm",
|
|
||||||
minify: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await bundleSharedObject();
|
|
||||||
|
|
||||||
async function bundle() {
|
|
||||||
const entryPoints = fg.default.sync([
|
|
||||||
"./src/client_js/*.ts",
|
|
||||||
"!./src/client_js/shared.bundle.ts",
|
|
||||||
]);
|
|
||||||
|
|
||||||
async function bundleClientJS() {
|
|
||||||
await esbuild.build({
|
|
||||||
plugins: [
|
|
||||||
...denoPlugins(),
|
|
||||||
],
|
|
||||||
entryPoints: entryPoints,
|
|
||||||
outbase: "./src/client_js",
|
|
||||||
outdir: "./public/js",
|
|
||||||
bundle: false,
|
|
||||||
format: "esm",
|
|
||||||
minify: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await bundleClientJS();
|
|
||||||
|
|
||||||
const bundledFiles = fg.default.sync([
|
|
||||||
"./public/js/*.js",
|
|
||||||
"!./public/js/shared.bundle.js",
|
|
||||||
]);
|
|
||||||
|
|
||||||
for (const file of bundledFiles) {
|
|
||||||
const content = await Deno.readTextFile(file);
|
|
||||||
|
|
||||||
const staticImportRegex =
|
|
||||||
/(import\s*[^'"]+from\s*['"])([^'"]+?)(\.ts)(['"])/g;
|
|
||||||
|
|
||||||
const dynamicImportRegex =
|
|
||||||
/(import\(\s*['"])([^'"]+?)(\.ts)(['"]\s*\))/g;
|
|
||||||
|
|
||||||
let updatedContent = content.replace(
|
|
||||||
staticImportRegex,
|
|
||||||
(_, p1, p2, p3, p4) => {
|
|
||||||
return `${p1}${p2}.js${p4}`;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
updatedContent = updatedContent.replace(
|
|
||||||
dynamicImportRegex,
|
|
||||||
(_, p1, p2, p3, p4) => {
|
|
||||||
return `${p1}${p2}.js${p4}`;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
await Deno.writeTextFile(file, updatedContent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default bundle;
|
|
||||||
@ -6,15 +6,13 @@
|
|||||||
"@db/sqlite": "jsr:@db/sqlite@^0.12.0",
|
"@db/sqlite": "jsr:@db/sqlite@^0.12.0",
|
||||||
"@eta-dev/eta": "jsr:@eta-dev/eta@^3.5.0",
|
"@eta-dev/eta": "jsr:@eta-dev/eta@^3.5.0",
|
||||||
"@felix/bcrypt": "jsr:@felix/bcrypt@^1.0.5",
|
"@felix/bcrypt": "jsr:@felix/bcrypt@^1.0.5",
|
||||||
"@minify-html/node": "npm:@minify-html/node@^0.15.0",
|
"@minify-html/node": "npm:@minify-html/node-linux-x64@^0.15.0",
|
||||||
"@ryanflorence/sqlite-typegen": "npm:@ryanflorence/sqlite-typegen@^0.2.0",
|
|
||||||
"@std/assert": "jsr:@std/assert@1",
|
"@std/assert": "jsr:@std/assert@1",
|
||||||
"@std/crypto": "jsr:@std/crypto@^1.0.3",
|
"@std/crypto": "jsr:@std/crypto@^1.0.3",
|
||||||
"@std/dotenv": "jsr:@std/dotenv@^0.225.3",
|
"@std/dotenv": "jsr:@std/dotenv@^0.225.3",
|
||||||
"@std/http": "jsr:@std/http@^1.0.12",
|
"@std/http": "jsr:@std/http@^1.0.12",
|
||||||
|
"@lib/": "./src/lib/",
|
||||||
"@src/": "./src/",
|
"@src/": "./src/",
|
||||||
"better-sqlite3": "npm:better-sqlite3@^11.8.0",
|
"esbuild": "npm:esbuild@^0.24.2"
|
||||||
"esbuild": "npm:esbuild@^0.24.2",
|
|
||||||
"mariadb": "npm:mariadb@^3.4.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
563
server/lock.json
Normal file
563
server/lock.json
Normal file
@ -0,0 +1,563 @@
|
|||||||
|
{
|
||||||
|
"version": "4",
|
||||||
|
"specifiers": {
|
||||||
|
"jsr:@luca/esbuild-deno-loader@*": "0.11.1",
|
||||||
|
"jsr:@std/bytes@^1.0.2": "1.0.4",
|
||||||
|
"jsr:@std/encoding@^1.0.5": "1.0.6",
|
||||||
|
"jsr:@std/path@^1.0.6": "1.0.8",
|
||||||
|
"jsr:@std/path@^1.0.8": "1.0.8",
|
||||||
|
"npm:@tauri-apps/api@2": "2.2.0",
|
||||||
|
"npm:@tauri-apps/cli@2": "2.2.5",
|
||||||
|
"npm:@tauri-apps/plugin-shell@2": "2.2.0",
|
||||||
|
"npm:esbuild-plugin-tsc@0.4": "0.4.0_typescript@5.7.3",
|
||||||
|
"npm:esbuild@*": "0.24.2",
|
||||||
|
"npm:esbuild@~0.24.2": "0.24.2",
|
||||||
|
"npm:fast-glob@^3.3.3": "3.3.3",
|
||||||
|
"npm:typescript@^5.2.2": "5.7.3",
|
||||||
|
"npm:typescript@^5.7.3": "5.7.3",
|
||||||
|
"npm:vite@^5.3.1": "5.4.13"
|
||||||
|
},
|
||||||
|
"jsr": {
|
||||||
|
"@luca/esbuild-deno-loader@0.11.1": {
|
||||||
|
"integrity": "dc020d16d75b591f679f6b9288b10f38bdb4f24345edb2f5732affa1d9885267",
|
||||||
|
"dependencies": [
|
||||||
|
"jsr:@std/bytes",
|
||||||
|
"jsr:@std/encoding",
|
||||||
|
"jsr:@std/path@^1.0.6"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@std/bytes@1.0.4": {
|
||||||
|
"integrity": "11a0debe522707c95c7b7ef89b478c13fb1583a7cfb9a85674cd2cc2e3a28abc"
|
||||||
|
},
|
||||||
|
"@std/encoding@1.0.6": {
|
||||||
|
"integrity": "ca87122c196e8831737d9547acf001766618e78cd8c33920776c7f5885546069"
|
||||||
|
},
|
||||||
|
"@std/path@1.0.8": {
|
||||||
|
"integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"npm": {
|
||||||
|
"@esbuild/aix-ppc64@0.21.5": {
|
||||||
|
"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="
|
||||||
|
},
|
||||||
|
"@esbuild/aix-ppc64@0.24.2": {
|
||||||
|
"integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA=="
|
||||||
|
},
|
||||||
|
"@esbuild/android-arm64@0.21.5": {
|
||||||
|
"integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="
|
||||||
|
},
|
||||||
|
"@esbuild/android-arm64@0.24.2": {
|
||||||
|
"integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg=="
|
||||||
|
},
|
||||||
|
"@esbuild/android-arm@0.21.5": {
|
||||||
|
"integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="
|
||||||
|
},
|
||||||
|
"@esbuild/android-arm@0.24.2": {
|
||||||
|
"integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q=="
|
||||||
|
},
|
||||||
|
"@esbuild/android-x64@0.21.5": {
|
||||||
|
"integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="
|
||||||
|
},
|
||||||
|
"@esbuild/android-x64@0.24.2": {
|
||||||
|
"integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw=="
|
||||||
|
},
|
||||||
|
"@esbuild/darwin-arm64@0.21.5": {
|
||||||
|
"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="
|
||||||
|
},
|
||||||
|
"@esbuild/darwin-arm64@0.24.2": {
|
||||||
|
"integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA=="
|
||||||
|
},
|
||||||
|
"@esbuild/darwin-x64@0.21.5": {
|
||||||
|
"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="
|
||||||
|
},
|
||||||
|
"@esbuild/darwin-x64@0.24.2": {
|
||||||
|
"integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA=="
|
||||||
|
},
|
||||||
|
"@esbuild/freebsd-arm64@0.21.5": {
|
||||||
|
"integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="
|
||||||
|
},
|
||||||
|
"@esbuild/freebsd-arm64@0.24.2": {
|
||||||
|
"integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg=="
|
||||||
|
},
|
||||||
|
"@esbuild/freebsd-x64@0.21.5": {
|
||||||
|
"integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="
|
||||||
|
},
|
||||||
|
"@esbuild/freebsd-x64@0.24.2": {
|
||||||
|
"integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-arm64@0.21.5": {
|
||||||
|
"integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-arm64@0.24.2": {
|
||||||
|
"integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-arm@0.21.5": {
|
||||||
|
"integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-arm@0.24.2": {
|
||||||
|
"integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-ia32@0.21.5": {
|
||||||
|
"integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-ia32@0.24.2": {
|
||||||
|
"integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-loong64@0.21.5": {
|
||||||
|
"integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-loong64@0.24.2": {
|
||||||
|
"integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-mips64el@0.21.5": {
|
||||||
|
"integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-mips64el@0.24.2": {
|
||||||
|
"integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-ppc64@0.21.5": {
|
||||||
|
"integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-ppc64@0.24.2": {
|
||||||
|
"integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-riscv64@0.21.5": {
|
||||||
|
"integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-riscv64@0.24.2": {
|
||||||
|
"integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-s390x@0.21.5": {
|
||||||
|
"integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-s390x@0.24.2": {
|
||||||
|
"integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-x64@0.21.5": {
|
||||||
|
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-x64@0.24.2": {
|
||||||
|
"integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q=="
|
||||||
|
},
|
||||||
|
"@esbuild/netbsd-arm64@0.24.2": {
|
||||||
|
"integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw=="
|
||||||
|
},
|
||||||
|
"@esbuild/netbsd-x64@0.21.5": {
|
||||||
|
"integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="
|
||||||
|
},
|
||||||
|
"@esbuild/netbsd-x64@0.24.2": {
|
||||||
|
"integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw=="
|
||||||
|
},
|
||||||
|
"@esbuild/openbsd-arm64@0.24.2": {
|
||||||
|
"integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A=="
|
||||||
|
},
|
||||||
|
"@esbuild/openbsd-x64@0.21.5": {
|
||||||
|
"integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="
|
||||||
|
},
|
||||||
|
"@esbuild/openbsd-x64@0.24.2": {
|
||||||
|
"integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA=="
|
||||||
|
},
|
||||||
|
"@esbuild/sunos-x64@0.21.5": {
|
||||||
|
"integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="
|
||||||
|
},
|
||||||
|
"@esbuild/sunos-x64@0.24.2": {
|
||||||
|
"integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig=="
|
||||||
|
},
|
||||||
|
"@esbuild/win32-arm64@0.21.5": {
|
||||||
|
"integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="
|
||||||
|
},
|
||||||
|
"@esbuild/win32-arm64@0.24.2": {
|
||||||
|
"integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ=="
|
||||||
|
},
|
||||||
|
"@esbuild/win32-ia32@0.21.5": {
|
||||||
|
"integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="
|
||||||
|
},
|
||||||
|
"@esbuild/win32-ia32@0.24.2": {
|
||||||
|
"integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA=="
|
||||||
|
},
|
||||||
|
"@esbuild/win32-x64@0.21.5": {
|
||||||
|
"integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="
|
||||||
|
},
|
||||||
|
"@esbuild/win32-x64@0.24.2": {
|
||||||
|
"integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg=="
|
||||||
|
},
|
||||||
|
"@nodelib/fs.scandir@2.1.5": {
|
||||||
|
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
|
||||||
|
"dependencies": [
|
||||||
|
"@nodelib/fs.stat",
|
||||||
|
"run-parallel"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@nodelib/fs.stat@2.0.5": {
|
||||||
|
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="
|
||||||
|
},
|
||||||
|
"@nodelib/fs.walk@1.2.8": {
|
||||||
|
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
|
||||||
|
"dependencies": [
|
||||||
|
"@nodelib/fs.scandir",
|
||||||
|
"fastq"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@rollup/rollup-android-arm-eabi@4.31.0": {
|
||||||
|
"integrity": "sha512-9NrR4033uCbUBRgvLcBrJofa2KY9DzxL2UKZ1/4xA/mnTNyhZCWBuD8X3tPm1n4KxcgaraOYgrFKSgwjASfmlA=="
|
||||||
|
},
|
||||||
|
"@rollup/rollup-android-arm64@4.31.0": {
|
||||||
|
"integrity": "sha512-iBbODqT86YBFHajxxF8ebj2hwKm1k8PTBQSojSt3d1FFt1gN+xf4CowE47iN0vOSdnd+5ierMHBbu/rHc7nq5g=="
|
||||||
|
},
|
||||||
|
"@rollup/rollup-darwin-arm64@4.31.0": {
|
||||||
|
"integrity": "sha512-WHIZfXgVBX30SWuTMhlHPXTyN20AXrLH4TEeH/D0Bolvx9PjgZnn4H677PlSGvU6MKNsjCQJYczkpvBbrBnG6g=="
|
||||||
|
},
|
||||||
|
"@rollup/rollup-darwin-x64@4.31.0": {
|
||||||
|
"integrity": "sha512-hrWL7uQacTEF8gdrQAqcDy9xllQ0w0zuL1wk1HV8wKGSGbKPVjVUv/DEwT2+Asabf8Dh/As+IvfdU+H8hhzrQQ=="
|
||||||
|
},
|
||||||
|
"@rollup/rollup-freebsd-arm64@4.31.0": {
|
||||||
|
"integrity": "sha512-S2oCsZ4hJviG1QjPY1h6sVJLBI6ekBeAEssYKad1soRFv3SocsQCzX6cwnk6fID6UQQACTjeIMB+hyYrFacRew=="
|
||||||
|
},
|
||||||
|
"@rollup/rollup-freebsd-x64@4.31.0": {
|
||||||
|
"integrity": "sha512-pCANqpynRS4Jirn4IKZH4tnm2+2CqCNLKD7gAdEjzdLGbH1iO0zouHz4mxqg0uEMpO030ejJ0aA6e1PJo2xrPA=="
|
||||||
|
},
|
||||||
|
"@rollup/rollup-linux-arm-gnueabihf@4.31.0": {
|
||||||
|
"integrity": "sha512-0O8ViX+QcBd3ZmGlcFTnYXZKGbFu09EhgD27tgTdGnkcYXLat4KIsBBQeKLR2xZDCXdIBAlWLkiXE1+rJpCxFw=="
|
||||||
|
},
|
||||||
|
"@rollup/rollup-linux-arm-musleabihf@4.31.0": {
|
||||||
|
"integrity": "sha512-w5IzG0wTVv7B0/SwDnMYmbr2uERQp999q8FMkKG1I+j8hpPX2BYFjWe69xbhbP6J9h2gId/7ogesl9hwblFwwg=="
|
||||||
|
},
|
||||||
|
"@rollup/rollup-linux-arm64-gnu@4.31.0": {
|
||||||
|
"integrity": "sha512-JyFFshbN5xwy6fulZ8B/8qOqENRmDdEkcIMF0Zz+RsfamEW+Zabl5jAb0IozP/8UKnJ7g2FtZZPEUIAlUSX8cA=="
|
||||||
|
},
|
||||||
|
"@rollup/rollup-linux-arm64-musl@4.31.0": {
|
||||||
|
"integrity": "sha512-kpQXQ0UPFeMPmPYksiBL9WS/BDiQEjRGMfklVIsA0Sng347H8W2iexch+IEwaR7OVSKtr2ZFxggt11zVIlZ25g=="
|
||||||
|
},
|
||||||
|
"@rollup/rollup-linux-loongarch64-gnu@4.31.0": {
|
||||||
|
"integrity": "sha512-pMlxLjt60iQTzt9iBb3jZphFIl55a70wexvo8p+vVFK+7ifTRookdoXX3bOsRdmfD+OKnMozKO6XM4zR0sHRrQ=="
|
||||||
|
},
|
||||||
|
"@rollup/rollup-linux-powerpc64le-gnu@4.31.0": {
|
||||||
|
"integrity": "sha512-D7TXT7I/uKEuWiRkEFbed1UUYZwcJDU4vZQdPTcepK7ecPhzKOYk4Er2YR4uHKme4qDeIh6N3XrLfpuM7vzRWQ=="
|
||||||
|
},
|
||||||
|
"@rollup/rollup-linux-riscv64-gnu@4.31.0": {
|
||||||
|
"integrity": "sha512-wal2Tc8O5lMBtoePLBYRKj2CImUCJ4UNGJlLwspx7QApYny7K1cUYlzQ/4IGQBLmm+y0RS7dwc3TDO/pmcneTw=="
|
||||||
|
},
|
||||||
|
"@rollup/rollup-linux-s390x-gnu@4.31.0": {
|
||||||
|
"integrity": "sha512-O1o5EUI0+RRMkK9wiTVpk2tyzXdXefHtRTIjBbmFREmNMy7pFeYXCFGbhKFwISA3UOExlo5GGUuuj3oMKdK6JQ=="
|
||||||
|
},
|
||||||
|
"@rollup/rollup-linux-x64-gnu@4.31.0": {
|
||||||
|
"integrity": "sha512-zSoHl356vKnNxwOWnLd60ixHNPRBglxpv2g7q0Cd3Pmr561gf0HiAcUBRL3S1vPqRC17Zo2CX/9cPkqTIiai1g=="
|
||||||
|
},
|
||||||
|
"@rollup/rollup-linux-x64-musl@4.31.0": {
|
||||||
|
"integrity": "sha512-ypB/HMtcSGhKUQNiFwqgdclWNRrAYDH8iMYH4etw/ZlGwiTVxBz2tDrGRrPlfZu6QjXwtd+C3Zib5pFqID97ZA=="
|
||||||
|
},
|
||||||
|
"@rollup/rollup-win32-arm64-msvc@4.31.0": {
|
||||||
|
"integrity": "sha512-JuhN2xdI/m8Hr+aVO3vspO7OQfUFO6bKLIRTAy0U15vmWjnZDLrEgCZ2s6+scAYaQVpYSh9tZtRijApw9IXyMw=="
|
||||||
|
},
|
||||||
|
"@rollup/rollup-win32-ia32-msvc@4.31.0": {
|
||||||
|
"integrity": "sha512-U1xZZXYkvdf5MIWmftU8wrM5PPXzyaY1nGCI4KI4BFfoZxHamsIe+BtnPLIvvPykvQWlVbqUXdLa4aJUuilwLQ=="
|
||||||
|
},
|
||||||
|
"@rollup/rollup-win32-x64-msvc@4.31.0": {
|
||||||
|
"integrity": "sha512-ul8rnCsUumNln5YWwz0ted2ZHFhzhRRnkpBZ+YRuHoRAlUji9KChpOUOndY7uykrPEPXVbHLlsdo6v5yXo/TXw=="
|
||||||
|
},
|
||||||
|
"@tauri-apps/api@2.2.0": {
|
||||||
|
"integrity": "sha512-R8epOeZl1eJEl603aUMIGb4RXlhPjpgxbGVEaqY+0G5JG9vzV/clNlzTeqc+NLYXVqXcn8mb4c5b9pJIUDEyAg=="
|
||||||
|
},
|
||||||
|
"@tauri-apps/cli-darwin-arm64@2.2.5": {
|
||||||
|
"integrity": "sha512-qdPmypQE7qj62UJy3Wl/ccCJZwsv5gyBByOrAaG7u5c/PB3QSxhNPegice2k4EHeIuApaVJOoe/CEYVgm/og2Q=="
|
||||||
|
},
|
||||||
|
"@tauri-apps/cli-darwin-x64@2.2.5": {
|
||||||
|
"integrity": "sha512-8JVlCAb2c3n0EcGW7n/1kU4Rq831SsoLDD/0hNp85Um8HGIH2Mg/qos/MLOc8Qv2mOaoKcRKf4hd0I1y0Rl9Cg=="
|
||||||
|
},
|
||||||
|
"@tauri-apps/cli-linux-arm-gnueabihf@2.2.5": {
|
||||||
|
"integrity": "sha512-mzxQCqZg7ljRVgekPpXQ5TOehCNgnXh/DNWU6kFjALaBvaw4fGzc369/hV94wOt29htNFyxf8ty2DaQaYljEHw=="
|
||||||
|
},
|
||||||
|
"@tauri-apps/cli-linux-arm64-gnu@2.2.5": {
|
||||||
|
"integrity": "sha512-M9nkzx5jsSJSNpp7aSza0qv0/N13SUNzH8ysYSZ7IaCN8coGeMg2KgQ5qC6tqUVij2rbg8A/X1n0pPo/gtLx0A=="
|
||||||
|
},
|
||||||
|
"@tauri-apps/cli-linux-arm64-musl@2.2.5": {
|
||||||
|
"integrity": "sha512-tFhZu950HNRLR1RM5Q9Xj5gAlA6AhyyiZgeoXGFAWto+s2jpWmmA3Qq2GUxnVDr7Xui8PF4UY5kANDIOschuwg=="
|
||||||
|
},
|
||||||
|
"@tauri-apps/cli-linux-x64-gnu@2.2.5": {
|
||||||
|
"integrity": "sha512-eaGhTQLr3EKeksGsp2tK/Ndi7/oyo3P53Pye6kg0zqXiqu8LQjg1CgvDm1l+5oit04S60zR4AqlDFpoeEtDGgw=="
|
||||||
|
},
|
||||||
|
"@tauri-apps/cli-linux-x64-musl@2.2.5": {
|
||||||
|
"integrity": "sha512-NLAO/SymDxeGuOWWQZCpwoED1K1jaHUvW+u9ip+rTetnxFPLvf3zXthx4QVKfCZLdj2WLQz4cLjHyQdMDXAM+w=="
|
||||||
|
},
|
||||||
|
"@tauri-apps/cli-win32-arm64-msvc@2.2.5": {
|
||||||
|
"integrity": "sha512-yG5KFbqrHfGjkAQAaaCD4i7cJklBjmMxZ2C92DEnqCOujSsEuLxrwwoKxQ4+hqEHOmF3lyX0vfqhgZcS03H38w=="
|
||||||
|
},
|
||||||
|
"@tauri-apps/cli-win32-ia32-msvc@2.2.5": {
|
||||||
|
"integrity": "sha512-G5lq+2EdxOc8ttg3uhME5t9U3hMGTxwaKz0X4DplTG2Iv4lcNWqw/AESIJVHa5a+EB+ZCC8I+yOfIykp/Cd5mQ=="
|
||||||
|
},
|
||||||
|
"@tauri-apps/cli-win32-x64-msvc@2.2.5": {
|
||||||
|
"integrity": "sha512-vw4fPVOo0rIQIlqw6xUvK2nwiRFBHNgayDE2Z/SomJlQJAJ1q4VgpHOPl12ouuicmTjK1gWKm7RTouQe3Nig0Q=="
|
||||||
|
},
|
||||||
|
"@tauri-apps/cli@2.2.5": {
|
||||||
|
"integrity": "sha512-PaefTQUCYYqvZWdH8EhXQkyJEjQwtoy/OHGoPcZx7Gk3D3K6AtGSxZ9OlHIz3Bu5LDGgVBk36vKtHW0WYsWnbw==",
|
||||||
|
"dependencies": [
|
||||||
|
"@tauri-apps/cli-darwin-arm64",
|
||||||
|
"@tauri-apps/cli-darwin-x64",
|
||||||
|
"@tauri-apps/cli-linux-arm-gnueabihf",
|
||||||
|
"@tauri-apps/cli-linux-arm64-gnu",
|
||||||
|
"@tauri-apps/cli-linux-arm64-musl",
|
||||||
|
"@tauri-apps/cli-linux-x64-gnu",
|
||||||
|
"@tauri-apps/cli-linux-x64-musl",
|
||||||
|
"@tauri-apps/cli-win32-arm64-msvc",
|
||||||
|
"@tauri-apps/cli-win32-ia32-msvc",
|
||||||
|
"@tauri-apps/cli-win32-x64-msvc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@tauri-apps/plugin-shell@2.2.0": {
|
||||||
|
"integrity": "sha512-iC3Ic1hLmasoboG7BO+7p+AriSoqAwKrIk+Hpk+S/bjTQdXqbl2GbdclghI4gM32X0bls7xHzIFqhRdrlvJeaA==",
|
||||||
|
"dependencies": [
|
||||||
|
"@tauri-apps/api"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@types/estree@1.0.6": {
|
||||||
|
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
|
||||||
|
},
|
||||||
|
"braces@3.0.3": {
|
||||||
|
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||||
|
"dependencies": [
|
||||||
|
"fill-range"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"esbuild-plugin-tsc@0.4.0_typescript@5.7.3": {
|
||||||
|
"integrity": "sha512-q9gWIovt1nkwchMLc2zhyksaiHOv3kDK4b0AUol8lkMCRhJ1zavgfb2fad6BKp7FT9rh/OHmEBXVjczLoi/0yw==",
|
||||||
|
"dependencies": [
|
||||||
|
"strip-comments",
|
||||||
|
"typescript"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"esbuild@0.21.5": {
|
||||||
|
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
|
||||||
|
"dependencies": [
|
||||||
|
"@esbuild/aix-ppc64@0.21.5",
|
||||||
|
"@esbuild/android-arm@0.21.5",
|
||||||
|
"@esbuild/android-arm64@0.21.5",
|
||||||
|
"@esbuild/android-x64@0.21.5",
|
||||||
|
"@esbuild/darwin-arm64@0.21.5",
|
||||||
|
"@esbuild/darwin-x64@0.21.5",
|
||||||
|
"@esbuild/freebsd-arm64@0.21.5",
|
||||||
|
"@esbuild/freebsd-x64@0.21.5",
|
||||||
|
"@esbuild/linux-arm@0.21.5",
|
||||||
|
"@esbuild/linux-arm64@0.21.5",
|
||||||
|
"@esbuild/linux-ia32@0.21.5",
|
||||||
|
"@esbuild/linux-loong64@0.21.5",
|
||||||
|
"@esbuild/linux-mips64el@0.21.5",
|
||||||
|
"@esbuild/linux-ppc64@0.21.5",
|
||||||
|
"@esbuild/linux-riscv64@0.21.5",
|
||||||
|
"@esbuild/linux-s390x@0.21.5",
|
||||||
|
"@esbuild/linux-x64@0.21.5",
|
||||||
|
"@esbuild/netbsd-x64@0.21.5",
|
||||||
|
"@esbuild/openbsd-x64@0.21.5",
|
||||||
|
"@esbuild/sunos-x64@0.21.5",
|
||||||
|
"@esbuild/win32-arm64@0.21.5",
|
||||||
|
"@esbuild/win32-ia32@0.21.5",
|
||||||
|
"@esbuild/win32-x64@0.21.5"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"esbuild@0.24.2": {
|
||||||
|
"integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==",
|
||||||
|
"dependencies": [
|
||||||
|
"@esbuild/aix-ppc64@0.24.2",
|
||||||
|
"@esbuild/android-arm@0.24.2",
|
||||||
|
"@esbuild/android-arm64@0.24.2",
|
||||||
|
"@esbuild/android-x64@0.24.2",
|
||||||
|
"@esbuild/darwin-arm64@0.24.2",
|
||||||
|
"@esbuild/darwin-x64@0.24.2",
|
||||||
|
"@esbuild/freebsd-arm64@0.24.2",
|
||||||
|
"@esbuild/freebsd-x64@0.24.2",
|
||||||
|
"@esbuild/linux-arm@0.24.2",
|
||||||
|
"@esbuild/linux-arm64@0.24.2",
|
||||||
|
"@esbuild/linux-ia32@0.24.2",
|
||||||
|
"@esbuild/linux-loong64@0.24.2",
|
||||||
|
"@esbuild/linux-mips64el@0.24.2",
|
||||||
|
"@esbuild/linux-ppc64@0.24.2",
|
||||||
|
"@esbuild/linux-riscv64@0.24.2",
|
||||||
|
"@esbuild/linux-s390x@0.24.2",
|
||||||
|
"@esbuild/linux-x64@0.24.2",
|
||||||
|
"@esbuild/netbsd-arm64",
|
||||||
|
"@esbuild/netbsd-x64@0.24.2",
|
||||||
|
"@esbuild/openbsd-arm64",
|
||||||
|
"@esbuild/openbsd-x64@0.24.2",
|
||||||
|
"@esbuild/sunos-x64@0.24.2",
|
||||||
|
"@esbuild/win32-arm64@0.24.2",
|
||||||
|
"@esbuild/win32-ia32@0.24.2",
|
||||||
|
"@esbuild/win32-x64@0.24.2"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fast-glob@3.3.3": {
|
||||||
|
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
|
||||||
|
"dependencies": [
|
||||||
|
"@nodelib/fs.stat",
|
||||||
|
"@nodelib/fs.walk",
|
||||||
|
"glob-parent",
|
||||||
|
"merge2",
|
||||||
|
"micromatch"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fastq@1.18.0": {
|
||||||
|
"integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==",
|
||||||
|
"dependencies": [
|
||||||
|
"reusify"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fill-range@7.1.1": {
|
||||||
|
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||||
|
"dependencies": [
|
||||||
|
"to-regex-range"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fsevents@2.3.3": {
|
||||||
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="
|
||||||
|
},
|
||||||
|
"glob-parent@5.1.2": {
|
||||||
|
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||||
|
"dependencies": [
|
||||||
|
"is-glob"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"is-extglob@2.1.1": {
|
||||||
|
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="
|
||||||
|
},
|
||||||
|
"is-glob@4.0.3": {
|
||||||
|
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||||
|
"dependencies": [
|
||||||
|
"is-extglob"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"is-number@7.0.0": {
|
||||||
|
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
|
||||||
|
},
|
||||||
|
"merge2@1.4.1": {
|
||||||
|
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="
|
||||||
|
},
|
||||||
|
"micromatch@4.0.8": {
|
||||||
|
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||||
|
"dependencies": [
|
||||||
|
"braces",
|
||||||
|
"picomatch"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nanoid@3.3.8": {
|
||||||
|
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="
|
||||||
|
},
|
||||||
|
"picocolors@1.1.1": {
|
||||||
|
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
|
||||||
|
},
|
||||||
|
"picomatch@2.3.1": {
|
||||||
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
|
||||||
|
},
|
||||||
|
"postcss@8.5.1": {
|
||||||
|
"integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==",
|
||||||
|
"dependencies": [
|
||||||
|
"nanoid",
|
||||||
|
"picocolors",
|
||||||
|
"source-map-js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"queue-microtask@1.2.3": {
|
||||||
|
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
|
||||||
|
},
|
||||||
|
"reusify@1.0.4": {
|
||||||
|
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="
|
||||||
|
},
|
||||||
|
"rollup@4.31.0": {
|
||||||
|
"integrity": "sha512-9cCE8P4rZLx9+PjoyqHLs31V9a9Vpvfo4qNcs6JCiGWYhw2gijSetFbH6SSy1whnkgcefnUwr8sad7tgqsGvnw==",
|
||||||
|
"dependencies": [
|
||||||
|
"@rollup/rollup-android-arm-eabi",
|
||||||
|
"@rollup/rollup-android-arm64",
|
||||||
|
"@rollup/rollup-darwin-arm64",
|
||||||
|
"@rollup/rollup-darwin-x64",
|
||||||
|
"@rollup/rollup-freebsd-arm64",
|
||||||
|
"@rollup/rollup-freebsd-x64",
|
||||||
|
"@rollup/rollup-linux-arm-gnueabihf",
|
||||||
|
"@rollup/rollup-linux-arm-musleabihf",
|
||||||
|
"@rollup/rollup-linux-arm64-gnu",
|
||||||
|
"@rollup/rollup-linux-arm64-musl",
|
||||||
|
"@rollup/rollup-linux-loongarch64-gnu",
|
||||||
|
"@rollup/rollup-linux-powerpc64le-gnu",
|
||||||
|
"@rollup/rollup-linux-riscv64-gnu",
|
||||||
|
"@rollup/rollup-linux-s390x-gnu",
|
||||||
|
"@rollup/rollup-linux-x64-gnu",
|
||||||
|
"@rollup/rollup-linux-x64-musl",
|
||||||
|
"@rollup/rollup-win32-arm64-msvc",
|
||||||
|
"@rollup/rollup-win32-ia32-msvc",
|
||||||
|
"@rollup/rollup-win32-x64-msvc",
|
||||||
|
"@types/estree",
|
||||||
|
"fsevents"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"run-parallel@1.2.0": {
|
||||||
|
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
|
||||||
|
"dependencies": [
|
||||||
|
"queue-microtask"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"source-map-js@1.2.1": {
|
||||||
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
|
||||||
|
},
|
||||||
|
"strip-comments@2.0.1": {
|
||||||
|
"integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw=="
|
||||||
|
},
|
||||||
|
"to-regex-range@5.0.1": {
|
||||||
|
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||||
|
"dependencies": [
|
||||||
|
"is-number"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"typescript@5.7.3": {
|
||||||
|
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="
|
||||||
|
},
|
||||||
|
"vite@5.4.13": {
|
||||||
|
"integrity": "sha512-7zp3N4YSjXOSAFfdBe9pPD3FrO398QlJ/5QpFGm3L8xDP1IxDn1XRxArPw4ZKk5394MM8rcTVPY4y1Hvo62bog==",
|
||||||
|
"dependencies": [
|
||||||
|
"esbuild@0.21.5",
|
||||||
|
"fsevents",
|
||||||
|
"postcss",
|
||||||
|
"rollup"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"remote": {
|
||||||
|
"https://wilsonl.in/minify-html/deno/0.15.0/index.js": "8e7ee5067ca84fb5d5a1f33118cac4998de0b7d80b3f56cc5c6728b84e6bfb70"
|
||||||
|
},
|
||||||
|
"workspace": {
|
||||||
|
"dependencies": [
|
||||||
|
"jsr:@luca/esbuild-deno-loader@~0.11.1",
|
||||||
|
"jsr:@std/assert@1",
|
||||||
|
"npm:esbuild-plugin-tsc@0.4",
|
||||||
|
"npm:esbuild@~0.24.2",
|
||||||
|
"npm:fast-glob@^3.3.3",
|
||||||
|
"npm:typescript@^5.7.3"
|
||||||
|
],
|
||||||
|
"members": {
|
||||||
|
"client": {
|
||||||
|
"packageJson": {
|
||||||
|
"dependencies": [
|
||||||
|
"npm:@tauri-apps/api@2",
|
||||||
|
"npm:@tauri-apps/cli@2",
|
||||||
|
"npm:@tauri-apps/plugin-shell@2",
|
||||||
|
"npm:typescript@^5.2.2",
|
||||||
|
"npm:vite@^5.3.1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"server": {
|
||||||
|
"dependencies": [
|
||||||
|
"jsr:@db/sqlite@0.12",
|
||||||
|
"jsr:@eta-dev/eta@^3.5.0",
|
||||||
|
"jsr:@felix/bcrypt@^1.0.5",
|
||||||
|
"jsr:@std/assert@1",
|
||||||
|
"jsr:@std/crypto@^1.0.3",
|
||||||
|
"jsr:@std/dotenv@~0.225.3",
|
||||||
|
"jsr:@std/http@^1.0.12",
|
||||||
|
"npm:esbuild@~0.24.2"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"shared": {
|
||||||
|
"dependencies": [
|
||||||
|
"jsr:@std/assert@1",
|
||||||
|
"jsr:@std/fmt@^1.0.3"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +1,11 @@
|
|||||||
import HttpRouter from "@src/router.ts";
|
import HttpRouter from "@lib/router.ts";
|
||||||
import { Eta } from "@eta-dev/eta";
|
import { Eta } from "@eta-dev/eta";
|
||||||
import { serveFile } from "jsr:@std/http/file-server";
|
import { serveFile } from "jsr:@std/http/file-server";
|
||||||
import rateLimitMiddleware from "@src/middleware/rateLimiter.ts";
|
import rateLimitMiddleware from "@src/middleware/rateLimiter.ts";
|
||||||
import authMiddleware from "@src/middleware/auth.ts";
|
import authMiddleware from "@src/middleware/auth.ts";
|
||||||
|
import { ok, ResultFromJSON } from "@shared/utils/result.ts";
|
||||||
|
import { ResultResponseFromJSON } from "@src/lib/context.ts";
|
||||||
|
import admin from "@src/lib/admin.ts";
|
||||||
|
|
||||||
const router = new HttpRouter();
|
const router = new HttpRouter();
|
||||||
|
|
||||||
@ -12,16 +15,20 @@ const eta = new Eta({ views });
|
|||||||
router.use(rateLimitMiddleware);
|
router.use(rateLimitMiddleware);
|
||||||
router.use(authMiddleware);
|
router.use(authMiddleware);
|
||||||
|
|
||||||
const filesToCache = new Set(["/public/js/shared.bundle.js"]);
|
const cache: Map<string, Response> = new Map();
|
||||||
|
|
||||||
router.get("/public/*", async (c) => {
|
router.get("/public/*", async (c) => {
|
||||||
const filePath = "." + c.path;
|
const filePath = "." + c.path;
|
||||||
|
|
||||||
|
const cached = cache.get(filePath);
|
||||||
|
|
||||||
|
if (cached) {
|
||||||
|
return cached.clone();
|
||||||
|
}
|
||||||
|
|
||||||
const res = await serveFile(c.req, filePath);
|
const res = await serveFile(c.req, filePath);
|
||||||
|
|
||||||
if (filesToCache.has(filePath)) {
|
cache.set(filePath, res.clone());
|
||||||
res.headers.set("Cache-Control", "public max-age=31536000");
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
});
|
});
|
||||||
@ -34,11 +41,9 @@ router
|
|||||||
return c.html(eta.render("./login.html", {}));
|
return c.html(eta.render("./login.html", {}));
|
||||||
})
|
})
|
||||||
.post("/login", async (c) => {
|
.post("/login", async (c) => {
|
||||||
const body = await c.req.text();
|
const r = await ResultFromJSON<{ password: string }>(
|
||||||
|
await c.req.text(),
|
||||||
console.log(JSON.parse(body));
|
);
|
||||||
|
|
||||||
return c.json({ mes: "got you" });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
import{ok as e}from"./shared.bundle.js";const a=e("test");console.log(a);const c=document.getElementById("loginForm"),d=document.getElementById("passwordInput");c.addEventListener("submit",async t=>{t.preventDefault();const s=d.value,o=JSON.stringify(e({password:s}));console.log(o);const n=await(await fetch("/login",{method:"POST",headers:{accept:"application/json"},body:o})).json();console.log(n)});
|
import{ok as n}from"./shared.bundle.js";const s=document.getElementById("loginForm"),a=document.getElementById("passwordInput");s.addEventListener("submit",async t=>{t.preventDefault();const o=a.value,e=JSON.stringify(n({password:o}).toJSON()),r=await(await fetch("/login",{method:"POST",headers:{accept:"application/json"},body:e})).json(),c=8});
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
|||||||
import{none as o}from"./shared.bundle.js";console.log(o);
|
const a=5;
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
import { none, some } from "./shared.bundle.ts";
|
|
||||||
|
|
||||||
console.log(none);
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
import mariadb from "npm:mariadb";
|
|
||||||
import "jsr:@std/dotenv/load";
|
|
||||||
import { getMessageFromError } from "@shared/utils/result.ts";
|
|
||||||
import { QueryExecutionError } from "@src/errors.ts";
|
|
||||||
import { none, some } from "@shared/utils/option.ts";
|
|
||||||
import { ResultAsync } from "@shared/utils/resultasync.ts";
|
|
||||||
import OkPacket from "../../../../../.cache/deno/npm/registry.npmjs.org/mariadb/3.4.0/lib/cmd/class/ok-packet.js";
|
|
||||||
|
|
||||||
export class DB {
|
|
||||||
constructor(private readonly pool: mariadb.Pool) {}
|
|
||||||
|
|
||||||
public query<T>(sql: string, values?: (string | number)[]) {
|
|
||||||
return ResultAsync.fromPromise(this.pool.query<T>(sql, values), (e) => {
|
|
||||||
const errorMessage = getMessageFromError(e);
|
|
||||||
return new QueryExecutionError(errorMessage);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public getAll<T>(sql: string, values?: (string | number)[]) {
|
|
||||||
return this.query<T[]>(sql, values).nullableToOption();
|
|
||||||
}
|
|
||||||
|
|
||||||
public getFirst<T>(sql: string, values?: (string | number)[]) {
|
|
||||||
return this.query<T[]>(sql, values).map((v) =>
|
|
||||||
v[0] ? some(v[0]) : none,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public insert(sql: string, values?: (string | number)[]) {
|
|
||||||
return this.query<OkPacket>(sql, values);
|
|
||||||
}
|
|
||||||
|
|
||||||
public update(sql: string, values?: (string | number)[]) {
|
|
||||||
return this.query<OkPacket>(sql, values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
import { generateMysqlTypes } from "npm:mysql-types-generator";
|
|
||||||
import { loadMariaDBConfigFromEnv } from "@lib/db/db.ts";
|
|
||||||
|
|
||||||
const { host, user, password, port, database } = loadMariaDBConfigFromEnv();
|
|
||||||
|
|
||||||
const dbConfig = {
|
|
||||||
host,
|
|
||||||
port,
|
|
||||||
user,
|
|
||||||
password,
|
|
||||||
database,
|
|
||||||
//ssl: {
|
|
||||||
// rejectUnauthorized: true,
|
|
||||||
//},
|
|
||||||
};
|
|
||||||
|
|
||||||
generateMysqlTypes({
|
|
||||||
db: dbConfig,
|
|
||||||
output: {
|
|
||||||
// Specify only one of the following 2 options:
|
|
||||||
file: "types/types.ts",
|
|
||||||
},
|
|
||||||
//suffix: "PO",
|
|
||||||
ignoreTables: ["_keyborgMigrations"],
|
|
||||||
//overrides: [
|
|
||||||
// {
|
|
||||||
// tableName: "my_table",
|
|
||||||
// columnName: "my_actual_tinyint_column",
|
|
||||||
// columnType: "int",
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// tableName: "my_table",
|
|
||||||
// columnName: "my_column",
|
|
||||||
// columnType: "enum",
|
|
||||||
// enumString: `enum('a','b','c')`,
|
|
||||||
// },
|
|
||||||
//],
|
|
||||||
tinyintIsBoolean: true,
|
|
||||||
});
|
|
||||||
@ -1,90 +0,0 @@
|
|||||||
import {
|
|
||||||
createAndTestMariaDBPool,
|
|
||||||
loadMariaDBConfigFromEnv,
|
|
||||||
} from "@src/db/mariadbCon.ts";
|
|
||||||
import { Migration, MigrationManager } from "@src/db/migrations.ts";
|
|
||||||
import "jsr:@std/dotenv/load";
|
|
||||||
import { DB } from "@src/db/db.ts";
|
|
||||||
|
|
||||||
// ── Establish connection to mariadb ─────────────────────────────────
|
|
||||||
|
|
||||||
const config = loadMariaDBConfigFromEnv();
|
|
||||||
const pool = await createAndTestMariaDBPool(config);
|
|
||||||
|
|
||||||
// ── Migrate database to the latest version ──────────────────────────
|
|
||||||
//#region Migrations
|
|
||||||
const migrations = [
|
|
||||||
Migration.create(
|
|
||||||
"initialize-database",
|
|
||||||
"Creates tables that are essential for the app",
|
|
||||||
[
|
|
||||||
`CREATE TABLE IF NOT EXISTS admin (
|
|
||||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
passwordHash CHAR(60) NOT NULL
|
|
||||||
);`,
|
|
||||||
|
|
||||||
`CREATE TABLE IF NOT EXISTS adminSessions (
|
|
||||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
sessionId CHAR(255) NOT NULL,
|
|
||||||
expiresAt DATETIME NOT NULL
|
|
||||||
);`,
|
|
||||||
|
|
||||||
`CREATE TABLE IF NOT EXISTS users (
|
|
||||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
login VARCHAR(255) NOT NULL UNIQUE,
|
|
||||||
name VARCHAR(512),
|
|
||||||
telegramId VARCHAR(32),
|
|
||||||
passwordHash CHAR(60) NOT NULL,
|
|
||||||
createdAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updatedAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
||||||
deleted TINYINT(1) NOT NULL DEFAULT 0
|
|
||||||
);`,
|
|
||||||
|
|
||||||
`CREATE TABLE IF NOT EXISTS userSessions (
|
|
||||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
userId INT NOT NULL,
|
|
||||||
sessionId CHAR(255) NOT NULL,
|
|
||||||
expiresAt DATETIME NOT NULL DEFAULT (CURRENT_TIMESTAMP + INTERVAL 2 WEEK),
|
|
||||||
FOREIGN KEY (userId) REFERENCES users(id) ON DELETE CASCADE
|
|
||||||
);`,
|
|
||||||
|
|
||||||
`CREATE TABLE IF NOT EXISTS devices (
|
|
||||||
uuid CHAR(36) PRIMARY KEY,
|
|
||||||
busid VARCHAR(512) NOT NULL,
|
|
||||||
usbid CHAR(9) NOT NULL,
|
|
||||||
vendor VARCHAR(1024) NOT NULL DEFAULT 'unknown vendor',
|
|
||||||
deviceName VARCHAR(1024) NOT NULL DEFAULT 'unknown device',
|
|
||||||
displayName VARCHAR(255),
|
|
||||||
description TEXT,
|
|
||||||
connectedAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updatedAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
||||||
);`,
|
|
||||||
|
|
||||||
`CREATE TABLE IF NOT EXISTS deviceEvents (
|
|
||||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
userId INT,
|
|
||||||
targetUuid CHAR(36),
|
|
||||||
event ENUM('attached', 'detached'),
|
|
||||||
occuredAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY(targetUuid) REFERENCES devices(uuid) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY(userId) REFERENCES users(id) ON DELETE CASCADE
|
|
||||||
);`,
|
|
||||||
],
|
|
||||||
[
|
|
||||||
`DROP TABLE IF EXISTS adminSessions;`,
|
|
||||||
`DROP TABLE IF EXISTS admin;`,
|
|
||||||
`DROP TABLE IF EXISTS deviceEvents;`,
|
|
||||||
`DROP TABLE IF EXISTS devices;`,
|
|
||||||
`DROP TABLE IF EXISTS userSessions;`,
|
|
||||||
`DROP TABLE IF EXISTS users;`,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
];
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
await MigrationManager.initialize(pool, config.database, migrations);
|
|
||||||
|
|
||||||
// ── initialize db helper ────────────────────────────────────────────
|
|
||||||
const db = new DB(pool);
|
|
||||||
|
|
||||||
export default db;
|
|
||||||
@ -1,72 +0,0 @@
|
|||||||
import log from "@shared/utils/logger.ts";
|
|
||||||
import * as mariadb from "npm:mariadb";
|
|
||||||
import { getMessageFromError } from "@shared/utils/result.ts";
|
|
||||||
|
|
||||||
interface MariaDBConfig {
|
|
||||||
user: string;
|
|
||||||
password: string;
|
|
||||||
host: string;
|
|
||||||
port: number;
|
|
||||||
database: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function loadMariaDBConfigFromEnv(): MariaDBConfig {
|
|
||||||
const requiredVars = [
|
|
||||||
"KBRG_MYSQL_USER",
|
|
||||||
"KBRG_MYSQL_PASSWORD",
|
|
||||||
"KBRG_MYSQL_DATABASE",
|
|
||||||
];
|
|
||||||
|
|
||||||
const missingVars = requiredVars.filter(
|
|
||||||
(varName) => !Deno.env.has(varName),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (missingVars.length > 0) {
|
|
||||||
log.critical(
|
|
||||||
`Missing required environment variables: ${missingVars.join(", ")}`,
|
|
||||||
);
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const host = Deno.env.get("KBRG_MYSQL_HOST") || "localhost";
|
|
||||||
const user = Deno.env.get("KBRG_MYSQL_USER")!;
|
|
||||||
const password = Deno.env.get("KBRG_MYSQL_PASSWORD")!;
|
|
||||||
const database = Deno.env.get("KBRG_MYSQL_DATABASE")!;
|
|
||||||
const port = parseInt(Deno.env.get("KBRG_MYSQL_PORT") || "3306");
|
|
||||||
|
|
||||||
if (isNaN(port)) {
|
|
||||||
log.critical(`Invalid port number for KBRG_MYSQL_PORT: ${port}`);
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { user, password, host, port, database };
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createAndTestMariaDBPool(
|
|
||||||
config: MariaDBConfig,
|
|
||||||
): Promise<mariadb.Pool> {
|
|
||||||
const { user, password, host, port, database } = config;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const pool = mariadb.createPool({
|
|
||||||
host,
|
|
||||||
port,
|
|
||||||
user,
|
|
||||||
password,
|
|
||||||
database,
|
|
||||||
connectionLimit: 10,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Test Connection
|
|
||||||
const connection = await pool.getConnection();
|
|
||||||
connection.release();
|
|
||||||
|
|
||||||
log.info("Successfully connected to MySQL");
|
|
||||||
|
|
||||||
return pool;
|
|
||||||
} catch (e) {
|
|
||||||
const errorMessage = getMessageFromError(e);
|
|
||||||
log.critical(`Failed to create MySQL pool: ${errorMessage}`);
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,222 +0,0 @@
|
|||||||
import { Pool } from "mariadb";
|
|
||||||
import log from "@shared/utils/logger.ts";
|
|
||||||
import { none, Option, some } from "@shared/utils/option.ts";
|
|
||||||
import { getMessageFromError } from "@shared/utils/result.ts";
|
|
||||||
|
|
||||||
interface MigrationTableEntry {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
step: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Migration {
|
|
||||||
constructor(
|
|
||||||
public readonly name: string,
|
|
||||||
public readonly description: string,
|
|
||||||
public readonly up: string[],
|
|
||||||
public readonly down: string[],
|
|
||||||
public readonly step: number,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
static create = (() => {
|
|
||||||
let step = 0;
|
|
||||||
|
|
||||||
return (
|
|
||||||
name: string,
|
|
||||||
description: string,
|
|
||||||
up: string[],
|
|
||||||
down: string[],
|
|
||||||
) => new Migration(name, description, up, down, step++);
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Exists {
|
|
||||||
table_exists: 0 | 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Count {
|
|
||||||
count: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MIGRATION_TABLE = "_keyborgMigrations";
|
|
||||||
|
|
||||||
export class MigrationManager {
|
|
||||||
constructor(
|
|
||||||
private readonly pool: Pool,
|
|
||||||
private readonly databaseName: string,
|
|
||||||
private readonly migrations: Migration[],
|
|
||||||
) {}
|
|
||||||
|
|
||||||
static async initialize(
|
|
||||||
pool: Pool,
|
|
||||||
databaseName: string,
|
|
||||||
migrations: Migration[],
|
|
||||||
) {
|
|
||||||
const manager = new MigrationManager(pool, databaseName, migrations);
|
|
||||||
|
|
||||||
if (await manager.doesMigrationTableExist()) {
|
|
||||||
if (!(await manager.areMigrationsAppliedInOrder())) {
|
|
||||||
log.critical("Migrations are not applied in order");
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (await manager.hasOtherTables()) {
|
|
||||||
log.critical(
|
|
||||||
"Migration table not found, but the database is not empty. Clean the database or create a new one.",
|
|
||||||
);
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
await manager.createMigrationTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
await manager.migrateToLatest();
|
|
||||||
//await manager.migrateDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async executeOrExit<T>(
|
|
||||||
query: string,
|
|
||||||
params?: string[],
|
|
||||||
): Promise<T[]> {
|
|
||||||
try {
|
|
||||||
const rows = await this.pool.query<T[]>(query, params);
|
|
||||||
return rows;
|
|
||||||
} catch (e) {
|
|
||||||
const errMsg = getMessageFromError(e);
|
|
||||||
log.critical(`Database query failed: ${errMsg}`);
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async doesMigrationTableExist(): Promise<boolean> {
|
|
||||||
const query = `
|
|
||||||
SELECT EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM INFORMATION_SCHEMA.TABLES
|
|
||||||
WHERE TABLE_SCHEMA = ?
|
|
||||||
AND TABLE_NAME = ?
|
|
||||||
) AS table_exists;
|
|
||||||
`;
|
|
||||||
const params = [this.databaseName, MIGRATION_TABLE];
|
|
||||||
const [result] = await this.executeOrExit<Exists>(query, params);
|
|
||||||
return result.table_exists === 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
async hasOtherTables(): Promise<boolean> {
|
|
||||||
const query = `
|
|
||||||
SELECT COUNT(*) AS count
|
|
||||||
FROM INFORMATION_SCHEMA.TABLES
|
|
||||||
WHERE TABLE_SCHEMA = ?
|
|
||||||
AND TABLE_NAME != ?;
|
|
||||||
`;
|
|
||||||
const params = [this.databaseName, MIGRATION_TABLE];
|
|
||||||
const [result] = await this.executeOrExit<Count>(query, params);
|
|
||||||
return result.count > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getAppliedMigrations(): Promise<
|
|
||||||
Option<MigrationTableEntry[]>
|
|
||||||
> {
|
|
||||||
const query = `SELECT * FROM ${MIGRATION_TABLE} ORDER BY step ASC;`;
|
|
||||||
const migrations = await this.executeOrExit<MigrationTableEntry>(query);
|
|
||||||
|
|
||||||
return migrations ? none : some(migrations);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getLastAppliedMigration(): Promise<
|
|
||||||
Option<MigrationTableEntry>
|
|
||||||
> {
|
|
||||||
const query = `
|
|
||||||
SELECT * FROM ${MIGRATION_TABLE}
|
|
||||||
ORDER BY step DESC
|
|
||||||
LIMIT 1;
|
|
||||||
`;
|
|
||||||
const [migration] =
|
|
||||||
await this.executeOrExit<MigrationTableEntry>(query);
|
|
||||||
return migration ? some(migration) : none;
|
|
||||||
}
|
|
||||||
|
|
||||||
async areMigrationsAppliedInOrder(): Promise<boolean> {
|
|
||||||
const migrations = await this.getAppliedMigrations();
|
|
||||||
|
|
||||||
if (migrations.isSome()) {
|
|
||||||
let step = 0;
|
|
||||||
for (const migration of migrations.value) {
|
|
||||||
if (migration.step !== step++) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async createMigrationTable() {
|
|
||||||
const query = `
|
|
||||||
CREATE TABLE ? (
|
|
||||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
name VARCHAR(256) NOT NULL,
|
|
||||||
description TEXT,
|
|
||||||
step INT NOT NULL,
|
|
||||||
appliedAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
`;
|
|
||||||
|
|
||||||
await this.executeOrExit(query, { MIGRATION_TABLE });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an Some(number) where number represents the step of an applied migration or none if no migrations were applied
|
|
||||||
*/
|
|
||||||
private async applyMigration(migration: Migration) {
|
|
||||||
for (const statement of migration.up) {
|
|
||||||
await this.executeOrExit(statement);
|
|
||||||
}
|
|
||||||
const query = `
|
|
||||||
INSERT INTO ${MIGRATION_TABLE} (name, description, step)
|
|
||||||
VALUES (?, ?, ?);
|
|
||||||
`;
|
|
||||||
await this.executeOrExit(query, [
|
|
||||||
migration.name,
|
|
||||||
migration.description,
|
|
||||||
migration.step.toString(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async destroyMigration(migration: Migration) {
|
|
||||||
for (const statement of migration.down) {
|
|
||||||
await this.executeOrExit(statement);
|
|
||||||
}
|
|
||||||
const query = `
|
|
||||||
DELETE FROM ${MIGRATION_TABLE} WHERE step = ?;
|
|
||||||
`;
|
|
||||||
await this.executeOrExit(query, [migration.step.toString()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async migrateToLatest() {
|
|
||||||
const lastAppliedMigration = (
|
|
||||||
await this.getLastAppliedMigration()
|
|
||||||
).toNullable();
|
|
||||||
let nextStep = lastAppliedMigration ? lastAppliedMigration.step + 1 : 0;
|
|
||||||
|
|
||||||
while (nextStep < this.migrations.length) {
|
|
||||||
const migration = this.migrations[nextStep];
|
|
||||||
await this.applyMigration(migration);
|
|
||||||
nextStep++;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("All migrations have been applied.");
|
|
||||||
}
|
|
||||||
|
|
||||||
async migrateDown() {
|
|
||||||
const lastAppliedMigration = await this.getLastAppliedMigration();
|
|
||||||
|
|
||||||
if (lastAppliedMigration.isNone()) {
|
|
||||||
log.warn(
|
|
||||||
"Cannot migrate down because no migrations has been applied",
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const step = lastAppliedMigration.value.step;
|
|
||||||
this.destroyMigration(this.migrations[step]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
/**
|
|
||||||
* This file is auto-generated and should not be edited.
|
|
||||||
* It will be overwritten the next time types are generated.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export type Admin = {
|
|
||||||
id: number;
|
|
||||||
passwordHash: string;
|
|
||||||
};
|
|
||||||
export type AdminSessions = {
|
|
||||||
id: number;
|
|
||||||
sessionId: string;
|
|
||||||
expiresAt: Date;
|
|
||||||
};
|
|
||||||
export type Users = {
|
|
||||||
id: number;
|
|
||||||
login: string;
|
|
||||||
name: string | null;
|
|
||||||
telegramId: string | null;
|
|
||||||
passwordHash: string;
|
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
deleted: boolean;
|
|
||||||
};
|
|
||||||
export type UserSessions = {
|
|
||||||
id: number;
|
|
||||||
userId: number;
|
|
||||||
sessionId: string;
|
|
||||||
expiresAt: Date;
|
|
||||||
};
|
|
||||||
export type Devices = {
|
|
||||||
uuid: string;
|
|
||||||
busid: string;
|
|
||||||
usbid: string;
|
|
||||||
vendor: string;
|
|
||||||
deviceName: string;
|
|
||||||
displayName: string | null;
|
|
||||||
description: string | null;
|
|
||||||
connectedAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
};
|
|
||||||
export type DeviceEvents = {
|
|
||||||
id: number;
|
|
||||||
userId: number | null;
|
|
||||||
targetUuid: string | null;
|
|
||||||
event: "attached" | "detached" | null;
|
|
||||||
occuredAt: Date;
|
|
||||||
};
|
|
||||||
@ -2,10 +2,6 @@
|
|||||||
|
|
||||||
import { ok } from "./shared.bundle.ts";
|
import { ok } from "./shared.bundle.ts";
|
||||||
|
|
||||||
const a = ok("test");
|
|
||||||
|
|
||||||
console.log(a);
|
|
||||||
|
|
||||||
const form = document.getElementById("loginForm") as HTMLFormElement;
|
const form = document.getElementById("loginForm") as HTMLFormElement;
|
||||||
const passwordInput = document.getElementById(
|
const passwordInput = document.getElementById(
|
||||||
"passwordInput",
|
"passwordInput",
|
||||||
@ -16,11 +12,11 @@ form.addEventListener("submit", async (e) => {
|
|||||||
|
|
||||||
const password = passwordInput.value;
|
const password = passwordInput.value;
|
||||||
|
|
||||||
const bodyReq = JSON.stringify(ok({
|
const bodyReq = JSON.stringify(
|
||||||
|
ok({
|
||||||
password: password,
|
password: password,
|
||||||
}));
|
}).toJSON(),
|
||||||
|
);
|
||||||
console.log(bodyReq);
|
|
||||||
|
|
||||||
const response = await fetch("/login", {
|
const response = await fetch("/login", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@ -30,5 +26,5 @@ form.addEventListener("submit", async (e) => {
|
|||||||
|
|
||||||
const body = await response.json();
|
const body = await response.json();
|
||||||
|
|
||||||
console.log(body);
|
const a = 8;
|
||||||
});
|
});
|
||||||
1
server/src/js/test.js
Normal file
1
server/src/js/test.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
const a = 5;
|
||||||
@ -1,9 +1,9 @@
|
|||||||
import { Option, some } from "@shared/utils/option.ts";
|
import { Option, some } from "@shared/utils/option.ts";
|
||||||
import db from "@src/db/index.ts";
|
import db from "@lib/db/index.ts";
|
||||||
import { ok, Result } from "@shared/utils/result.ts";
|
import { ok, Result } from "@shared/utils/result.ts";
|
||||||
import { AdminPasswordNotSetError, QueryExecutionError } from "@src/errors.ts";
|
import { AdminPasswordNotSetError, QueryExecutionError } from "@lib/errors.ts";
|
||||||
import { AdminRaw, AdminSessionRaw } from "@src/db/types/index.ts";
|
import { AdminRaw, AdminSessionRaw } from "@lib/db/types/index.ts";
|
||||||
import { generateRandomString, passwd } from "@src/utils.ts";
|
import { generateRandomString, passwd } from "@lib/utils.ts";
|
||||||
import { errAsync, ResultAsync } from "@shared/utils/resultasync.ts";
|
import { errAsync, ResultAsync } from "@shared/utils/resultasync.ts";
|
||||||
|
|
||||||
const TOKEN_LENGTH = 128;
|
const TOKEN_LENGTH = 128;
|
||||||
@ -113,10 +113,13 @@ class AdminSessions {
|
|||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
this.clearExpiredSessions().match(
|
this.clearExpiredSessions().match(
|
||||||
(clearedCount) => {
|
(clearedCount) => {
|
||||||
if (clearedCount > 0)
|
if (clearedCount > 0) {
|
||||||
console.info(
|
console.info(
|
||||||
`cleared ${clearedCount} expired token${clearedCount === 1 ? "" : "s"}`,
|
`cleared ${clearedCount} expired token${
|
||||||
|
clearedCount === 1 ? "" : "s"
|
||||||
|
}`,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
(error) =>
|
(error) =>
|
||||||
console.error(`failed to clear expired tokens: ${error}`),
|
console.error(`failed to clear expired tokens: ${error}`),
|
||||||
@ -164,7 +167,7 @@ class AdminSessions {
|
|||||||
return this.deleteSessionById(session.id).map(() => false);
|
return this.deleteSessionById(session.id).map(() => false);
|
||||||
},
|
},
|
||||||
() => ok(false),
|
() => ok(false),
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1,8 +1,9 @@
|
|||||||
import { type Params } from "@src/router.ts";
|
import { type Params } from "@lib/router.ts";
|
||||||
import { ExtractRouteParams } from "@src/router.ts";
|
import { type ExtractRouteParams } from "@lib/router.ts";
|
||||||
import { fromNullableVal, none, Option, some } from "@shared/utils/option.ts";
|
import { fromNullableVal, none, Option, some } from "@shared/utils/option.ts";
|
||||||
import { deleteCookie, getCookies, setCookie } from "@std/http/cookie";
|
import { deleteCookie, getCookies, setCookie } from "@std/http/cookie";
|
||||||
import { type Cookie } from "@std/http/cookie";
|
import { type Cookie } from "@std/http/cookie";
|
||||||
|
import { Err, Ok, type Result, ResultFromJSON } from "@shared/utils/result.ts";
|
||||||
|
|
||||||
// https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html
|
// https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html
|
||||||
const SECURITY_HEADERS: Headers = new Headers({
|
const SECURITY_HEADERS: Headers = new Headers({
|
||||||
@ -120,7 +121,7 @@ export class Context<S extends string = string> {
|
|||||||
responseBody = JSON.stringify({
|
responseBody = JSON.stringify({
|
||||||
err: "Internal Server Error",
|
err: "Internal Server Error",
|
||||||
});
|
});
|
||||||
status = 400;
|
status = 500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { Database, RestBindParameters, Statement } from "@db/sqlite";
|
import { Database, RestBindParameters } from "@db/sqlite";
|
||||||
import { err, getMessageFromError, ok, Result } from "@shared/utils/result.ts";
|
import { err, getMessageFromError, ok, Result } from "@shared/utils/result.ts";
|
||||||
import { QueryExecutionError } from "@src/errors.ts";
|
import { QueryExecutionError } from "@lib/errors.ts";
|
||||||
import { fromNullableVal, none, Option, some } from "@shared/utils/option.ts";
|
import { fromNullableVal, none, Option, some } from "@shared/utils/option.ts";
|
||||||
|
|
||||||
export class DatabaseClient {
|
export class DatabaseClient {
|
||||||
@ -27,7 +27,7 @@ export class DatabaseClient {
|
|||||||
...params: RestBindParameters
|
...params: RestBindParameters
|
||||||
): Result<Option<T>, QueryExecutionError> {
|
): Result<Option<T>, QueryExecutionError> {
|
||||||
return this.safeExecute(() =>
|
return this.safeExecute(() =>
|
||||||
fromNullableVal(this.db.prepare(sql).get<T>(params)),
|
fromNullableVal(this.db.prepare(sql).get<T>(params))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ export class DatabaseClient {
|
|||||||
...params: P
|
...params: P
|
||||||
): Result<Option<NonNullable<T[]>>, QueryExecutionError> =>
|
): Result<Option<NonNullable<T[]>>, QueryExecutionError> =>
|
||||||
this.safeExecute(() => stmt.all<T>(params)).map((result) =>
|
this.safeExecute(() => stmt.all<T>(params)).map((result) =>
|
||||||
result.length > 0 ? some(result) : none,
|
result.length > 0 ? some(result) : none
|
||||||
);
|
);
|
||||||
|
|
||||||
return { get, all };
|
return { get, all };
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { Migration, MigrationManager } from "@src/db/migrations.ts";
|
import { Migration, MigrationManager } from "@lib/db/migrations.ts";
|
||||||
|
import { DatabaseClient } from "@lib/db/dbWrapper.ts";
|
||||||
import { Database } from "@db/sqlite";
|
import { Database } from "@db/sqlite";
|
||||||
import { DatabaseClient } from "@src/db/dbWrapper.ts";
|
|
||||||
|
|
||||||
const sqliteDbPath = Deno.env.get("KBRG_SQLITE_DB_PATH") || "./test.db";
|
const sqliteDbPath = Deno.env.get("KBRG_SQLITE_DB_PATH") || "./test.db";
|
||||||
|
|
||||||
@ -107,7 +107,6 @@ const migrationManager = new MigrationManager(
|
|||||||
WHERE id = OLD.id;
|
WHERE id = OLD.id;
|
||||||
END;`,
|
END;`,
|
||||||
],
|
],
|
||||||
|
|
||||||
[
|
[
|
||||||
`DELETE TABLE admin;`,
|
`DELETE TABLE admin;`,
|
||||||
`DELETE TABLE adminSessions;`,
|
`DELETE TABLE adminSessions;`,
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { RouterTree } from "@src/routerTree.ts";
|
import { RouterTree } from "@lib/routerTree.ts";
|
||||||
import { none, Option, some } from "@shared/utils/option.ts";
|
import { none, Option, some } from "@shared/utils/option.ts";
|
||||||
import { Context } from "@src/context.ts";
|
import { Context } from "@lib/context.ts";
|
||||||
|
|
||||||
type RequestHandler<S extends string> = (
|
type RequestHandler<S extends string> = (
|
||||||
c: Context<S>,
|
c: Context<S>,
|
||||||
@ -1,10 +1,9 @@
|
|||||||
import { Middleware } from "@src/router.ts";
|
import { Middleware } from "@lib/router.ts";
|
||||||
import { none } from "@shared/utils/option.ts";
|
import admin from "@lib/admin.ts";
|
||||||
import admin from "@src/admin.ts";
|
|
||||||
|
|
||||||
const LOGIN_PATH = "/login";
|
const LOGIN_PATH = "/login";
|
||||||
|
|
||||||
const authMiddleware: Middleware = async (c) => {
|
const authMiddleware: Middleware = (c) => {
|
||||||
const token = c.cookies.get("token");
|
const token = c.cookies.get("token");
|
||||||
const isValid = token
|
const isValid = token
|
||||||
.map((token) => admin.sessions.verifyToken(token))
|
.map((token) => admin.sessions.verifyToken(token))
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { Middleware } from "@src/router.ts";
|
import { Middleware } from "@lib/router.ts";
|
||||||
import { none, some } from "@shared/utils/option.ts";
|
|
||||||
|
|
||||||
const requestCounts: Partial<
|
const requestCounts: Partial<
|
||||||
Record<string, { count: number; lastReset: number }>
|
Record<string, { count: number; lastReset: number }>
|
||||||
|
|||||||
@ -1,49 +0,0 @@
|
|||||||
type Methods = "GET" | "POST";
|
|
||||||
|
|
||||||
class RequestHandler {
|
|
||||||
constructor(
|
|
||||||
public readonly method: Methods,
|
|
||||||
public readonly handler: (req: Request) => Response,
|
|
||||||
) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Router {
|
|
||||||
handlers: {
|
|
||||||
[x in string]: {
|
|
||||||
[method in Methods]?: RequestHandler;
|
|
||||||
};
|
|
||||||
} = {};
|
|
||||||
|
|
||||||
add<T extends string>(
|
|
||||||
route: T,
|
|
||||||
method: "GET" | "POST",
|
|
||||||
handler: (req: Request, params: Params<ExtractParams<T>>) => Response,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
handleRequest(req: Request) {
|
|
||||||
const url = new URL(req.url);
|
|
||||||
const pathname = url.pathname;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const router = new Router();
|
|
||||||
|
|
||||||
router.add("/users/:slug", "POST", (req, params) => {
|
|
||||||
const _url = req.url;
|
|
||||||
|
|
||||||
return new Response(_url);
|
|
||||||
});
|
|
||||||
|
|
||||||
type Params<Keys extends string> = Keys extends never
|
|
||||||
? never
|
|
||||||
: {
|
|
||||||
[K in Keys]: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ExtractParams<T extends string> = T extends string
|
|
||||||
? T extends `${infer _Start}:${infer Param}/${infer Rest}`
|
|
||||||
? Param | ExtractParams<Rest>
|
|
||||||
: T extends `${infer _Start}:${infer Param}`
|
|
||||||
? Param
|
|
||||||
: never
|
|
||||||
: never;
|
|
||||||
3
server/src/views/index.html
Normal file
3
server/src/views/index.html
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<% layout("./layouts/layout.html") %>
|
||||||
|
|
||||||
|
this is an index.html
|
||||||
15
server/src/views/layouts/basic.html
Normal file
15
server/src/views/layouts/basic.html
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Keyborg</title>
|
||||||
|
<link href="/public/css/style.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<%~ it.body %>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
7
server/src/views/layouts/layout.html
Normal file
7
server/src/views/layouts/layout.html
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<% layout("./basic.html") %>
|
||||||
|
|
||||||
|
<header></header>
|
||||||
|
<main>
|
||||||
|
<%~ it.body %>
|
||||||
|
</main>
|
||||||
|
<footer></footer>
|
||||||
7
server/src/views/login.html
Normal file
7
server/src/views/login.html
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<% layout("./layouts/basic.html") %>
|
||||||
|
<main>
|
||||||
|
<form id=loginForm method=POST>
|
||||||
|
<p>password</p><input id=passwordInput name=password type=password><input value="sign in" type=submit>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
<script defer src=/public/js/login.js type=module></script>
|
||||||
15
server/src/views/test.html
Normal file
15
server/src/views/test.html
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title></title>
|
||||||
|
<link href="css/style.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
166
server/src/wasm_example/Cargo.lock
generated
166
server/src/wasm_example/Cargo.lock
generated
@ -1,166 +0,0 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
version = 4
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bumpalo"
|
|
||||||
version = "3.16.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfg-if"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "js-sys"
|
|
||||||
version = "0.3.76"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7"
|
|
||||||
dependencies = [
|
|
||||||
"once_cell",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "log"
|
|
||||||
version = "0.4.22"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "once_cell"
|
|
||||||
version = "1.20.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro2"
|
|
||||||
version = "1.0.92"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quote"
|
|
||||||
version = "1.0.38"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde"
|
|
||||||
version = "1.0.217"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
|
||||||
dependencies = [
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde-wasm-bindgen"
|
|
||||||
version = "0.4.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf"
|
|
||||||
dependencies = [
|
|
||||||
"js-sys",
|
|
||||||
"serde",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_derive"
|
|
||||||
version = "1.0.217"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "2.0.95"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-ident"
|
|
||||||
version = "1.0.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen"
|
|
||||||
version = "0.2.99"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"once_cell",
|
|
||||||
"wasm-bindgen-macro",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-backend"
|
|
||||||
version = "0.2.99"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
|
|
||||||
dependencies = [
|
|
||||||
"bumpalo",
|
|
||||||
"log",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
"wasm-bindgen-shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-macro"
|
|
||||||
version = "0.2.99"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
|
|
||||||
dependencies = [
|
|
||||||
"quote",
|
|
||||||
"wasm-bindgen-macro-support",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-macro-support"
|
|
||||||
version = "0.2.99"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
"wasm-bindgen-backend",
|
|
||||||
"wasm-bindgen-shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-shared"
|
|
||||||
version = "0.2.99"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm_example"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
"serde-wasm-bindgen",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "wasm_example"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
wasm-bindgen = "0.2.99"
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
|
||||||
serde-wasm-bindgen = "0.4"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
crate-type = ["cdylib"]
|
|
||||||
@ -1,309 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use wasm_bindgen::prelude::*;
|
|
||||||
|
|
||||||
type Handler = Option<JsValue>;
|
|
||||||
type Params = HashMap<String, String>;
|
|
||||||
|
|
||||||
const PARAM_PREFIX_DEFAULT: &str = ":";
|
|
||||||
const PATH_SEPARATOR_DEFAULT: &str = "/";
|
|
||||||
const WILDCARD_SYMBOL_DEFAULT: char = '*';
|
|
||||||
|
|
||||||
enum TreeNode<'a> {
|
|
||||||
Static(&'a StaticTreeNode),
|
|
||||||
Dynamic(&'a DynamicTreeNode),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> TreeNode<'a> {
|
|
||||||
pub fn extract_static_node(&self) -> &StaticTreeNode {
|
|
||||||
match self {
|
|
||||||
Self::Static(n) => n,
|
|
||||||
Self::Dynamic(n) => &n.node,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum TreeNodeMut<'a> {
|
|
||||||
Static(&'a mut StaticTreeNode),
|
|
||||||
Dynamic(&'a mut DynamicTreeNode),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TreeNodeMut<'_> {
|
|
||||||
pub fn extract_static_node(&mut self) -> &mut StaticTreeNode {
|
|
||||||
match self {
|
|
||||||
Self::Static(n) => n,
|
|
||||||
Self::Dynamic(n) => &mut n.node,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct StaticTreeNode {
|
|
||||||
handler: Handler,
|
|
||||||
wildcard_handler: Handler,
|
|
||||||
|
|
||||||
static_children: HashMap<String, StaticTreeNode>,
|
|
||||||
dynamic_child: Option<Box<DynamicTreeNode>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DynamicTreeNode {
|
|
||||||
node: StaticTreeNode,
|
|
||||||
param_name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DynamicTreeNode {
|
|
||||||
pub fn new(handler: Handler, param_name: &str) -> Self {
|
|
||||||
DynamicTreeNode {
|
|
||||||
node: StaticTreeNode::new(handler),
|
|
||||||
param_name: param_name.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StaticTreeNode {
|
|
||||||
pub fn new(handler: Handler) -> Self {
|
|
||||||
StaticTreeNode {
|
|
||||||
handler,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_static_child(&mut self, segment: &str, handler: Handler) {
|
|
||||||
let child = StaticTreeNode::new(handler);
|
|
||||||
|
|
||||||
self.static_children.insert(segment.to_string(), child);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete_static_child(&mut self, segment: &str) -> Option<StaticTreeNode> {
|
|
||||||
self.static_children.remove(segment)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_dynamic_child(&mut self, param_name: &str, handler: Handler) {
|
|
||||||
let child = DynamicTreeNode::new(handler, param_name);
|
|
||||||
|
|
||||||
self.dynamic_child = Some(Box::new(child));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete_dynamic_child(&mut self) {
|
|
||||||
self.dynamic_child = None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_wildcard_handler(&mut self, handler: Handler) {
|
|
||||||
self.wildcard_handler = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete_wildcard_handler(&mut self) {
|
|
||||||
self.wildcard_handler = None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_static_child(&self, segment: &str) -> Option<&StaticTreeNode> {
|
|
||||||
self.static_children.get(segment)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_static_child(&self, segment: &str) -> bool {
|
|
||||||
self.static_children.contains_key(segment)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_dynamic_child(&self) -> Option<&DynamicTreeNode> {
|
|
||||||
self.dynamic_child.as_ref().map(|n| n.as_ref())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_dynamic_child(&self) -> bool {
|
|
||||||
match self.dynamic_child {
|
|
||||||
Some(_) => true,
|
|
||||||
None => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_child(&self, segment: &str) -> Option<TreeNode> {
|
|
||||||
self.get_static_child(segment)
|
|
||||||
.map(|n| TreeNode::Static(&n))
|
|
||||||
.or_else(|| self.get_dynamic_child().map(|n| TreeNode::Dynamic(n)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_static_child_mut(&mut self, segment: &str) -> Option<&mut StaticTreeNode> {
|
|
||||||
self.static_children.get_mut(segment)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_dynamic_child_mut(&mut self) -> Option<&mut DynamicTreeNode> {
|
|
||||||
self.dynamic_child.as_mut().map(|n| n.as_mut())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_child_mut(&mut self, segment: &str) -> Option<TreeNodeMut> {
|
|
||||||
let static_child = self
|
|
||||||
.static_children
|
|
||||||
.get_mut(segment)
|
|
||||||
.map(|c| TreeNodeMut::Static(c));
|
|
||||||
|
|
||||||
if let Some(static_child) = static_child {
|
|
||||||
Some(static_child)
|
|
||||||
} else {
|
|
||||||
self.dynamic_child.as_mut().map(|n| TreeNodeMut::Dynamic(n))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TraversePathReturn<'a> {
|
|
||||||
node: &'a StaticTreeNode,
|
|
||||||
params: Params,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TraversePathReturn<'_> {
|
|
||||||
pub fn extract_handler(&self) -> Option<HandlerAndParams> {
|
|
||||||
self.node.handler.as_ref().map(|handler| HandlerAndParams {
|
|
||||||
handler: handler.clone(),
|
|
||||||
params: serde_wasm_bindgen::to_value(&self.params).unwrap(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn js_value_to_option(js_value: JsValue) -> Handler {
|
|
||||||
if js_value.is_undefined() || js_value.is_null() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(js_value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
struct HandlerAndParams {
|
|
||||||
handler: JsValue,
|
|
||||||
params: JsValue,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
impl HandlerAndParams {
|
|
||||||
#[wasm_bindgen(getter)]
|
|
||||||
pub fn handler(&self) -> JsValue {
|
|
||||||
self.handler.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(getter)]
|
|
||||||
pub fn params(&self) -> JsValue {
|
|
||||||
self.params.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
struct RouterTree {
|
|
||||||
root: StaticTreeNode,
|
|
||||||
path_separator: String,
|
|
||||||
param_prefix: String,
|
|
||||||
wildcard_symbol: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
impl RouterTree {
|
|
||||||
#[wasm_bindgen(constructor)]
|
|
||||||
pub fn new(
|
|
||||||
handler: JsValue,
|
|
||||||
param_prefix: Option<String>,
|
|
||||||
path_separator: Option<String>,
|
|
||||||
wildcard_symbol: Option<String>,
|
|
||||||
) -> Self {
|
|
||||||
let root = StaticTreeNode::new(js_value_to_option(handler));
|
|
||||||
|
|
||||||
RouterTree {
|
|
||||||
root,
|
|
||||||
path_separator: path_separator.unwrap_or(PATH_SEPARATOR_DEFAULT.to_string()),
|
|
||||||
param_prefix: param_prefix.unwrap_or(PARAM_PREFIX_DEFAULT.to_string()),
|
|
||||||
wildcard_symbol: wildcard_symbol.unwrap_or(WILDCARD_SYMBOL_DEFAULT.to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn add(&mut self, path: String, handler: JsValue) {
|
|
||||||
let segments = self.parse_path(&path);
|
|
||||||
let param_prefix = self.param_prefix.as_str();
|
|
||||||
let mut current_node = &mut self.root;
|
|
||||||
|
|
||||||
for segment in segments {
|
|
||||||
current_node = if RouterTree::is_dynamic_segment(segment, param_prefix) {
|
|
||||||
let param_name = RouterTree::strip_param_prefix(segment, param_prefix);
|
|
||||||
|
|
||||||
if !current_node.has_dynamic_child() {
|
|
||||||
current_node.set_dynamic_child(param_name, None);
|
|
||||||
}
|
|
||||||
&mut current_node.get_dynamic_child_mut().unwrap().node
|
|
||||||
} else {
|
|
||||||
if !current_node.has_static_child(segment) {
|
|
||||||
current_node.add_static_child(segment, None);
|
|
||||||
}
|
|
||||||
current_node.get_static_child_mut(segment).unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
current_node.handler = js_value_to_option(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn get(&self, path: String) -> Option<HandlerAndParams> {
|
|
||||||
self.traverse_path(&path)
|
|
||||||
.map(|v| v.extract_handler())
|
|
||||||
.flatten()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn traverse_path(&self, path: &String) -> Option<TraversePathReturn> {
|
|
||||||
let segments = self.parse_path(path);
|
|
||||||
let mut params: Params = HashMap::new();
|
|
||||||
let mut current_node = &self.root;
|
|
||||||
|
|
||||||
for segment in segments {
|
|
||||||
if current_node.wildcard_handler.is_some() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if let Some(child) = current_node.get_child(segment) {
|
|
||||||
match child {
|
|
||||||
TreeNode::Static(node) => current_node = node,
|
|
||||||
TreeNode::Dynamic(node) => {
|
|
||||||
params.insert(node.param_name.clone(), segment.to_string());
|
|
||||||
current_node = &node.node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Some(TraversePathReturn {
|
|
||||||
node: current_node,
|
|
||||||
params,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_path<'a>(&self, path: &'a String) -> Vec<&'a str> {
|
|
||||||
path.trim_start_matches(&self.path_separator)
|
|
||||||
.split(&self.path_separator)
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_root_node(&self) -> TreeNode {
|
|
||||||
TreeNode::Static(&self.root)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_dynamic_segment(segment: &str, param_prefix: &str) -> bool {
|
|
||||||
segment.starts_with(param_prefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn strip_param_prefix<'a>(segment: &'a str, param_prefix: &str) -> &'a str {
|
|
||||||
segment.strip_prefix(param_prefix).unwrap_or("")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn it_works() {
|
|
||||||
let mut router = RouterTree::new(JsValue::null(), None, None, None);
|
|
||||||
|
|
||||||
router.add("/user/:id".to_string(), JsValue::null());
|
|
||||||
|
|
||||||
let result = router.get("/user/123".to_string());
|
|
||||||
|
|
||||||
dbg!("I am here!!!");
|
|
||||||
|
|
||||||
assert_eq!(1, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BIN
server/test.db
BIN
server/test.db
Binary file not shown.
@ -1,3 +1 @@
|
|||||||
<% layout("./layouts/layout.html") %>
|
<% layout("./layouts/layout.html") %> this is an index.html
|
||||||
|
|
||||||
this is an index.html
|
|
||||||
@ -1,17 +1 @@
|
|||||||
<!DOCTYPE html>
|
<!doctypehtml><html lang=en><meta charset=UTF-8><meta content=width=device-width,initial-scale=1 name=viewport><title>Keyborg</title><link href=/public/css/style.css rel=stylesheet><body><%~ it.body %>
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>Keyborg</title>
|
|
||||||
<link href="/public/css/style.css" rel="stylesheet">
|
|
||||||
<script type="module" src="/public/js/shared.bundle.js"></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<%~ it.body %>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@ -1,7 +1 @@
|
|||||||
<% layout("./basic.html") %>
|
<% layout("./basic.html") %> <header></header><main><%~ it.body %></main><footer></footer>
|
||||||
|
|
||||||
<header></header>
|
|
||||||
<main>
|
|
||||||
<%~ it.body %>
|
|
||||||
</main>
|
|
||||||
<footer></footer>
|
|
||||||
@ -1,11 +1 @@
|
|||||||
<% layout("./layouts/basic.html") %>
|
<% layout("./layouts/basic.html") %> <main><form id=loginForm method=POST><p>password</p><input id=passwordInput name=password type=password><input value="sign in" type=submit></form></main><script defer src=/public/js/login.js type=module></script>
|
||||||
|
|
||||||
<main>
|
|
||||||
<form method="POST" id="loginForm">
|
|
||||||
<p>password</p>
|
|
||||||
<input type="password" name="password" id="passwordInput">
|
|
||||||
<input type="submit" value="sign in">
|
|
||||||
</form>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<script type="module" src="/public/js/login.js" defer></script>
|
|
||||||
Reference in New Issue
Block a user