diff options
| -rw-r--r-- | apps/web/app/api/v1/bookmarks/[bookmarkId]/lists/route.ts | 18 | ||||
| -rw-r--r-- | apps/web/app/api/v1/bookmarks/[bookmarkId]/tags/route.ts | 45 | ||||
| -rw-r--r-- | apps/web/app/api/v1/lists/[listId]/bookmarks/[bookmarkId]/route.ts | 35 | ||||
| -rw-r--r-- | apps/web/app/api/v1/lists/[listId]/bookmarks/route.ts | 18 | ||||
| -rw-r--r-- | apps/web/app/api/v1/lists/[listId]/route.ts | 22 | ||||
| -rw-r--r-- | apps/web/app/api/v1/tags/[tagId]/bookmarks/route.ts | 25 | ||||
| -rw-r--r-- | apps/web/app/api/v1/tags/[tagId]/route.ts | 22 | ||||
| -rw-r--r-- | packages/shared/types/bookmarks.ts | 12 | ||||
| -rw-r--r-- | packages/trpc/routers/bookmarks.ts | 27 |
9 files changed, 166 insertions, 58 deletions
diff --git a/apps/web/app/api/v1/bookmarks/[bookmarkId]/lists/route.ts b/apps/web/app/api/v1/bookmarks/[bookmarkId]/lists/route.ts new file mode 100644 index 00000000..ad3052c9 --- /dev/null +++ b/apps/web/app/api/v1/bookmarks/[bookmarkId]/lists/route.ts @@ -0,0 +1,18 @@ +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]/tags/route.ts b/apps/web/app/api/v1/bookmarks/[bookmarkId]/tags/route.ts new file mode 100644 index 00000000..df464618 --- /dev/null +++ b/apps/web/app/api/v1/bookmarks/[bookmarkId]/tags/route.ts @@ -0,0 +1,45 @@ +import { NextRequest } from "next/server"; +import { buildHandler } from "@/app/api/v1/utils/handler"; +import { z } from "zod"; + +import { zManipulatedTagSchema } from "@hoarder/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/lists/[listId]/bookmarks/[bookmarkId]/route.ts b/apps/web/app/api/v1/lists/[listId]/bookmarks/[bookmarkId]/route.ts new file mode 100644 index 00000000..6efe2055 --- /dev/null +++ b/apps/web/app/api/v1/lists/[listId]/bookmarks/[bookmarkId]/route.ts @@ -0,0 +1,35 @@ +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 new file mode 100644 index 00000000..72d4aa5f --- /dev/null +++ b/apps/web/app/api/v1/lists/[listId]/bookmarks/route.ts @@ -0,0 +1,18 @@ +import { NextRequest } from "next/server"; +import { buildHandler } from "@/app/api/v1/utils/handler"; +import { adaptPagination, zPagination } from "@/app/api/v1/utils/pagination"; + +export const dynamic = "force-dynamic"; + +export const GET = (req: NextRequest, params: { params: { listId: string } }) => + buildHandler({ + req, + searchParamsSchema: zPagination, + 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 index d3e1f17c..f5af286d 100644 --- a/apps/web/app/api/v1/lists/[listId]/route.ts +++ b/apps/web/app/api/v1/lists/[listId]/route.ts @@ -1,6 +1,5 @@ import { NextRequest } from "next/server"; import { buildHandler } from "@/app/api/v1/utils/handler"; -import { adaptPagination, zPagination } from "@/app/api/v1/utils/pagination"; export const dynamic = "force-dynamic"; @@ -10,24 +9,13 @@ export const GET = ( ) => buildHandler({ req, - searchParamsSchema: zPagination, - handler: async ({ api, searchParams }) => { - const [list, bookmarks] = await Promise.all([ - api.lists.get({ - listId: params.listId, - }), - api.bookmarks.getBookmarks({ - listId: params.listId, - limit: searchParams.limit, - cursor: searchParams.cursor, - }), - ]); + handler: async ({ api }) => { + const list = await api.lists.get({ + listId: params.listId, + }); return { status: 200, - resp: { - ...list, - ...adaptPagination(bookmarks), - }, + 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 new file mode 100644 index 00000000..98133ec7 --- /dev/null +++ b/apps/web/app/api/v1/tags/[tagId]/bookmarks/route.ts @@ -0,0 +1,25 @@ +import { NextRequest } from "next/server"; +import { buildHandler } from "@/app/api/v1/utils/handler"; +import { adaptPagination, zPagination } from "@/app/api/v1/utils/pagination"; + +export const dynamic = "force-dynamic"; + +export const GET = ( + req: NextRequest, + { params }: { params: { tagId: string } }, +) => + buildHandler({ + req, + searchParamsSchema: zPagination, + handler: async ({ api, searchParams }) => { + const bookmarks = await api.bookmarks.getBookmarks({ + tagId: params.tagId, + 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 index a82e693c..439b6149 100644 --- a/apps/web/app/api/v1/tags/[tagId]/route.ts +++ b/apps/web/app/api/v1/tags/[tagId]/route.ts @@ -1,6 +1,5 @@ import { NextRequest } from "next/server"; import { buildHandler } from "@/app/api/v1/utils/handler"; -import { adaptPagination, zPagination } from "@/app/api/v1/utils/pagination"; export const dynamic = "force-dynamic"; @@ -10,24 +9,13 @@ export const GET = ( ) => buildHandler({ req, - searchParamsSchema: zPagination, - handler: async ({ api, searchParams }) => { - const [tag, bookmarks] = await Promise.all([ - api.tags.get({ - tagId: params.tagId, - }), - api.bookmarks.getBookmarks({ - tagId: params.tagId, - limit: searchParams.limit, - cursor: searchParams.cursor, - }), - ]); + handler: async ({ api }) => { + const tag = await api.tags.get({ + tagId: params.tagId, + }); return { status: 200, - resp: { - ...tag, - ...adaptPagination(bookmarks), - }, + resp: tag, }; }, }); diff --git a/packages/shared/types/bookmarks.ts b/packages/shared/types/bookmarks.ts index a9708f73..c8ab96f9 100644 --- a/packages/shared/types/bookmarks.ts +++ b/packages/shared/types/bookmarks.ts @@ -157,3 +157,15 @@ export const zUpdateBookmarksRequestSchema = z.object({ export type ZUpdateBookmarksRequest = z.infer< typeof zUpdateBookmarksRequestSchema >; + +// The schema that's used to for attachig/detaching tags +export const zManipulatedTagSchema = z + .object({ + // At least one of the two must be set + tagId: z.string().optional(), // If the tag already exists and we know its id we should pass it + tagName: z.string().optional(), + }) + .refine((val) => !!val.tagId || !!val.tagName, { + message: "You must provide either a tagId or a tagName", + path: ["tagId", "tagName"], + }); diff --git a/packages/trpc/routers/bookmarks.ts b/packages/trpc/routers/bookmarks.ts index f272433a..6439111a 100644 --- a/packages/trpc/routers/bookmarks.ts +++ b/packages/trpc/routers/bookmarks.ts @@ -36,6 +36,7 @@ import { zBookmarkSchema, zGetBookmarksRequestSchema, zGetBookmarksResponseSchema, + zManipulatedTagSchema, zNewBookmarkRequestSchema, zUpdateBookmarksRequestSchema, } from "@hoarder/shared/types/bookmarks"; @@ -732,30 +733,8 @@ export const bookmarksAppRouter = router({ .input( z.object({ bookmarkId: z.string(), - attach: z.array( - z - .object({ - // At least one of the two must be set - tagId: z.string().optional(), // If the tag already exists and we know its id we should pass it - tagName: z.string().optional(), - }) - .refine((val) => !!val.tagId || !!val.tagName, { - message: "You must provide either a tagId or a tagName", - path: ["tagId", "tagName"], - }), - ), - detach: z.array( - z - .object({ - // At least one of the two must be set - tagId: z.string().optional(), - tagName: z.string().optional(), // Also allow removing by tagName, to make CLI usage easier - }) - .refine((val) => !!val.tagId || !!val.tagName, { - message: "You must provide either a tagId or a tagName", - path: ["tagId", "tagName"], - }), - ), + attach: z.array(zManipulatedTagSchema), + detach: z.array(zManipulatedTagSchema), }), ) .output( |
