Pages corrections pages missions

This commit is contained in:
alma 2026-01-16 14:19:55 +01:00
parent 6fc50e1765
commit 312ed5894d

View File

@ -5,26 +5,20 @@ import { prisma } from '@/lib/prisma';
import { S3Client, ListObjectsV2Command, GetObjectCommand, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3'; import { S3Client, ListObjectsV2Command, GetObjectCommand, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3';
import { Readable } from 'stream'; import { Readable } from 'stream';
// S3 Configuration for missions bucket // Use the exact same S3 client configuration as mission-uploads.ts and image route
// Use the same configuration as mission-uploads.ts for consistency // This ensures we use the same credentials and settings that work for other mission operations
const MISSIONS_S3_CONFIG = {
endpoint: 'https://dome-api.slm-lab.net',
region: 'us-east-1',
bucket: 'missions',
accessKey: process.env.MINIO_ACCESS_KEY || '4aBT4CMb7JIMMyUtp4Pl',
secretKey: process.env.MINIO_SECRET_KEY || 'HGn39XhCIlqOjmDVzRK9MED2Fci2rYvDDgbLFElg'
};
const missionsS3Client = new S3Client({ const missionsS3Client = new S3Client({
region: MISSIONS_S3_CONFIG.region, region: 'us-east-1',
endpoint: MISSIONS_S3_CONFIG.endpoint, endpoint: 'https://dome-api.slm-lab.net',
credentials: { credentials: {
accessKeyId: MISSIONS_S3_CONFIG.accessKey, accessKeyId: '4aBT4CMb7JIMMyUtp4Pl',
secretAccessKey: MISSIONS_S3_CONFIG.secretKey secretAccessKey: 'HGn39XhCIlqOjmDVzRK9MED2Fci2rYvDDgbLFElg'
}, },
forcePathStyle: true // Required for MinIO forcePathStyle: true // Required for MinIO
}); });
const MISSIONS_BUCKET = 'missions';
// Helper function to check if user has access to mission // Helper function to check if user has access to mission
async function checkMissionAccess(userId: string, missionId: string): Promise<boolean> { async function checkMissionAccess(userId: string, missionId: string): Promise<boolean> {
const mission = await prisma.mission.findFirst({ const mission = await prisma.mission.findFirst({
@ -71,54 +65,46 @@ export async function GET(
const { searchParams } = new URL(request.url); const { searchParams } = new URL(request.url);
const path = searchParams.get('path') || ''; // Subfolder path within mission const path = searchParams.get('path') || ''; // Subfolder path within mission
// Get attachments from database first (like the missions page does)
const attachments = await prisma.attachment.findMany({
where: { missionId },
select: {
id: true,
filename: true,
filePath: true,
fileType: true,
fileSize: true,
createdAt: true
},
orderBy: { createdAt: 'desc' }
});
// Try to list files from S3, but don't fail if it doesn't work
let s3Folders: any[] = [];
let s3Files: any[] = [];
try {
// Construct prefix for listing // Construct prefix for listing
// Based on mission-uploads.ts, files are stored in MinIO without the "missions/" prefix // Based on mission-uploads.ts, files are stored in MinIO without the "missions/" prefix
// The filePath in DB is "missions/{missionId}/attachments/{filename}" // The filePath in DB is "missions/{missionId}/attachments/{filename}"
// But in MinIO it's stored as "{missionId}/attachments/{filename}" // But in MinIO it's stored as "{missionId}/attachments/{filename}"
// So we need to use just "{missionId}/" or "{missionId}/{path}/" as prefix
const prefix = path ? `${missionId}/${path}/` : `${missionId}/`; const prefix = path ? `${missionId}/${path}/` : `${missionId}/`;
console.log(`[GET /api/missions/${missionId}/files] Listing with prefix: "${prefix}" in bucket: "${MISSIONS_S3_CONFIG.bucket}"`); console.log(`[GET /api/missions/${missionId}/files] Listing with prefix: "${prefix}" in bucket: "${MISSIONS_BUCKET}"`);
const command = new ListObjectsV2Command({ const command = new ListObjectsV2Command({
Bucket: MISSIONS_S3_CONFIG.bucket, Bucket: MISSIONS_BUCKET,
Prefix: prefix, Prefix: prefix,
Delimiter: '/' Delimiter: '/'
}); });
console.log(`[GET /api/missions/${missionId}/files] S3 command:`, { const response = await missionsS3Client.send(command);
bucket: MISSIONS_S3_CONFIG.bucket,
prefix: prefix,
endpoint: MISSIONS_S3_CONFIG.endpoint,
accessKey: MISSIONS_S3_CONFIG.accessKey.substring(0, 4) + '...'
});
let response;
try {
response = await missionsS3Client.send(command);
console.log(`[GET /api/missions/${missionId}/files] S3 response success:`, {
commonPrefixes: response.CommonPrefixes?.length || 0,
contents: response.Contents?.length || 0
});
} catch (s3Error: any) {
console.error(`[GET /api/missions/${missionId}/files] S3 error:`, {
code: s3Error.Code,
message: s3Error.message,
bucket: MISSIONS_S3_CONFIG.bucket,
prefix: prefix,
endpoint: MISSIONS_S3_CONFIG.endpoint,
fullError: s3Error
});
throw s3Error;
}
// Extract folders (CommonPrefixes) // Extract folders (CommonPrefixes)
const folders = (response.CommonPrefixes || []).map(commonPrefix => { s3Folders = (response.CommonPrefixes || []).map(commonPrefix => {
const folderPath = commonPrefix.Prefix || ''; const folderPath = commonPrefix.Prefix || '';
// Extract folder name from path (e.g., "123/docs/" -> "docs")
const pathParts = folderPath.replace(prefix, '').split('/').filter(Boolean); const pathParts = folderPath.replace(prefix, '').split('/').filter(Boolean);
const folderName = pathParts[pathParts.length - 1] || folderPath; const folderName = pathParts[pathParts.length - 1] || folderPath;
// Store full path with missions/ prefix for consistency
const fullPath = `missions/${folderPath}`; const fullPath = `missions/${folderPath}`;
return { return {
type: 'folder', type: 'folder',
@ -129,18 +115,15 @@ export async function GET(
}); });
// Extract files (Contents, excluding folder markers) // Extract files (Contents, excluding folder markers)
const files = (response.Contents || []) s3Files = (response.Contents || [])
.filter(obj => { .filter(obj => {
if (!obj.Key) return false; if (!obj.Key) return false;
// Exclude folder markers
if (obj.Key.endsWith('/')) return false; if (obj.Key.endsWith('/')) return false;
// Exclude placeholder files
if (obj.Key.includes('.placeholder')) return false; if (obj.Key.includes('.placeholder')) return false;
return true; return true;
}) })
.map(obj => { .map(obj => {
const key = obj.Key || ''; const key = obj.Key || '';
// Store full path with missions/ prefix for consistency
const fullPath = `missions/${key}`; const fullPath = `missions/${key}`;
return { return {
type: 'file', type: 'file',
@ -151,16 +134,41 @@ export async function GET(
lastModified: obj.LastModified lastModified: obj.LastModified
}; };
}); });
} catch (s3Error: any) {
console.warn(`[GET /api/missions/${missionId}/files] S3 listing failed, using database attachments only:`, {
code: s3Error.Code,
message: s3Error.message
});
// Continue with database attachments only
}
return NextResponse.json({ // Convert database attachments to file format
folders, const dbFiles = attachments.map(att => ({
files: [...folders, ...files].sort((a, b) => { type: 'file' as const,
// Folders first, then files, both alphabetically name: att.filename,
path: att.filePath,
key: att.filePath,
size: att.fileSize,
lastModified: att.createdAt
}));
// Combine S3 files and database attachments, removing duplicates
const allFiles = [...s3Files, ...dbFiles];
const uniqueFiles = allFiles.filter((file, index, self) =>
index === self.findIndex(f => f.key === file.key)
);
// Combine folders and files
const allItems = [...s3Folders, ...uniqueFiles].sort((a, b) => {
if (a.type !== b.type) { if (a.type !== b.type) {
return a.type === 'folder' ? -1 : 1; return a.type === 'folder' ? -1 : 1;
} }
return a.name.localeCompare(b.name); return a.name.localeCompare(b.name);
}) });
return NextResponse.json({
folders: s3Folders,
files: allItems
}); });
} catch (error) { } catch (error) {
console.error('Error listing mission files:', error); console.error('Error listing mission files:', error);
@ -207,7 +215,7 @@ export async function POST(
const minioKey = key.replace(/^missions\//, ''); const minioKey = key.replace(/^missions\//, '');
const command = new GetObjectCommand({ const command = new GetObjectCommand({
Bucket: MISSIONS_S3_CONFIG.bucket, Bucket: MISSIONS_BUCKET,
Key: minioKey Key: minioKey
}); });
@ -260,7 +268,7 @@ export async function PUT(
const minioKey = key.replace(/^missions\//, ''); const minioKey = key.replace(/^missions\//, '');
const command = new PutObjectCommand({ const command = new PutObjectCommand({
Bucket: MISSIONS_S3_CONFIG.bucket, Bucket: MISSIONS_BUCKET,
Key: minioKey, Key: minioKey,
Body: content || Buffer.alloc(0), Body: content || Buffer.alloc(0),
ContentType: 'text/plain' ContentType: 'text/plain'