158 lines
4.9 KiB
TypeScript
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 }
|
|
);
|
|
}
|
|
}
|
|
|