diff options
Diffstat (limited to 'apps/web/app')
| -rw-r--r-- | apps/web/app/api/assets/[assetId]/route.ts | 73 | ||||
| -rw-r--r-- | apps/web/app/api/v1/bookmarks/[bookmarkId]/assets/[assetId]/route.ts | 37 | ||||
| -rw-r--r-- | apps/web/app/api/v1/bookmarks/[bookmarkId]/assets/route.ts | 36 | ||||
| -rw-r--r-- | apps/web/app/api/v1/bookmarks/search/route.ts | 39 | ||||
| -rw-r--r-- | apps/web/app/api/v1/highlights/route.ts | 2 | ||||
| -rw-r--r-- | apps/web/app/api/v1/lists/[listId]/route.ts | 6 | ||||
| -rw-r--r-- | apps/web/app/dashboard/lists/[listId]/page.tsx | 16 |
7 files changed, 177 insertions, 32 deletions
diff --git a/apps/web/app/api/assets/[assetId]/route.ts b/apps/web/app/api/assets/[assetId]/route.ts index 3bff79ba..66ec6754 100644 --- a/apps/web/app/api/assets/[assetId]/route.ts +++ b/apps/web/app/api/assets/[assetId]/route.ts @@ -2,7 +2,11 @@ import { createContextFromRequest } from "@/server/api/client"; import { and, eq } from "drizzle-orm"; import { assets } from "@hoarder/db/schema"; -import { readAsset } from "@hoarder/shared/assetdb"; +import { + createAssetReadStream, + getAssetSize, + readAssetMetadata, +} from "@hoarder/shared/assetdb"; export const dynamic = "force-dynamic"; export async function GET( @@ -22,35 +26,60 @@ export async function GET( return Response.json({ error: "Asset not found" }, { status: 404 }); } - const { asset, metadata } = await readAsset({ - userId: ctx.user.id, - assetId: params.assetId, - }); + const [metadata, size] = await Promise.all([ + readAssetMetadata({ + userId: ctx.user.id, + assetId: params.assetId, + }), + + getAssetSize({ + userId: ctx.user.id, + assetId: params.assetId, + }), + ]); const range = request.headers.get("Range"); if (range) { const parts = range.replace(/bytes=/, "").split("-"); const start = parseInt(parts[0], 10); - const end = parts[1] ? parseInt(parts[1], 10) : asset.length - 1; + const end = parts[1] ? parseInt(parts[1], 10) : size - 1; - // TODO: Don't read the whole asset into memory in the first place - const chunk = asset.subarray(start, end + 1); - return new Response(chunk, { - status: 206, // Partial Content - headers: { - "Content-Range": `bytes ${start}-${end}/${asset.length}`, - "Accept-Ranges": "bytes", - "Content-Length": chunk.length.toString(), - "Content-type": metadata.contentType, - }, + const stream = createAssetReadStream({ + userId: ctx.user.id, + assetId: params.assetId, + start, + end, }); - } else { - return new Response(asset, { - status: 200, - headers: { - "Content-Length": asset.length.toString(), - "Content-type": metadata.contentType, + + return new Response( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any + stream as any, + { + status: 206, // Partial Content + headers: { + "Content-Range": `bytes ${start}-${end}/${size}`, + "Accept-Ranges": "bytes", + "Content-Length": (end - start + 1).toString(), + "Content-type": metadata.contentType, + }, }, + ); + } else { + const stream = createAssetReadStream({ + userId: ctx.user.id, + assetId: params.assetId, }); + + return new Response( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any + stream as any, + { + status: 200, + headers: { + "Content-Length": size.toString(), + "Content-type": metadata.contentType, + }, + }, + ); } } 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 new file mode 100644 index 00000000..3fc50801 --- /dev/null +++ b/apps/web/app/api/v1/bookmarks/[bookmarkId]/assets/[assetId]/route.ts @@ -0,0 +1,37 @@ +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.bookmarks.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.bookmarks.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 new file mode 100644 index 00000000..e5284a39 --- /dev/null +++ b/apps/web/app/api/v1/bookmarks/[bookmarkId]/assets/route.ts @@ -0,0 +1,36 @@ +import { NextRequest } from "next/server"; +import { buildHandler } from "@/app/api/v1/utils/handler"; + +import { zAssetSchema } from "@hoarder/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.bookmarks.attachAsset({ + bookmarkId: params.params.bookmarkId, + asset: body!, + }); + return { status: 201, resp: asset }; + }, + }); diff --git a/apps/web/app/api/v1/bookmarks/search/route.ts b/apps/web/app/api/v1/bookmarks/search/route.ts new file mode 100644 index 00000000..f0c5417a --- /dev/null +++ b/apps/web/app/api/v1/bookmarks/search/route.ts @@ -0,0 +1,39 @@ +import { NextRequest } from "next/server"; +import { z } from "zod"; + +import { buildHandler } from "../../utils/handler"; + +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(), + }), + handler: async ({ api, searchParams }) => { + const bookmarks = await api.bookmarks.searchBookmarks({ + text: searchParams.q, + cursor: searchParams.cursor, + limit: searchParams.limit, + }); + return { + status: 200, + resp: { + bookmarks: bookmarks.bookmarks, + nextCursor: bookmarks.nextCursor + ? `${bookmarks.nextCursor.offset}` + : null, + }, + }; + }, + }); diff --git a/apps/web/app/api/v1/highlights/route.ts b/apps/web/app/api/v1/highlights/route.ts index ebb96bae..a324d498 100644 --- a/apps/web/app/api/v1/highlights/route.ts +++ b/apps/web/app/api/v1/highlights/route.ts @@ -25,6 +25,6 @@ export const POST = (req: NextRequest) => bodySchema: zNewHighlightSchema, handler: async ({ body, api }) => { const resp = await api.highlights.create(body!); - return { status: 200, resp }; + return { status: 201, resp }; }, }); diff --git a/apps/web/app/api/v1/lists/[listId]/route.ts b/apps/web/app/api/v1/lists/[listId]/route.ts index 69c99fda..3fd0a32d 100644 --- a/apps/web/app/api/v1/lists/[listId]/route.ts +++ b/apps/web/app/api/v1/lists/[listId]/route.ts @@ -1,7 +1,7 @@ import { NextRequest } from "next/server"; import { buildHandler } from "@/app/api/v1/utils/handler"; -import { zNewBookmarkListSchema } from "@hoarder/shared/types/lists"; +import { zEditBookmarkListSchema } from "@hoarder/shared/types/lists"; export const dynamic = "force-dynamic"; @@ -28,11 +28,11 @@ export const PATCH = ( ) => buildHandler({ req, - bodySchema: zNewBookmarkListSchema.partial(), + bodySchema: zEditBookmarkListSchema.omit({ listId: true }), handler: async ({ api, body }) => { const list = await api.lists.edit({ - listId: params.listId, ...body!, + listId: params.listId, }); return { status: 200, resp: list }; }, diff --git a/apps/web/app/dashboard/lists/[listId]/page.tsx b/apps/web/app/dashboard/lists/[listId]/page.tsx index f8c5e0b6..159730a1 100644 --- a/apps/web/app/dashboard/lists/[listId]/page.tsx +++ b/apps/web/app/dashboard/lists/[listId]/page.tsx @@ -4,6 +4,8 @@ import ListHeader from "@/components/dashboard/lists/ListHeader"; import { api } from "@/server/api/client"; import { TRPCError } from "@trpc/server"; +import { BookmarkListContextProvider } from "@hoarder/shared-react/hooks/bookmark-list-context"; + export default async function ListPage({ params, }: { @@ -22,11 +24,13 @@ export default async function ListPage({ } return ( - <Bookmarks - query={{ listId: list.id }} - showDivider={true} - showEditorCard={true} - header={<ListHeader initialData={list} />} - /> + <BookmarkListContextProvider list={list}> + <Bookmarks + query={{ listId: list.id }} + showDivider={true} + showEditorCard={list.type === "manual"} + header={<ListHeader initialData={list} />} + /> + </BookmarkListContextProvider> ); } |
