import { NextResponse } from 'next/server'; import { getServerSession } from 'next-auth'; import { authOptions } from "@/app/api/auth/options"; import { prisma } from '@/lib/prisma'; import { logger } from '@/lib/logger'; /** * POST /api/missions/[missionId]/close * * Closes a mission by calling N8N webhook to close it in external services * and marking it as closed in the database. */ export async function POST( request: Request, props: { params: Promise<{ missionId: string }> } ) { const params = await props.params; try { const session = await getServerSession(authOptions); if (!session?.user) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const { missionId } = params; if (!missionId) { return NextResponse.json({ error: 'Mission ID is required' }, { status: 400 }); } // Get the mission with all details needed for N8N const mission = await prisma.mission.findUnique({ where: { id: missionId }, include: { missionUsers: { include: { user: true } } } }); if (!mission) { return NextResponse.json({ error: 'Mission not found' }, { status: 404 }); } // Check if user has permission (creator or admin) const isCreator = mission.creatorId === session.user.id; const userRoles = Array.isArray(session.user.role) ? session.user.role : []; const isAdmin = userRoles.includes('admin') || userRoles.includes('ADMIN'); if (!isCreator && !isAdmin) { return NextResponse.json({ error: 'Forbidden' }, { status: 403 }); } // Check if already closed if ((mission as any).isClosed) { return NextResponse.json({ error: 'Mission is already closed' }, { status: 400 }); } // Extract repo name from giteaRepositoryUrl if present let repoName = ''; if (mission.giteaRepositoryUrl) { try { const url = new URL(mission.giteaRepositoryUrl); const pathParts = url.pathname.split('/').filter(Boolean); repoName = pathParts[pathParts.length - 1] || ''; logger.debug('Extracted repo name from URL', { repoName }); } catch (error) { const match = mission.giteaRepositoryUrl.match(/\/([^\/]+)\/?$/); repoName = match ? match[1] : ''; } } // Prepare data for N8N webhook (same format as deletion) const n8nCloseData = { missionId: mission.id, name: mission.name, repoName: repoName, leantimeProjectId: mission.leantimeProjectId || 0, documentationCollectionId: mission.outlineCollectionId || '', rocketchatChannelId: mission.rocketChatChannelId || '', giteaRepositoryUrl: mission.giteaRepositoryUrl, outlineCollectionId: mission.outlineCollectionId, rocketChatChannelId: mission.rocketChatChannelId, penpotProjectId: mission.penpotProjectId, action: 'close' // Indicate this is a close action, not delete }; logger.debug('Calling N8N NeahMissionClose webhook', { missionId: mission.id, missionName: mission.name, hasRepoName: !!repoName }); // Call N8N webhook const webhookUrl = process.env.N8N_CLOSE_MISSION_WEBHOOK_URL || 'https://brain.slm-lab.net/webhook/NeahMissionClose'; const apiKey = process.env.N8N_API_KEY || ''; const response = await fetch(webhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey }, body: JSON.stringify(n8nCloseData), }); logger.debug('N8N close webhook response', { status: response.status }); if (!response.ok) { const errorText = await response.text(); logger.error('N8N close webhook error', { status: response.status, error: errorText.substring(0, 200) }); // Continue with closing even if N8N fails (non-blocking) logger.warn('Continuing with mission close despite N8N error'); } // Mark mission as closed in database // Using 'as any' until prisma generate is run const updatedMission = await (prisma.mission as any).update({ where: { id: missionId }, data: { isClosed: true, closedAt: new Date() } }); logger.debug('Mission closed successfully', { missionId: updatedMission.id, closedAt: updatedMission.closedAt }); return NextResponse.json({ success: true, message: 'Mission closed successfully', mission: { id: updatedMission.id, name: updatedMission.name, isClosed: updatedMission.isClosed, closedAt: updatedMission.closedAt } }); } catch (error) { logger.error('Error closing mission', { error: error instanceof Error ? error.message : String(error), missionId: params.missionId }); return NextResponse.json( { error: 'Failed to close mission', details: error instanceof Error ? error.message : String(error) }, { status: 500 } ); } }