NeahNew/app/api/missions/[missionId]/close/route.ts
2026-01-09 14:13:59 +01:00

158 lines
4.9 KiB
TypeScript

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 }
);
}
}