atelier/shelves/backend/routes.ts
2025-02-02 12:24:00 -05:00

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