Pages corrections pages missions
This commit is contained in:
parent
6fc50e1765
commit
312ed5894d
@ -5,26 +5,20 @@ import { prisma } from '@/lib/prisma';
|
||||
import { S3Client, ListObjectsV2Command, GetObjectCommand, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
// S3 Configuration for missions bucket
|
||||
// Use the same configuration as mission-uploads.ts for consistency
|
||||
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'
|
||||
};
|
||||
|
||||
// Use the exact same S3 client configuration as mission-uploads.ts and image route
|
||||
// This ensures we use the same credentials and settings that work for other mission operations
|
||||
const missionsS3Client = new S3Client({
|
||||
region: MISSIONS_S3_CONFIG.region,
|
||||
endpoint: MISSIONS_S3_CONFIG.endpoint,
|
||||
region: 'us-east-1',
|
||||
endpoint: 'https://dome-api.slm-lab.net',
|
||||
credentials: {
|
||||
accessKeyId: MISSIONS_S3_CONFIG.accessKey,
|
||||
secretAccessKey: MISSIONS_S3_CONFIG.secretKey
|
||||
accessKeyId: '4aBT4CMb7JIMMyUtp4Pl',
|
||||
secretAccessKey: 'HGn39XhCIlqOjmDVzRK9MED2Fci2rYvDDgbLFElg'
|
||||
},
|
||||
forcePathStyle: true // Required for MinIO
|
||||
});
|
||||
|
||||
const MISSIONS_BUCKET = 'missions';
|
||||
|
||||
// Helper function to check if user has access to mission
|
||||
async function checkMissionAccess(userId: string, missionId: string): Promise<boolean> {
|
||||
const mission = await prisma.mission.findFirst({
|
||||
@ -71,96 +65,110 @@ export async function GET(
|
||||
const { searchParams } = new URL(request.url);
|
||||
const path = searchParams.get('path') || ''; // Subfolder path within mission
|
||||
|
||||
// Construct prefix for listing
|
||||
// Based on mission-uploads.ts, files are stored in MinIO without the "missions/" prefix
|
||||
// The filePath in DB is "missions/{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}/`;
|
||||
|
||||
console.log(`[GET /api/missions/${missionId}/files] Listing with prefix: "${prefix}" in bucket: "${MISSIONS_S3_CONFIG.bucket}"`);
|
||||
|
||||
const command = new ListObjectsV2Command({
|
||||
Bucket: MISSIONS_S3_CONFIG.bucket,
|
||||
Prefix: prefix,
|
||||
Delimiter: '/'
|
||||
// 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' }
|
||||
});
|
||||
|
||||
console.log(`[GET /api/missions/${missionId}/files] S3 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;
|
||||
}
|
||||
// Try to list files from S3, but don't fail if it doesn't work
|
||||
let s3Folders: any[] = [];
|
||||
let s3Files: any[] = [];
|
||||
|
||||
// Extract folders (CommonPrefixes)
|
||||
const folders = (response.CommonPrefixes || []).map(commonPrefix => {
|
||||
const folderPath = commonPrefix.Prefix || '';
|
||||
// Extract folder name from path (e.g., "123/docs/" -> "docs")
|
||||
const pathParts = folderPath.replace(prefix, '').split('/').filter(Boolean);
|
||||
const folderName = pathParts[pathParts.length - 1] || folderPath;
|
||||
// Store full path with missions/ prefix for consistency
|
||||
const fullPath = `missions/${folderPath}`;
|
||||
return {
|
||||
type: 'folder',
|
||||
name: folderName,
|
||||
path: fullPath,
|
||||
key: fullPath
|
||||
};
|
||||
});
|
||||
try {
|
||||
// Construct prefix for listing
|
||||
// Based on mission-uploads.ts, files are stored in MinIO without the "missions/" prefix
|
||||
// The filePath in DB is "missions/{missionId}/attachments/{filename}"
|
||||
// But in MinIO it's stored as "{missionId}/attachments/{filename}"
|
||||
const prefix = path ? `${missionId}/${path}/` : `${missionId}/`;
|
||||
|
||||
// Extract files (Contents, excluding folder markers)
|
||||
const files = (response.Contents || [])
|
||||
.filter(obj => {
|
||||
if (!obj.Key) return false;
|
||||
// Exclude folder markers
|
||||
if (obj.Key.endsWith('/')) return false;
|
||||
// Exclude placeholder files
|
||||
if (obj.Key.includes('.placeholder')) return false;
|
||||
return true;
|
||||
})
|
||||
.map(obj => {
|
||||
const key = obj.Key || '';
|
||||
// Store full path with missions/ prefix for consistency
|
||||
const fullPath = `missions/${key}`;
|
||||
console.log(`[GET /api/missions/${missionId}/files] Listing with prefix: "${prefix}" in bucket: "${MISSIONS_BUCKET}"`);
|
||||
|
||||
const command = new ListObjectsV2Command({
|
||||
Bucket: MISSIONS_BUCKET,
|
||||
Prefix: prefix,
|
||||
Delimiter: '/'
|
||||
});
|
||||
|
||||
const response = await missionsS3Client.send(command);
|
||||
|
||||
// Extract folders (CommonPrefixes)
|
||||
s3Folders = (response.CommonPrefixes || []).map(commonPrefix => {
|
||||
const folderPath = commonPrefix.Prefix || '';
|
||||
const pathParts = folderPath.replace(prefix, '').split('/').filter(Boolean);
|
||||
const folderName = pathParts[pathParts.length - 1] || folderPath;
|
||||
const fullPath = `missions/${folderPath}`;
|
||||
return {
|
||||
type: 'file',
|
||||
name: key.split('/').pop() || key,
|
||||
type: 'folder',
|
||||
name: folderName,
|
||||
path: fullPath,
|
||||
key: fullPath,
|
||||
size: obj.Size,
|
||||
lastModified: obj.LastModified
|
||||
key: fullPath
|
||||
};
|
||||
});
|
||||
|
||||
// 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({
|
||||
folders,
|
||||
files: [...folders, ...files].sort((a, b) => {
|
||||
// Folders first, then files, both alphabetically
|
||||
if (a.type !== b.type) {
|
||||
return a.type === 'folder' ? -1 : 1;
|
||||
}
|
||||
return a.name.localeCompare(b.name);
|
||||
})
|
||||
folders: s3Folders,
|
||||
files: allItems
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error listing mission files:', error);
|
||||
@ -207,7 +215,7 @@ export async function POST(
|
||||
const minioKey = key.replace(/^missions\//, '');
|
||||
|
||||
const command = new GetObjectCommand({
|
||||
Bucket: MISSIONS_S3_CONFIG.bucket,
|
||||
Bucket: MISSIONS_BUCKET,
|
||||
Key: minioKey
|
||||
});
|
||||
|
||||
@ -260,7 +268,7 @@ export async function PUT(
|
||||
const minioKey = key.replace(/^missions\//, '');
|
||||
|
||||
const command = new PutObjectCommand({
|
||||
Bucket: MISSIONS_S3_CONFIG.bucket,
|
||||
Bucket: MISSIONS_BUCKET,
|
||||
Key: minioKey,
|
||||
Body: content || Buffer.alloc(0),
|
||||
ContentType: 'text/plain'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user