From 6274f3a4016140f988411a0c8e569dcadfa149f0 Mon Sep 17 00:00:00 2001 From: alma Date: Sat, 24 May 2025 19:50:15 +0200 Subject: [PATCH] W n8n attention vm --- app/api/missions/route.ts | 264 ++++++++++++++++++++------------------ lib/s3.ts | 13 ++ 2 files changed, 155 insertions(+), 122 deletions(-) diff --git a/app/api/missions/route.ts b/app/api/missions/route.ts index 0314cd86..abee43ff 100644 --- a/app/api/missions/route.ts +++ b/app/api/missions/route.ts @@ -4,6 +4,9 @@ import { authOptions } from "@/app/api/auth/options"; 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 { uploadMissionLogo, uploadMissionAttachment } from '@/lib/mission-uploads'; // Types interface MissionCreateInput { @@ -186,6 +189,8 @@ export async function GET(request: Request) { // POST endpoint to create a new mission export async function POST(request: Request) { + let uploadedFiles: { type: 'logo' | 'attachment', path: string }[] = []; + try { console.log('=== Mission Creation Started ==='); const { authorized, userId } = await checkAuth(request); @@ -204,145 +209,160 @@ export async function POST(request: Request) { }, { status: 400 }); } - // Check if this is a request from n8n - const isN8nRequest = request.headers.get('x-api-key') === process.env.N8N_API_KEY; - console.log('Is N8N request:', isN8nRequest); - - if (!isN8nRequest) { + // Step 1: Upload files to Minio if present + let logoPath = null; + if (body.logo?.data) { try { - console.log('=== Starting N8N Workflow ==='); - const n8nService = new N8nService(); + // Convert base64 to File object + const base64Data = body.logo.data.split(',')[1]; + const buffer = Buffer.from(base64Data, 'base64'); + const file = new File([buffer], body.logo.name || 'logo.png', { type: body.logo.type || 'image/png' }); - // Prepare data for n8n - const n8nData = { - ...body, - creatorId: userId, - config: { - N8N_API_KEY: process.env.N8N_API_KEY, - MISSION_API_URL: process.env.NEXT_PUBLIC_API_URL - } - }; - console.log('Sending to N8N:', JSON.stringify(n8nData, null, 2)); - - // Trigger n8n workflow first - const workflowResult = await n8nService.triggerMissionCreation(n8nData); - console.log('N8N Workflow Result:', JSON.stringify(workflowResult, null, 2)); + // Upload logo + const { filePath } = await uploadMissionLogo(userId, 'temp', file); + logoPath = filePath; + uploadedFiles.push({ type: 'logo', path: filePath }); - if (!workflowResult.success) { - console.error('N8N workflow failed:', workflowResult.error); - return NextResponse.json({ - error: 'Failed to create mission resources', - details: workflowResult.error - }, { status: 500 }); - } - - // Only create mission in database after n8n succeeds - try { - // Extract logo path from workflow result - let logoPath = null; - console.log('Full workflow result:', JSON.stringify(workflowResult, null, 2)); - - if (workflowResult.results && Array.isArray(workflowResult.results)) { - const firstResult = workflowResult.results[0]; - console.log('First result from workflow:', JSON.stringify(firstResult, null, 2)); - - // Check if the result is HTML (error case) - if (typeof firstResult === 'string' && firstResult.includes('')) { - console.error('Received HTML response instead of JSON:', firstResult); - return NextResponse.json({ - error: 'Invalid response from workflow', - details: 'Received HTML instead of JSON data' - }, { status: 500 }); - } - - // Get logo path from the nested structure - if (firstResult?.integrationResults?.uploadResults?.logoPath) { - logoPath = firstResult.integrationResults.uploadResults.logoPath; - console.log('Extracted logo path from uploadResults:', logoPath); - } else if (firstResult?.logoPath) { - logoPath = firstResult.logoPath; - console.log('Extracted logo path from root:', logoPath); - } else { - console.log('No logo path found in result:', firstResult); - } - } - - const missionData = { - name: body.name, - oddScope: body.oddScope, - niveau: body.niveau, - intention: body.intention, - missionType: body.missionType, - donneurDOrdre: body.donneurDOrdre, - projection: body.projection, - services: body.services, - profils: body.profils, - participation: body.participation, - creatorId: userId, - logo: logoPath ? `missions/${logoPath}` : null - }; - - console.log('Creating mission with data:', JSON.stringify(missionData, null, 2)); - - const mission = await prisma.mission.create({ - data: missionData - }); - - console.log('Mission created successfully:', JSON.stringify(mission, null, 2)); - - return NextResponse.json({ - success: true, - mission, - message: 'Mission created successfully with all integrations' - }); - } catch (dbError) { - console.error('Database error creating mission:', dbError); - return NextResponse.json({ - error: 'Failed to create mission in database', - details: dbError instanceof Error ? dbError.message : String(dbError) - }, { status: 500 }); - } - } catch (n8nError) { - console.error('Error with n8n service:', n8nError); - return NextResponse.json({ - error: 'Failed to create mission resources', - details: n8nError instanceof Error ? n8nError.message : String(n8nError) - }, { status: 500 }); + console.log('Logo uploaded successfully:', { logoPath }); + } catch (uploadError) { + console.error('Error uploading logo:', uploadError); + throw new Error('Failed to upload logo'); } - } else { - console.log('=== Handling N8N Callback ==='); - console.log('N8N callback body:', JSON.stringify(body, null, 2)); - // Handle n8n callback - update mission with integration IDs + } + + // Upload attachments if present + const attachmentPaths = []; + if (body.attachments?.length > 0) { + for (const attachment of body.attachments) { + try { + const base64Data = attachment.data.split(',')[1]; + const buffer = Buffer.from(base64Data, 'base64'); + const file = new File([buffer], attachment.name, { type: attachment.type }); + + const result = await uploadMissionAttachment(userId, 'temp', file); + attachmentPaths.push(result.filePath); + uploadedFiles.push({ type: 'attachment', path: result.filePath }); + + console.log('Attachment uploaded successfully:', { path: result.filePath }); + } catch (uploadError) { + console.error('Error uploading attachment:', uploadError); + throw new Error(`Failed to upload attachment: ${attachment.name}`); + } + } + } + + // Step 2: Trigger n8n workflow + try { + console.log('=== Starting N8N Workflow ==='); + const n8nService = new N8nService(); + + // Prepare data for n8n + const n8nData = { + ...body, + creatorId: userId, + logoPath, // Add the uploaded logo path + attachmentPaths, // Add the uploaded attachment paths + config: { + N8N_API_KEY: process.env.N8N_API_KEY, + MISSION_API_URL: process.env.NEXT_PUBLIC_API_URL + } + }; + 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)); + + if (!workflowResult.success) { + throw new Error(workflowResult.error || 'N8N workflow failed'); + } + + // Step 3: Create mission in database try { - const mission = await prisma.mission.update({ - where: { id: body.missionId }, - data: { - leantimeProjectId: body.leantimeProjectId ? String(body.leantimeProjectId) : null, - outlineCollectionId: body.outlineCollectionId || null, - rocketChatChannelId: body.rocketChatChannelId || null, - giteaRepositoryUrl: body.giteaRepositoryUrl || null, - penpotProjectId: body.penpotProjectId || null, - logo: body.logoPath ? `missions/${body.logoPath}` : null // Add logo path update here - } as Prisma.MissionUpdateInput + const missionData = { + name: body.name, + oddScope: body.oddScope, + niveau: body.niveau, + intention: body.intention, + missionType: body.missionType, + donneurDOrdre: body.donneurDOrdre, + projection: body.projection, + services: body.services, + profils: body.profils, + participation: body.participation, + creatorId: userId, + logo: logoPath, + leantimeProjectId: workflowResult.leantimeProjectId, + outlineCollectionId: workflowResult.outlineCollectionId, + rocketChatChannelId: workflowResult.rocketChatChannelId, + giteaRepositoryUrl: workflowResult.giteaRepositoryUrl, + penpotProjectId: workflowResult.penpotProjectId + }; + + console.log('Creating mission with data:', JSON.stringify(missionData, null, 2)); + + const mission = await prisma.mission.create({ + data: missionData }); - console.log('Mission updated with integrations:', JSON.stringify(mission, null, 2)); + console.log('Mission created successfully:', JSON.stringify(mission, null, 2)); + + // Move uploaded files to final location + if (logoPath) { + const finalLogoPath = logoPath.replace('temp', mission.id); + await s3Client.send(new CopyObjectCommand({ + Bucket: 'missions', + CopySource: `missions/${logoPath}`, + Key: finalLogoPath.replace('missions/', '') + })); + await s3Client.send(new DeleteObjectCommand({ + Bucket: 'missions', + Key: logoPath.replace('missions/', '') + })); + } + + for (const attachmentPath of attachmentPaths) { + const finalAttachmentPath = attachmentPath.replace('temp', mission.id); + await s3Client.send(new CopyObjectCommand({ + Bucket: 'missions', + CopySource: `missions/${attachmentPath}`, + Key: finalAttachmentPath.replace('missions/', '') + })); + await s3Client.send(new DeleteObjectCommand({ + Bucket: 'missions', + Key: attachmentPath.replace('missions/', '') + })); + } return NextResponse.json({ success: true, mission, - message: 'Mission updated with integration IDs' + message: 'Mission created successfully with all integrations' }); } catch (dbError) { - console.error('Database error updating mission:', dbError); - return NextResponse.json({ - error: 'Failed to update mission with integration IDs', - details: dbError instanceof Error ? dbError.message : String(dbError) - }, { status: 500 }); + console.error('Database error creating mission:', dbError); + throw new Error('Failed to create mission in database'); } + } catch (n8nError) { + console.error('Error with n8n service:', n8nError); + throw new Error('Failed to create mission resources'); } } catch (error) { console.error('Error in mission creation:', error); + + // Cleanup: Delete any uploaded files + for (const file of uploadedFiles) { + try { + await s3Client.send(new DeleteObjectCommand({ + Bucket: 'missions', + Key: file.path.replace('missions/', '') + })); + console.log('Cleaned up file:', file.path); + } catch (cleanupError) { + console.error('Error cleaning up file:', file.path, cleanupError); + } + } + return NextResponse.json({ error: 'Failed to create mission', details: error instanceof Error ? error.message : String(error) diff --git a/lib/s3.ts b/lib/s3.ts index 28261fda..a0538fe5 100644 --- a/lib/s3.ts +++ b/lib/s3.ts @@ -1,3 +1,16 @@ +import { S3Client } from '@aws-sdk/client-s3'; + +// Initialize S3 client for Minio +export const s3Client = new S3Client({ + region: 'us-east-1', + endpoint: 'https://dome-api.slm-lab.net', + credentials: { + accessKeyId: '4aBT4CMb7JIMMyUtp4Pl', + secretAccessKey: 'HGn39XhCIlqOjmDVzRK9MED2Fci2rYvDDgbLFElg' + }, + forcePathStyle: true // Required for MinIO +}); + export async function uploadMissionFile({ missionId, file,