// /api/thumbnails/[...path]/route.ts // This endpoint serves thumbnails from the thumbnails directory import { NextResponse } from 'next/server'; import * as fs from 'fs'; import * as path from 'path'; // Path to thumbnails directory (should match UPLOADS_DIR/thumbnails) const THUMBNAILS_DIR = path.join(process.env.UPLOADS_DIR || '/uploads', 'thumbnails'); export async function GET( request: Request, context: { params: Promise<{ path?: string[] }> } ) { try { const params = await context.params; const pathSegments = params?.path || []; if (pathSegments.length === 0) { return NextResponse.json( { error: 'Invalid thumbnail request' }, { status: 400 } ); } // Construct the file path and validate it const requestedPath = path.join(THUMBNAILS_DIR, ...pathSegments); // Security: prevent directory traversal attacks if (!requestedPath.startsWith(THUMBNAILS_DIR)) { return NextResponse.json( { error: 'Invalid request' }, { status: 403 } ); } // Check if file exists if (!fs.existsSync(requestedPath)) { console.log(`[Thumbnails] File not found: ${requestedPath}`); return NextResponse.json( { error: 'Not found' }, { status: 404 } ); } // Read the file const fileContent = fs.readFileSync(requestedPath); // Determine content type based on file extension let contentType = 'application/octet-stream'; if (requestedPath.endsWith('.jpg') || requestedPath.endsWith('.jpeg')) { contentType = 'image/jpeg'; } else if (requestedPath.endsWith('.png')) { contentType = 'image/png'; } else if (requestedPath.endsWith('.webp')) { contentType = 'image/webp'; } else if (requestedPath.endsWith('.gif')) { contentType = 'image/gif'; } // Return the file with appropriate headers return new NextResponse(fileContent, { status: 200, headers: { 'Content-Type': contentType, 'Cache-Control': 'public, max-age=31536000, immutable', // Cache for 1 year (thumbnails don't change) 'Access-Control-Allow-Origin': '*', // Allow CORS if needed }, }); } catch (error) { console.error('[Thumbnails] Error serving thumbnail file:', error); return NextResponse.json( { error: 'Internal server error' }, { status: 500 } ); } } // HEAD request support for thumbnail validation export async function HEAD( request: Request, context: { params: Promise<{ path?: string[] }> } ) { try { const params = await context.params; const pathSegments = params?.path || []; if (pathSegments.length === 0) { return new NextResponse(null, { status: 400 }); } const requestedPath = path.join(THUMBNAILS_DIR, ...pathSegments); // Security: prevent directory traversal attacks if (!requestedPath.startsWith(THUMBNAILS_DIR)) { return new NextResponse(null, { status: 403 }); } // Check if file exists if (!fs.existsSync(requestedPath)) { return new NextResponse(null, { status: 404 }); } // Return headers only return new NextResponse(null, { status: 200, headers: { 'Content-Type': 'image/jpeg', // Default for HEAD requests 'Cache-Control': 'public, max-age=31536000, immutable', }, }); } catch (error) { console.error('[Thumbnails] Error in HEAD request:', error); return new NextResponse(null, { status: 500 }); } }