NeahNew/MISSION_CREATION_FLOW_EXPLANATION.md
2026-01-09 11:19:32 +01:00

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

  1. Mission is created in database (line 260)
  2. Files are uploaded to Minio
  3. N8N is called but fails (no API key, webhook not registered, etc.)
  4. Error is thrown (line 437)
  5. Files are cleaned up (line 458)
  6. 500 error is returned to frontend
  7. 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)

  1. User creates mission
  2. Mission saved to database
  3. Files uploaded to Minio
  4. N8N called → Fails (no API key)
  5. Error thrown
  6. Files cleaned up
  7. Mission still in database ⚠️
  8. Frontend shows error
  9. User sees error but mission exists
  10. 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

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)

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 Behavior

  • Mission created even if N8N fails
  • No integration IDs saved
  • Deletion won't work
  • Orphaned missions
  • 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