126 lines
4.5 KiB
TypeScript
126 lines
4.5 KiB
TypeScript
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 });
|
|
}
|
|
}
|