diff --git a/app/api/missions/route.ts b/app/api/missions/route.ts index 75d54da4..d13dc75c 100644 --- a/app/api/missions/route.ts +++ b/app/api/missions/route.ts @@ -5,7 +5,7 @@ import { prisma } from '@/lib/prisma'; import { N8nService } from '@/lib/services/n8n-service'; import { Prisma } from '@prisma/client'; import { s3Client } from '@/lib/s3'; -import { CopyObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3'; +import { CopyObjectCommand, DeleteObjectCommand, HeadObjectCommand } from '@aws-sdk/client-s3'; import { uploadMissionLogo, uploadMissionAttachment } from '@/lib/mission-uploads'; // Types @@ -187,6 +187,20 @@ export async function GET(request: Request) { } } +// Helper function to verify file exists in Minio +async function verifyFileExists(filePath: string): Promise { + try { + await s3Client.send(new HeadObjectCommand({ + Bucket: 'missions', + Key: filePath.replace('missions/', '') + })); + return true; + } catch (error) { + console.error('Error verifying file:', filePath, error); + return false; + } +} + // POST endpoint to create a new mission export async function POST(request: Request) { let uploadedFiles: { type: 'logo' | 'attachment', path: string }[] = []; @@ -293,12 +307,32 @@ export async function POST(request: Request) { } } - // Step 4: Trigger n8n workflow after all file operations are complete + // Step 4: Verify all files are in Minio before triggering n8n try { + // Verify logo if present + if (logoPath) { + const logoExists = await verifyFileExists(logoPath); + if (!logoExists) { + throw new Error('Logo file not found in Minio'); + } + } + + // Verify attachments if present + if (body.attachments?.length > 0) { + const attachmentVerifications = uploadedFiles + .filter(f => f.type === 'attachment') + .map(f => verifyFileExists(f.path)); + + const attachmentResults = await Promise.all(attachmentVerifications); + if (attachmentResults.some(exists => !exists)) { + throw new Error('One or more attachment files not found in Minio'); + } + } + + // Only trigger n8n after verifying all files console.log('=== Starting N8N Workflow ==='); const n8nService = new N8nService(); - // Prepare data for n8n with final file paths const n8nData = { ...body, creatorId: userId, @@ -310,7 +344,6 @@ export async function POST(request: Request) { }; console.log('Sending to N8N:', JSON.stringify(n8nData, null, 2)); - // Trigger n8n workflow const workflowResult = await n8nService.triggerMissionCreation(n8nData); console.log('N8N Workflow Result:', JSON.stringify(workflowResult, null, 2)); @@ -323,9 +356,9 @@ export async function POST(request: Request) { mission, message: 'Mission created successfully with all integrations' }); - } catch (n8nError) { - console.error('Error with n8n service:', n8nError); - throw new Error('Failed to create mission resources'); + } catch (error) { + console.error('Error in final verification or n8n:', error); + throw error; } } catch (error) { console.error('Error in mission creation:', error); diff --git a/components/missions/file-upload.tsx b/components/missions/file-upload.tsx index 4312a209..aaef13be 100644 --- a/components/missions/file-upload.tsx +++ b/components/missions/file-upload.tsx @@ -10,7 +10,7 @@ interface FileUploadProps { type: 'logo' | 'attachment'; missionId?: string; // Make missionId optional onUploadComplete?: (data: any) => void; - onFileSelect?: (file: File) => void; // New callback for when file is selected but not uploaded yet + onFileSelect?: (file: { data: string; name: string; type: string }) => void; // Updated type definition maxSize?: number; // in bytes, default 5MB acceptedFileTypes?: string; isNewMission?: boolean; // Flag to indicate if this is a new mission being created @@ -115,23 +115,57 @@ export function FileUpload({ setFile(droppedFile); // If this is a new mission, call onFileSelect instead of waiting for upload if (isNewMission && onFileSelect) { - onFileSelect(droppedFile); + onFileSelect({ + data: URL.createObjectURL(droppedFile), + name: droppedFile.name, + type: droppedFile.type + }); } } } }; - const handleFileChange = (e: React.ChangeEvent) => { + const handleFileChange = async (e: React.ChangeEvent) => { if (e.target.files && e.target.files.length > 0) { const selectedFile = e.target.files[0]; if (validateFile(selectedFile)) { setFile(selectedFile); - // Immediately upload the file - handleUpload(selectedFile); + + // For new missions, convert to base64 and notify parent + if (isNewMission && onFileSelect) { + try { + const base64 = await convertFileToBase64(selectedFile); + onFileSelect({ + data: base64, + name: selectedFile.name, + type: selectedFile.type + }); + } catch (error) { + console.error('Error converting file to base64:', error); + toast({ + title: 'Error', + description: 'Failed to process file. Please try again.', + variant: 'destructive', + }); + } + } else { + // For existing missions, upload immediately + handleUpload(selectedFile); + } } } }; + // Helper function to convert File to base64 + const convertFileToBase64 = (file: File): Promise => { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => resolve(reader.result as string); + reader.onerror = error => reject(error); + }); + }; + const handleUpload = async (uploadFile?: File) => { const fileToUpload = uploadFile || file; if (!fileToUpload) { @@ -157,7 +191,11 @@ export function FileUpload({ // For new missions, just notify through the callback and don't try to upload if (isNewMission) { if (onFileSelect) { - onFileSelect(fileToUpload); + onFileSelect({ + data: URL.createObjectURL(fileToUpload), + name: fileToUpload.name, + type: fileToUpload.type + }); } toast({ title: 'File selected',