import { Hono } from "hono"; import { cors } from "hono/cors"; import { ZodError } from "zod"; import { createSessionDb, selectHttpSessionConstraint } from "@/worker/db/d1/client"; import type { AppContext } from "@/worker/app-context"; import { auth } from "@/worker/routes/auth"; import { invitesRouter } from "@/worker/routes/invites"; import { oidcRouter } from "@/worker/routes/oidc"; import { workspacesRouter } from "@/worker/routes/workspaces"; import { pagesRouter } from "@/worker/routes/pages"; import { pageTreeRouter } from "@/worker/routes/page-tree"; import { pageContextRouter } from "@/worker/routes/page-context"; import { pageMentionsRouter } from "@/worker/routes/page-mentions"; import { aiRouter } from "@/worker/routes/ai"; import { uploadsRouter, uploadServingRouter } from "@/worker/routes/uploads"; import { searchRouter } from "@/worker/routes/search"; import { sharesRouter, shareLinkRouter } from "@/worker/routes/shares"; import { sitesRouter } from "@/worker/routes/sites"; import { health } from "@/worker/routes/health"; import { isLocalRequestUrl } from "@/worker/http"; import { D1_BOOKMARK_HEADER } from "@/shared/bookmark"; import { createLogger, errorContext } from "@/worker/lib/logger"; import { isAllowedOrigin } from "@/worker/lib/origins"; import { applyBaselineSecurityHeaders } from "@/worker/lib/security-headers"; const log = createLogger("router"); const app = new Hono(); app.use( "*", cors({ origin: (origin, c) => (isAllowedOrigin(origin, c.env) ? origin : null), allowMethods: ["GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"], allowHeaders: ["Content-Type", "Authorization", D1_BOOKMARK_HEADER], exposeHeaders: [D1_BOOKMARK_HEADER], credentials: true, maxAge: 86400, }), ); app.use("*", async (c, next) => { await next(); c.res = applyBaselineSecurityHeaders(c.res); }); app.use("*", async (c, next) => { const bookmark = c.req.header(D1_BOOKMARK_HEADER); const constraint = selectHttpSessionConstraint(c.req.method, bookmark); const { db, session } = createSessionDb(c.env.DB, constraint); c.set("db", db); c.set("user", null); c.set("jwtPayload", null); await next(); // Return the bookmark for client to use on next request const latestBookmark = session.getBookmark(); if (latestBookmark) { c.header(D1_BOOKMARK_HEADER, latestBookmark); } }); app.use("*", async (c, next) => { await next(); // Local dev uses 403 here because the current Miniflare/Vite bridge can mis-handle some 401 // responses and surface them as `fetch failed` overlays instead of returning the JSON body. if (!isLocalRequestUrl(c.req.url) || c.res.status !== 401) { return; } c.res = new Response(c.res.body, { status: 403, statusText: c.res.statusText, headers: c.res.headers, }); }); app.route("/api/v1", health); app.route("/api/v1", auth); app.route("/api/v1", oidcRouter); app.route("/api/v1", invitesRouter); app.route("/api/v1", workspacesRouter); app.route("/api/v1", pagesRouter); app.route("/api/v1", pageTreeRouter); app.route("/api/v1", uploadsRouter); app.route("/api/v1", searchRouter); app.route("/api/v1", sharesRouter); app.route("/api/v1", shareLinkRouter); app.route("/api/v1", pageContextRouter); app.route("/api/v1", pageMentionsRouter); app.route("/api/v1", aiRouter); app.route("/api/v1", sitesRouter); app.route("/uploads", uploadServingRouter); app.notFound((c) => { return c.json({ error: "not_found", message: "Route not found" }, 404); }); app.onError((err, c) => { if (err instanceof ZodError) { return c.json({ error: "validation_error", message: "Invalid request body", issues: err.issues }, 400); } log.error("unhandled_error", errorContext(err)); return c.json({ error: "internal_error", message: "An unexpected error occurred" }, 500); }); export { app };