import { StrRenderer } from "ren/str.ts"; import { Layout } from "./comp/layout.ts"; import { Context } from "./context.ts"; import { E404Page } from "./views/e404.ts"; import * as log from "./log.ts"; import { HomePage } from "./views/home.ts"; import { RecipesPage } from "./views/recipes.ts"; import { IngredientsPage } from "./views/ingredients.ts"; import { RestIngredientRepo } from "./repo/ingredient/rest.ts"; if (import.meta.main) { await main(); } async function main() { await startServer({ port: 33334 }); } async function startServer(cfg: ServerConfig) { const srv = Deno.listen({ hostname: "localhost", port: cfg.port }); log.info(`Server listening at http://localhost:${cfg.port}`); for await (const conn of srv) { serveHttp(conn); } } interface ServerConfig { port: number; } async function serveHttp(conn: Deno.Conn) { const httpConn = Deno.serveHttp(conn); for await (const reqEvt of httpConn) { const res = await handleRequest(reqEvt.request); reqEvt.respondWith(res); } } async function handleRequest(req: Request): Promise { log.debug({ url: req.url }); const ctx = createContextFromRequest(req); try { const res = await tryCreateFileResponse(ctx.locPath); return res; } catch (_) { const ren = new StrRenderer({ wrapNode: Layout }); if (ctx.locPath === "/") { return createHtmlResponse(ren.render(HomePage(ctx))); } else if (ctx.locPath === "/recipes") { return createHtmlResponse(ren.render(RecipesPage(ctx))); } else if (ctx.locPath === "/ingredients") { const repo = new RestIngredientRepo(); const ingredients = await repo.fetchIngredients(); return createHtmlResponse( ren.render(IngredientsPage(ctx, { ingredients })), ); } else { return createHtmlResponse(ren.render(E404Page(ctx)), 404); } } } function createContextFromRequest(req: Request): Context { return { locPath: new URL(req.url).pathname, }; } function createHtmlResponse(body: string, status = 200): Response { return new Response(body, { status, headers: getContentTypeHeader("html"), }); } async function tryCreateFileResponse(urlPath: string): Promise { const filePath = extractFilePath(urlPath); if (!filePath) throw new SkipFile(); const content = await Deno.readTextFile(filePath).catch(() => { throw new SkipFile(); }); return createFileResponse(content, getFileExt(filePath)); } class SkipFile extends Error {} function createFileResponse(content: string, fileExt: string): Response { return new Response(content, { headers: getContentTypeHeader(fileExt), }); } function extractFilePath(urlPath: string): string | null { if (urlPath.startsWith("/static")) { return urlPath.slice(1); } return null; } function getContentTypeHeader(fileExt: string): Record { return { "content-type": getContentTypeByExt(fileExt) }; } function getContentTypeByExt(fileExt: string): string { switch (fileExt) { case "html": return "text/html"; case "css": return "text/css"; default: return "text/plain"; } } function getFileExt(filePath: string): string { return filePath.slice((filePath.lastIndexOf(".") - 1 >>> 0) + 2); }