import { getAsset } from "@/lib/server/data-store"; import { jsonError } from "@/lib/server/api"; import { requireAppUser } from "@/lib/server/auth/current-user"; import { readAssetForDownload } from "@/lib/server/storage"; export const runtime = "nodejs"; export async function GET(_request: Request, context: { params: Promise<{ id: string }> }) { try { const user = await requireAppUser(); const { id } = await context.params; const asset = await getAsset(id); if (!asset || asset.ownerId !== user.id) return jsonError("资产不存在", 404); const file = await readAssetForDownload(asset); if (!file) return jsonError("资产文件不可下载", 404); return new Response(new Uint8Array(file.bytes), { headers: { "Content-Type": file.contentType, "Content-Length": String(file.bytes.length), "Content-Disposition": contentDisposition(asset.name || `${asset.id}${extensionForContentType(file.contentType)}`), "Cache-Control": "private, no-store" } }); } catch (error) { return jsonError(error, 500); } } function contentDisposition(fileName: string): string { const clean = fileName.replace(/[\r\n/\\]/g, "_").trim() || "download"; const ascii = clean.replace(/[^\x20-\x7E]/g, "_").replace(/"/g, "_"); return `attachment; filename="${ascii}"; filename*=UTF-8''${encodeURIComponent(clean)}`; } function extensionForContentType(contentType: string): string { const normalized = contentType.split(";")[0]?.trim().toLowerCase(); if (normalized === "image/png") return ".png"; if (normalized === "image/jpeg") return ".jpg"; if (normalized === "image/webp") return ".webp"; if (normalized === "image/svg+xml") return ".svg"; if (normalized === "video/mp4") return ".mp4"; if (normalized === "audio/mpeg") return ".mp3"; return ""; }