NeahNew/N8N_COMPLETE_WORKFLOW_MAPPING.md
2026-01-04 14:24:56 +01:00

674 lines
17 KiB
Markdown

# 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)