import { NextResponse } from 'next/server'; import { getServerSession } from 'next-auth'; import { authOptions } from "@/app/api/auth/options"; import { prisma } from '@/lib/prisma'; import { getPublicUrl } from '@/lib/s3'; import { S3_CONFIG } from '@/lib/s3'; import { N8nService } from '@/lib/services/n8n-service'; import { MissionUser, Prisma } from '@prisma/client'; interface MissionUserInput { role: string; userId: string; missionId: string; } // Helper function to check authentication async function checkAuth(request: Request) { // Check for API key in headers first const apiKey = request.headers.get('x-api-key'); console.log('Received API key from headers:', apiKey); // If no API key in headers, try to get it from the request body let bodyApiKey = null; if (request.method === 'POST') { const body = await request.clone().json(); bodyApiKey = body?.config?.N8N_API_KEY; console.log('Received API key from body:', bodyApiKey); } const receivedApiKey = apiKey || bodyApiKey; console.log('Final API key used:', receivedApiKey); console.log('Expected API key:', process.env.N8N_API_KEY); console.log('API key match:', receivedApiKey === process.env.N8N_API_KEY); if (receivedApiKey === process.env.N8N_API_KEY) { return { authorized: true, userId: 'system' }; } // If no API key, check for session const session = await getServerSession(authOptions); if (!session?.user?.id) { console.error('Unauthorized access attempt:', { url: request.url, method: request.method, headers: Object.fromEntries(request.headers) }); return { authorized: false, userId: null }; } return { authorized: true, userId: session.user.id }; } // GET endpoint to list missions with filters 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'); // Build query conditions const where: any = {}; // Add search filter if provided if (search) { where.OR = [ { name: { contains: search, mode: 'insensitive' } }, { intention: { contains: search, mode: 'insensitive' } } ]; } // Get missions with basic info const missions = await (prisma as any).mission.findMany({ where, skip: offset, take: limit, orderBy: { createdAt: 'desc' }, select: { id: true, name: true, logo: true, oddScope: true, niveau: true, missionType: true, projection: true, participation: true, services: true, intention: true, createdAt: true, creator: { select: { id: true, email: true } }, missionUsers: { select: { id: true, role: true, user: { select: { id: true, email: true } } } } } }); // Get total count const totalCount = await (prisma as any).mission.count({ where }); // Transform logo paths to public URLs const missionsWithPublicUrls = missions.map((mission: any) => ({ ...mission, logo: mission.logo ? `/api/missions/image/${mission.logo}` : null })); return NextResponse.json({ missions: missionsWithPublicUrls, 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) { console.error('Unauthorized access attempt - no session or user'); return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const body = await request.json(); console.log('Received mission creation request:', JSON.stringify(body, null, 2)); const { name, oddScope, niveau, intention, missionType, donneurDOrdre, projection, services, participation, profils, guardians, volunteers } = body; // Validate required fields const requiredFields = { name, niveau, intention, missionType, donneurDOrdre, projection }; const missingFields = Object.entries(requiredFields) .filter(([_, value]) => !value) .map(([key]) => key); if (missingFields.length > 0) { console.error('Missing required fields:', missingFields); return NextResponse.json({ error: 'Missing required fields', missingFields }, { status: 400 }); } // Check if mission with same name exists const existingMission = await prisma.mission.findFirst({ where: { name } }); if (existingMission) { console.error('Mission with same name already exists:', name); return NextResponse.json({ error: 'A mission with this name already exists' }, { status: 400 }); } // Trigger n8n workflow first const n8nService = new N8nService(); const n8nData = { ...body, creatorId: userId }; console.log('Sending data to n8n service:', { name: n8nData.name, creatorId: n8nData.creatorId, oddScope: n8nData.oddScope, niveau: n8nData.niveau, intention: n8nData.intention, missionType: n8nData.missionType, donneurDOrdre: n8nData.donneurDOrdre, projection: n8nData.projection, services: n8nData.services, participation: n8nData.participation, profils: n8nData.profils, fullData: JSON.stringify(n8nData, null, 2) }); try { const workflowResult = await n8nService.triggerMissionCreation(n8nData); console.log('Received workflow result:', JSON.stringify(workflowResult, null, 2)); if (!workflowResult.success) { console.error('N8n workflow failed:', workflowResult.error); return NextResponse.json({ error: 'Failed to create mission resources' }, { status: 500 }); } // Process workflow results const results = workflowResult.results || {}; console.log('Processing workflow results:', JSON.stringify(results, null, 2)); // Now create the mission with the logo URL from n8n console.log('Creating mission in database...'); const mission = await prisma.mission.create({ data: { name, oddScope: oddScope || ['default'], niveau, intention, missionType, donneurDOrdre, projection, services: Array.isArray(services) ? services.filter(Boolean) : [], profils: Array.isArray(profils) ? profils.filter(Boolean) : [], participation: participation || 'default', creatorId: userId, logo: results.logoUrl || null, // Store integration IDs directly in the mission record leantimeProjectId: results.leantimeProjectId?.toString() || null, outlineCollectionId: results.outlineCollectionId?.toString() || null, rocketChatChannelId: results.rocketChatChannelId?.toString() || null, giteaRepositoryUrl: results.giteaRepositoryUrl?.toString() || null, penpotProjectId: results.penpotProjectId?.toString() || null } as Prisma.MissionUncheckedCreateInput }); console.log('Created mission:', JSON.stringify(mission, null, 2)); // Add guardians and volunteers if (guardians || volunteers) { console.log('Adding guardians and volunteers...'); const missionUsers: MissionUserInput[] = []; // Add guardians if (guardians) { Object.entries(guardians).forEach(([role, userId]) => { if (userId) { missionUsers.push({ role, userId: userId as string, missionId: mission.id }); } }); } // Add volunteers if (volunteers && Array.isArray(volunteers)) { volunteers.forEach(userId => { if (userId) { missionUsers.push({ role: 'volontaire', userId, missionId: mission.id }); } }); } if (missionUsers.length > 0) { console.log('Creating mission users:', JSON.stringify(missionUsers, null, 2)); await prisma.missionUser.createMany({ data: missionUsers }); } } return NextResponse.json(mission); } catch (error) { console.error('Error in n8n workflow:', error); return NextResponse.json( { error: 'Failed to create mission resources', details: 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 } ); } }