import * as Sentry from "@sentry/nextjs"; import { NextResponse } from "next/server"; import { requireClient, unauthorizedJson, UnauthorizedError } from "@/lib/nextcloud-session"; import { normalizePath } from "@/lib/paths"; import { Readable } from "node:stream"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; function badRequest(message: string) { return NextResponse.json({ error: message }, { status: 400 }); } export async function GET(req: Request) { try { const { searchParams } = new URL(req.url); const rawPath = searchParams.get("path"); if (!rawPath) return badRequest("Missing path"); const path = normalizePath(rawPath); let client; try { client = await requireClient(req); } catch (err) { if (err instanceof UnauthorizedError) return unauthorizedJson(); throw err; } const result = await Sentry.startSpan( { op: "function", name: "api.files.download" }, async (span) => { span.setAttribute("path", path); // Optionally stat to get metadata (mime/name). Non-fatal if it fails. const stat = await client.stat(path).catch(() => null); const nodeStream = await client.downloadStream(path); // Convert Node.js readable to Web ReadableStream const asReadable = Readable as unknown as { toWeb?: (s: NodeJS.ReadableStream) => ReadableStream; }; const webStream = typeof asReadable.toWeb === "function" ? asReadable.toWeb(nodeStream) : (nodeStream as unknown as ReadableStream); const filename = stat?.name ?? path.split("/").pop() ?? "download"; const contentType = stat?.contentType ?? "application/octet-stream"; const headers = new Headers(); headers.set("Content-Type", contentType); // Default to attachment; UI may change to inline for previews later headers.set( "Content-Disposition", `attachment; filename*=UTF-8''${encodeURIComponent(filename)}`, ); if (stat?.etag) headers.set("ETag", stat.etag); return new Response(webStream, { headers }); }, ); return result; } catch (error) { Sentry.captureException(error); return NextResponse.json( { error: "Failed to download file", message: error instanceof Error ? error.message : String(error), }, { status: 500 }, ); } }