import { NextRequest, NextResponse } from 'next/server' import { getPayloadClient } from '@/lib/payload' function cleanFilenamePart(value: string | null | undefined, fallback: string) { const cleaned = value ?.trim() .replace(/[/\\?%*:|"<>]/g, '-') .replace(/\s+/g, '-') .replace(/-+/g, '-') .replace(/^-|-$/g, '') return cleaned || fallback } function buildFilename(title: string | null | undefined, format: string | null | undefined) { const name = cleanFilenamePart(title, 'media-asset') const extension = format?.trim().replace(/^\./, '').toLowerCase() return `${name}${extension ? `.${extension}` : ''}` } function getOriginalFilenameFromUrl(value: string) { try { const parsed = new URL(value, 'http://localhost') const filename = decodeURIComponent(parsed.pathname.split('/').filter(Boolean).pop() || '') const cleaned = filename .trim() .replace(/[/\\?%*:|"<>]/g, '-') .replace(/^-|-$/g, '') return cleaned && cleaned.includes('.') ? cleaned : null } catch { return null } } function contentDisposition(filename: string) { const ascii = filename.replace(/[^\x20-\x7E]/g, '_') return `attachment; filename="${ascii}"; filename*=UTF-8''${encodeURIComponent(filename)}` } function resolveDownloadUrl(value: string, request: NextRequest) { const trimmed = value.trim() if (trimmed.startsWith('/')) return new URL(trimmed, request.url).toString() const parsed = new URL(trimmed) if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { throw new Error('Unsupported download URL protocol.') } return parsed.toString() } async function proxyAttachment(url: string, filename: string, request: NextRequest) { const resolvedUrl = resolveDownloadUrl(url, request) const upstream = await fetch(resolvedUrl, { cache: 'no-store', headers: { Accept: '*/*', }, }) if (!upstream.ok || !upstream.body) { return NextResponse.json({ error: 'Download source is not available.' }, { status: 502 }) } const headers = new Headers() headers.set('Content-Disposition', contentDisposition(getOriginalFilenameFromUrl(resolvedUrl) || filename)) headers.set('Content-Type', upstream.headers.get('content-type') || 'application/octet-stream') headers.set('Cache-Control', 'private, no-store') const contentLength = upstream.headers.get('content-length') if (contentLength) headers.set('Content-Length', contentLength) return new NextResponse(upstream.body, { headers, status: 200, }) } export async function GET( request: NextRequest, { params }: { params: Promise<{ id: string }> }, ) { const { id } = await params const payload = await getPayloadClient() const asset = await payload.findByID({ collection: 'mediaAssets', depth: 0, id, }) if (!asset.externalUrl || asset.status !== 'published') { return NextResponse.json({ error: 'Download not found.' }, { status: 404 }) } return proxyAttachment(asset.externalUrl, buildFilename(asset.title, asset.fileFormat), request) }