100 lines
2.8 KiB
TypeScript
100 lines
2.8 KiB
TypeScript
import { Match, Method, Router } from '@atelier/router.ts';
|
|
import { OrPromise } from '@atelier/util.ts';
|
|
|
|
import { Database } from '@db/sqlite';
|
|
|
|
import { viewShelf, viewShelves } from '@/backend/routes/shelves.ts';
|
|
import { postItem, viewCreateItem, viewItem, viewItems } from '@/backend/routes/items.ts';
|
|
import { viewHome } from '@/backend/routes/home.ts';
|
|
|
|
const { GET, POST, PATCH } = Method;
|
|
|
|
export type RequestCtx = {
|
|
pathParams: Record<string, string>;
|
|
db: Database;
|
|
};
|
|
|
|
export type Handler = (req: Request, ctx: RequestCtx) => OrPromise<Response>;
|
|
|
|
export function makeRoutes(db: Database) {
|
|
const router = new Router();
|
|
const r = (m: Method, p: Match, h: Handler) =>
|
|
router.register(m, p, (req, pathParams) => {
|
|
return h(req, { pathParams, db });
|
|
});
|
|
|
|
r(GET, '/', viewHome);
|
|
r(GET, '/shelves', viewShelves);
|
|
r(GET, '/shelves/:shelfXid', viewShelf);
|
|
r(GET, '/items', viewItems);
|
|
r(GET, '/items/create', viewCreateItem);
|
|
r(GET, '/items/:itemXid', viewItem);
|
|
r(POST, '/items/create', postItem);
|
|
|
|
r(GET, '/static/js/bundle.js', serveJsBundle);
|
|
r(GET, '/static/*', serveStatic);
|
|
|
|
return router.handle.bind(router);
|
|
}
|
|
|
|
const EXT_TO_MIME = {
|
|
'.css': 'text/css',
|
|
};
|
|
async function serveStatic(req: Request) {
|
|
const url = new URL(req.url);
|
|
const path = url.pathname.substring('/static'.length);
|
|
|
|
const staticPath = import.meta.dirname + '/../static' + path;
|
|
const data = await Deno.readFile(staticPath);
|
|
let mimetype = 'text/plain';
|
|
for (const [ext, mime] of Object.entries(EXT_TO_MIME)) {
|
|
if (path.endsWith(ext)) {
|
|
mimetype = mime;
|
|
}
|
|
}
|
|
|
|
return new Response(data, { headers: { 'Content-Type': mimetype } });
|
|
}
|
|
|
|
const isProduction = false;
|
|
let cachedBundlePath: string | null = null;
|
|
let cachedBundleAt = new Date(0);
|
|
async function serveJsBundle(req: Request) {
|
|
if (isProduction) {
|
|
throw new Error('todo');
|
|
}
|
|
|
|
if (cachedBundlePath === null) {
|
|
cachedBundlePath = await Deno.makeTempFile({ prefix: 'bundle', suffix: '.js' });
|
|
}
|
|
|
|
const bundlePath = import.meta.dirname + '/../frontend/bundle.ts';
|
|
const stat = await Deno.stat(bundlePath);
|
|
const mtime = stat.mtime ?? new Date(0);
|
|
if (cachedBundleAt > mtime) {
|
|
const file = await Deno.open(cachedBundlePath);
|
|
return new Response(file.readable, {
|
|
headers: { 'Content-Type': 'text/javascript' },
|
|
});
|
|
}
|
|
|
|
const esbuild = await import('npm:esbuild');
|
|
const { denoPlugins } = await import('jsr:@luca/esbuild-deno-loader');
|
|
|
|
const result = await esbuild.build({
|
|
plugins: [...denoPlugins()],
|
|
entryPoints: [import.meta.dirname + '/../frontend/bundle.ts'],
|
|
outfile: cachedBundlePath,
|
|
bundle: true,
|
|
format: 'esm',
|
|
sourcemap: 'inline',
|
|
});
|
|
esbuild.stop();
|
|
|
|
cachedBundleAt = new Date();
|
|
|
|
return new Response((await Deno.open(cachedBundlePath)).readable, {
|
|
headers: { 'Content-Type': 'text/javascript' },
|
|
});
|
|
}
|