missions finition
This commit is contained in:
parent
1638dfec50
commit
680abdbc54
@ -201,7 +201,15 @@ export async function DELETE(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 3: Call N8N deletion webhook (non-blocking)
|
// Step 3: Call N8N deletion webhook
|
||||||
|
logger.debug('Preparing N8N deletion webhook call', {
|
||||||
|
missionId: mission.id,
|
||||||
|
hasGiteaUrl: !!mission.giteaRepositoryUrl,
|
||||||
|
hasLeantimeId: !!mission.leantimeProjectId,
|
||||||
|
hasOutlineId: !!mission.outlineCollectionId,
|
||||||
|
hasRocketChatId: !!mission.rocketChatChannelId
|
||||||
|
});
|
||||||
|
|
||||||
const n8nService = new N8nService();
|
const n8nService = new N8nService();
|
||||||
const n8nData = {
|
const n8nData = {
|
||||||
missionId: mission.id,
|
missionId: mission.id,
|
||||||
@ -216,13 +224,34 @@ export async function DELETE(
|
|||||||
penpotProjectId: mission.penpotProjectId
|
penpotProjectId: mission.penpotProjectId
|
||||||
};
|
};
|
||||||
|
|
||||||
// Call N8N but don't fail if it errors
|
logger.debug('Calling N8N deletion webhook', {
|
||||||
n8nService.triggerMissionDeletion(n8nData).catch(n8nError => {
|
webhookUrl: process.env.N8N_DELETE_WEBHOOK_URL || 'https://brain.slm-lab.net/webhook/mission-delete',
|
||||||
logger.error('N8N deletion webhook failed (mission still deleted from database)', {
|
hasApiKey: !!process.env.N8N_API_KEY,
|
||||||
error: n8nError instanceof Error ? n8nError.message : String(n8nError),
|
dataKeys: Object.keys(n8nData)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Call N8N but don't fail if it errors (mission deletion should still succeed)
|
||||||
|
try {
|
||||||
|
const n8nResult = await n8nService.triggerMissionDeletion(n8nData);
|
||||||
|
logger.debug('N8N deletion webhook result', {
|
||||||
|
success: n8nResult.success,
|
||||||
|
hasError: !!n8nResult.error,
|
||||||
missionId
|
missionId
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
if (!n8nResult.success) {
|
||||||
|
logger.warn('N8N deletion webhook returned error (mission still deleted)', {
|
||||||
|
error: n8nResult.error,
|
||||||
|
missionId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (n8nError) {
|
||||||
|
logger.error('N8N deletion webhook failed (mission still deleted from database)', {
|
||||||
|
error: n8nError instanceof Error ? n8nError.message : String(n8nError),
|
||||||
|
errorType: n8nError instanceof Error ? n8nError.name : typeof n8nError,
|
||||||
|
missionId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Step 4: Delete from database (cascade will handle related records)
|
// Step 4: Delete from database (cascade will handle related records)
|
||||||
await prisma.mission.delete({
|
await prisma.mission.delete({
|
||||||
@ -236,13 +265,23 @@ export async function DELETE(
|
|||||||
message: 'Mission deleted successfully'
|
message: 'Mission deleted successfully'
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const { missionId: errorMissionId } = await params;
|
// Get missionId from params safely
|
||||||
|
let errorMissionId = 'unknown';
|
||||||
|
try {
|
||||||
|
const paramsResult = await params;
|
||||||
|
errorMissionId = paramsResult.missionId;
|
||||||
|
} catch (paramsError) {
|
||||||
|
// If we can't get params, use unknown
|
||||||
|
logger.warn('Could not get missionId from params in error handler');
|
||||||
|
}
|
||||||
|
|
||||||
logger.error('Error deleting mission', {
|
logger.error('Error deleting mission', {
|
||||||
error: error instanceof Error ? error.message : String(error),
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
errorStack: error instanceof Error ? error.stack : undefined,
|
||||||
missionId: errorMissionId
|
missionId: errorMissionId
|
||||||
});
|
});
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Failed to delete mission', details: error.message },
|
{ error: 'Failed to delete mission', details: error instanceof Error ? error.message : String(error) },
|
||||||
{ status: 500 }
|
{ status: 500 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -496,16 +496,23 @@ export async function POST(request: Request) {
|
|||||||
MISSION_API_URL: process.env.NEXT_PUBLIC_API_URL
|
MISSION_API_URL: process.env.NEXT_PUBLIC_API_URL
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Log detailed information for debugging
|
||||||
logger.debug('Sending to N8N', {
|
logger.debug('Sending to N8N', {
|
||||||
missionId: n8nData.missionId,
|
missionId: n8nData.missionId,
|
||||||
name: n8nData.name,
|
name: n8nData.name,
|
||||||
hasLogo: !!n8nData.logoPath
|
hasLogo: !!n8nData.logoPath,
|
||||||
|
webhookUrl: process.env.N8N_WEBHOOK_URL || 'https://brain.slm-lab.net/webhook/mission-created',
|
||||||
|
hasApiKey: !!process.env.N8N_API_KEY,
|
||||||
|
apiKeyLength: process.env.N8N_API_KEY ? process.env.N8N_API_KEY.length : 0,
|
||||||
|
missionApiUrl: process.env.NEXT_PUBLIC_API_URL
|
||||||
});
|
});
|
||||||
|
|
||||||
const workflowResult = await n8nService.triggerMissionCreation(n8nData);
|
const workflowResult = await n8nService.triggerMissionCreation(n8nData);
|
||||||
logger.debug('N8N workflow result', {
|
logger.debug('N8N workflow result', {
|
||||||
success: workflowResult.success,
|
success: workflowResult.success,
|
||||||
hasError: !!workflowResult.error
|
hasError: !!workflowResult.error,
|
||||||
|
error: workflowResult.error
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!workflowResult.success) {
|
if (!workflowResult.success) {
|
||||||
|
|||||||
@ -161,6 +161,7 @@ services:
|
|||||||
# N8N Integration (required for mission creation workflow)
|
# N8N Integration (required for mission creation workflow)
|
||||||
N8N_API_KEY: ${N8N_API_KEY}
|
N8N_API_KEY: ${N8N_API_KEY}
|
||||||
N8N_WEBHOOK_URL: ${N8N_WEBHOOK_URL:-https://brain.slm-lab.net/webhook/mission-created}
|
N8N_WEBHOOK_URL: ${N8N_WEBHOOK_URL:-https://brain.slm-lab.net/webhook/mission-created}
|
||||||
|
N8N_DELETE_WEBHOOK_URL: ${N8N_DELETE_WEBHOOK_URL:-https://brain.slm-lab.net/webhook/mission-delete}
|
||||||
|
|
||||||
# Autres variables d'environnement (ajoutez les vôtres)
|
# Autres variables d'environnement (ajoutez les vôtres)
|
||||||
# volumes:
|
# volumes:
|
||||||
|
|||||||
@ -63,6 +63,7 @@ NEXT_PUBLIC_APP_URL=https://hub.slm-lab.net
|
|||||||
# N8N est utilisé pour créer automatiquement les projets Leantime, repos Git, etc.
|
# N8N est utilisé pour créer automatiquement les projets Leantime, repos Git, etc.
|
||||||
N8N_API_KEY=VOTRE_N8N_API_KEY
|
N8N_API_KEY=VOTRE_N8N_API_KEY
|
||||||
N8N_WEBHOOK_URL=https://brain.slm-lab.net/webhook/mission-created
|
N8N_WEBHOOK_URL=https://brain.slm-lab.net/webhook/mission-created
|
||||||
|
N8N_DELETE_WEBHOOK_URL=https://brain.slm-lab.net/webhook/mission-delete
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# Autres services (ajoutez selon vos besoins)
|
# Autres services (ajoutez selon vos besoins)
|
||||||
|
|||||||
@ -13,6 +13,12 @@ export class N8nService {
|
|||||||
this.rollbackWebhookUrl = process.env.N8N_ROLLBACK_WEBHOOK_URL || 'https://brain.slm-lab.net/webhook/mission-rollback';
|
this.rollbackWebhookUrl = process.env.N8N_ROLLBACK_WEBHOOK_URL || 'https://brain.slm-lab.net/webhook/mission-rollback';
|
||||||
this.apiKey = process.env.N8N_API_KEY || '';
|
this.apiKey = process.env.N8N_API_KEY || '';
|
||||||
|
|
||||||
|
logger.debug('N8nService initialized', {
|
||||||
|
webhookUrl: this.webhookUrl,
|
||||||
|
hasApiKey: !!this.apiKey,
|
||||||
|
apiKeyLength: this.apiKey ? this.apiKey.length : 0
|
||||||
|
});
|
||||||
|
|
||||||
if (!this.apiKey) {
|
if (!this.apiKey) {
|
||||||
logger.error('N8N_API_KEY is not set in environment variables');
|
logger.error('N8N_API_KEY is not set in environment variables');
|
||||||
}
|
}
|
||||||
@ -25,22 +31,45 @@ export class N8nService {
|
|||||||
logger.debug('Triggering n8n mission deletion workflow', {
|
logger.debug('Triggering n8n mission deletion workflow', {
|
||||||
missionId: data.missionId,
|
missionId: data.missionId,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
hasRepoName: !!data.repoName
|
hasRepoName: !!data.repoName,
|
||||||
|
repoName: data.repoName
|
||||||
});
|
});
|
||||||
logger.debug('Using deletion webhook URL', { url: deleteWebhookUrl });
|
logger.debug('Using deletion webhook URL', { url: deleteWebhookUrl });
|
||||||
logger.debug('API key present', { present: !!this.apiKey });
|
logger.debug('API key present', {
|
||||||
|
present: !!this.apiKey,
|
||||||
const response = await fetchWithTimeout(deleteWebhookUrl, {
|
length: this.apiKey ? this.apiKey.length : 0
|
||||||
method: 'POST',
|
});
|
||||||
timeout: 30000, // 30 seconds
|
logger.debug('Request payload', {
|
||||||
headers: {
|
keys: Object.keys(data),
|
||||||
'Content-Type': 'application/json',
|
payloadSize: JSON.stringify(data).length
|
||||||
'x-api-key': this.apiKey
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.debug('Deletion webhook response', { status: response.status });
|
let response: Response;
|
||||||
|
try {
|
||||||
|
response = await fetchWithTimeout(deleteWebhookUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
timeout: 30000, // 30 seconds
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-api-key': this.apiKey
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('Deletion webhook response received', {
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
|
headers: Object.fromEntries(response.headers.entries())
|
||||||
|
});
|
||||||
|
} catch (fetchError) {
|
||||||
|
logger.error('Failed to send deletion webhook request', {
|
||||||
|
error: fetchError instanceof Error ? fetchError.message : String(fetchError),
|
||||||
|
errorType: fetchError instanceof Error ? fetchError.name : typeof fetchError,
|
||||||
|
url: deleteWebhookUrl,
|
||||||
|
stack: fetchError instanceof Error ? fetchError.stack : undefined
|
||||||
|
});
|
||||||
|
throw fetchError;
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorText = await response.text();
|
const errorText = await response.text();
|
||||||
@ -157,19 +186,40 @@ export class N8nService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
logger.debug('Using webhook URL', { url: this.webhookUrl });
|
logger.debug('Using webhook URL', { url: this.webhookUrl });
|
||||||
logger.debug('API key present', { present: !!this.apiKey });
|
logger.debug('API key present', { present: !!this.apiKey, length: this.apiKey ? this.apiKey.length : 0 });
|
||||||
|
logger.debug('Request payload size', { size: JSON.stringify(cleanData).length });
|
||||||
const response = await fetchWithTimeout(this.webhookUrl, {
|
logger.debug('Request headers', {
|
||||||
method: 'POST',
|
contentType: 'application/json',
|
||||||
timeout: 30000, // 30 seconds
|
hasApiKey: !!this.apiKey,
|
||||||
headers: {
|
apiKeyPrefix: this.apiKey ? this.apiKey.substring(0, 4) + '...' : 'none'
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'x-api-key': this.apiKey
|
|
||||||
},
|
|
||||||
body: JSON.stringify(cleanData),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.debug('Webhook response', { status: response.status });
|
let response: Response;
|
||||||
|
try {
|
||||||
|
response = await fetchWithTimeout(this.webhookUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
timeout: 30000, // 30 seconds
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-api-key': this.apiKey
|
||||||
|
},
|
||||||
|
body: JSON.stringify(cleanData),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('Webhook response received', {
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
|
headers: Object.fromEntries(response.headers.entries())
|
||||||
|
});
|
||||||
|
} catch (fetchError) {
|
||||||
|
logger.error('Failed to send webhook request', {
|
||||||
|
error: fetchError instanceof Error ? fetchError.message : String(fetchError),
|
||||||
|
errorType: fetchError instanceof Error ? fetchError.name : typeof fetchError,
|
||||||
|
url: this.webhookUrl,
|
||||||
|
stack: fetchError instanceof Error ? fetchError.stack : undefined
|
||||||
|
});
|
||||||
|
throw fetchError;
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorText = await response.text();
|
const errorText = await response.text();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user