diff --git a/app/api/missions/[missionId]/attachments/route.ts b/app/api/missions/[missionId]/attachments/route.ts index 18afc045..e2eaeff1 100644 --- a/app/api/missions/[missionId]/attachments/route.ts +++ b/app/api/missions/[missionId]/attachments/route.ts @@ -64,7 +64,7 @@ export async function GET(request: Request, props: { params: Promise<{ missionId // Add public URLs to attachments const attachmentsWithUrls = attachments.map(attachment => ({ ...attachment, - publicUrl: getPublicUrl(attachment.filePath, S3_CONFIG.bucket) + publicUrl: `/api/missions/image/${attachment.filePath}` })); return NextResponse.json(attachmentsWithUrls); diff --git a/app/api/missions/[missionId]/route.ts b/app/api/missions/[missionId]/route.ts index b42630ae..1e72168d 100644 --- a/app/api/missions/[missionId]/route.ts +++ b/app/api/missions/[missionId]/route.ts @@ -82,10 +82,10 @@ export async function GET(request: Request, props: { params: Promise<{ missionId // Add public URLs to mission logo and attachments const missionWithUrls = { ...mission, - logoUrl: mission.logo ? getPublicUrl(mission.logo, S3_CONFIG.bucket) : null, + logoUrl: mission.logo ? `/api/missions/image/${mission.logo}` : null, attachments: mission.attachments.map((attachment: { id: string; filename: string; filePath: string; fileType: string; fileSize: number; createdAt: Date }) => ({ ...attachment, - publicUrl: getPublicUrl(attachment.filePath, S3_CONFIG.bucket) + publicUrl: `/api/missions/image/${attachment.filePath}` })) }; diff --git a/app/api/missions/image/[...path]/route.ts b/app/api/missions/image/[...path]/route.ts new file mode 100644 index 00000000..21dc71a5 --- /dev/null +++ b/app/api/missions/image/[...path]/route.ts @@ -0,0 +1,62 @@ +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 { S3_CONFIG } from '@/lib/s3'; + +// Initialize S3 client +const s3Client = new S3Client({ + region: S3_CONFIG.region, + endpoint: S3_CONFIG.endpoint, + credentials: { + accessKeyId: S3_CONFIG.accessKey || '', + secretAccessKey: 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: { path: string[] } }) { + try { + // Check if path is provided + if (!params.path || params.path.length === 0) { + return new NextResponse('Path is required', { status: 400 }); + } + + // Reconstruct the full path from path segments + const filePath = params.path.join('/'); + console.log('Fetching mission image:', filePath); + + // Create S3 command to get the object + const command = new GetObjectCommand({ + Bucket: S3_CONFIG.bucket, // Use the pages bucket + Key: filePath, + }); + + // Get the object from S3/Minio + const response = await s3Client.send(command); + + if (!response.Body) { + console.error('File not found in Minio:', filePath); + return new NextResponse('File not found', { status: 404 }); + } + + // Get the readable web stream directly + const stream = response.Body.transformToWebStream(); + + // Determine content type + const contentType = response.ContentType || 'application/octet-stream'; + + // Create and return a new response with the file stream + return new NextResponse(stream as ReadableStream, { + headers: { + 'Content-Type': contentType, + 'Cache-Control': 'public, max-age=31536000', // Cache for 1 year + }, + }); + } catch (error) { + console.error('Error serving mission image:', error); + console.error('Error details:', JSON.stringify(error, null, 2)); + return new NextResponse('Internal Server Error', { status: 500 }); + } +} \ No newline at end of file diff --git a/app/api/missions/route.ts b/app/api/missions/route.ts index 0843ab13..f11a375d 100644 --- a/app/api/missions/route.ts +++ b/app/api/missions/route.ts @@ -87,7 +87,7 @@ export async function GET(request: Request) { // Transform logo paths to public URLs const missionsWithPublicUrls = missions.map(mission => ({ ...mission, - logo: mission.logo ? getPublicUrl(mission.logo, S3_CONFIG.bucket) : null + logo: mission.logo ? `/api/missions/image/${mission.logo}` : null })); return NextResponse.json({ diff --git a/app/missions/page.tsx b/app/missions/page.tsx index 636915bd..893a26ce 100644 --- a/app/missions/page.tsx +++ b/app/missions/page.tsx @@ -187,112 +187,121 @@ export default function MissionsPage() { ) : filteredMissions.length > 0 ? (