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 | |
| 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')
24 files changed, 0 insertions, 933 deletions
diff --git a/apps/web/app/api/v1/bookmarks/[bookmarkId]/assets/[assetId]/route.ts b/apps/web/app/api/v1/bookmarks/[bookmarkId]/assets/[assetId]/route.ts deleted file mode 100644 index 88e203de..00000000 --- a/apps/web/app/api/v1/bookmarks/[bookmarkId]/assets/[assetId]/route.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { NextRequest } from "next/server"; -import { buildHandler } from "@/app/api/v1/utils/handler"; -import { z } from "zod"; - -export const dynamic = "force-dynamic"; - -export const PUT = ( - req: NextRequest, - params: { params: { bookmarkId: string; assetId: string } }, -) => - buildHandler({ - req, - bodySchema: z.object({ assetId: z.string() }), - handler: async ({ api, body }) => { - await api.assets.replaceAsset({ - bookmarkId: params.params.bookmarkId, - oldAssetId: params.params.assetId, - newAssetId: body!.assetId, - }); - return { status: 204 }; - }, - }); - -export const DELETE = ( - req: NextRequest, - params: { params: { bookmarkId: string; assetId: string } }, -) => - buildHandler({ - req, - handler: async ({ api }) => { - await api.assets.detachAsset({ - bookmarkId: params.params.bookmarkId, - assetId: params.params.assetId, - }); - return { status: 204 }; - }, - }); diff --git a/apps/web/app/api/v1/bookmarks/[bookmarkId]/assets/route.ts b/apps/web/app/api/v1/bookmarks/[bookmarkId]/assets/route.ts deleted file mode 100644 index 6c7c70d7..00000000 --- a/apps/web/app/api/v1/bookmarks/[bookmarkId]/assets/route.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { NextRequest } from "next/server"; -import { buildHandler } from "@/app/api/v1/utils/handler"; - -import { zAssetSchema } from "@karakeep/shared/types/bookmarks"; - -export const dynamic = "force-dynamic"; - -export const GET = ( - req: NextRequest, - params: { params: { bookmarkId: string } }, -) => - buildHandler({ - req, - handler: async ({ api }) => { - const resp = await api.bookmarks.getBookmark({ - bookmarkId: params.params.bookmarkId, - }); - return { status: 200, resp: { assets: resp.assets } }; - }, - }); - -export const POST = ( - req: NextRequest, - params: { params: { bookmarkId: string } }, -) => - buildHandler({ - req, - bodySchema: zAssetSchema, - handler: async ({ api, body }) => { - const asset = await api.assets.attachAsset({ - bookmarkId: params.params.bookmarkId, - asset: body!, - }); - return { status: 201, resp: asset }; - }, - }); diff --git a/apps/web/app/api/v1/bookmarks/[bookmarkId]/highlights/route.ts b/apps/web/app/api/v1/bookmarks/[bookmarkId]/highlights/route.ts deleted file mode 100644 index 4e1f87a0..00000000 --- a/apps/web/app/api/v1/bookmarks/[bookmarkId]/highlights/route.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { NextRequest } from "next/server"; -import { buildHandler } from "@/app/api/v1/utils/handler"; - -export const dynamic = "force-dynamic"; - -export const GET = ( - req: NextRequest, - params: { params: { bookmarkId: string } }, -) => - buildHandler({ - req, - handler: async ({ api }) => { - const resp = await api.highlights.getForBookmark({ - bookmarkId: params.params.bookmarkId, - }); - return { status: 200, resp }; - }, - }); diff --git a/apps/web/app/api/v1/bookmarks/[bookmarkId]/lists/route.ts b/apps/web/app/api/v1/bookmarks/[bookmarkId]/lists/route.ts deleted file mode 100644 index ad3052c9..00000000 --- a/apps/web/app/api/v1/bookmarks/[bookmarkId]/lists/route.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { NextRequest } from "next/server"; -import { buildHandler } from "@/app/api/v1/utils/handler"; - -export const dynamic = "force-dynamic"; - -export const GET = ( - req: NextRequest, - params: { params: { bookmarkId: string } }, -) => - buildHandler({ - req, - handler: async ({ api }) => { - const resp = await api.lists.getListsOfBookmark({ - bookmarkId: params.params.bookmarkId, - }); - return { status: 200, resp }; - }, - }); diff --git a/apps/web/app/api/v1/bookmarks/[bookmarkId]/route.ts b/apps/web/app/api/v1/bookmarks/[bookmarkId]/route.ts deleted file mode 100644 index 9ad18fd3..00000000 --- a/apps/web/app/api/v1/bookmarks/[bookmarkId]/route.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { NextRequest } from "next/server"; -import { buildHandler } from "@/app/api/v1/utils/handler"; - -import { zUpdateBookmarksRequestSchema } from "@karakeep/shared/types/bookmarks"; - -import { zGetBookmarkQueryParamsSchema } from "../../utils/types"; - -export const dynamic = "force-dynamic"; - -export const GET = ( - req: NextRequest, - { params }: { params: { bookmarkId: string } }, -) => - buildHandler({ - req, - searchParamsSchema: zGetBookmarkQueryParamsSchema, - handler: async ({ api, searchParams }) => { - const bookmark = await api.bookmarks.getBookmark({ - bookmarkId: params.bookmarkId, - includeContent: searchParams.includeContent, - }); - return { status: 200, resp: bookmark }; - }, - }); - -export const PATCH = ( - req: NextRequest, - { params }: { params: { bookmarkId: string } }, -) => - buildHandler({ - req, - bodySchema: zUpdateBookmarksRequestSchema.omit({ bookmarkId: true }), - handler: async ({ api, body }) => { - const bookmark = await api.bookmarks.updateBookmark({ - bookmarkId: params.bookmarkId, - ...body!, - }); - return { status: 200, resp: bookmark }; - }, - }); - -export const DELETE = ( - req: NextRequest, - { params }: { params: { bookmarkId: string } }, -) => - buildHandler({ - req, - handler: async ({ api }) => { - await api.bookmarks.deleteBookmark({ - bookmarkId: params.bookmarkId, - }); - return { status: 204 }; - }, - }); diff --git a/apps/web/app/api/v1/bookmarks/[bookmarkId]/summarize/route.ts b/apps/web/app/api/v1/bookmarks/[bookmarkId]/summarize/route.ts deleted file mode 100644 index ea41cad4..00000000 --- a/apps/web/app/api/v1/bookmarks/[bookmarkId]/summarize/route.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { NextRequest } from "next/server"; -import { buildHandler } from "@/app/api/v1/utils/handler"; - -export const dynamic = "force-dynamic"; - -export const POST = ( - req: NextRequest, - params: { params: { bookmarkId: string } }, -) => - buildHandler({ - req, - handler: async ({ api }) => { - const bookmark = await api.bookmarks.summarizeBookmark({ - bookmarkId: params.params.bookmarkId, - }); - - return { status: 200, resp: bookmark }; - }, - }); diff --git a/apps/web/app/api/v1/bookmarks/[bookmarkId]/tags/route.ts b/apps/web/app/api/v1/bookmarks/[bookmarkId]/tags/route.ts deleted file mode 100644 index 00c28afa..00000000 --- a/apps/web/app/api/v1/bookmarks/[bookmarkId]/tags/route.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { NextRequest } from "next/server"; -import { buildHandler } from "@/app/api/v1/utils/handler"; -import { z } from "zod"; - -import { zManipulatedTagSchema } from "@karakeep/shared/types/bookmarks"; - -export const dynamic = "force-dynamic"; - -export const POST = ( - req: NextRequest, - params: { params: { bookmarkId: string } }, -) => - buildHandler({ - req, - bodySchema: z.object({ - tags: z.array(zManipulatedTagSchema), - }), - handler: async ({ api, body }) => { - const resp = await api.bookmarks.updateTags({ - bookmarkId: params.params.bookmarkId, - attach: body!.tags, - detach: [], - }); - return { status: 200, resp: { attached: resp.attached } }; - }, - }); - -export const DELETE = ( - req: NextRequest, - params: { params: { bookmarkId: string } }, -) => - buildHandler({ - req, - bodySchema: z.object({ - tags: z.array(zManipulatedTagSchema), - }), - handler: async ({ api, body }) => { - const resp = await api.bookmarks.updateTags({ - bookmarkId: params.params.bookmarkId, - detach: body!.tags, - attach: [], - }); - return { status: 200, resp: { detached: resp.detached } }; - }, - }); diff --git a/apps/web/app/api/v1/bookmarks/route.ts b/apps/web/app/api/v1/bookmarks/route.ts deleted file mode 100644 index 4df4f6ad..00000000 --- a/apps/web/app/api/v1/bookmarks/route.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { NextRequest } from "next/server"; -import { z } from "zod"; - -import { - zNewBookmarkRequestSchema, - zSortOrder, -} from "@karakeep/shared/types/bookmarks"; - -import { buildHandler } from "../utils/handler"; -import { adaptPagination, zPagination } from "../utils/pagination"; -import { zStringBool } from "../utils/types"; - -export const dynamic = "force-dynamic"; - -export const GET = (req: NextRequest) => - buildHandler({ - req, - searchParamsSchema: z - .object({ - favourited: zStringBool.optional(), - archived: zStringBool.optional(), - sortOrder: zSortOrder - .exclude([zSortOrder.Enum.relevance]) - .optional() - .default(zSortOrder.Enum.desc), - // TODO: Change the default to false in a couple of releases. - includeContent: zStringBool.optional().default("true"), - }) - .and(zPagination), - handler: async ({ api, searchParams }) => { - const bookmarks = await api.bookmarks.getBookmarks({ - ...searchParams, - }); - return { status: 200, resp: adaptPagination(bookmarks) }; - }, - }); - -export const POST = (req: NextRequest) => - buildHandler({ - req, - bodySchema: zNewBookmarkRequestSchema, - handler: async ({ api, body }) => { - const bookmark = await api.bookmarks.createBookmark(body!); - return { status: 201, resp: bookmark }; - }, - }); diff --git a/apps/web/app/api/v1/bookmarks/search/route.ts b/apps/web/app/api/v1/bookmarks/search/route.ts deleted file mode 100644 index e85c7954..00000000 --- a/apps/web/app/api/v1/bookmarks/search/route.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { NextRequest } from "next/server"; -import { z } from "zod"; - -import { buildHandler } from "../../utils/handler"; -import { zGetBookmarkSearchParamsSchema } from "../../utils/types"; - -export const dynamic = "force-dynamic"; - -export const GET = (req: NextRequest) => - buildHandler({ - req, - searchParamsSchema: z - .object({ - q: z.string(), - limit: z.coerce.number().optional(), - cursor: z - .string() - // Search cursor V1 is just a number - .pipe(z.coerce.number()) - .transform((val) => { - return { ver: 1 as const, offset: val }; - }) - .optional(), - }) - .and(zGetBookmarkSearchParamsSchema), - handler: async ({ api, searchParams }) => { - const bookmarks = await api.bookmarks.searchBookmarks({ - text: searchParams.q, - cursor: searchParams.cursor, - sortOrder: searchParams.sortOrder, - limit: searchParams.limit, - includeContent: searchParams.includeContent, - }); - return { - status: 200, - resp: { - bookmarks: bookmarks.bookmarks, - nextCursor: bookmarks.nextCursor - ? `${bookmarks.nextCursor.offset}` - : null, - }, - }; - }, - }); diff --git a/apps/web/app/api/v1/bookmarks/singlefile/route.ts b/apps/web/app/api/v1/bookmarks/singlefile/route.ts deleted file mode 100644 index 7c1d7201..00000000 --- a/apps/web/app/api/v1/bookmarks/singlefile/route.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { createContextFromRequest } from "@/server/api/client"; -import { TRPCError } from "@trpc/server"; - -import serverConfig from "@karakeep/shared/config"; -import { BookmarkTypes } from "@karakeep/shared/types/bookmarks"; -import { createCallerFactory } from "@karakeep/trpc"; -import { appRouter } from "@karakeep/trpc/routers/_app"; - -import { uploadFromPostData } from "../../../assets/route"; - -export const dynamic = "force-dynamic"; - -export async function POST(req: Request) { - const ctx = await createContextFromRequest(req); - if (!ctx.user) { - return Response.json({ error: "Unauthorized" }, { status: 401 }); - } - if (serverConfig.demoMode) { - throw new TRPCError({ - message: "Mutations are not allowed in demo mode", - code: "FORBIDDEN", - }); - } - const formData = await req.formData(); - const up = await uploadFromPostData(ctx.user, ctx.db, formData); - - if ("error" in up) { - return Response.json({ error: up.error }, { status: up.status }); - } - - const url = formData.get("url"); - if (!url) { - throw new TRPCError({ - message: "URL is required", - code: "BAD_REQUEST", - }); - } - if (typeof url !== "string") { - throw new TRPCError({ - message: "URL must be a string", - code: "BAD_REQUEST", - }); - } - - const createCaller = createCallerFactory(appRouter); - const api = createCaller(ctx); - - const bookmark = await api.bookmarks.createBookmark({ - type: BookmarkTypes.LINK, - url, - precrawledArchiveId: up.assetId, - }); - return Response.json(bookmark, { status: 201 }); -} diff --git a/apps/web/app/api/v1/highlights/[highlightId]/route.ts b/apps/web/app/api/v1/highlights/[highlightId]/route.ts deleted file mode 100644 index 50420427..00000000 --- a/apps/web/app/api/v1/highlights/[highlightId]/route.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { NextRequest } from "next/server"; -import { buildHandler } from "@/app/api/v1/utils/handler"; - -import { zUpdateHighlightSchema } from "@karakeep/shared/types/highlights"; - -export const dynamic = "force-dynamic"; - -export const GET = ( - req: NextRequest, - { params }: { params: { highlightId: string } }, -) => - buildHandler({ - req, - handler: async ({ api }) => { - const highlight = await api.highlights.get({ - highlightId: params.highlightId, - }); - return { status: 200, resp: highlight }; - }, - }); - -export const PATCH = ( - req: NextRequest, - { params }: { params: { highlightId: string } }, -) => - buildHandler({ - req, - bodySchema: zUpdateHighlightSchema.omit({ highlightId: true }), - handler: async ({ api, body }) => { - const highlight = await api.highlights.update({ - highlightId: params.highlightId, - ...body!, - }); - return { status: 200, resp: highlight }; - }, - }); - -export const DELETE = ( - req: NextRequest, - { params }: { params: { highlightId: string } }, -) => - buildHandler({ - req, - handler: async ({ api }) => { - const highlight = await api.highlights.delete({ - highlightId: params.highlightId, - }); - return { status: 200, resp: highlight }; - }, - }); diff --git a/apps/web/app/api/v1/highlights/route.ts b/apps/web/app/api/v1/highlights/route.ts deleted file mode 100644 index e95d84f6..00000000 --- a/apps/web/app/api/v1/highlights/route.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { NextRequest } from "next/server"; -import { buildHandler } from "@/app/api/v1/utils/handler"; - -import { zNewHighlightSchema } from "@karakeep/shared/types/highlights"; - -import { adaptPagination, zPagination } from "../utils/pagination"; - -export const dynamic = "force-dynamic"; - -export const GET = (req: NextRequest) => - buildHandler({ - req, - searchParamsSchema: zPagination, - handler: async ({ api, searchParams }) => { - const resp = await api.highlights.getAll({ - ...searchParams, - }); - return { status: 200, resp: adaptPagination(resp) }; - }, - }); - -export const POST = (req: NextRequest) => - buildHandler({ - req, - bodySchema: zNewHighlightSchema, - handler: async ({ body, api }) => { - const resp = await api.highlights.create(body!); - return { status: 201, resp }; - }, - }); diff --git a/apps/web/app/api/v1/lists/[listId]/bookmarks/[bookmarkId]/route.ts b/apps/web/app/api/v1/lists/[listId]/bookmarks/[bookmarkId]/route.ts deleted file mode 100644 index 6efe2055..00000000 --- a/apps/web/app/api/v1/lists/[listId]/bookmarks/[bookmarkId]/route.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { NextRequest } from "next/server"; -import { buildHandler } from "@/app/api/v1/utils/handler"; - -export const dynamic = "force-dynamic"; - -export const PUT = ( - req: NextRequest, - { params }: { params: { listId: string; bookmarkId: string } }, -) => - buildHandler({ - req, - handler: async ({ api }) => { - // TODO: PUT is supposed to be idempotent, but we currently fail if the bookmark is already in the list. - await api.lists.addToList({ - listId: params.listId, - bookmarkId: params.bookmarkId, - }); - return { status: 204 }; - }, - }); - -export const DELETE = ( - req: NextRequest, - { params }: { params: { listId: string; bookmarkId: string } }, -) => - buildHandler({ - req, - handler: async ({ api }) => { - await api.lists.removeFromList({ - listId: params.listId, - bookmarkId: params.bookmarkId, - }); - return { status: 204 }; - }, - }); diff --git a/apps/web/app/api/v1/lists/[listId]/bookmarks/route.ts b/apps/web/app/api/v1/lists/[listId]/bookmarks/route.ts deleted file mode 100644 index daf78449..00000000 --- a/apps/web/app/api/v1/lists/[listId]/bookmarks/route.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { NextRequest } from "next/server"; -import { buildHandler } from "@/app/api/v1/utils/handler"; -import { adaptPagination, zPagination } from "@/app/api/v1/utils/pagination"; -import { zGetBookmarkQueryParamsSchema } from "@/app/api/v1/utils/types"; - -export const dynamic = "force-dynamic"; - -export const GET = (req: NextRequest, params: { params: { listId: string } }) => - buildHandler({ - req, - searchParamsSchema: zPagination.and(zGetBookmarkQueryParamsSchema), - handler: async ({ api, searchParams }) => { - const bookmarks = await api.bookmarks.getBookmarks({ - listId: params.params.listId, - ...searchParams, - }); - return { status: 200, resp: adaptPagination(bookmarks) }; - }, - }); diff --git a/apps/web/app/api/v1/lists/[listId]/route.ts b/apps/web/app/api/v1/lists/[listId]/route.ts deleted file mode 100644 index 2cddbfdb..00000000 --- a/apps/web/app/api/v1/lists/[listId]/route.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { NextRequest } from "next/server"; -import { buildHandler } from "@/app/api/v1/utils/handler"; - -import { zEditBookmarkListSchema } from "@karakeep/shared/types/lists"; - -export const dynamic = "force-dynamic"; - -export const GET = ( - req: NextRequest, - { params }: { params: { listId: string } }, -) => - buildHandler({ - req, - handler: async ({ api }) => { - const list = await api.lists.get({ - listId: params.listId, - }); - return { - status: 200, - resp: list, - }; - }, - }); - -export const PATCH = ( - req: NextRequest, - { params }: { params: { listId: string } }, -) => - buildHandler({ - req, - bodySchema: zEditBookmarkListSchema.omit({ listId: true }), - handler: async ({ api, body }) => { - const list = await api.lists.edit({ - ...body!, - listId: params.listId, - }); - return { status: 200, resp: list }; - }, - }); - -export const DELETE = ( - req: NextRequest, - { params }: { params: { listId: string } }, -) => - buildHandler({ - req, - handler: async ({ api }) => { - await api.lists.delete({ - listId: params.listId, - }); - return { - status: 204, - }; - }, - }); diff --git a/apps/web/app/api/v1/lists/route.ts b/apps/web/app/api/v1/lists/route.ts deleted file mode 100644 index 5def2506..00000000 --- a/apps/web/app/api/v1/lists/route.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { NextRequest } from "next/server"; - -import { zNewBookmarkListSchema } from "@karakeep/shared/types/lists"; - -import { buildHandler } from "../utils/handler"; - -export const dynamic = "force-dynamic"; - -export const GET = (req: NextRequest) => - buildHandler({ - req, - handler: async ({ api }) => { - const lists = await api.lists.list(); - return { status: 200, resp: lists }; - }, - }); - -export const POST = (req: NextRequest) => - buildHandler({ - req, - bodySchema: zNewBookmarkListSchema, - handler: async ({ api, body }) => { - const list = await api.lists.create(body!); - return { status: 201, resp: list }; - }, - }); diff --git a/apps/web/app/api/v1/tags/[tagId]/bookmarks/route.ts b/apps/web/app/api/v1/tags/[tagId]/bookmarks/route.ts deleted file mode 100644 index aaa5087b..00000000 --- a/apps/web/app/api/v1/tags/[tagId]/bookmarks/route.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { NextRequest } from "next/server"; -import { buildHandler } from "@/app/api/v1/utils/handler"; -import { adaptPagination, zPagination } from "@/app/api/v1/utils/pagination"; -import { zGetBookmarkQueryParamsSchema } from "@/app/api/v1/utils/types"; - -export const dynamic = "force-dynamic"; - -export const GET = ( - req: NextRequest, - { params }: { params: { tagId: string } }, -) => - buildHandler({ - req, - searchParamsSchema: zPagination.and(zGetBookmarkQueryParamsSchema), - handler: async ({ api, searchParams }) => { - const bookmarks = await api.bookmarks.getBookmarks({ - tagId: params.tagId, - sortOrder: searchParams.sortOrder, - limit: searchParams.limit, - cursor: searchParams.cursor, - }); - return { - status: 200, - resp: adaptPagination(bookmarks), - }; - }, - }); diff --git a/apps/web/app/api/v1/tags/[tagId]/route.ts b/apps/web/app/api/v1/tags/[tagId]/route.ts deleted file mode 100644 index 234d952d..00000000 --- a/apps/web/app/api/v1/tags/[tagId]/route.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { NextRequest } from "next/server"; -import { buildHandler } from "@/app/api/v1/utils/handler"; - -import { zUpdateTagRequestSchema } from "@karakeep/shared/types/tags"; - -export const dynamic = "force-dynamic"; - -export const GET = ( - req: NextRequest, - { params }: { params: { tagId: string } }, -) => - buildHandler({ - req, - handler: async ({ api }) => { - const tag = await api.tags.get({ - tagId: params.tagId, - }); - return { - status: 200, - resp: tag, - }; - }, - }); - -export const PATCH = ( - req: NextRequest, - { params }: { params: { tagId: string } }, -) => - buildHandler({ - req, - bodySchema: zUpdateTagRequestSchema.omit({ tagId: true }), - handler: async ({ api, body }) => { - const tag = await api.tags.update({ - tagId: params.tagId, - ...body!, - }); - return { status: 200, resp: tag }; - }, - }); - -export const DELETE = ( - req: NextRequest, - { params }: { params: { tagId: string } }, -) => - buildHandler({ - req, - handler: async ({ api }) => { - await api.tags.delete({ - tagId: params.tagId, - }); - return { - status: 204, - }; - }, - }); diff --git a/apps/web/app/api/v1/tags/route.ts b/apps/web/app/api/v1/tags/route.ts deleted file mode 100644 index 9625820c..00000000 --- a/apps/web/app/api/v1/tags/route.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { NextRequest } from "next/server"; - -import { buildHandler } from "../utils/handler"; - -export const dynamic = "force-dynamic"; - -export const GET = (req: NextRequest) => - buildHandler({ - req, - handler: async ({ api }) => { - const tags = await api.tags.list(); - return { status: 200, resp: tags }; - }, - }); diff --git a/apps/web/app/api/v1/users/me/route.ts b/apps/web/app/api/v1/users/me/route.ts deleted file mode 100644 index bf0a3ba2..00000000 --- a/apps/web/app/api/v1/users/me/route.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { NextRequest } from "next/server"; - -import { buildHandler } from "../../utils/handler"; - -export const dynamic = "force-dynamic"; - -export const GET = (req: NextRequest) => - buildHandler({ - req, - handler: async ({ api }) => { - const user = await api.users.whoami(); - return { status: 200, resp: user }; - }, - }); diff --git a/apps/web/app/api/v1/users/me/stats/route.ts b/apps/web/app/api/v1/users/me/stats/route.ts deleted file mode 100644 index 359c3156..00000000 --- a/apps/web/app/api/v1/users/me/stats/route.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { NextRequest } from "next/server"; - -import { buildHandler } from "../../../utils/handler"; - -export const dynamic = "force-dynamic"; - -export const GET = (req: NextRequest) => - buildHandler({ - req, - handler: async ({ api }) => { - const stats = await api.users.stats(); - return { status: 200, resp: stats }; - }, - }); 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", - }, - }); - } - } -} diff --git a/apps/web/app/api/v1/utils/pagination.ts b/apps/web/app/api/v1/utils/pagination.ts deleted file mode 100644 index 12a0b950..00000000 --- a/apps/web/app/api/v1/utils/pagination.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { z } from "zod"; - -import { MAX_NUM_BOOKMARKS_PER_PAGE } from "@karakeep/shared/types/bookmarks"; -import { zCursorV2 } from "@karakeep/shared/types/pagination"; - -export const zPagination = z.object({ - limit: z.coerce.number().max(MAX_NUM_BOOKMARKS_PER_PAGE).optional(), - cursor: z - .string() - .refine((val) => val.includes("_"), "Must be a valid cursor") - .transform((val) => { - const [id, createdAt] = val.split("_"); - return { id, createdAt }; - }) - .pipe(z.object({ id: z.string(), createdAt: z.coerce.date() })) - .optional(), -}); - -export function adaptPagination< - T extends { nextCursor: z.infer<typeof zCursorV2> | null }, ->(input: T) { - const { nextCursor, ...rest } = input; - if (!nextCursor) { - return input; - } - return { - ...rest, - nextCursor: `${nextCursor.id}_${nextCursor.createdAt.toISOString()}`, - }; -} diff --git a/apps/web/app/api/v1/utils/types.ts b/apps/web/app/api/v1/utils/types.ts deleted file mode 100644 index bf181ce4..00000000 --- a/apps/web/app/api/v1/utils/types.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { z } from "zod"; - -import { zSortOrder } from "@karakeep/shared/types/bookmarks"; - -export const zStringBool = z - .string() - .refine((val) => val === "true" || val === "false", "Must be true or false") - .transform((val) => val === "true"); - -export const zGetBookmarkQueryParamsSchema = z.object({ - sortOrder: zSortOrder - .exclude([zSortOrder.Enum.relevance]) - .optional() - .default(zSortOrder.Enum.desc), - // TODO: Change the default to false in a couple of releases. - includeContent: zStringBool.optional().default("true"), -}); - -export const zGetBookmarkSearchParamsSchema = z.object({ - sortOrder: zSortOrder.optional().default(zSortOrder.Enum.relevance), - // TODO: Change the default to false in a couple of releases. - includeContent: zStringBool.optional().default("true"), -}); |
