aboutsummaryrefslogtreecommitdiffstats
path: root/apps/web/app
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/app')
-rw-r--r--apps/web/app/api/assets/[assetId]/route.ts73
-rw-r--r--apps/web/app/api/v1/bookmarks/[bookmarkId]/assets/[assetId]/route.ts37
-rw-r--r--apps/web/app/api/v1/bookmarks/[bookmarkId]/assets/route.ts36
-rw-r--r--apps/web/app/api/v1/bookmarks/search/route.ts39
-rw-r--r--apps/web/app/api/v1/highlights/route.ts2
-rw-r--r--apps/web/app/api/v1/lists/[listId]/route.ts6
-rw-r--r--apps/web/app/dashboard/lists/[listId]/page.tsx16
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>
);
}