75 lines
2.4 KiB
TypeScript
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 },
|
|
);
|
|
}
|
|
}
|