diff options
| author | Mohamed Bassem <me@mbassem.com> | 2025-05-18 16:58:08 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-05-18 16:58:08 +0100 |
| commit | 3505cb7d6416d101a4fcb1be27fc22e0171bacd2 (patch) | |
| tree | ef9f55504b8a5b20add8c0ebe916972ab4ab0178 /apps/web/app/api/v1/utils/handler.ts | |
| parent | 74e74fa6425f072107de3a9bc9dd8f91c5ac9a7d (diff) | |
| download | karakeep-3505cb7d6416d101a4fcb1be27fc22e0171bacd2.tar.zst | |
refactor: Migrate from NextJs's API routes to Hono based routes for the API (#1432)
* Setup Hono and migrate the highlights API there
* Implement the tags and lists endpoint
* Implement the bookmarks and users endpoints
* Add the trpc error code adapter
* Remove the old nextjs handlers
* fix api key not found handling
* Fix trpc error handling
* Fix 204 handling
* Fix search ordering
* Implement the singlefile endpoint
* Implement the asset serving endpoints
* Implement webauth
* Add hono as a catch all route under api
* fix tests
Diffstat (limited to 'apps/web/app/api/v1/utils/handler.ts')
| -rw-r--r-- | apps/web/app/api/v1/utils/handler.ts | 170 |
1 files changed, 0 insertions, 170 deletions
diff --git a/apps/web/app/api/v1/utils/handler.ts b/apps/web/app/api/v1/utils/handler.ts deleted file mode 100644 index 9154506d..00000000 --- a/apps/web/app/api/v1/utils/handler.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { NextRequest } from "next/server"; -import { - createContextFromRequest, - createTrcpClientFromCtx, -} from "@/server/api/client"; -import { TRPCError } from "@trpc/server"; -import { z, ZodError } from "zod"; - -import { Context } from "@karakeep/trpc"; - -function trpcCodeToHttpCode(code: TRPCError["code"]) { - switch (code) { - case "BAD_REQUEST": - case "PARSE_ERROR": - return 400; - case "UNAUTHORIZED": - return 401; - case "FORBIDDEN": - return 403; - case "NOT_FOUND": - return 404; - case "METHOD_NOT_SUPPORTED": - return 405; - case "TIMEOUT": - return 408; - case "PAYLOAD_TOO_LARGE": - return 413; - case "INTERNAL_SERVER_ERROR": - return 500; - default: - return 500; - } -} - -interface ErrorMessage { - path: (string | number)[]; - message: string; -} - -function formatZodError(error: ZodError): string { - if (!error.issues) { - return error.message || "An unknown error occurred"; - } - - const errors: ErrorMessage[] = error.issues.map((issue) => ({ - path: issue.path, - message: issue.message, - })); - - const formattedErrors = errors.map((err) => { - const path = err.path.join("."); - return path ? `${path}: ${err.message}` : err.message; - }); - - return `${formattedErrors.join(", ")}`; -} - -export interface TrpcAPIRequest<SearchParamsT, BodyType> { - ctx: Context; - api: ReturnType<typeof createTrcpClientFromCtx>; - searchParams: SearchParamsT extends z.ZodTypeAny - ? z.infer<SearchParamsT> - : undefined; - body: BodyType extends z.ZodTypeAny - ? z.infer<BodyType> | undefined - : undefined; -} - -type SchemaType<T> = T extends z.ZodTypeAny - ? z.infer<T> | undefined - : undefined; - -export async function buildHandler< - SearchParamsT extends z.ZodTypeAny | undefined, - BodyT extends z.ZodTypeAny | undefined, - InputT extends TrpcAPIRequest<SearchParamsT, BodyT>, ->({ - req, - handler, - searchParamsSchema, - bodySchema, -}: { - req: NextRequest; - handler: (req: InputT) => Promise<{ status: number; resp?: object }>; - searchParamsSchema?: SearchParamsT | undefined; - bodySchema?: BodyT | undefined; -}) { - try { - const ctx = await createContextFromRequest(req); - const api = createTrcpClientFromCtx(ctx); - - let searchParams: SchemaType<SearchParamsT> | undefined = undefined; - if (searchParamsSchema !== undefined) { - searchParams = searchParamsSchema.parse( - Object.fromEntries(req.nextUrl.searchParams.entries()), - ) as SchemaType<SearchParamsT>; - } - - let body: SchemaType<BodyT> | undefined = undefined; - if (bodySchema) { - if (req.headers.get("Content-Type") !== "application/json") { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Content-Type must be application/json", - }); - } - - let bodyJson = undefined; - try { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - bodyJson = await req.json(); - } catch (e) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: `Invalid JSON: ${(e as Error).message}`, - }); - } - body = bodySchema.parse(bodyJson) as SchemaType<BodyT>; - } - - const { status, resp } = await handler({ - ctx, - api, - searchParams, - body, - } as InputT); - - return new Response(resp ? JSON.stringify(resp) : null, { - status, - headers: { - "Content-Type": "application/json", - }, - }); - } catch (e) { - if (e instanceof ZodError) { - return new Response( - JSON.stringify({ code: "ParseError", message: formatZodError(e) }), - { - status: 400, - headers: { - "Content-Type": "application/json", - }, - }, - ); - } - if (e instanceof TRPCError) { - let message = e.message; - if (e.cause instanceof ZodError) { - message = formatZodError(e.cause); - } - return new Response(JSON.stringify({ code: e.code, error: message }), { - status: trpcCodeToHttpCode(e.code), - headers: { - "Content-Type": "application/json", - }, - }); - } else { - const error = e as Error; - console.error( - `Unexpected error in: ${req.method} ${req.nextUrl.pathname}:\n${error.stack}`, - ); - return new Response(JSON.stringify({ code: "UnknownError" }), { - status: 500, - headers: { - "Content-Type": "application/json", - }, - }); - } - } -} |
