8.0 KiB
Mission Creation Flow - Why You Can Create Without N8N API Key
🔍 Current Behavior Explained
You're absolutely right! You CAN create missions without N8N_API_KEY because of how the code is structured.
📋 Current Flow Order
Looking at app/api/missions/route.ts, here's the actual execution order:
1. ✅ Create mission in database (line 260)
↓
2. ✅ Create mission users (line 298)
↓
3. ✅ Upload logo to Minio (line 318)
↓
4. ✅ Upload attachments to Minio (line 362)
↓
5. ✅ Verify files exist (line 391)
↓
6. ⚠️ Trigger N8N workflow (line 430)
↓
7. ❌ If N8N fails → Error thrown (line 437)
↓
8. ⚠️ Error caught → Cleanup files (line 458)
↓
9. ❌ Return 500 error BUT mission stays in database!
🎯 The Problem
What Happens When N8N Fails
- Mission is created in database (line 260) ✅
- Files are uploaded to Minio ✅
- N8N is called but fails (no API key, webhook not registered, etc.) ❌
- Error is thrown (line 437) ❌
- Files are cleaned up (line 458) ✅
- 500 error is returned to frontend ❌
- BUT: Mission remains in database! ⚠️
Result
- ✅ Mission exists in database
- ❌ No integration IDs saved (N8N never called
/mission-created) - ❌ Files deleted from Minio (cleanup)
- ❌ Frontend shows error
- ⚠️ Orphaned mission in database
🔍 Code Analysis
Step 1: Mission Created (Line 260)
const mission = await prisma.mission.create({
data: missionData
});
This happens FIRST, before N8N is even called.
Step 2: N8N Called (Line 430)
const workflowResult = await n8nService.triggerMissionCreation(n8nData);
if (!workflowResult.success) {
throw new Error(workflowResult.error || 'N8N workflow failed');
}
If N8N fails, an error is thrown.
Step 3: Error Handling (Line 445-477)
} catch (error) {
logger.error('Error in final verification or n8n', {
error: error instanceof Error ? error.message : String(error)
});
throw error; // Re-throws to outer catch
}
// Outer catch (line 451)
} catch (error) {
// Cleanup files
for (const file of uploadedFiles) {
await s3Client.send(new DeleteObjectCommand({...}));
}
return NextResponse.json({
error: 'Failed to create mission',
details: error instanceof Error ? error.message : String(error)
}, { status: 500 });
}
Notice: The outer catch block:
- ✅ Cleans up files from Minio
- ❌ Does NOT delete the mission from database
- ❌ Returns 500 error
⚠️ Why This Is a Problem
Scenario: N8N Fails (No API Key)
- User creates mission
- Mission saved to database ✅
- Files uploaded to Minio ✅
- N8N called → Fails (no API key) ❌
- Error thrown
- Files cleaned up ✅
- Mission still in database ⚠️
- Frontend shows error
- User sees error but mission exists
- Mission has no integration IDs (N8N never saved them)
Result
- Orphaned missions in database without integration IDs
- Inconsistent state: Mission exists but integrations don't
- Deletion won't work: No IDs to send to N8N deletion workflow
✅ Solutions
Solution 1: Make N8N Optional (Current Behavior - But Better Error Handling)
Keep current flow but improve error handling:
// After N8N call
if (!workflowResult.success) {
logger.warn('N8N workflow failed, but mission created', {
error: workflowResult.error,
missionId: mission.id
});
// Don't throw error - mission is created, N8N is optional
// Return success but with warning
return NextResponse.json({
success: true,
mission,
warning: 'Mission created but integrations may not be set up',
n8nError: workflowResult.error
});
}
Pros:
- Mission creation succeeds even if N8N fails
- User gets feedback about partial success
Cons:
- Mission exists without integration IDs
- Deletion won't work properly
Solution 2: Delete Mission If N8N Fails (Strict)
Delete mission if N8N fails:
} catch (error) {
logger.error('Error in final verification or n8n', {
error: error instanceof Error ? error.message : String(error)
});
// Delete mission if N8N fails
try {
await prisma.mission.delete({
where: { id: mission.id }
});
logger.debug('Mission deleted due to N8N failure', { missionId: mission.id });
} catch (deleteError) {
logger.error('Failed to delete mission after N8N failure', {
missionId: mission.id,
error: deleteError
});
}
throw error;
}
Pros:
- No orphaned missions
- Consistent state
Cons:
- Mission creation fails completely if N8N is down
- User loses all work if N8N has issues
Solution 3: Make N8N Non-Blocking (Recommended)
Don't throw error if N8N fails, just log it:
const workflowResult = await n8nService.triggerMissionCreation(n8nData);
if (!workflowResult.success) {
logger.warn('N8N workflow failed, but continuing', {
error: workflowResult.error,
missionId: mission.id
});
// Don't throw - mission is created, N8N can be retried later
}
return NextResponse.json({
success: true,
mission,
message: workflowResult.success
? 'Mission created successfully with all integrations'
: 'Mission created but integrations may need to be set up manually'
});
Pros:
- Mission creation succeeds
- User gets clear feedback
- Can retry N8N later
Cons:
- Mission may exist without integration IDs
- Need manual retry mechanism
Solution 4: Transaction-Based (Best But Complex)
Use database transaction and rollback if N8N fails:
const result = await prisma.$transaction(async (tx) => {
// Create mission
const mission = await tx.mission.create({...});
// Upload files
// ...
// Try N8N
const workflowResult = await n8nService.triggerMissionCreation(n8nData);
if (!workflowResult.success) {
throw new Error('N8N workflow failed');
}
return mission;
});
Pros:
- Atomic operation
- No orphaned missions
Cons:
- Complex to implement
- Files already uploaded (can't rollback Minio in transaction)
🎯 Recommended Approach
Hybrid Solution: Make N8N non-blocking but add retry mechanism
// After N8N call
if (!workflowResult.success) {
logger.warn('N8N workflow failed, mission created without integrations', {
error: workflowResult.error,
missionId: mission.id
});
// Mission is created, but mark it for retry
await prisma.mission.update({
where: { id: mission.id },
data: {
// Add a flag to indicate N8N needs retry
// Or just log it and handle manually
}
});
}
return NextResponse.json({
success: true,
mission,
message: workflowResult.success
? 'Mission created successfully with all integrations'
: 'Mission created. Integrations will be set up shortly.'
});
📊 Current vs Recommended
Current Behavior
- ✅ Mission created even if N8N fails
- ❌ No integration IDs saved
- ❌ Deletion won't work
- ❌ Orphaned missions
Recommended Behavior
- ✅ Mission created even if N8N fails
- ⚠️ Integration IDs may be missing (but can be retried)
- ✅ User gets clear feedback
- ✅ Can retry N8N later
🔧 Quick Fix
If you want to keep current behavior but improve it:
Change line 436-438 from:
if (!workflowResult.success) {
throw new Error(workflowResult.error || 'N8N workflow failed');
}
To:
if (!workflowResult.success) {
logger.warn('N8N workflow failed, but mission created', {
error: workflowResult.error,
missionId: mission.id
});
// Continue - mission is created, N8N can be retried
}
This way:
- ✅ Mission creation succeeds
- ⚠️ User gets warning about integrations
- ✅ Can manually trigger N8N later or add retry mechanism
Document Created: $(date) Issue: Mission creation succeeds even when N8N fails, leading to orphaned missions without integration IDs