Skip to content
File

Blob: src/worker/router.ts

typescript112 lines
1import { Hono } from "hono";
2import { cors } from "hono/cors";
3import { ZodError } from "zod";
4 
5import { createSessionDb, selectHttpSessionConstraint } from "@/worker/db/d1/client";
6import type { AppContext } from "@/worker/app-context";
7import { auth } from "@/worker/routes/auth";
8import { invitesRouter } from "@/worker/routes/invites";
9import { oidcRouter } from "@/worker/routes/oidc";
10import { workspacesRouter } from "@/worker/routes/workspaces";
11import { pagesRouter } from "@/worker/routes/pages";
12import { pageTreeRouter } from "@/worker/routes/page-tree";
13import { pageContextRouter } from "@/worker/routes/page-context";
14import { pageMentionsRouter } from "@/worker/routes/page-mentions";
15import { aiRouter } from "@/worker/routes/ai";
16import { uploadsRouter, uploadServingRouter } from "@/worker/routes/uploads";
17import { searchRouter } from "@/worker/routes/search";
18import { sharesRouter, shareLinkRouter } from "@/worker/routes/shares";
19import { sitesRouter } from "@/worker/routes/sites";
20import { health } from "@/worker/routes/health";
21import { isLocalRequestUrl } from "@/worker/http";
22import { D1_BOOKMARK_HEADER } from "@/shared/bookmark";
23import { createLogger, errorContext } from "@/worker/lib/logger";
24import { isAllowedOrigin } from "@/worker/lib/origins";
25import { applyBaselineSecurityHeaders } from "@/worker/lib/security-headers";
26 
27const log = createLogger("router");
28const app = new Hono<AppContext>();
29 
30app.use(
31 "*",
32 cors({
33 origin: (origin, c) => (isAllowedOrigin(origin, c.env) ? origin : null),
34 allowMethods: ["GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"],
35 allowHeaders: ["Content-Type", "Authorization", D1_BOOKMARK_HEADER],
36 exposeHeaders: [D1_BOOKMARK_HEADER],
37 credentials: true,
38 maxAge: 86400,
39 }),
40);
41 
42app.use("*", async (c, next) => {
43 await next();
44 c.res = applyBaselineSecurityHeaders(c.res);
45});
46 
47app.use("*", async (c, next) => {
48 const bookmark = c.req.header(D1_BOOKMARK_HEADER);
49 const constraint = selectHttpSessionConstraint(c.req.method, bookmark);
50 
51 const { db, session } = createSessionDb(c.env.DB, constraint);
52 c.set("db", db);
53 c.set("user", null);
54 c.set("jwtPayload", null);
55 
56 await next();
57 
58 // Return the bookmark for client to use on next request
59 const latestBookmark = session.getBookmark();
60 if (latestBookmark) {
61 c.header(D1_BOOKMARK_HEADER, latestBookmark);
62 }
63});
64 
65app.use("*", async (c, next) => {
66 await next();
67 
68 // Local dev uses 403 here because the current Miniflare/Vite bridge can mis-handle some 401
69 // responses and surface them as `fetch failed` overlays instead of returning the JSON body.
70 if (!isLocalRequestUrl(c.req.url) || c.res.status !== 401) {
71 return;
72 }
73 
74 c.res = new Response(c.res.body, {
75 status: 403,
76 statusText: c.res.statusText,
77 headers: c.res.headers,
78 });
79});
80 
81app.route("/api/v1", health);
82app.route("/api/v1", auth);
83app.route("/api/v1", oidcRouter);
84app.route("/api/v1", invitesRouter);
85app.route("/api/v1", workspacesRouter);
86app.route("/api/v1", pagesRouter);
87app.route("/api/v1", pageTreeRouter);
88app.route("/api/v1", uploadsRouter);
89app.route("/api/v1", searchRouter);
90app.route("/api/v1", sharesRouter);
91app.route("/api/v1", shareLinkRouter);
92app.route("/api/v1", pageContextRouter);
93app.route("/api/v1", pageMentionsRouter);
94app.route("/api/v1", aiRouter);
95app.route("/api/v1", sitesRouter);
96app.route("/uploads", uploadServingRouter);
97 
98app.notFound((c) => {
99 return c.json({ error: "not_found", message: "Route not found" }, 404);
100});
101 
102app.onError((err, c) => {
103 if (err instanceof ZodError) {
104 return c.json({ error: "validation_error", message: "Invalid request body", issues: err.issues }, 400);
105 }
106 
107 log.error("unhandled_error", errorContext(err));
108 return c.json({ error: "internal_error", message: "An unexpected error occurred" }, 500);
109});
110 
111export { app };