233 lines
6.9 KiB
TypeScript
233 lines
6.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]/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 - must be nested under "mission" key
|
|
const webhookData = {
|
|
mission: {
|
|
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,
|
|
webhookData
|
|
});
|
|
|
|
// 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 {
|
|
let result = JSON.parse(responseText);
|
|
|
|
// N8N might return an array like [{"response":"..."}] when responseData is "allEntries"
|
|
if (Array.isArray(result) && result.length > 0) {
|
|
result = result[0];
|
|
logger.debug('N8N returned array, using first element');
|
|
}
|
|
|
|
// N8N returns { "response": "..." } based on the workflow
|
|
actionPlan = result.response || result.plan || result.actionPlan || result.content || result.text || responseText;
|
|
|
|
logger.debug('Parsed N8N response', {
|
|
hasResponse: !!result.response,
|
|
responseLength: actionPlan?.length || 0
|
|
});
|
|
} catch {
|
|
// If not JSON, use the raw text
|
|
actionPlan = responseText;
|
|
logger.debug('Using raw response text', { length: actionPlan.length });
|
|
}
|
|
|
|
logger.debug('Received action plan from N8N', {
|
|
missionId,
|
|
planLength: actionPlan.length
|
|
});
|
|
|
|
// Save the action plan to the mission
|
|
// Note: Using 'as any' until prisma generate is run to update types
|
|
const updatedMission = await (prisma.mission as any).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
|
|
// Note: Using 'as any' until prisma generate is run to update types
|
|
const updatedMission = await (prisma.mission as any).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 }
|
|
);
|
|
}
|
|
}
|
|
|