From c98722c4781379c680d9ec9efc066e555bad318c Mon Sep 17 00:00:00 2001 From: "Mohamed Bassem (aider)" Date: Sun, 5 Jan 2025 14:01:40 +0000 Subject: refactor: Implement file streaming for asset route. Fixes #818 --- apps/web/app/api/assets/[assetId]/route.ts | 75 +++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 23 deletions(-) (limited to 'apps/web/app/api/assets') 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, + }, + }, + ); } } -- cgit v1.2.3-70-g09d2