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 { 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'
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user