working bundling

This commit is contained in:
2025-01-22 20:05:24 +03:00
parent f8012d9b78
commit e024187a18
46 changed files with 792 additions and 1225 deletions

View File

@ -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) {
if (
event.kind === "modify" || event.kind === "create" ||
event.kind === "remove"
) {
try {
await bundle();
} catch (e) {
console.log(e);
const cwd = Deno.cwd();
function getPublic(paths: string[]): string[] {
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 {
const content = await Deno.readTextFile(path);
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 (_) {}
}
}
})();

View File

@ -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;

View File

@ -6,15 +6,13 @@
"@db/sqlite": "jsr:@db/sqlite@^0.12.0",
"@eta-dev/eta": "jsr:@eta-dev/eta@^3.5.0",
"@felix/bcrypt": "jsr:@felix/bcrypt@^1.0.5",
"@minify-html/node": "npm:@minify-html/node@^0.15.0",
"@ryanflorence/sqlite-typegen": "npm:@ryanflorence/sqlite-typegen@^0.2.0",
"@minify-html/node": "npm:@minify-html/node-linux-x64@^0.15.0",
"@std/assert": "jsr:@std/assert@1",
"@std/crypto": "jsr:@std/crypto@^1.0.3",
"@std/dotenv": "jsr:@std/dotenv@^0.225.3",
"@std/http": "jsr:@std/http@^1.0.12",
"@lib/": "./src/lib/",
"@src/": "./src/",
"better-sqlite3": "npm:better-sqlite3@^11.8.0",
"esbuild": "npm:esbuild@^0.24.2",
"mariadb": "npm:mariadb@^3.4.0"
"esbuild": "npm:esbuild@^0.24.2"
}
}

563
server/lock.json Normal file
View 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"
]
}
}
}
}

View File

@ -1,8 +1,11 @@
import HttpRouter from "@src/router.ts";
import HttpRouter from "@lib/router.ts";
import { Eta } from "@eta-dev/eta";
import { serveFile } from "jsr:@std/http/file-server";
import rateLimitMiddleware from "@src/middleware/rateLimiter.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();
@ -12,16 +15,20 @@ const eta = new Eta({ views });
router.use(rateLimitMiddleware);
router.use(authMiddleware);
const filesToCache = new Set(["/public/js/shared.bundle.js"]);
const cache: Map<string, Response> = new Map();
router.get("/public/*", async (c) => {
const filePath = "." + c.path;
const cached = cache.get(filePath);
if (cached) {
return cached.clone();
}
const res = await serveFile(c.req, filePath);
if (filesToCache.has(filePath)) {
res.headers.set("Cache-Control", "public max-age=31536000");
}
cache.set(filePath, res.clone());
return res;
});
@ -34,11 +41,9 @@ router
return c.html(eta.render("./login.html", {}));
})
.post("/login", async (c) => {
const body = await c.req.text();
console.log(JSON.parse(body));
return c.json({ mes: "got you" });
const r = await ResultFromJSON<{ password: string }>(
await c.req.text(),
);
});
export default {

View File

@ -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

View File

@ -1 +1 @@
import{none as o}from"./shared.bundle.js";console.log(o);
const a=5;

View File

@ -1,3 +0,0 @@
import { none, some } from "./shared.bundle.ts";
console.log(none);

View File

@ -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);
}
}

View File

@ -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,
});

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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]);
}
}
}

View File

@ -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;
};

View File

@ -2,10 +2,6 @@
import { ok } from "./shared.bundle.ts";
const a = ok("test");
console.log(a);
const form = document.getElementById("loginForm") as HTMLFormElement;
const passwordInput = document.getElementById(
"passwordInput",
@ -16,11 +12,11 @@ form.addEventListener("submit", async (e) => {
const password = passwordInput.value;
const bodyReq = JSON.stringify(ok({
password: password,
}));
console.log(bodyReq);
const bodyReq = JSON.stringify(
ok({
password: password,
}).toJSON(),
);
const response = await fetch("/login", {
method: "POST",
@ -30,5 +26,5 @@ form.addEventListener("submit", async (e) => {
const body = await response.json();
console.log(body);
const a = 8;
});

1
server/src/js/test.js Normal file
View File

@ -0,0 +1 @@
const a = 5;

View File

@ -1,9 +1,9 @@
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 { AdminPasswordNotSetError, QueryExecutionError } from "@src/errors.ts";
import { AdminRaw, AdminSessionRaw } from "@src/db/types/index.ts";
import { generateRandomString, passwd } from "@src/utils.ts";
import { AdminPasswordNotSetError, QueryExecutionError } from "@lib/errors.ts";
import { AdminRaw, AdminSessionRaw } from "@lib/db/types/index.ts";
import { generateRandomString, passwd } from "@lib/utils.ts";
import { errAsync, ResultAsync } from "@shared/utils/resultasync.ts";
const TOKEN_LENGTH = 128;
@ -113,10 +113,13 @@ class AdminSessions {
setInterval(() => {
this.clearExpiredSessions().match(
(clearedCount) => {
if (clearedCount > 0)
if (clearedCount > 0) {
console.info(
`cleared ${clearedCount} expired token${clearedCount === 1 ? "" : "s"}`,
`cleared ${clearedCount} expired token${
clearedCount === 1 ? "" : "s"
}`,
);
}
},
(error) =>
console.error(`failed to clear expired tokens: ${error}`),
@ -164,7 +167,7 @@ class AdminSessions {
return this.deleteSessionById(session.id).map(() => false);
},
() => ok(false),
),
)
);
}

View File

@ -1,8 +1,9 @@
import { type Params } from "@src/router.ts";
import { ExtractRouteParams } from "@src/router.ts";
import { type Params } from "@lib/router.ts";
import { type ExtractRouteParams } from "@lib/router.ts";
import { fromNullableVal, none, Option, some } from "@shared/utils/option.ts";
import { deleteCookie, getCookies, setCookie } 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
const SECURITY_HEADERS: Headers = new Headers({
@ -120,7 +121,7 @@ export class Context<S extends string = string> {
responseBody = JSON.stringify({
err: "Internal Server Error",
});
status = 400;
status = 500;
}
}

View File

@ -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 { QueryExecutionError } from "@src/errors.ts";
import { QueryExecutionError } from "@lib/errors.ts";
import { fromNullableVal, none, Option, some } from "@shared/utils/option.ts";
export class DatabaseClient {
@ -27,7 +27,7 @@ export class DatabaseClient {
...params: RestBindParameters
): Result<Option<T>, QueryExecutionError> {
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
): Result<Option<NonNullable<T[]>>, QueryExecutionError> =>
this.safeExecute(() => stmt.all<T>(params)).map((result) =>
result.length > 0 ? some(result) : none,
result.length > 0 ? some(result) : none
);
return { get, all };

View File

@ -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 { DatabaseClient } from "@src/db/dbWrapper.ts";
const sqliteDbPath = Deno.env.get("KBRG_SQLITE_DB_PATH") || "./test.db";
@ -107,7 +107,6 @@ const migrationManager = new MigrationManager(
WHERE id = OLD.id;
END;`,
],
[
`DELETE TABLE admin;`,
`DELETE TABLE adminSessions;`,

View File

@ -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 { Context } from "@src/context.ts";
import { Context } from "@lib/context.ts";
type RequestHandler<S extends string> = (
c: Context<S>,

View File

@ -1,10 +1,9 @@
import { Middleware } from "@src/router.ts";
import { none } from "@shared/utils/option.ts";
import admin from "@src/admin.ts";
import { Middleware } from "@lib/router.ts";
import admin from "@lib/admin.ts";
const LOGIN_PATH = "/login";
const authMiddleware: Middleware = async (c) => {
const authMiddleware: Middleware = (c) => {
const token = c.cookies.get("token");
const isValid = token
.map((token) => admin.sessions.verifyToken(token))

View File

@ -1,5 +1,4 @@
import { Middleware } from "@src/router.ts";
import { none, some } from "@shared/utils/option.ts";
import { Middleware } from "@lib/router.ts";
const requestCounts: Partial<
Record<string, { count: number; lastReset: number }>

View File

@ -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;

View File

@ -0,0 +1,3 @@
<% layout("./layouts/layout.html") %>
this is an index.html

View 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>

View File

@ -0,0 +1,7 @@
<% layout("./basic.html") %>
<header></header>
<main>
<%~ it.body %>
</main>
<footer></footer>

View 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>

View 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>

View File

@ -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",
]

View File

@ -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"]

View File

@ -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)
}
}

Binary file not shown.

View File

@ -1,3 +1 @@
<% layout("./layouts/layout.html") %>
this is an index.html
<% layout("./layouts/layout.html") %> this is an index.html

View File

@ -1,17 +1 @@
<!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">
<script type="module" src="/public/js/shared.bundle.js"></script>
</head>
<body>
<%~ it.body %>
</body>
</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 %>

View File

@ -1,7 +1 @@
<% layout("./basic.html") %>
<header></header>
<main>
<%~ it.body %>
</main>
<footer></footer>
<% layout("./basic.html") %> <header></header><main><%~ it.body %></main><footer></footer>

View File

@ -1,11 +1 @@
<% layout("./layouts/basic.html") %>
<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>
<% 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>