605 lines
16 KiB
Markdown
605 lines
16 KiB
Markdown
# 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
|
|
|