diff --git a/N8N_DELETION_WORKFLOW_MAPPING.md b/N8N_DELETION_WORKFLOW_MAPPING.md new file mode 100644 index 00000000..f7c21f2d --- /dev/null +++ b/N8N_DELETION_WORKFLOW_MAPPING.md @@ -0,0 +1,342 @@ +# Mapping N8N Workflow - Mission Deletion + +## 📋 Vue d'Ensemble + +Ce document décrit le mapping entre les données de notre API et le format attendu par le workflow N8N `NeahMissionDelete_Pro`. + +--- + +## 🔄 Workflow N8N - Structure + +### Nodes du Workflow + +1. **Webhook Delete** : Reçoit POST sur `/mission-delete` +2. **Process Delete Data** : Transforme les données d'entrée +3. **Get Keycloak Token** : Obtient un token d'authentification +4. **Delete Gitea Repo** : Supprime le repository Gitea (continueOnFail: true) +5. **Close Leantime Project** : Ferme le projet Leantime (continueOnFail: true) +6. **Delete Outline Collection** : Supprime la collection Outline (continueOnFail: true) +7. **Close RocketChat Channel** : Ferme le canal RocketChat (continueOnFail: true) +8. **Combine Results** : Combine les résultats de toutes les suppressions +9. **Save Deletion To API** : Envoie les résultats à l'API + +--- + +## 📊 Mapping des Données + +### Données Envoyées par Notre API + +```typescript +{ + missionId: string, + name: string, + repoName: string, // Extrait de giteaRepositoryUrl + leantimeProjectId: number | null, + documentationCollectionId: string, // Mappé depuis outlineCollectionId + rocketchatChannelId: string, // Mappé depuis rocketChatChannelId + // Champs originaux conservés pour référence + giteaRepositoryUrl: string | null, + outlineCollectionId: string | null, + rocketChatChannelId: string | null, + penpotProjectId: string | null, + config: { + N8N_API_KEY: string, + MISSION_API_URL: string + } +} +``` + +### Données Attendues par N8N (Process Delete Data) + +Le node "Process Delete Data" transforme les données en : + +```javascript +{ + missionData: { + repoName: input.repoName || '', + leantimeId: input.leantimeProjectId || 0, + collectionId: input.documentationCollectionId || '', + rocketChatRoomId: input.rocketchatChannelId || '' + }, + config: { + GITEA_API_URL: "https://gite.slm-lab.net/api/v1", + GITEA_API_TOKEN: "...", + GITEA_OWNER: "alma", + LEANTIME_API_URL: "https://agilite.slm-lab.net", + LEANTIME_API_TOKEN: "...", + ROCKETCHAT_API_URL: "https://parole.slm-lab.net/", + ROCKETCHAT_AUTH_TOKEN: "...", + ROCKETCHAT_USER_ID: "...", + OUTLINE_API_URL: "https://chapitre.slm-lab.net/api", + OUTLINE_API_TOKEN: "...", + MISSION_API_URL: "https://hub.slm-lab.net", + KEYCLOAK_BASE_URL: "https://connect.slm-lab.net", + KEYCLOAK_REALM: "cercle", + KEYCLOAK_CLIENT_ID: "lab", + KEYCLOAK_CLIENT_SECRET: "..." + } +} +``` + +--- + +## 🔧 Transformations Effectuées + +### 1. Extraction du Nom du Repository Gitea + +**Problème** : Notre base stocke `giteaRepositoryUrl` (URL complète), mais N8N attend `repoName` (nom seul) + +**Solution** : Extraction du nom depuis l'URL + +```typescript +// Format possible: +// - https://gite.slm-lab.net/alma/repo-name +// - https://gite.slm-lab.net/api/v1/repos/alma/repo-name + +let repoName = ''; +if (mission.giteaRepositoryUrl) { + try { + const url = new URL(mission.giteaRepositoryUrl); + const pathParts = url.pathname.split('/').filter(Boolean); + repoName = pathParts[pathParts.length - 1] || ''; + } catch (error) { + // Fallback: extraction regex + const match = mission.giteaRepositoryUrl.match(/\/([^\/]+)\/?$/); + repoName = match ? match[1] : ''; + } +} +``` + +**Exemples** : +- `https://gite.slm-lab.net/alma/mission-abc` → `mission-abc` +- `https://gite.slm-lab.net/api/v1/repos/alma/mission-xyz` → `mission-xyz` + +### 2. Mapping des Champs + +| Notre Base de Données | N8N Attend | Transformation | +|----------------------|------------|----------------| +| `giteaRepositoryUrl` | `repoName` | Extraction du nom depuis URL | +| `leantimeProjectId` | `leantimeProjectId` | Direct (converti en number) | +| `outlineCollectionId` | `documentationCollectionId` | Direct mapping | +| `rocketChatChannelId` | `rocketchatChannelId` | Direct mapping (lowercase 'c') | + +--- + +## 🎯 Actions N8N par Service + +### 1. Gitea Repository + +**Node** : "Delete Gitea Repo" +- **Méthode** : DELETE +- **URL** : `{{ GITEA_API_URL }}/repos/{{ GITEA_OWNER }}/{{ repoName }}` +- **Headers** : `Authorization: token {{ GITEA_API_TOKEN }}` +- **ContinueOnFail** : `true` (continue même si échoue) + +**Résultat attendu** : Status 204 (No Content) = succès + +### 2. Leantime Project + +**Node** : "Close Leantime Project" +- **Méthode** : POST +- **URL** : `{{ LEANTIME_API_URL }}/api/jsonrpc` +- **Headers** : `X-API-Key: {{ LEANTIME_API_TOKEN }}` +- **Body** : + ```json + { + "method": "leantime.rpc.Projects.Projects.patch", + "jsonrpc": "2.0", + "id": 1, + "params": { + "id": {{ leantimeId }}, + "params": { "status": "closed" } + } + } + ``` +- **ContinueOnFail** : `true` + +**Note** : Le projet est **fermé** (status: "closed"), pas supprimé + +### 3. Outline Collection + +**Node** : "Delete Outline Collection" +- **Méthode** : POST +- **URL** : `{{ OUTLINE_API_URL }}/api/collections.delete` +- **Headers** : `Authorization: Bearer {{ OUTLINE_API_TOKEN }}` +- **Body** : `{ "id": "{{ collectionId }}" }` +- **ContinueOnFail** : `true` + +**Résultat attendu** : Status 200 = succès + +### 4. RocketChat Channel + +**Node** : "Close RocketChat Channel" +- **Méthode** : POST +- **URL** : `{{ ROCKETCHAT_API_URL }}/api/v1/channels.close` +- **Headers** : + - `X-Auth-Token: {{ ROCKETCHAT_AUTH_TOKEN }}` + - `X-User-Id: {{ ROCKETCHAT_USER_ID }}` +- **Body** : `{ "roomId": "{{ rocketChatRoomId }}" }` +- **ContinueOnFail** : `true` + +**Note** : Le canal est **fermé**, pas supprimé + +--- + +## 📤 Réponse N8N + +### Format de Réponse (Combine Results) + +```javascript +{ + status: "deleted", + timestamp: "2024-01-01T12:00:00.000Z", + details: { + gitea: true || "already_deleted", + leantime: true || false, + outline: true || false, + rocketchat: true || false + } +} +``` + +### Envoi à l'API (Save Deletion To API) + +Le workflow envoie ensuite les résultats à : +- **URL** : `{{ MISSION_API_URL }}/mission-deleted` +- **Méthode** : POST +- **Headers** : `Authorization: Bearer {{ Keycloak Token }}` +- **Body** : + ```json + { + "status": "archived", + "results": { + "gitea": true, + "leantime": true, + "outline": true, + "rocketchat": true + } + } + ``` + +--- + +## ⚠️ Points d'Attention + +### 1. Gestion des Erreurs + +- Tous les nodes de suppression ont `continueOnFail: true` +- Si une suppression échoue, le workflow continue avec les autres +- Les résultats indiquent quelles suppressions ont réussi/échoué + +### 2. Différences de Comportement + +- **Gitea** : Suppression complète du repository +- **Leantime** : Fermeture (status: "closed"), pas suppression +- **Outline** : Suppression complète de la collection +- **RocketChat** : Fermeture du canal, pas suppression + +### 3. Extraction du Repo Name + +- L'extraction doit gérer différents formats d'URL +- Si l'extraction échoue, `repoName` sera vide +- Le workflow N8N gérera le cas où `repoName` est vide + +### 4. Mapping des Champs + +- **documentationCollectionId** : Mappé depuis `outlineCollectionId` +- **rocketchatChannelId** : Mappé depuis `rocketChatChannelId` (attention au 'c' minuscule) +- **leantimeProjectId** : Converti en number (0 si null) + +--- + +## 🔍 Debugging + +### Logs à Surveiller + +1. **Extraction repo name** : + ``` + Extracted repo name from URL: { url: "...", repoName: "..." } + ``` + +2. **Données envoyées à N8N** : + ``` + Sending deletion data to N8N: { ... } + ``` + +3. **Résultat N8N** : + ``` + N8N Deletion Workflow Result: { success: true, results: {...} } + ``` + +### Vérifications + +1. **Repo name extrait correctement** ? + - Vérifier les logs d'extraction + - Format attendu : nom simple sans URL + +2. **Mapping des champs correct** ? + - `documentationCollectionId` = `outlineCollectionId` + - `rocketchatChannelId` = `rocketChatChannelId` + +3. **N8N a reçu les données** ? + - Vérifier les logs N8N + - Vérifier le webhook a été appelé + +--- + +## 📝 Exemple Complet + +### Données en Base + +```typescript +{ + id: "abc-123", + name: "Mission Example", + giteaRepositoryUrl: "https://gite.slm-lab.net/alma/mission-example", + leantimeProjectId: "123", + outlineCollectionId: "collection-456", + rocketChatChannelId: "channel-789" +} +``` + +### Données Envoyées à N8N + +```typescript +{ + missionId: "abc-123", + name: "Mission Example", + repoName: "mission-example", // Extrait de l'URL + leantimeProjectId: 123, // Converti en number + documentationCollectionId: "collection-456", // Mappé + rocketchatChannelId: "channel-789", // Mappé (lowercase 'c') + giteaRepositoryUrl: "https://gite.slm-lab.net/alma/mission-example", + outlineCollectionId: "collection-456", + rocketChatChannelId: "channel-789", + config: { + N8N_API_KEY: "...", + MISSION_API_URL: "https://hub.slm-lab.net" + } +} +``` + +### Données Traitées par N8N + +```javascript +{ + missionData: { + repoName: "mission-example", + leantimeId: 123, + collectionId: "collection-456", + rocketChatRoomId: "channel-789" + }, + config: { ... } +} +``` + +--- + +**Document généré le** : $(date) +**Version** : 1.0 +**Workflow N8N** : NeahMissionDelete_Pro +**Webhook URL** : https://brain.slm-lab.net/webhook-test/mission-delete + diff --git a/app/api/missions/[missionId]/route.ts b/app/api/missions/[missionId]/route.ts index 71a79815..184a0d1a 100644 --- a/app/api/missions/[missionId]/route.ts +++ b/app/api/missions/[missionId]/route.ts @@ -332,17 +332,41 @@ export async function DELETE( console.log('=== Starting N8N Deletion Workflow ==='); const n8nService = new N8nService(); + // Extract repo name from giteaRepositoryUrl if present + // Format: https://gite.slm-lab.net/alma/repo-name or https://gite.slm-lab.net/api/v1/repos/alma/repo-name + let repoName = ''; + if (mission.giteaRepositoryUrl) { + try { + const url = new URL(mission.giteaRepositoryUrl); + // Extract repo name from path (last segment) + const pathParts = url.pathname.split('/').filter(Boolean); + repoName = pathParts[pathParts.length - 1] || ''; + console.log('Extracted repo name from URL:', { url: mission.giteaRepositoryUrl, repoName }); + } catch (error) { + console.error('Error extracting repo name from URL:', error); + // If URL parsing fails, try to extract from the string directly + const match = mission.giteaRepositoryUrl.match(/\/([^\/]+)\/?$/); + repoName = match ? match[1] : ''; + } + } + + // Prepare data according to N8N workflow expectations + // The workflow expects: repoName, leantimeProjectId, documentationCollectionId, rocketchatChannelId const n8nDeletionData = { missionId: mission.id, name: mission.name, - leantimeProjectId: mission.leantimeProjectId, + repoName: repoName, // N8N expects repoName, not giteaRepositoryUrl + leantimeProjectId: mission.leantimeProjectId || 0, + documentationCollectionId: mission.outlineCollectionId || '', // N8N expects documentationCollectionId + rocketchatChannelId: mission.rocketChatChannelId || '', // N8N expects rocketchatChannelId (lowercase 'c') + // Keep original fields for reference + giteaRepositoryUrl: mission.giteaRepositoryUrl, outlineCollectionId: mission.outlineCollectionId, rocketChatChannelId: mission.rocketChatChannelId, - giteaRepositoryUrl: mission.giteaRepositoryUrl, penpotProjectId: mission.penpotProjectId, config: { N8N_API_KEY: process.env.N8N_API_KEY, - MISSION_API_URL: process.env.NEXT_PUBLIC_API_URL + MISSION_API_URL: process.env.NEXT_PUBLIC_API_URL || 'https://hub.slm-lab.net' } };