import { Readable } from "node:stream"; import { NextRequest } from "next/server"; import { getServerSession } from "@/lib/auth/guards"; import { prisma } from "@/lib/db"; import { storage } from "@/lib/storage"; export const dynamic = "force-dynamic"; const CONTENT_TYPES: Record = { mp3: "audio/mpeg", png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", webp: "image/webp", zip: "application/zip", txt: "text/plain; charset=utf-8", }; /** * Serve a stored asset by key after verifying the requester owns the episode * (or is an admin). Private MP3 downloads flow through here; public cover art is * served directly by nginx from /media. */ export async function GET( req: NextRequest, { params }: { params: Promise<{ key: string[] }> } ) { const { key: segments } = await params; const key = segments.join("/"); const session = await getServerSession(); if (!session) return new Response("Unauthorized", { status: 401 }); // Resolve the owning episode from the asset record so we can authorize. const [audio, art] = await Promise.all([ prisma.audioAsset.findFirst({ where: { storageKey: key }, select: { episode: { select: { userId: true } } } }), prisma.coverArt.findFirst({ where: { storageKey: key }, select: { episode: { select: { userId: true } } } }), ]); const ownerId = audio?.episode.userId ?? art?.episode.userId; if (!ownerId) return new Response("Not found", { status: 404 }); const isOwner = ownerId === session.user.id; const isAdmin = session.user.role === "admin"; if (!isOwner && !isAdmin) return new Response("Forbidden", { status: 403 }); // Stream off disk instead of buffering the whole file into memory. const total = await storage().size(key); if (total === null) return new Response("Not found", { status: 404 }); const ext = key.split(".").pop()?.toLowerCase() ?? ""; const contentType = CONTENT_TYPES[ext] ?? "application/octet-stream"; const download = req.nextUrl.searchParams.get("download"); const filename = key.split("/").pop() ?? "asset"; const node = storage().createReadStream!(key); const body = Readable.toWeb(node as Readable) as unknown as BodyInit; return new Response(body, { headers: { "Content-Type": contentType, "Content-Length": String(total), "Cache-Control": "private, max-age=3600", ...(download ? { "Content-Disposition": `attachment; filename="${filename}"` } : {}), }, }); }