import { NextRequest, NextResponse } from 'next/server'; import { getServerSession } from 'next-auth'; import { authOptions } from '@/app/api/auth/options'; import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'; import { NoSuchKey } from '@aws-sdk/client-s3'; import { logger } from '@/lib/logger'; // S3 Configuration for missions - uses environment variables const MISSIONS_S3_CONFIG = { endpoint: (process.env.MINIO_S3_UPLOAD_BUCKET_URL || process.env.S3_ENDPOINT || 'https://dome-api.slm-lab.net').replace(/\/$/, ''), region: process.env.MINIO_AWS_REGION || process.env.S3_REGION || 'us-east-1', accessKey: process.env.MINIO_ACCESS_KEY || process.env.S3_ACCESS_KEY || '', secretKey: process.env.MINIO_SECRET_KEY || process.env.S3_SECRET_KEY || '', bucket: 'missions' // Missions bucket is always 'missions' }; // Validate required S3 configuration if (!MISSIONS_S3_CONFIG.accessKey || !MISSIONS_S3_CONFIG.secretKey) { const errorMsg = '⚠️ S3 credentials are missing! Please set MINIO_ACCESS_KEY and MINIO_SECRET_KEY environment variables.'; console.error(errorMsg); if (process.env.NODE_ENV === 'production') { throw new Error('S3 credentials are required in production environment'); } } // Initialize S3 client const s3Client = new S3Client({ region: MISSIONS_S3_CONFIG.region, endpoint: MISSIONS_S3_CONFIG.endpoint, credentials: { accessKeyId: MISSIONS_S3_CONFIG.accessKey, secretAccessKey: MISSIONS_S3_CONFIG.secretKey }, forcePathStyle: true // Required for MinIO }); // This endpoint serves mission images from Minio using the server's credentials export async function GET( request: NextRequest, { params }: { params: Promise<{ path: string[] }> } ) { try { const { path: pathSegments } = await params; if (!pathSegments || pathSegments.length === 0) { logger.error('No path segments provided'); return new NextResponse('Path is required', { status: 400 }); } // Reconstruct the full path from path segments const filePath = pathSegments.join('/'); logger.debug('Fetching mission image', { originalPath: filePath, segments: pathSegments }); // Remove the missions/ prefix from the URL path since the file is already in the missions bucket const minioPath = filePath.replace(/^missions\//, ''); logger.debug('Full Minio path', { minioPath, bucket: 'missions' }); const command = new GetObjectCommand({ Bucket: MISSIONS_S3_CONFIG.bucket, Key: minioPath, }); try { const response = await s3Client.send(command); if (!response.Body) { logger.error('File not found in Minio', { path: filePath, minioPath, bucket: 'missions' }); return new NextResponse('File not found', { status: 404 }); } // Set appropriate content type and cache control const contentType = response.ContentType || 'image/png'; // Default to image/png if not specified const headers = new Headers(); headers.set('Content-Type', contentType); headers.set('Cache-Control', 'public, max-age=31536000'); logger.debug('Serving image', { path: filePath, minioPath, contentType, contentLength: response.ContentLength }); return new NextResponse(response.Body as any, { headers }); } catch (error) { // Check if it's a NoSuchKey error (file doesn't exist) const isNoSuchKey = error instanceof NoSuchKey || (error instanceof Error && error.name === 'NoSuchKey') || (error instanceof Error && error.message.includes('does not exist')); logger.error('Error fetching file from Minio', { error: error instanceof Error ? error.message : String(error), path: filePath, minioPath, bucket: MISSIONS_S3_CONFIG.bucket, errorType: isNoSuchKey ? 'NoSuchKey' : 'Unknown', errorName: error instanceof Error ? error.name : typeof error }); if (isNoSuchKey) { // Return 404 with proper headers for image requests return new NextResponse('File not found', { status: 404, headers: { 'Content-Type': 'text/plain', 'Cache-Control': 'no-cache' } }); } return new NextResponse('Internal Server Error', { status: 500 }); } } catch (error) { logger.error('Error in image serving', { error: error instanceof Error ? error.message : String(error) }); return new NextResponse('Internal Server Error', { status: 500 }); } }