import { NextResponse } from 'next/server'; import path from 'path'; import { readFile, stat, access } from 'fs/promises'; import { createHash } from 'crypto'; import { UPLOADS_DIR } from '@/lib/db'; export const dynamic = 'force-dynamic'; type Params = { params: Promise<{ filename: string }> }; const MIME_MAP: Record = { jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png', gif: 'image/gif', webp: 'image/webp', }; export async function GET(req: Request, { params }: Params) { const { filename } = await params; // Prevent path traversal const safe = path.basename(filename); const filePath = path.join(UPLOADS_DIR, safe); try { await access(filePath); } catch { return NextResponse.json({ error: 'Not found' }, { status: 404 }); } const ext = safe.split('.').pop()?.toLowerCase() ?? ''; const contentType = MIME_MAP[ext] ?? 'application/octet-stream'; const stats = await stat(filePath); const etag = `"${createHash('md5').update(`${stats.size}-${stats.mtimeMs}`).digest('hex')}"`; // Return 304 if browser already has this version if (req.headers.get('if-none-match') === etag) { return new NextResponse(null, { status: 304 }); } const buffer = await readFile(filePath); return new NextResponse(buffer, { headers: { 'Content-Type': contentType, 'Cache-Control': 'public, max-age=31536000, immutable', 'ETag': etag, 'Last-Modified': stats.mtime.toUTCString(), }, }); }