# Mission Deletion N8N IDs Issue - Complete Analysis ## πŸ” Problem Statement When deleting a mission, the N8N deletion workflow is not working because the database does not contain the integration IDs (Leantime, Outline, Gitea, RocketChat). This prevents N8N from properly cleaning up external integrations. --- ## πŸ”„ Current Flow Analysis ### Mission Creation Flow ``` 1. Frontend β†’ POST /api/missions ↓ 2. Backend creates mission in Prisma βœ… Mission created with NULL integration IDs: - leantimeProjectId: null - outlineCollectionId: null - giteaRepositoryUrl: null - rocketChatChannelId: null ↓ 3. Backend uploads files to Minio ↓ 4. Backend triggers N8N workflow (async) βœ… Sends missionId to N8N ↓ 5. N8N creates external integrations: - Gitea repository - Leantime project - Outline collection - RocketChat channel ↓ 6. N8N should call β†’ POST /api/missions/mission-created ⚠️ PROBLEM: This callback may fail or not be called ↓ 7. Backend should save IDs to database ❌ If step 6 fails, IDs are never saved ``` ### Mission Deletion Flow ``` 1. Frontend β†’ DELETE /api/missions/[missionId] ↓ 2. Backend reads mission from database ❌ Integration IDs are NULL (if step 6 above failed) ↓ 3. Backend prepares deletion data for N8N: { repoName: "", // Empty because giteaRepositoryUrl is null leantimeProjectId: 0, // 0 because leantimeProjectId is null documentationCollectionId: "", // Empty because outlineCollectionId is null rocketchatChannelId: "" // Empty because rocketChatChannelId is null } ↓ 4. Backend sends to N8N deletion workflow ❌ N8N receives empty IDs and cannot delete integrations ↓ 5. N8N fails to clean up external resources ``` --- ## πŸ“‹ Code Analysis ### 1. Mission Creation - Saving IDs **File**: `app/api/missions/route.ts` **Lines 260-262**: Mission is created WITHOUT integration IDs ```typescript const mission = await prisma.mission.create({ data: missionData // No integration IDs here }); ``` **Lines 413-423**: N8N is triggered with missionId ```typescript const n8nData = { ...body, missionId: mission.id, // βœ… missionId is sent to N8N creatorId: userId, logoPath: logoPath, logoUrl: logoUrl, config: { N8N_API_KEY: process.env.N8N_API_KEY, MISSION_API_URL: process.env.NEXT_PUBLIC_API_URL } }; const workflowResult = await n8nService.triggerMissionCreation(n8nData); ``` **Issue**: The API returns success immediately without waiting for N8N to save the IDs. ### 2. N8N Callback Endpoint **File**: `app/api/missions/mission-created/route.ts` **Lines 64-69**: Endpoint prefers `missionId` over `name + creatorId` ```typescript if (body.missionId) { // βœ… Use missionId if provided (more reliable) logger.debug('Looking up mission by ID', { missionId: body.missionId }); mission = await prisma.mission.findUnique({ where: { id: body.missionId } }); } ``` **Lines 128-150**: Maps N8N fields to Prisma fields ```typescript // Mapper les champs N8N vers notre schΓ©ma Prisma if (body.gitRepoUrl !== undefined) { updateData.giteaRepositoryUrl = body.gitRepoUrl || null; } if (body.leantimeProjectId !== undefined) { updateData.leantimeProjectId = body.leantimeProjectId ? String(body.leantimeProjectId) : null; } if (body.documentationCollectionId !== undefined) { updateData.outlineCollectionId = body.documentationCollectionId || null; } if (body.rocketchatChannelId !== undefined) { updateData.rocketChatChannelId = body.rocketchatChannelId || null; } ``` **Status**: βœ… Endpoint exists and should work correctly ### 3. Mission Deletion - Reading IDs **File**: `app/api/missions/[missionId]/route.ts` **Lines 302-311**: Mission is fetched from database ```typescript const mission = await prisma.mission.findUnique({ where: { id: params.missionId }, include: { missionUsers: { include: { user: true } } } }); ``` **Lines 356-372**: Deletion data is prepared ```typescript const n8nDeletionData = { missionId: mission.id, name: mission.name, repoName: repoName, // Extracted from giteaRepositoryUrl (may be empty) leantimeProjectId: mission.leantimeProjectId || 0, // ❌ 0 if null documentationCollectionId: mission.outlineCollectionId || '', // ❌ Empty if null rocketchatChannelId: mission.rocketChatChannelId || '', // ❌ Empty if null // ... }; ``` **Problem**: If IDs are null in database, N8N receives empty values. --- ## πŸ” Root Cause Analysis ### Possible Causes 1. **N8N Workflow Not Calling `/mission-created`** - N8N workflow might not be configured to call the callback endpoint - The "Save Mission To API" node might be missing or misconfigured - Network issues preventing the callback 2. **N8N Callback Failing** - API key mismatch - Mission lookup failing (name/creatorId mismatch) - Network timeout - Server error 3. **Timing Issues** - N8N workflow takes time to complete - User deletes mission before N8N saves IDs - Race condition 4. **N8N Not Sending `missionId`** - N8N might only send `name + creatorId` - If mission name is not unique, lookup fails --- ## βœ… Verification Steps ### Step 1: Check if IDs are being saved **Query the database**: ```sql SELECT id, name, giteaRepositoryUrl, leantimeProjectId, outlineCollectionId, rocketChatChannelId, createdAt FROM "Mission" WHERE createdAt > NOW() - INTERVAL '7 days' ORDER BY createdAt DESC; ``` **Expected**: Recent missions should have integration IDs populated. **If NULL**: N8N callback is not working. ### Step 2: Check N8N Workflow Configuration **Verify N8N workflow has "Save Mission To API" node**: - Node should POST to: `{{ MISSION_API_URL }}/mission-created` - Should include `missionId` in body - Should include `x-api-key` header - Should include integration IDs in body **Expected format**: ```json { "missionId": "uuid-here", "name": "Mission Name", "creatorId": "user-id", "gitRepoUrl": "https://gite.slm-lab.net/alma/repo-name", "leantimeProjectId": "123", "documentationCollectionId": "collection-id", "rocketchatChannelId": "channel-id" } ``` ### Step 3: Check Server Logs **Look for**: ``` Mission Created Webhook Received Received mission-created data: { ... } Found mission: { id: "...", name: "..." } Updating giteaRepositoryUrl: ... Mission updated successfully ``` **If missing**: N8N is not calling the endpoint. ### Step 4: Test N8N Callback Manually **Send test request**: ```bash curl -X POST https://hub.slm-lab.net/api/missions/mission-created \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_N8N_API_KEY" \ -d '{ "missionId": "existing-mission-id", "gitRepoUrl": "https://gite.slm-lab.net/alma/test-repo", "leantimeProjectId": "999", "documentationCollectionId": "test-collection", "rocketchatChannelId": "test-channel" }' ``` **Expected**: 200 OK with updated mission data. --- ## πŸ”§ Solutions ### Solution 1: Verify N8N Workflow Configuration (IMMEDIATE) **Check N8N workflow "Save Mission To API" node**: 1. **URL should be**: ``` {{ $node['Process Mission Data'].json.config.MISSION_API_URL }}/mission-created ``` Or hardcoded: ``` https://hub.slm-lab.net/api/missions/mission-created ``` 2. **Headers should include**: ``` Content-Type: application/json x-api-key: {{ $node['Process Mission Data'].json.config.N8N_API_KEY }} ``` 3. **Body should include**: - βœ… `missionId` (from original request) - βœ… `gitRepoUrl` (from Git repository creation) - βœ… `leantimeProjectId` (from Leantime project creation) - βœ… `documentationCollectionId` (from Outline collection creation) - βœ… `rocketchatChannelId` (from RocketChat channel creation) 4. **Verify node execution**: - Check N8N execution logs - Verify node is not set to "continueOnFail" - Check for errors in node execution ### Solution 2: Add Logging to Track Callback (DEBUGGING) **Add more detailed logging** in `app/api/missions/mission-created/route.ts`: ```typescript logger.debug('Mission Created Webhook Received', { headers: { hasApiKey: !!request.headers.get('x-api-key'), contentType: request.headers.get('content-type') } }); const body = await request.json(); logger.debug('Received mission-created data', { missionId: body.missionId, name: body.name, creatorId: body.creatorId, hasGitRepoUrl: !!body.gitRepoUrl, hasLeantimeProjectId: !!body.leantimeProjectId, hasDocumentationCollectionId: !!body.documentationCollectionId, hasRocketchatChannelId: !!body.rocketchatChannelId, fullBody: body // Log full body for debugging }); ``` ### Solution 3: Add Fallback Lookup (ROBUSTNESS) **Improve mission lookup** in `app/api/missions/mission-created/route.ts`: ```typescript // Try missionId first if (body.missionId) { mission = await prisma.mission.findUnique({ where: { id: body.missionId } }); if (!mission) { logger.warn('Mission not found by ID, trying name + creatorId', { missionId: body.missionId }); } } // Fallback to name + creatorId if (!mission && body.name && body.creatorId) { mission = await prisma.mission.findFirst({ where: { name: body.name, creatorId: body.creatorId }, orderBy: { createdAt: 'desc' } }); } // If still not found, try just by name (last resort) if (!mission && body.name) { logger.warn('Mission not found by name + creatorId, trying just name', { name: body.name, creatorId: body.creatorId }); mission = await prisma.mission.findFirst({ where: { name: body.name }, orderBy: { createdAt: 'desc' } }); } ``` ### Solution 4: Add Retry Mechanism (RELIABILITY) **Add retry logic** for N8N callback (if N8N supports it): - Configure N8N to retry failed callbacks - Or implement a webhook retry queue ### Solution 5: Manual ID Update Script (MIGRATION) **Create a script** to manually update existing missions: ```typescript // scripts/update-mission-ids.ts import { prisma } from '@/lib/prisma'; async function updateMissionIds() { // Get missions without IDs const missions = await prisma.mission.findMany({ where: { OR: [ { giteaRepositoryUrl: null }, { leantimeProjectId: null }, { outlineCollectionId: null }, { rocketChatChannelId: null } ] } }); for (const mission of missions) { // Manually update IDs if you know them // Or query external services to find them await prisma.mission.update({ where: { id: mission.id }, data: { giteaRepositoryUrl: '...', // From Gitea leantimeProjectId: '...', // From Leantime outlineCollectionId: '...', // From Outline rocketChatChannelId: '...' // From RocketChat } }); } } ``` --- ## πŸ§ͺ Testing Plan ### Test 1: Create Mission and Verify IDs Saved 1. Create a new mission via frontend 2. Wait 30-60 seconds for N8N to complete 3. Query database to verify IDs are saved: ```sql SELECT * FROM "Mission" WHERE name = 'Test Mission'; ``` 4. **Expected**: All integration IDs should be populated ### Test 2: Check N8N Execution Logs 1. Go to N8N execution history 2. Find the latest mission creation execution 3. Check "Save Mission To API" node: - βœ… Node executed successfully - βœ… Response is 200 OK - βœ… Body contains integration IDs ### Test 3: Test Deletion with IDs 1. Delete a mission that has IDs saved 2. Check N8N deletion workflow execution 3. **Expected**: N8N should receive non-empty IDs and successfully delete integrations ### Test 4: Test Deletion without IDs 1. Delete a mission that has NULL IDs 2. Check N8N deletion workflow execution 3. **Expected**: N8N receives empty IDs and logs warning (but mission still deleted) --- ## πŸ“Š Expected vs Actual Behavior ### Expected Behavior **Mission Creation**: 1. Mission created in database 2. N8N workflow triggered 3. N8N creates integrations 4. N8N calls `/mission-created` with IDs 5. IDs saved to database βœ… **Mission Deletion**: 1. Mission fetched from database (with IDs) βœ… 2. IDs sent to N8N deletion workflow βœ… 3. N8N deletes integrations βœ… 4. Mission deleted from database βœ… ### Actual Behavior (Current Issue) **Mission Creation**: 1. Mission created in database βœ… 2. N8N workflow triggered βœ… 3. N8N creates integrations βœ… 4. N8N calls `/mission-created` ❓ (May fail) 5. IDs saved to database ❌ (If step 4 fails) **Mission Deletion**: 1. Mission fetched from database (IDs are NULL) ❌ 2. Empty IDs sent to N8N deletion workflow ❌ 3. N8N cannot delete integrations ❌ 4. Mission deleted from database βœ… (But integrations remain) --- ## 🎯 Immediate Action Items 1. **βœ… Verify N8N Workflow Configuration** - Check "Save Mission To API" node exists - Verify URL, headers, and body format - Check execution logs for errors 2. **βœ… Check Server Logs** - Look for `/mission-created` endpoint calls - Check for errors or missing API key - Verify mission lookup is working 3. **βœ… Test Manually** - Create a test mission - Wait for N8N to complete - Check database for IDs - If missing, manually test the callback endpoint 4. **βœ… Fix N8N Workflow (if needed)** - Ensure `missionId` is included in callback - Verify all integration IDs are included - Test the workflow end-to-end 5. **βœ… Update Existing Missions (if needed)** - Manually update IDs for critical missions - Or create migration script --- ## πŸ“ Code Changes Needed ### No Code Changes Required (if N8N is configured correctly) The endpoint `/api/missions/mission-created` already exists and should work. The issue is likely: - N8N workflow not calling it - N8N workflow calling it incorrectly - Network/authentication issues ### Optional Improvements 1. **Better error handling** in `/mission-created` endpoint 2. **Retry mechanism** for failed callbacks 3. **Monitoring/alerting** when IDs are not saved 4. **Migration script** for existing missions --- ## πŸ” Debugging Commands ### Check Recent Missions Without IDs ```sql SELECT id, name, createdAt, CASE WHEN giteaRepositoryUrl IS NULL THEN 'MISSING' ELSE 'OK' END as gitea, CASE WHEN leantimeProjectId IS NULL THEN 'MISSING' ELSE 'OK' END as leantime, CASE WHEN outlineCollectionId IS NULL THEN 'MISSING' ELSE 'OK' END as outline, CASE WHEN rocketChatChannelId IS NULL THEN 'MISSING' ELSE 'OK' END as rocketchat FROM "Mission" WHERE createdAt > NOW() - INTERVAL '7 days' ORDER BY createdAt DESC; ``` ### Check Mission with IDs ```sql SELECT id, name, giteaRepositoryUrl, leantimeProjectId, outlineCollectionId, rocketChatChannelId FROM "Mission" WHERE id = 'your-mission-id'; ``` ### Test Callback Endpoint ```bash # Replace with actual values curl -X POST https://hub.slm-lab.net/api/missions/mission-created \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_N8N_API_KEY" \ -d '{ "missionId": "mission-uuid-here", "gitRepoUrl": "https://gite.slm-lab.net/alma/test", "leantimeProjectId": "123", "documentationCollectionId": "collection-456", "rocketchatChannelId": "channel-789" }' ``` --- ## βœ… Conclusion **Root Cause**: N8N workflow is likely not calling `/api/missions/mission-created` endpoint, or the callback is failing silently. **Solution**: 1. Verify N8N workflow configuration 2. Check N8N execution logs 3. Test callback endpoint manually 4. Fix N8N workflow if needed 5. Manually update existing missions if necessary **Status**: Endpoint exists and should work. Issue is in N8N workflow configuration or execution. --- **Document Created**: $(date) **Last Updated**: $(date) **Priority**: HIGH - Blocks proper mission deletion