75 lines
2.4 KiB
TypeScript

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 },
);
}
}