import { Hono } from "hono"; import { requestId } from "hono/request-id"; import { reactRenderer } from "@hono/react-renderer"; import { describeRoute, resolver } from "hono-openapi"; import YAML from "yaml"; import { handleSubjectDavRequest } from "@/worker/dav/subject"; import { HealthResponseSchema } from "@/worker/openapi/schemas"; import { buildOpenApiDocument } from "@/worker/openapi/document"; import { requestLogging } from "@/worker/middleware/request-logging"; import { createApiRoutes } from "@/worker/routes/api"; import { classifyHost } from "@/worker/routes/host"; import type { AppEnv } from "@/worker/types"; export function createApp(): Hono { const app = new Hono(); app.use("*", reactRenderer()); app.use( "*", requestId({ headerName: "", generator: (c) => c.req.raw.headers.get("cf-ray") ?? crypto.randomUUID(), }), ); app.use("*", async (c, next) => { c.set("hostInfo", classifyHost(c.req.raw, c.env)); await next(); }); app.use("*", requestLogging()); app.get( "/healthz", describeRoute({ hide: true, responses: { 200: { description: "Control-plane health status", content: { "application/json": { schema: resolver(HealthResponseSchema), }, }, }, }, }), (c) => { if (c.get("hostInfo").kind !== "control") return c.notFound(); return c.json({ ok: true, service: "dab" } as const); }, ); const api = createApiRoutes(); app.route("/api/v1", api); if (import.meta.env.DEV) { app.post("/__dab_test/fixtures/:fixture", async (c) => { const { handleTestFixtureRequest } = await import("@/worker/routes/test-fixtures"); return await handleTestFixtureRequest(c); }); } app.get( "/api/v1/openapi.json", describeRoute({ summary: "Get OpenAPI JSON document", tags: ["openapi"], responses: { 200: { description: "OpenAPI JSON document", }, }, }), async (c) => { if (c.get("hostInfo").kind !== "control") return c.notFound(); return c.json(await buildOpenApiDocument(app, c)); }, ); app.get( "/api/v1/openapi.yaml", describeRoute({ summary: "Get OpenAPI YAML document", tags: ["openapi"], responses: { 200: { description: "OpenAPI YAML document", }, }, }), async (c) => { if (c.get("hostInfo").kind !== "control") return c.notFound(); const document = await buildOpenApiDocument(app, c); return c.text(YAML.stringify(document, { aliasDuplicateObjects: false }), 200, { "Content-Type": "text/yaml; charset=utf-8", }); }, ); app.all("*", async (c) => { const hostInfo = c.get("hostInfo"); if (hostInfo.kind === "subject") return await handleSubjectDavRequest(c); // On the control plane only browser-navigation methods reach the SPA. // DAV verbs (PROPFIND, REPORT, PROPPATCH, LOCK, ...) on control-plane // non-API paths are misrouted clients — fall through to 404 so they fail // closed instead of receiving the SPA shell. const method = c.req.method; if (hostInfo.kind === "control" && (method === "GET" || method === "HEAD" || method === "OPTIONS")) { return await c.env.ASSETS.fetch(c.req.raw); } return c.notFound(); }); return app; }