NeahNew/app/api/missions/[missionId]/generate-plan/route.ts
2026-01-09 12:13:23 +01:00

215 lines
6.1 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]/generate-plan
*
* Generates an action plan by calling N8N webhook which uses an LLM.
* Saves the generated plan to the mission.
*/
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
const mission = await prisma.mission.findUnique({
where: { id: missionId }
});
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 });
}
// Prepare data for N8N webhook
const webhookData = {
name: mission.name,
oddScope: mission.oddScope,
niveau: mission.niveau,
intention: mission.intention,
missionType: mission.missionType,
donneurDOrdre: mission.donneurDOrdre,
projection: mission.projection,
services: mission.services,
participation: mission.participation,
profils: mission.profils,
};
logger.debug('Calling N8N GeneratePlan webhook', {
missionId,
missionName: mission.name
});
// Call N8N webhook
const webhookUrl = process.env.N8N_GENERATE_PLAN_WEBHOOK_URL || 'https://brain.slm-lab.net/webhook/GeneratePlan';
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(webhookData),
});
if (!response.ok) {
const errorText = await response.text();
logger.error('N8N GeneratePlan webhook error', {
status: response.status,
error: errorText.substring(0, 200)
});
throw new Error(`Failed to generate plan: ${response.status}`);
}
// Parse the response
const responseText = await response.text();
let actionPlan: string;
try {
const result = JSON.parse(responseText);
// The LLM response might be in different formats
actionPlan = result.plan || result.actionPlan || result.content || result.text || responseText;
} catch {
// If not JSON, use the raw text
actionPlan = responseText;
}
logger.debug('Received action plan from N8N', {
missionId,
planLength: actionPlan.length
});
// Save the action plan to the mission
const updatedMission = await prisma.mission.update({
where: { id: missionId },
data: {
actionPlan: actionPlan,
actionPlanGeneratedAt: new Date()
}
});
logger.debug('Action plan saved successfully', {
missionId,
generatedAt: updatedMission.actionPlanGeneratedAt
});
return NextResponse.json({
success: true,
actionPlan: updatedMission.actionPlan,
generatedAt: updatedMission.actionPlanGeneratedAt
});
} catch (error) {
logger.error('Error generating action plan', {
error: error instanceof Error ? error.message : String(error),
missionId: params.missionId
});
return NextResponse.json(
{ error: 'Failed to generate action plan', details: error instanceof Error ? error.message : String(error) },
{ status: 500 }
);
}
}
/**
* PUT /api/missions/[missionId]/generate-plan
*
* Updates the action plan (allows manual editing by creator)
*/
export async function PUT(
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 });
}
const body = await request.json();
const { actionPlan } = body;
if (typeof actionPlan !== 'string') {
return NextResponse.json({ error: 'actionPlan must be a string' }, { status: 400 });
}
// Get the mission
const mission = await prisma.mission.findUnique({
where: { id: missionId }
});
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 });
}
// Update the action plan
const updatedMission = await prisma.mission.update({
where: { id: missionId },
data: {
actionPlan: actionPlan
}
});
logger.debug('Action plan updated successfully', {
missionId
});
return NextResponse.json({
success: true,
actionPlan: updatedMission.actionPlan
});
} catch (error) {
logger.error('Error updating action plan', {
error: error instanceof Error ? error.message : String(error),
missionId: params.missionId
});
return NextResponse.json(
{ error: 'Failed to update action plan', details: error instanceof Error ? error.message : String(error) },
{ status: 500 }
);
}
}