diff --git a/app/api/missions/route.ts b/app/api/missions/route.ts index 36189f59..50b7d667 100644 --- a/app/api/missions/route.ts +++ b/app/api/missions/route.ts @@ -154,329 +154,79 @@ export async function POST(request: Request) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } - const body = await request.json() as MissionCreateInput; + const body = await request.json(); - // Add detailed logging - console.log('Mission API - Request body:', { - name: body.name, - hasServices: Array.isArray(body.services), - services: body.services, - hasGite: body.services?.includes('Gite'), - isN8nRequest: request.headers.get('x-api-key') === process.env.N8N_API_KEY - }); - - // Validate required fields - const requiredFields = ['name', 'oddScope']; - const missingFields = requiredFields.filter(field => !body[field as keyof MissionCreateInput]); - - if (missingFields.length > 0) { + // Simple validation + if (!body.name || !body.oddScope) { return NextResponse.json({ error: 'Missing required fields', - missingFields + missingFields: ['name', 'oddScope'].filter(field => !body[field]) }, { status: 400 }); } // Check if this is a request from n8n const isN8nRequest = request.headers.get('x-api-key') === process.env.N8N_API_KEY; - // Get the creator ID from the appropriate source - const creatorId = isN8nRequest ? body.creatorId : userId; - - // Verify creator exists - const creator = await prisma.user.findUnique({ - where: { id: creatorId } - }); - - if (!creator) { - return NextResponse.json({ - error: 'Invalid creator ID', - details: 'The specified creator does not exist', - code: 'INVALID_CREATOR' - }, { status: 400 }); - } - - // Check if mission with same name exists - const existingMission = await prisma.mission.findFirst({ - where: { name: body.name } - }); - - if (existingMission) { - // Update existing mission with new integration IDs - const updatedMission = await prisma.mission.update({ - where: { id: existingMission.id }, - data: { - leantimeProjectId: body.leantimeProjectId ? String(body.leantimeProjectId) : null, - outlineCollectionId: body.outlineCollectionId || null, - rocketChatChannelId: body.rocketChatChannelId || null, - giteaRepositoryUrl: body.giteaRepositoryUrl || null, - penpotProjectId: body.penpotProjectId || null - } as Prisma.MissionUpdateInput - }); - - return NextResponse.json({ - message: 'Mission updated successfully', - mission: updatedMission, - isUpdate: true - }); - } - - // Handle n8n workflow if (!isN8nRequest) { const n8nService = new N8nService(); - // Prepare the data for n8n + // Simple data preparation for n8n const n8nData = { - name: body.name, - oddScope: Array.isArray(body.oddScope) ? body.oddScope : [body.oddScope], - niveau: body.niveau || 'default', - intention: body.intention?.trim() || '', - missionType: body.missionType || 'default', - donneurDOrdre: body.donneurDOrdre || 'default', - projection: body.projection || 'default', - services: Array.isArray(body.services) ? body.services : [], - participation: body.participation || 'default', - profils: Array.isArray(body.profils) ? body.profils : [], - guardians: body.guardians || {}, - volunteers: Array.isArray(body.volunteers) ? body.volunteers : [], + ...body, creatorId: userId, - logo: body.logo ? { - data: body.logo.data, - name: body.logo.name || 'logo.png', - type: body.logo.type || 'image/png' - } : null, - attachments: body.attachments || [], config: { N8N_API_KEY: process.env.N8N_API_KEY, - MISSION_API_URL: process.env.NEXT_PUBLIC_API_URL || 'https://api.slm-lab.net/api' + MISSION_API_URL: process.env.NEXT_PUBLIC_API_URL } }; - try { - const workflowResult = await n8nService.triggerMissionCreation(n8nData); - - if (!workflowResult.success) { - console.error('n8n workflow failed:', workflowResult.error); - return NextResponse.json({ - error: 'Failed to create mission resources', - details: workflowResult.error || 'The mission creation process failed', - code: 'WORKFLOW_ERROR' - }, { status: 500 }); - } - - return NextResponse.json({ - success: true, - status: 'success', - message: 'Mission creation initiated', - mission: { - name: n8nData.name, - oddScope: n8nData.oddScope, - niveau: n8nData.niveau, - intention: n8nData.intention, - missionType: n8nData.missionType, - donneurDOrdre: n8nData.donneurDOrdre, - projection: n8nData.projection, - services: n8nData.services, - profils: n8nData.profils, - participation: n8nData.participation, - creatorId: n8nData.creatorId - } - }); - } catch (error) { - console.error('Error triggering n8n workflow:', error); + const workflowResult = await n8nService.triggerMissionCreation(n8nData); + + if (!workflowResult.success) { return NextResponse.json({ - error: 'Failed to initiate mission creation', - details: error instanceof Error ? error.message : String(error) + error: 'Failed to create mission resources', + details: workflowResult.error }, { status: 500 }); } - } - - // Create mission directly (n8n request) - try { - const mission = await prisma.mission.create({ - data: { - name: body.name, - oddScope: body.oddScope || ['default'], - niveau: body.niveau, - intention: body.intention, - missionType: body.missionType, - donneurDOrdre: body.donneurDOrdre, - projection: body.projection, - services: Array.isArray(body.services) ? body.services.filter(Boolean) : [], - profils: Array.isArray(body.profils) ? body.profils.filter(Boolean) : [], - participation: body.participation || 'default', - creatorId: creatorId, - logo: body.logo || null, - leantimeProjectId: body.leantimeProjectId ? String(body.leantimeProjectId) : null, - outlineCollectionId: body.outlineCollectionId || null, - rocketChatChannelId: body.rocketChatChannelId || null, - giteaRepositoryUrl: body.giteaRepositoryUrl || null, - penpotProjectId: body.penpotProjectId || null - } as Prisma.MissionUncheckedCreateInput - }); - - // Add guardians and volunteers - if (body.guardians || body.volunteers) { - const missionUsers: MissionUserInput[] = []; - - // Add guardians - if (body.guardians) { - Object.entries(body.guardians).forEach(([role, userId]) => { - if (userId) { - missionUsers.push({ - role, - userId: userId as string, - missionId: mission.id - }); - } - }); - } - - // Add volunteers - if (body.volunteers && Array.isArray(body.volunteers)) { - body.volunteers.forEach(userId => { - if (userId) { - missionUsers.push({ - role: 'volontaire', - userId, - missionId: mission.id - }); - } - }); - } - - if (missionUsers.length > 0) { - await prisma.missionUser.createMany({ - data: missionUsers - }); - } - } - - // Handle Git repository creation - if (mission.services.includes('Gite')) { - try { - // Sanitize the mission name similar to n8n workflow - const sanitizedName = mission.name.toLowerCase() - .split('') - .map(c => { - if (c >= 'a' && c <= 'z') return c; - if (c >= '0' && c <= '9') return c; - if (c === ' ' || c === '-') return c; - return ''; - }) - .join('') - .split(' ') - .filter(Boolean) - .join('-'); - - console.log('Creating Git repository with name:', sanitizedName); - - const giteaResponse = await fetch(`${process.env.GITEA_API_URL}/user/repos`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `token ${process.env.GITEA_API_TOKEN}` - }, - body: JSON.stringify({ - name: sanitizedName, - private: true, - auto_init: true, - avatar_url: mission.logo || null - }) - }); - - if (!giteaResponse.ok) { - const errorData = await giteaResponse.json(); - console.error('Git repository creation failed:', { - status: giteaResponse.status, - statusText: giteaResponse.statusText, - error: errorData, - requestBody: { - name: sanitizedName, - private: true, - auto_init: true - } - }); - throw new Error(`Git repository creation failed: ${errorData.message || giteaResponse.statusText}`); - } - - const giteaData = await giteaResponse.json(); - console.log('Git repository created successfully:', giteaData.html_url); - - // Update the mission with the Git repository URL using the correct Prisma field - await prisma.mission.update({ - where: { id: mission.id }, - data: { - giteaRepositoryUrl: giteaData.html_url - } as Prisma.MissionUpdateInput - }); - - } catch (error) { - console.error('Error creating Git repository:', error); - if (error instanceof Error) { - throw new Error(`Failed to create Git repository: ${error.message}`); - } - throw new Error('Failed to create Git repository: Unknown error'); - } - } return NextResponse.json({ success: true, - status: 'success', - message: 'Mission created successfully', - mission: { - id: (mission as any).id, - name: (mission as any).name, - oddScope: (mission as any).oddScope, - niveau: (mission as any).niveau, - intention: (mission as any).intention, - missionType: (mission as any).missionType, - donneurDOrdre: (mission as any).donneurDOrdre, - projection: (mission as any).projection, - services: (mission as any).services, - profils: (mission as any).profils, - participation: (mission as any).participation, - creatorId: (mission as any).creatorId, - logo: (mission as any).logo, - leantimeProjectId: (mission as any).leantimeProjectId, - outlineCollectionId: (mission as any).outlineCollectionId, - rocketChatChannelId: (mission as any).rocketChatChannelId, - giteaRepositoryUrl: (mission as any).giteaRepositoryUrl, - penpotProjectId: (mission as any).penpotProjectId, - createdAt: (mission as any).createdAt, - updatedAt: (mission as any).updatedAt - } as MissionResponse + message: 'Mission creation initiated', + mission: body }); - } catch (error) { - console.error('Error creating mission:', error); - if (error instanceof Prisma.PrismaClientKnownRequestError) { - if (error.code === 'P2003') { - return NextResponse.json({ - success: false, - status: 'error', - error: 'Invalid reference', - message: 'One or more referenced users do not exist', - code: 'INVALID_REFERENCE' - }, { status: 400 }); - } - } - return NextResponse.json( - { - success: false, - status: 'error', - error: 'Failed to create mission', - message: error instanceof Error ? error.message : String(error) - }, - { status: 500 } - ); } + + // Create mission directly (n8n request) + const missionData: Prisma.MissionUncheckedCreateInput = { + 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: body.logo, + leantimeProjectId: body.leantimeProjectId, + outlineCollectionId: body.outlineCollectionId, + rocketChatChannelId: body.rocketChatChannelId, + giteaRepositoryUrl: body.giteaRepositoryUrl, + penpotProjectId: body.penpotProjectId + }; + + const mission = await prisma.mission.create({ + data: missionData + }); + + return NextResponse.json({ success: true, mission }); } catch (error) { console.error('Error creating mission:', error); - return NextResponse.json( - { - error: 'Failed to create mission', - details: error instanceof Error ? error.message : String(error) - }, - { status: 500 } - ); + return NextResponse.json({ + error: 'Failed to create mission', + details: error instanceof Error ? error.message : String(error) + }, { status: 500 }); } } \ No newline at end of file diff --git a/components/missions/missions-admin-panel.tsx b/components/missions/missions-admin-panel.tsx index 523ad008..f7cd7f43 100644 --- a/components/missions/missions-admin-panel.tsx +++ b/components/missions/missions-admin-panel.tsx @@ -378,34 +378,21 @@ export function MissionsAdminPanel() { // Handle mission submission const handleSubmitMission = async () => { - console.log('Starting mission submission...'); - console.log('Current mission data:', JSON.stringify(missionData, null, 2)); - console.log('Selected services:', selectedServices); - console.log('Guardians:', { - gardienDuTemps, - gardienDeLaParole, - gardienDeLaMemoire - }); - console.log('Volunteers:', volontaires); - - if (!validateMission()) { - console.log('Mission validation failed'); - return; - } + if (!validateMission()) return; setIsSubmitting(true); try { - // Format the data before sending - const formattedData = { + // Simplified data structure + const submissionData = { name: missionData.name, - oddScope: Array.isArray(missionData.oddScope) ? missionData.oddScope : [missionData.oddScope], + oddScope: missionData.oddScope, niveau: missionData.niveau, intention: missionData.intention, missionType: missionData.missionType, donneurDOrdre: missionData.donneurDOrdre, projection: missionData.projection, + services: selectedServices, // Direct array of selected services participation: missionData.participation, - services: selectedServices, profils: selectedProfils, guardians: { 'gardien-temps': gardienDuTemps, @@ -417,23 +404,16 @@ export function MissionsAdminPanel() { attachments: selectedAttachments }; - console.log('Submitting mission data:', JSON.stringify(formattedData, null, 2)); - const response = await fetch('/api/missions', { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify(formattedData), + body: JSON.stringify(submissionData) }); - console.log('Response status:', response.status); - const data = await response.json(); - console.log('Response data:', JSON.stringify(data, null, 2)); - if (!response.ok) { - console.error('Error response:', data); - throw new Error(data.error || 'Failed to create mission'); + throw new Error('Failed to create mission'); } toast({ @@ -443,10 +423,9 @@ export function MissionsAdminPanel() { router.push('/missions'); } catch (error) { - console.error('Error creating mission:', error); toast({ title: "Erreur", - description: error instanceof Error ? error.message : "Une erreur est survenue lors de la création de la mission", + description: error instanceof Error ? error.message : "Une erreur est survenue", variant: "destructive", }); } finally { diff --git a/lib/services/n8n-service.ts b/lib/services/n8n-service.ts index d2944fbb..c1038cf1 100644 --- a/lib/services/n8n-service.ts +++ b/lib/services/n8n-service.ts @@ -1,188 +1,70 @@ import { env } from '@/lib/env'; export class N8nService { - private webhookUrl: string; - private rollbackWebhookUrl: string; - private apiKey: string; + private readonly n8nUrl: string; + private readonly apiKey: string; constructor() { - // Use consistent webhook URLs without -test suffix - this.webhookUrl = process.env.N8N_WEBHOOK_URL || 'https://brain.slm-lab.net/webhook/mission-created'; - this.rollbackWebhookUrl = process.env.N8N_ROLLBACK_WEBHOOK_URL || 'https://brain.slm-lab.net/webhook/mission-rollback'; + this.n8nUrl = process.env.N8N_URL || ''; this.apiKey = process.env.N8N_API_KEY || ''; - - if (!this.apiKey) { - console.error('N8N_API_KEY is not set in environment variables'); + + if (!this.n8nUrl || !this.apiKey) { + throw new Error('N8N_URL and N8N_API_KEY must be set in environment variables'); } } - async triggerMissionCreation(data: any): Promise { + async triggerMissionCreation(data: any): Promise<{ success: boolean; error?: string }> { try { - console.log('N8nService - Input data:', { - hasServices: Array.isArray(data.services), - services: data.services, - hasGite: data.services?.includes('Gite'), - missionProcessed: data.missionProcessed, - config: data.config - }); - - // Clean and validate the data - const cleanData = { - name: data.name, - oddScope: Array.isArray(data.oddScope) ? data.oddScope : [data.oddScope], - niveau: data.niveau || 'default', - intention: data.intention?.trim() || '', - missionType: data.missionType || 'default', - donneurDOrdre: data.donneurDOrdre || 'default', - projection: data.projection || 'default', - services: Array.isArray(data.services) ? data.services : [], - participation: data.participation || 'default', - profils: Array.isArray(data.profils) ? data.profils : [], - guardians: data.guardians || {}, - volunteers: Array.isArray(data.volunteers) ? data.volunteers : [], - creatorId: data.creatorId, - config: { - ...data.config, // Preserve original config - N8N_API_KEY: this.apiKey, - MISSION_API_URL: process.env.NEXT_PUBLIC_API_URL || 'https://api.slm-lab.net/api' - } - }; - - // Log the cleaned data - console.log('Sending cleaned data to n8n:', { - name: cleanData.name, - creatorId: cleanData.creatorId, - oddScope: cleanData.oddScope, - niveau: cleanData.niveau, - intention: cleanData.intention?.substring(0, 100) + '...', // Log first 100 chars - missionType: cleanData.missionType, - donneurDOrdre: cleanData.donneurDOrdre, - projection: cleanData.projection, - services: cleanData.services, - participation: cleanData.participation, - profils: cleanData.profils, - hasGuardians: !!cleanData.guardians, - volunteersCount: cleanData.volunteers.length, - hasConfig: !!cleanData.config, - configKeys: cleanData.config ? Object.keys(cleanData.config) : [] - }); - - console.log('Using webhook URL:', this.webhookUrl); - console.log('API key present:', !!this.apiKey); - - const response = await fetch(this.webhookUrl, { + const response = await fetch(`${this.n8nUrl}/webhook/mission-creation`, { method: 'POST', headers: { 'Content-Type': 'application/json', - 'x-api-key': this.apiKey + 'X-API-Key': this.apiKey }, - body: JSON.stringify(cleanData), + body: JSON.stringify(data) }); - console.log('Webhook response status:', response.status); - console.log('Webhook response headers:', Object.fromEntries(response.headers.entries())); - if (!response.ok) { - const errorText = await response.text(); - console.error('Webhook error response:', errorText); - // Try to parse the error response as JSON for more details - try { - const errorJson = JSON.parse(errorText); - console.error('Parsed error response:', errorJson); - } catch (e) { - console.error('Error response is not JSON'); - } - throw new Error(`HTTP error! status: ${response.status}, body: ${errorText}`); - } - - const responseText = await response.text(); - console.log('N8nService - Raw response:', responseText); - - // Try to parse the response as JSON - try { - const result = JSON.parse(responseText); - console.log('Parsed workflow result:', JSON.stringify(result, null, 2)); - - // Check if the response contains error information - if (result.error || result.message?.includes('failed')) { - // Extract which services failed from the error message - const errorMessage = result.message || result.error; - const failedServices = { - gitRepo: errorMessage.includes('Git repository creation failed'), - leantimeProject: errorMessage.includes('Leantime project creation failed'), - docCollection: errorMessage.includes('Documentation collection creation failed'), - rocketChatChannel: errorMessage.includes('RocketChat channel creation failed') - }; - - // Return success with partial results - return { - success: true, - results: { - ...result, - failedServices - } - }; - } - + const errorData = await response.json(); return { - success: true, - results: result - }; - } catch (parseError) { - console.log('Response is not JSON, treating as workflow trigger confirmation'); - return { - success: true, - results: { - logoUrl: null, - leantimeProjectId: null, - outlineCollectionId: null, - rocketChatChannelId: null, - giteaRepositoryUrl: null - } + success: false, + error: errorData.message || 'Failed to trigger n8n workflow' }; } + + return { success: true }; } catch (error) { - console.error('Error triggering n8n workflow:', error); return { success: false, - error: error instanceof Error ? error.message : 'Unknown error' + error: error instanceof Error ? error.message : 'Failed to trigger n8n workflow' }; } } - async triggerMissionRollback(data: any): Promise { + async rollbackMissionCreation(data: any): Promise<{ success: boolean; error?: string }> { try { - console.log('Triggering n8n rollback workflow with data:', JSON.stringify(data, null, 2)); - console.log('Using rollback webhook URL:', this.rollbackWebhookUrl); - console.log('API key present:', !!this.apiKey); - - const response = await fetch(this.rollbackWebhookUrl, { + const response = await fetch(`${this.n8nUrl}/webhook/mission-rollback`, { method: 'POST', headers: { 'Content-Type': 'application/json', - 'x-api-key': this.apiKey + 'X-API-Key': this.apiKey }, - body: JSON.stringify(data), + body: JSON.stringify(data) }); if (!response.ok) { - const errorText = await response.text(); - console.error('Rollback webhook error response:', errorText); - throw new Error(`HTTP error! status: ${response.status}, body: ${errorText}`); + const errorData = await response.json(); + return { + success: false, + error: errorData.message || 'Failed to trigger n8n rollback workflow' + }; } - const result = await response.json(); - console.log('Received response from n8n rollback:', JSON.stringify(result, null, 2)); - - return { - success: true, - results: result - }; + return { success: true }; } catch (error) { - console.error('Error triggering n8n rollback workflow:', error); return { success: false, - error: error instanceof Error ? error.message : 'Unknown error' + error: error instanceof Error ? error.message : 'Failed to trigger n8n rollback workflow' }; } }