# Mapping Complet N8N - Création et Suppression de Mission ## 📋 Vue d'Ensemble Ce document décrit le mapping complet entre notre API et les workflows N8N pour la création et la suppression de missions, basé sur les workflows réels partagés. --- ## 🔄 Workflow de Création - NeahMissionCreate ### Structure du Workflow ``` Webhook (mission-created) ↓ Process Mission Data ↓ Get Keycloak Token ↓ Process Token ↓ Debug Service Data ↓ Merge Paths ↓ IF Run Integrations ├─ IF Needs Git Repository │ ├─ Create Git Repository (si Gite ou Calcul) │ ├─ Create Readme │ └─ Git Wiki ├─ Create Documentation Collection ├─ Create Leantime Project │ └─ Leantime Avatar └─ Create RocketChat Channel ↓ Combine Results ↓ Save Mission To API (POST /mission-created) ↓ Process Results ↓ Respond To Webhook ``` ### Données Envoyées par Notre API → N8N **Endpoint** : `POST https://brain.slm-lab.net/webhook/mission-created` **Format** : ```typescript { name: string, oddScope: string[], niveau: string, intention: string, missionType: string, donneurDOrdre: string, projection: string, services: string[], participation: string, profils: string[], guardians: { "gardien-temps": userId, "gardien-parole": userId, "gardien-memoire": userId }, volunteers: string[], creatorId: string, logo: { data: "data:image/png;base64,...", name: string, type: string }, attachments: Array<{ data: "data:...;base64,...", name: string, type: string }>, config: { N8N_API_KEY: string, MISSION_API_URL: string } } ``` ### Traitement N8N - Process Mission Data Le node "Process Mission Data" transforme les données en : ```javascript { missionOriginal: { ... }, // Données originales missionProcessed: { name: "Mission Example", sanitizedName: "mission-example", // Nom nettoyé pour URLs intention: "...", description: "...", startDate: "2024-01-01", endDate: "2024-01-31", missionType: "remote", guardians: { ... }, volunteers: [ ... ], profils: [ ... ], services: ["Gite", "ArtLab"], // Détermine quelles intégrations créer clientId: 2, rocketChatUsernames: [userId1, userId2, ...], // Gardiens + volontaires logo: { data: "...", name: "...", type: "..." }, attachments: [ ... ] }, 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", // ... autres configs }, creatorId: "user-id" } ``` ### Intégrations Créées par N8N #### 1. Gitea Repository (Conditionnel) **Condition** : `services.includes('Gite') || services.includes('Calcul')` **Node** : "Create Git Repository" - **Méthode** : POST - **URL** : `{{ GITEA_API_URL }}/user/repos` - **Body** : ```json { "name": "{{ sanitizedName }}", "private": true, "auto_init": true, "avatar_url": "{{ logo.data }}" } ``` - **Résultat** : `{ html_url: "https://gite.slm-lab.net/alma/mission-example" }` **Actions supplémentaires** : - Create Readme : Crée un document README dans Outline - Git Wiki : Configure le wiki externe du repo vers Outline #### 2. Leantime Project **Node** : "Create Leantime Project" - **Méthode** : POST - **URL** : `{{ LEANTIME_API_URL }}/api/jsonrpc` - **Body** : ```json { "method": "leantime.rpc.Projects.Projects.addProject", "jsonrpc": "2.0", "id": 1, "params": { "values": { "name": "{{ name }}", "clientId": {{ clientId }}, "details": "{{ intention }}", "type": "project", "start": "{{ startDate }}", "end": "{{ endDate }}", "status": "open", "psettings": "restricted", "avatar": "{{ logo.data }}" } } } ``` - **Résultat** : `{ result: [projectId] }` (array avec 1 élément) **Action supplémentaire** : - Leantime Avatar : Met à jour l'avatar du projet #### 3. Outline Collection **Node** : "Create Documentation Collection" - **Méthode** : POST - **URL** : `{{ OUTLINE_API_URL }}/api/collections.create` - **Body** : ```json { "name": "{{ sanitizedName }}", "description": "{{ description }}", "permission": "read", "private": true } ``` - **Résultat** : `{ data: { id: "collection-id", url: "/collection/..." } }` #### 4. RocketChat Channel **Node** : "Create RocketChat Channel" - **Méthode** : POST - **URL** : `{{ ROCKETCHAT_API_URL }}/api/v1/channels.create` - **Body** : ```json { "name": "{{ sanitizedName }}", "members": [{{ rocketChatUsernames }}], "readOnly": false, "avatarUrl": "{{ logo.data }}" } ``` - **Résultat** : `{ channel: { _id: "channel-id", ... } }` ### Save Mission To API - Retour vers Notre API **Node** : "Save Mission To API" - **Méthode** : POST - **URL** : `{{ MISSION_API_URL }}/mission-created` - **Headers** : - `Content-Type: application/json` - `Authorization: Bearer {{ Keycloak Token }}` - `x-api-key: {{ N8N_API_KEY }}` - **Body** : ```json { "name": "{{ name }}", "niveau": "{{ niveau }}", "intention": "{{ intention }}", "description": "{{ description }}", "gitRepoUrl": "{{ gitRepo.html_url }}", "leantimeProjectId": "{{ leantimeProject.result[0] }}", "documentationCollectionId": "{{ docCollection.data.id }}", "rocketchatChannelId": "{{ rocketChatChannel.channel._id }}", "donneurDOrdre": "{{ donneurDOrdre }}", "projection": "{{ projection }}", "missionType": "{{ missionType }}", "creatorId": "{{ creatorId }}" } ``` **⚠️ IMPORTANT** : Cet endpoint `/mission-created` n'existe **PAS** actuellement dans notre codebase. Il devrait : 1. Recevoir les IDs des intégrations créées 2. Mettre à jour la mission en base avec ces IDs 3. Mapper les champs : - `gitRepoUrl` → `giteaRepositoryUrl` - `documentationCollectionId` → `outlineCollectionId` - `rocketchatChannelId` → `rocketChatChannelId` --- ## 🗑️ Workflow de Suppression - NeahMissionDelete_Pro ### Structure du Workflow ``` Webhook Delete (mission-delete) ↓ Process Delete Data ↓ Get Keycloak Token ↓ [En parallèle] ├─ Delete Gitea Repo ├─ Close Leantime Project ├─ Delete Outline Collection └─ Close RocketChat Channel ↓ Combine Results ↓ Save Deletion To API (POST /mission-deleted) ``` ### Données Envoyées par Notre API → N8N **Endpoint** : `POST https://brain.slm-lab.net/webhook-test/mission-delete` **Format** : ```typescript { missionId: string, name: string, repoName: string, // ✅ Extrait de giteaRepositoryUrl leantimeProjectId: number | 0, // ✅ Converti en number documentationCollectionId: string, // ✅ Mappé depuis outlineCollectionId rocketchatChannelId: string, // ✅ Mappé depuis rocketChatChannelId // Champs originaux 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 } } ``` ### Traitement 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: "..." } } ``` ### Actions de Suppression N8N #### 1. Delete Gitea Repo **Node** : "Delete Gitea Repo" - **Méthode** : DELETE - **URL** : `{{ GITEA_API_URL }}/repos/{{ GITEA_OWNER }}/{{ repoName }}` - **Headers** : `Authorization: token {{ GITEA_API_TOKEN }}` - **ContinueOnFail** : `true` - **Résultat attendu** : Status 204 = succès #### 2. Close Leantime Project **Node** : "Close Leantime Project" - **Méthode** : POST - **URL** : `{{ LEANTIME_API_URL }}/api/jsonrpc` - **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. Delete Outline Collection **Node** : "Delete Outline Collection" - **Méthode** : POST - **URL** : `{{ OUTLINE_API_URL }}/api/collections.delete` - **Body** : `{ "id": "{{ collectionId }}" }` - **ContinueOnFail** : `true` - **Résultat attendu** : Status 200 = succès #### 4. Close RocketChat Channel **Node** : "Close RocketChat Channel" - **Méthode** : POST - **URL** : `{{ ROCKETCHAT_API_URL }}/api/v1/channels.close` - **Body** : `{ "roomId": "{{ rocketChatRoomId }}" }` - **ContinueOnFail** : `true` - **Note** : Le canal est **fermé**, pas supprimé ### Combine Results Le node "Combine Results" combine les résultats : ```javascript { status: "deleted", timestamp: "2024-01-01T12:00:00.000Z", details: { gitea: true || "already_deleted", leantime: true || false, outline: true || false, rocketchat: true || false } } ``` ### Save Deletion To API - Retour vers Notre API **Node** : "Save Deletion To API" - **Méthode** : POST - **URL** : `{{ MISSION_API_URL }}/mission-deleted` - **Headers** : - `Authorization: Bearer {{ Keycloak Token }}` - **Body** : ```json { "status": "archived", "results": { "gitea": true, "leantime": true, "outline": true, "rocketchat": true } } ``` **⚠️ IMPORTANT** : Cet endpoint `/mission-deleted` n'existe **PAS** actuellement dans notre codebase. Il pourrait servir à : 1. Confirmer la suppression 2. Logger les résultats 3. Nettoyer des données supplémentaires si nécessaire --- ## 📊 Mapping Complet des Champs ### Création (Notre API → N8N → Retour) | Notre Base | Envoyé à N8N | N8N Crée | Retour N8N | Stocké en Base | |-----------|--------------|----------|------------|----------------| | - | `name` | - | `name` | `name` | | - | `services` | Détermine intégrations | - | `services` | | - | `logo.data` | Avatar/Logo | - | `logo` (path) | | - | - | Gitea Repo | `gitRepoUrl` | `giteaRepositoryUrl` | | - | - | Leantime Project | `leantimeProjectId` | `leantimeProjectId` | | - | - | Outline Collection | `documentationCollectionId` | `outlineCollectionId` | | - | - | RocketChat Channel | `rocketchatChannelId` | `rocketChatChannelId` | ### Suppression (Notre Base → N8N) | Notre Base | Extrait/Transformé | Envoyé à N8N | N8N Attend | |-----------|-------------------|--------------|------------| | `giteaRepositoryUrl` | Extraction nom | `repoName` | `repoName` | | `leantimeProjectId` | Converti en number | `leantimeProjectId` | `leantimeId` | | `outlineCollectionId` | Direct | `documentationCollectionId` | `collectionId` | | `rocketChatChannelId` | Direct | `rocketchatChannelId` | `rocketChatRoomId` | --- ## 🔧 Transformations Clés ### 1. Extraction du Nom du Repository Gitea **Problème** : Notre base stocke l'URL complète, N8N attend le nom seul **Solution** : ```typescript // Format: https://gite.slm-lab.net/alma/mission-example // ou: https://gite.slm-lab.net/api/v1/repos/alma/mission-example let repoName = ''; if (giteaRepositoryUrl) { try { const url = new URL(giteaRepositoryUrl); const pathParts = url.pathname.split('/').filter(Boolean); repoName = pathParts[pathParts.length - 1] || ''; } catch (error) { const match = giteaRepositoryUrl.match(/\/([^\/]+)\/?$/); repoName = match ? match[1] : ''; } } ``` ### 2. Mapping des Champs **Création** : - N8N retourne `gitRepoUrl` → On stocke `giteaRepositoryUrl` - N8N retourne `documentationCollectionId` → On stocke `outlineCollectionId` - N8N retourne `rocketchatChannelId` → On stocke `rocketChatChannelId` **Suppression** : - On stocke `giteaRepositoryUrl` → On envoie `repoName` (extrait) - On stocke `outlineCollectionId` → On envoie `documentationCollectionId` - On stocke `rocketChatChannelId` → On envoie `rocketchatChannelId` ### 3. Conversion de Types **Leantime Project ID** : - Stocké en base : `string | null` - Envoyé à N8N : `number | 0` (converti) - N8N attend : `number` (dans `leantimeId`) --- ## ⚠️ Endpoints Manquants ### 1. POST /mission-created **Rôle** : Recevoir les IDs des intégrations créées par N8N **Format attendu** : ```typescript POST /mission-created Headers: { Authorization: "Bearer {keycloak_token}", x-api-key: "{N8N_API_KEY}" } Body: { name: string, niveau: string, intention: string, description: string, gitRepoUrl: string, // À mapper vers giteaRepositoryUrl leantimeProjectId: string, // À mapper vers leantimeProjectId documentationCollectionId: string, // À mapper vers outlineCollectionId rocketchatChannelId: string, // À mapper vers rocketChatChannelId donneurDOrdre: string, projection: string, missionType: string, creatorId: string } ``` **Action requise** : 1. Trouver la mission par `name` + `creatorId` 2. Mettre à jour avec les IDs retournés 3. Mapper les champs correctement ### 2. POST /mission-deleted **Rôle** : Confirmer la suppression (optionnel) **Format attendu** : ```typescript POST /mission-deleted Headers: { Authorization: "Bearer {keycloak_token}" } Body: { status: "archived", results: { gitea: boolean, leantime: boolean, outline: boolean, rocketchat: boolean } } ``` **Action requise** : - Logger les résultats - Potentiellement nettoyer des données supplémentaires --- ## 🔄 Flow Complet - Vue d'Ensemble ### Création ``` 1. Frontend → POST /api/missions ↓ 2. Backend crée mission en Prisma ↓ 3. Backend upload fichiers Minio ↓ 4. Backend → POST N8N webhook (mission-created) ↓ 5. N8N crée intégrations (Gitea, Leantime, Outline, RocketChat) ↓ 6. N8N → POST /mission-created (⚠️ endpoint manquant) ↓ 7. Backend met à jour mission avec IDs (⚠️ non implémenté) ``` ### Suppression ``` 1. Frontend → DELETE /api/missions/[id] ↓ 2. Backend récupère mission ↓ 3. Backend extrait/mappe les données ↓ 4. Backend → POST N8N webhook (mission-delete) ↓ 5. N8N supprime/ferme intégrations ↓ 6. N8N → POST /mission-deleted (⚠️ endpoint manquant) ↓ 7. Backend supprime logo Minio ↓ 8. Backend supprime attachments Minio ↓ 9. Backend supprime mission Prisma (CASCADE) ``` --- ## 📝 Notes Importantes ### 1. Noms de Champs Incohérents - **Création** : N8N retourne `gitRepoUrl`, `documentationCollectionId`, `rocketchatChannelId` - **Suppression** : N8N attend `repoName`, `documentationCollectionId`, `rocketchatChannelId` - **Notre Base** : Stocke `giteaRepositoryUrl`, `outlineCollectionId`, `rocketChatChannelId` **Solution** : Mapping cohérent dans les deux sens ### 2. Endpoint /mission-created Manquant Actuellement, les IDs retournés par N8N ne sont **PAS** sauvegardés en base. Il faudrait : - Créer l'endpoint `/mission-created` - Trouver la mission (par `name` + `creatorId` ou `missionId`) - Mettre à jour avec les IDs ### 3. Services Conditionnels - **Gitea** : Créé seulement si `services.includes('Gite') || services.includes('Calcul')` - **Leantime** : Toujours créé - **Outline** : Toujours créé - **RocketChat** : Toujours créé ### 4. Gestion d'Erreurs - Tous les nodes N8N ont `continueOnFail: true` - Les erreurs sont loggées mais n'arrêtent pas le workflow - Les résultats indiquent quelles intégrations ont réussi/échoué --- ## 🔍 Points de Debugging ### Création 1. **Vérifier données envoyées à N8N** : ``` Sending to N8N: { ... } ``` 2. **Vérifier réponse N8N** : ``` N8N Workflow Result: { success: true, results: {...} } ``` 3. **Vérifier endpoint /mission-created** : - Doit recevoir les IDs - Doit mettre à jour la mission ### Suppression 1. **Vérifier extraction repoName** : ``` Extracted repo name from URL: { url: "...", repoName: "..." } ``` 2. **Vérifier données envoyées à N8N** : ``` Sending deletion data to N8N: { ... } ``` 3. **Vérifier réponse N8N** : ``` N8N Deletion Workflow Result: { success: true, results: {...} } ``` --- **Document généré le** : $(date) **Version** : 1.0 **Workflows N8N** : - NeahMissionCreate (création) - NeahMissionDelete_Pro (suppression)