import { NextResponse } from 'next/server'; import { readFile, stat } from 'node:fs/promises'; import { extname, join, normalize } from 'node:path'; import { UPLOADS_DIR } from '@/lib/db/store'; export const runtime = 'nodejs'; const MIME: Record = { '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.webp': 'image/webp', '.gif': 'image/gif', '.svg': 'image/svg+xml', '.avif': 'image/avif', }; // Serves admin-uploaded media from the DATA_DIR volume. Public (not gated by // middleware) so images render on the marketing site. export async function GET( _req: Request, { params }: { params: { path: string[] } }, ) { const rel = normalize(params.path.join('/')); // Reject path traversal — the resolved file must stay inside UPLOADS_DIR. if (rel.includes('..') || rel.startsWith('/') || rel.startsWith('\\')) { return new NextResponse('bad path', { status: 400 }); } const filePath = join(UPLOADS_DIR, rel); try { const info = await stat(filePath); if (!info.isFile()) return new NextResponse('not found', { status: 404 }); const buf = await readFile(filePath); const type = MIME[extname(filePath).toLowerCase()] ?? 'application/octet-stream'; return new NextResponse(buf, { headers: { 'content-type': type, 'cache-control': 'public, max-age=31536000, immutable', }, }); } catch { return new NextResponse('not found', { status: 404 }); } }