import { NextResponse } from 'next/server'; import { getServerSession } from 'next-auth'; import { authOptions } from "@/app/api/auth/options"; import { prisma } from '@/lib/prisma'; import { N8nService } from '@/lib/services/n8n-service'; import { Prisma } from '@prisma/client'; // Types interface MissionCreateInput { name: string; oddScope: string[]; niveau?: string; intention?: string; missionType?: string; donneurDOrdre?: string; projection?: string; services?: string[]; participation?: string; profils?: string[]; guardians?: Record; volunteers?: string[]; creatorId?: string; config?: { N8N_API_KEY: string; MISSION_API_URL: string; }; logo?: { data: string; name?: string; type?: string; } | null; leantimeProjectId?: string | null; outlineCollectionId?: string | null; rocketChatChannelId?: string | null; gitRepositoryId?: string | null; giteaRepositoryUrl?: string | null; penpotProjectId?: string | null; status?: string; createdAt?: Date; updatedAt?: Date; } interface MissionUserInput { role: string; userId: string; missionId: string; } interface MissionResponse { name: string; oddScope: string[]; niveau: string; intention: string; missionType: string; donneurDOrdre: string; projection: string; services: string[]; profils: string[]; participation: string; creatorId: string; logo: string | null; leantimeProjectId: string | null; outlineCollectionId: string | null; rocketChatChannelId: string | null; giteaRepositoryUrl: string | null; penpotProjectId: string | null; } // Helper function to check authentication async function checkAuth(request: Request) { const apiKey = request.headers.get('x-api-key'); if (apiKey === process.env.N8N_API_KEY) { return { authorized: true, userId: 'system' }; } const session = await getServerSession(authOptions); if (!session?.user?.id) { return { authorized: false, userId: null }; } return { authorized: true, userId: session.user.id }; } // GET endpoint to list missions export async function GET(request: Request) { try { const { authorized, userId } = await checkAuth(request); if (!authorized || !userId) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const { searchParams } = new URL(request.url); const limit = Number(searchParams.get('limit') || '10'); const offset = Number(searchParams.get('offset') || '0'); const search = searchParams.get('search'); const name = searchParams.get('name'); const where: Prisma.MissionWhereInput = {}; if (search) { where.OR = [ { name: { contains: search, mode: 'insensitive' } }, { intention: { contains: search, mode: 'insensitive' } } ]; } if (name) { where.name = name; } const missions = await prisma.mission.findMany({ where, skip: offset, take: limit, orderBy: { createdAt: 'desc' }, include: { creator: { select: { id: true, email: true } }, missionUsers: { include: { user: { select: { id: true, email: true } } } } } }); const totalCount = await prisma.mission.count({ where }); return NextResponse.json({ missions, pagination: { total: totalCount, offset, limit } }); } catch (error) { console.error('Error listing missions:', error); return NextResponse.json({ error: 'Internal server error', details: error instanceof Error ? error.message : String(error) }, { status: 500 }); } } // POST endpoint to create a new mission export async function POST(request: Request) { try { const { authorized, userId } = await checkAuth(request); if (!authorized || !userId) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const requestId = request.headers.get('x-request-id'); const body = await request.json() as MissionCreateInput; // Validate required fields const requiredFields = ['name', 'niveau', 'intention', 'missionType', 'donneurDOrdre', 'projection']; const missingFields = requiredFields.filter(field => !body[field as keyof MissionCreateInput]); if (missingFields.length > 0) { return NextResponse.json({ error: 'Missing required fields', missingFields }, { 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 for both n8n and non-n8n requests 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 }); } // Check for mission creation in progress const missionInProgress = await prisma.mission.findFirst({ where: { name: body.name, createdAt: { gte: new Date(Date.now() - 5 * 60 * 1000) // Within last 5 minutes } } }); if (missionInProgress) { return NextResponse.json({ error: 'Mission creation already in progress', details: 'Please wait a few minutes and try again', code: 'MISSION_CREATION_IN_PROGRESS' }, { status: 409 }); } // Handle n8n workflow if (!isN8nRequest) { const n8nService = new N8nService(); // Prepare the data 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 : [], creatorId: userId, logo: body.logo ? { data: body.logo.data, name: body.logo.name || 'logo.png', type: body.logo.type || 'image/png' } : null, config: { N8N_API_KEY: process.env.N8N_API_KEY, MISSION_API_URL: process.env.NEXT_PUBLIC_API_URL || 'https://api.slm-lab.net/api' } }; try { // Log the data we're sending (without sensitive info) console.log('Sending data to n8n workflow:', { name: n8nData.name, creatorId: n8nData.creatorId, oddScope: n8nData.oddScope, niveau: n8nData.niveau, intention: n8nData.intention?.substring(0, 100) + '...', missionType: n8nData.missionType, donneurDOrdre: n8nData.donneurDOrdre, projection: n8nData.projection, services: n8nData.services, participation: n8nData.participation, profils: n8nData.profils, hasGuardians: Object.keys(n8nData.guardians).length > 0, volunteersCount: n8nData.volunteers.length, hasConfig: true, configKeys: Object.keys(n8nData.config) }); 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(workflowResult); } catch (error) { console.error('Error triggering n8n workflow:', error); // Check if it's an n8n workflow error if (error instanceof Error && error.message.includes('HTTP error! status: 500')) { // Try to parse the error message for more details const errorMatch = error.message.match(/body: ({.*})/); let errorDetails = 'The mission creation process encountered an error'; if (errorMatch) { try { const errorBody = JSON.parse(errorMatch[1]); errorDetails = errorBody.message || errorDetails; } catch (e) { console.error('Failed to parse error body:', e); } } return NextResponse.json( { error: 'Mission creation workflow failed', details: errorDetails, code: 'WORKFLOW_ERROR', originalError: error.message }, { status: 500 } ); } // Handle other types of errors return NextResponse.json( { error: 'Failed to create mission resources', details: error instanceof Error ? error.message : 'The mission creation process failed. Please try again later.', code: 'WORKFLOW_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 }); } } // Format response to match workflow output const missionResponse: MissionResponse = { name: mission.name, oddScope: mission.oddScope, niveau: mission.niveau, intention: mission.intention, missionType: mission.missionType, donneurDOrdre: mission.donneurDOrdre, projection: mission.projection, services: mission.services, profils: mission.profils, participation: mission.participation || 'default', creatorId: mission.creatorId, logo: mission.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 }; return NextResponse.json({ success: true, status: 'success', message: 'Mission created successfully', mission: missionResponse, integrationStatus: { gitRepo: !!missionResponse.giteaRepositoryUrl, leantimeProject: !!missionResponse.leantimeProjectId, docCollection: !!missionResponse.outlineCollectionId, rocketChatChannel: !!missionResponse.rocketChatChannelId }, resourceStatus: { gitRepo: true, leantimeProject: true, docCollection: true, rocketChatChannel: true } }); } 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 } ); } } 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 } ); } }