diff options
| author | Mohamed Bassem (aider) <me@mbassem.com> | 2025-01-05 14:01:40 +0000 |
|---|---|---|
| committer | Mohamed Bassem <me@mbassem.com> | 2025-01-05 14:11:50 +0000 |
| commit | c98722c4781379c680d9ec9efc066e555bad318c (patch) | |
| tree | 7e4918b96613c1c6b6998450e3c580d5392de48c /apps/web/app | |
| parent | 88dc6f911bdae49ca72fc82d6da9dce84853bce0 (diff) | |
| download | karakeep-c98722c4781379c680d9ec9efc066e555bad318c.tar.zst | |
refactor: Implement file streaming for asset route. Fixes #818
Diffstat (limited to 'apps/web/app')
| -rw-r--r-- | apps/web/app/api/assets/[assetId]/route.ts | 75 |
1 files changed, 52 insertions, 23 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; - - // 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 end = parts[1] ? parseInt(parts[1], 10) : size - 1; + + 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, + }, + }, + ); } } |
