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,96 +65,110 @@ 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
// Construct prefix for listing // Get attachments from database first (like the missions page does)
// Based on mission-uploads.ts, files are stored in MinIO without the "missions/" prefix const attachments = await prisma.attachment.findMany({
// The filePath in DB is "missions/{missionId}/attachments/{filename}" where: { missionId },
// But in MinIO it's stored as "{missionId}/attachments/{filename}" select: {
// So we need to use just "{missionId}/" or "{missionId}/{path}/" as prefix id: true,
const prefix = path ? `${missionId}/${path}/` : `${missionId}/`; filename: true,
filePath: true,
console.log(`[GET /api/missions/${missionId}/files] Listing with prefix: "${prefix}" in bucket: "${MISSIONS_S3_CONFIG.bucket}"`); fileType: true,
fileSize: true,
const command = new ListObjectsV2Command({ createdAt: true
Bucket: MISSIONS_S3_CONFIG.bucket, },
Prefix: prefix, orderBy: { createdAt: 'desc' }
Delimiter: '/'
}); });
console.log(`[GET /api/missions/${missionId}/files] S3 command:`, { // Try to list files from S3, but don't fail if it doesn't work
bucket: MISSIONS_S3_CONFIG.bucket, let s3Folders: any[] = [];
prefix: prefix, let s3Files: any[] = [];
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) try {
const folders = (response.CommonPrefixes || []).map(commonPrefix => { // Construct prefix for listing
const folderPath = commonPrefix.Prefix || ''; // Based on mission-uploads.ts, files are stored in MinIO without the "missions/" prefix
// Extract folder name from path (e.g., "123/docs/" -> "docs") // The filePath in DB is "missions/{missionId}/attachments/{filename}"
const pathParts = folderPath.replace(prefix, '').split('/').filter(Boolean); // But in MinIO it's stored as "{missionId}/attachments/{filename}"
const folderName = pathParts[pathParts.length - 1] || folderPath; const prefix = path ? `${missionId}/${path}/` : `${missionId}/`;
// Store full path with missions/ prefix for consistency
const fullPath = `missions/${folderPath}`;
return {
type: 'folder',
name: folderName,
path: fullPath,
key: fullPath
};
});
// Extract files (Contents, excluding folder markers) console.log(`[GET /api/missions/${missionId}/files] Listing with prefix: "${prefix}" in bucket: "${MISSIONS_BUCKET}"`);
const files = (response.Contents || [])
.filter(obj => { const command = new ListObjectsV2Command({
if (!obj.Key) return false; Bucket: MISSIONS_BUCKET,
// Exclude folder markers Prefix: prefix,
if (obj.Key.endsWith('/')) return false; Delimiter: '/'
// Exclude placeholder files });
if (obj.Key.includes('.placeholder')) return false;
return true; const response = await missionsS3Client.send(command);
})
.map(obj => { // Extract folders (CommonPrefixes)
const key = obj.Key || ''; s3Folders = (response.CommonPrefixes || []).map(commonPrefix => {
// Store full path with missions/ prefix for consistency const folderPath = commonPrefix.Prefix || '';
const fullPath = `missions/${key}`; const pathParts = folderPath.replace(prefix, '').split('/').filter(Boolean);
const folderName = pathParts[pathParts.length - 1] || folderPath;
const fullPath = `missions/${folderPath}`;
return { return {
type: 'file', type: 'folder',
name: key.split('/').pop() || key, name: folderName,
path: fullPath, path: fullPath,
key: fullPath, key: fullPath
size: obj.Size,
lastModified: obj.LastModified
}; };
}); });
// Extract files (Contents, excluding folder markers)
s3Files = (response.Contents || [])
.filter(obj => {
if (!obj.Key) return false;
if (obj.Key.endsWith('/')) return false;
if (obj.Key.includes('.placeholder')) return false;
return true;
})
.map(obj => {
const key = obj.Key || '';
const fullPath = `missions/${key}`;
return {
type: 'file',
name: key.split('/').pop() || key,
path: fullPath,
key: fullPath,
size: obj.Size,
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
}
// Convert database attachments to file format
const dbFiles = attachments.map(att => ({
type: 'file' as const,
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) {
return a.type === 'folder' ? -1 : 1;
}
return a.name.localeCompare(b.name);
});
return NextResponse.json({ return NextResponse.json({
folders, folders: s3Folders,
files: [...folders, ...files].sort((a, b) => { files: allItems
// Folders first, then files, both alphabetically
if (a.type !== b.type) {
return a.type === 'folder' ? -1 : 1;
}
return a.name.localeCompare(b.name);
})
}); });
} 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'