mission-tab

This commit is contained in:
alma 2026-01-04 12:51:58 +01:00
parent eb71deca88
commit 6cf131921b
6 changed files with 1429 additions and 5 deletions

162
CAPROVER_NGINX_FIX.md Normal file
View File

@ -0,0 +1,162 @@
# Fix Nginx CapRover - Erreur "upstream sent too big header"
## 🔍 Problème
Erreur 502 avec message Nginx :
```
upstream sent too big header while reading response header from upstream
```
**Cause** : Le cookie de session NextAuth (JWT avec tokens Keycloak) dépasse 4KB, la limite par défaut de Nginx.
## ✅ Solution : Modifier la configuration CapRover
### Option 1 : Via CapRover Dashboard (RECOMMANDÉ)
1. **Aller dans CapRover Dashboard**
2. **Sélectionner votre app** (hub.slm-lab.net)
3. **Aller dans "HTTP Settings"**
4. **Cliquer sur "Edit Nginx Configuration"** (si disponible)
5. **OU aller dans "App Configs" → "nginx"**
### Option 2 : Modifier le template Nginx directement
Si vous avez accès au serveur CapRover, modifier le template dans :
- `/captain/templates/nginx.conf` (template principal)
- OU créer un override dans votre app
## 📝 Configuration à Ajouter
**Dans le bloc `location /`**, ajouter ces directives **AVANT** `proxy_pass` :
```nginx
location / {
# ============================================
# FIX: Augmenter la limite des headers pour NextAuth
# ============================================
proxy_buffer_size 16k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
large_client_header_buffers 4 32k;
# Timeouts (pour éviter les timeouts)
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Configuration proxy existante
proxy_pass $upstream;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support (si activé)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
}
```
## 🔧 Configuration Complète Modifiée
Voici le bloc `location /` complet avec les corrections :
```nginx
location / {
# FIX: Headers trop grands pour NextAuth
proxy_buffer_size 16k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
large_client_header_buffers 4 32k;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Proxy configuration
proxy_pass $upstream;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket (si activé)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
}
```
## 🎯 Méthode via CapRover Dashboard
### Si CapRover permet l'édition Nginx :
1. **Dashboard CapRover** → Votre app
2. **"App Configs"** → **"nginx"**
3. **Ajouter dans "Custom Nginx Configuration"** :
```nginx
location / {
proxy_buffer_size 16k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
large_client_header_buffers 4 32k;
}
```
4. **Sauvegarder** → CapRover regénère la config
### Si CapRover ne permet pas l'édition :
**Option A** : Modifier le template CapRover (avancé)
- Accéder au serveur CapRover
- Modifier `/captain/templates/nginx.conf`
- Redémarrer CapRover
**Option B** : Créer un fichier de configuration personnalisé
- Créer un fichier dans votre app
- L'inclure dans la config Nginx
## 🔄 Après Modification
1. **Vérifier la config Nginx** :
```bash
sudo nginx -t
```
2. **Recharger Nginx** :
```bash
sudo systemctl reload nginx
# OU si CapRover gère Nginx
docker exec captain-nginx nginx -s reload
```
3. **Tester la connexion** :
- Se connecter via Keycloak
- Vérifier que l'erreur 502 ne se produit plus
## 📊 Explication
**Avant** :
- Limite par défaut Nginx : 4KB pour les headers
- Cookie NextAuth : ~4-7KB (JWT avec tokens Keycloak)
- Résultat : ❌ Erreur 502
**Après** :
- Limite augmentée : 32KB pour les headers
- Cookie NextAuth : ~4-7KB
- Résultat : ✅ Fonctionne
## ⚠️ Note Importante
Si vous modifiez le template CapRover directement, **vos modifications seront écrasées** lors d'une mise à jour de CapRover.
**Recommandation** : Utiliser la méthode "Custom Nginx Configuration" dans CapRover si disponible, ou documenter vos modifications pour les réappliquer après mise à jour.
---
**Document créé le** : $(date)
**Priorité** : HAUTE - Résout l'erreur 502

View File

@ -0,0 +1,930 @@
# Analyse Complète : Pages Missions et Centrale - Workflow Complet
## 📋 Table des Matières
1. [Vue d'ensemble](#vue-densemble)
2. [Architecture des Pages](#architecture-des-pages)
3. [Workflow de Navigation](#workflow-de-navigation)
4. [Workflow de Création de Mission](#workflow-de-création-de-mission)
5. [Workflow de Consultation](#workflow-de-consultation)
6. [API Routes](#api-routes)
7. [Base de Données](#base-de-données)
8. [Intégrations Externes](#intégrations-externes)
9. [Stockage de Fichiers](#stockage-de-fichiers)
10. [Composants Réutilisables](#composants-réutilisables)
---
## 🎯 Vue d'ensemble
### Page "Centrale"
- **Route**: `/missions`
- **Nom dans le menu**: "Centrale"
- **Accès**: Rôles `entrepreneurship` ou `admin` (défini dans `components/main-nav.tsx`)
- **Description**: Centre d'Administration et de Pilotage (CAP) - Interface principale pour gérer les missions
### Page "Missions"
- **Route principale**: `/missions`
- **Sous-routes**:
- `/missions` - Liste des missions de l'utilisateur
- `/missions/new` - Création d'une nouvelle mission
- `/missions/[missionId]` - Détails d'une mission
- `/missions/[missionId]/edit` - Édition d'une mission
### Page "Mission Tab" (Tableau des Missions)
- **Route**: `/mission-tab`
- **Description**: Vue publique de toutes les missions disponibles
- **Sous-routes**:
- `/mission-tab` - Liste de toutes les missions
- `/mission-tab/[missionId]` - Détails d'une mission (vue publique)
---
## 🏗️ Architecture des Pages
### 1. Layout Principal - Missions (`app/missions/layout.tsx`)
**Structure**:
```
┌─────────────────────────────────────────┐
│ Sidebar (CAP) - Fond rose clair │
│ ┌───────────────────────────────────┐ │
│ │ CAP │ │
│ │ Centre d'Administration et de │ │
│ │ Pilotage │ │
│ └───────────────────────────────────┘ │
│ • Mes Missions (/missions) │
│ • Nouvelle Mission (/missions/new) │
└─────────────────────────────────────────┘
│ Contenu Principal (children) │
└─────────────────────────────────────────┘
```
**Fonctionnalités**:
- Sidebar fixe avec navigation
- Fond rose clair (`bg-pink-50`) pour la sidebar
- Fond blanc pour le contenu principal
- Navigation active highlightée
### 2. Page Liste des Missions (`app/missions/page.tsx`)
**Fonctionnalités**:
- Affichage en grille (responsive: 1/2/3 colonnes)
- Recherche par nom, niveau, type, ODD scope
- Filtrage en temps réel
- Cartes de mission avec:
- Logo (ou initiales si pas de logo)
- Nom de la mission
- Badge niveau (A/B/C/S) avec couleurs
- Icône ODD (Objectifs de Développement Durable)
- Services associés
- Description (intention) tronquée
- Date de création
- Bouton "Voir détails"
**API utilisée**: `GET /api/missions`
- Retourne uniquement les missions où l'utilisateur est:
- Créateur (`creatorId`)
- Ou membre (`missionUsers`)
### 3. Page Création de Mission (`app/missions/new/page.tsx`)
**Composant principal**: `MissionsAdminPanel`
- Formulaire multi-onglets (5 onglets)
- Navigation séquentielle avec boutons Précédent/Suivant
### 4. Page Détails Mission (`app/missions/[missionId]/page.tsx`)
**Fonctionnalités**:
- Affichage complet des informations
- Logo de la mission
- Grille d'informations (Type, Donneur d'ordre, Durée, Niveau, Participation, ODD)
- Description complète
- Liste des documents/attachments
- Profils recherchés
- Services
- Bouton de suppression (si créateur ou admin)
**API utilisée**: `GET /api/missions/[missionId]`
### 5. Page Mission Tab (`app/mission-tab/page.tsx`)
**Différences avec `/missions`**:
- Affiche **TOUTES** les missions (pas de filtre utilisateur)
- API utilisée: `GET /api/missions/all`
- Vue publique pour découvrir toutes les missions disponibles
---
## 🔄 Workflow de Navigation
### Accès à la Centrale
```
1. Utilisateur connecté avec rôle "entrepreneurship" ou "admin"
2. Menu déroulant utilisateur (MainNav)
3. Clic sur "Centrale" (href: '/missions')
4. Redirection vers /missions
5. Layout Missions s'affiche avec sidebar CAP
6. Page Liste des Missions (/missions/page.tsx)
```
### Navigation dans la Centrale
```
┌─────────────────────────────────────────┐
│ Sidebar CAP │
│ ├─ Mes Missions (/missions) │
│ └─ Nouvelle Mission (/missions/new) │
└─────────────────────────────────────────┘
│ │
│ │
▼ ▼
┌─────────────────┐ ┌──────────────────┐
│ Liste Missions │ │ Création Mission │
│ │ │ │
│ [Carte Mission] │ │ [Formulaire] │
│ └─► Détails │ │ │
└─────────────────┘ └──────────────────┘
┌─────────────────┐
│ Détails Mission │
│ │
│ [Éditer] │
│ [Supprimer] │
└─────────────────┘
```
---
## 🚀 Workflow de Création de Mission
### Étape 1: Accès au Formulaire
```
User → /missions/new → MissionsAdminPanel
```
### Étape 2: Formulaire Multi-Onglets
**Onglet 1: General**
- Nom de la mission (requis)
- Logo (upload)
- ODD scope (requis) - Sélection parmi 17 ODD
- Niveau (requis) - A/B/C/S
- Intention (requis) - Description avec éditeur de texte
**Onglet 2: Details**
- Type de mission (requis) - Remote/Onsite/Hybrid
- Donneur d'ordre (requis) - Individu/ONG/Start-ups
- Projection (requis) - Short/Medium/Long term
- Services - Checkboxes (Gite, ArtLab, Calcul)
- Participation (requis) - Volontaire/Cooptation
- Profils - Checkboxes (DataIntelligence, Expression, Mediation, Investigation, Coding, Lean)
**Onglet 3: Attachments**
- Upload de fichiers (PDF, DOC, DOCX, XLS, XLSX, JPG, JPEG, PNG)
- Liste des fichiers sélectionnés
- Upload immédiat vers Minio (bucket 'missions')
**Onglet 4: Skills**
- Liste de compétences (non fonctionnel actuellement - placeholders)
**Onglet 5: Membres**
- **Les Gardiens de l'Intention** (3 gardiens requis):
- Gardien du Temps
- Gardien de la Parole
- Gardien de la Mémoire
- **Volontaires** (optionnel)
- Recherche d'utilisateurs ou groupes
- Assignation de rôles
### Étape 3: Validation et Soumission
**Validation**:
```typescript
const requiredFields = {
name: !!missionData.name,
oddScope: Array.isArray(missionData.oddScope) && missionData.oddScope.length > 0,
niveau: !!missionData.niveau,
intention: !!missionData.intention,
missionType: !!missionData.missionType,
donneurDOrdre: !!missionData.donneurDOrdre,
projection: !!missionData.projection,
participation: !!missionData.participation,
gardiens: gardienDuTemps !== null &&
gardienDeLaParole !== null &&
gardienDeLaMemoire !== null
}
```
**Soumission**:
```typescript
POST /api/missions
Body: {
name, oddScope, niveau, intention, missionType,
donneurDOrdre, projection, services, profils,
participation, guardians, volunteers, logo, attachments
}
```
### Étape 4: Traitement Backend
**Séquence d'exécution**:
1. **Création de la mission en base de données**
```typescript
prisma.mission.create({
data: { name, oddScope, niveau, intention, ... }
})
```
2. **Création des MissionUsers (gardiens + volontaires)**
```typescript
prisma.missionUser.createMany({
data: [
{ missionId, userId, role: 'gardien-temps' },
{ missionId, userId, role: 'gardien-parole' },
{ missionId, userId, role: 'gardien-memoire' },
{ missionId, userId, role: 'volontaire' }, // pour chaque volontaire
]
})
```
3. **Upload du logo vers Minio**
- Path: `missions/{missionId}/logo{extension}`
- Bucket: `missions`
- Mise à jour du champ `logo` dans la mission
4. **Upload des attachments vers Minio**
- Path: `missions/{missionId}/attachments/{filename}`
- Création des enregistrements `Attachment` en base
5. **Vérification des fichiers dans Minio**
- Vérifie que tous les fichiers sont bien présents avant de continuer
6. **Déclenchement du workflow N8N**
```typescript
n8nService.triggerMissionCreation({
...missionData,
creatorId,
logoPath,
config: { N8N_API_KEY, MISSION_API_URL }
})
```
7. **Intégrations externes (via N8N)**:
- Création projet Leantime (si applicable)
- Création collection Outline (si applicable)
- Création canal RocketChat (si applicable)
- Création repository Gitea (si applicable)
- Création projet Penpot (si applicable)
8. **Retour succès/erreur**
- Si succès: Redirection vers `/missions`
- Si erreur: Nettoyage des fichiers uploadés + message d'erreur
---
## 👀 Workflow de Consultation
### Consultation Liste des Missions
**Route**: `/missions` ou `/mission-tab`
**Flux**:
```
1. Chargement de la page
2. useEffect → fetch('/api/missions') ou fetch('/api/missions/all')
3. Affichage du loader
4. Réception des données
5. Transformation des données:
- Ajout des logoUrl (si logo existe)
- Formatage des dates
- Calcul des couleurs de badges
- Extraction des infos ODD
6. Filtrage par terme de recherche (si présent)
7. Affichage en grille
```
**Recherche**:
- Filtre en temps réel sur: nom, niveau, type, ODD scope
- Pas de requête API supplémentaire (filtrage côté client)
### Consultation Détails Mission
**Route**: `/missions/[missionId]` ou `/mission-tab/[missionId]`
**Flux**:
```
1. Chargement de la page
2. Récupération du missionId depuis les params
3. useEffect → fetch(`/api/missions/${missionId}`)
4. Affichage du loader
5. Réception des données complètes:
- Mission avec tous les champs
- Creator (id, email)
- MissionUsers (avec user details)
- Attachments (avec publicUrl)
6. Transformation:
- Ajout des logoUrl
- Formatage des dates
- Labels pour les types/niveaux
- URLs publiques pour les attachments
7. Affichage des sections:
- Header avec nom et logo
- Grille d'informations
- Description
- Documents
- Profils recherchés
- Services
- Actions (Éditer/Supprimer)
```
---
## 🔌 API Routes
### 1. `GET /api/missions`
**Fichier**: `app/api/missions/route.ts`
**Fonctionnalité**: Liste les missions de l'utilisateur connecté
**Filtres**:
- `limit` (default: 10)
- `offset` (default: 0)
- `search` (recherche dans name et intention)
- `name` (filtre exact)
**Where Clause**:
```typescript
{
OR: [
{ creatorId: userId },
{ missionUsers: { some: { userId } } }
]
}
```
**Retour**:
```json
{
"missions": [
{
"id": "...",
"name": "...",
"logo": "missions/{id}/logo.png",
"logoUrl": "/api/missions/image/missions/{id}/logo.png",
"oddScope": ["odd-3"],
"niveau": "a",
"missionType": "remote",
"projection": "short",
"services": ["Gite"],
"intention": "...",
"createdAt": "...",
"creator": { "id": "...", "email": "..." },
"missionUsers": [...],
"attachments": [...]
}
],
"pagination": {
"total": 10,
"offset": 0,
"limit": 10
}
}
```
### 2. `POST /api/missions`
**Fichier**: `app/api/missions/route.ts`
**Fonctionnalité**: Crée une nouvelle mission
**Body**:
```typescript
{
name: string;
oddScope: string[];
niveau?: string;
intention?: string;
missionType?: string;
donneurDOrdre?: string;
projection?: string;
services?: string[];
profils?: string[];
participation?: string;
guardians?: {
"gardien-temps": string;
"gardien-parole": string;
"gardien-memoire": string;
};
volunteers?: string[];
logo?: {
data: string; // base64
name?: string;
type?: string;
};
attachments?: Array<{
data: string; // base64
name?: string;
type?: string;
}>;
}
```
**Retour**:
```json
{
"success": true,
"mission": { ... },
"message": "Mission created successfully with all integrations"
}
```
### 3. `GET /api/missions/[missionId]`
**Fichier**: `app/api/missions/[missionId]/route.ts`
**Fonctionnalité**: Récupère les détails d'une mission
**Contrôle d'accès**:
- Utilisateur doit être créateur OU membre de la mission
**Retour**: Mission complète avec relations
### 4. `PUT /api/missions/[missionId]`
**Fichier**: `app/api/missions/[missionId]/route.ts`
**Fonctionnalité**: Met à jour une mission
**Contrôle d'accès**:
- Créateur OU gardien-temps/gardien-parole
**Body**: Même structure que POST (tous les champs optionnels)
### 5. `DELETE /api/missions/[missionId]`
**Fichier**: `app/api/missions/[missionId]/route.ts`
**Fonctionnalité**: Supprime une mission
**Contrôle d'accès**:
- Créateur OU admin uniquement
**Actions**:
- Suppression du logo dans Minio
- Suppression de la mission en base (cascade sur MissionUsers et Attachments)
- TODO: Rollback N8N (non implémenté)
### 6. `GET /api/missions/all`
**Fichier**: `app/api/missions/all/route.ts`
**Fonctionnalité**: Liste TOUTES les missions (pas de filtre utilisateur)
**Différences avec `/api/missions`**:
- Pas de filtre par utilisateur
- Retourne toutes les missions publiques
- Utilisé par `/mission-tab`
### 7. `GET /api/missions/image/[...path]`
**Fichier**: `app/api/missions/image/[...path]/route.ts`
**Fonctionnalité**: Sert les images (logos et attachments) depuis Minio
**Path**: `missions/{missionId}/logo.png` ou `missions/{missionId}/attachments/{filename}`
### 8. `POST /api/missions/upload`
**Fichier**: `app/api/missions/upload/route.ts`
**Fonctionnalité**: Upload de fichiers (logo ou attachments)
### 9. `GET /api/missions/[missionId]/attachments`
**Fichier**: `app/api/missions/[missionId]/attachments/route.ts`
**Fonctionnalité**: Liste les attachments d'une mission
### 10. `POST /api/missions/[missionId]/attachments`
**Fichier**: `app/api/missions/[missionId]/attachments/route.ts`
**Fonctionnalité**: Ajoute un attachment à une mission existante
### 11. `DELETE /api/missions/[missionId]/attachments/[attachmentId]`
**Fichier**: `app/api/missions/[missionId]/attachments/[attachmentId]/route.ts`
**Fonctionnalité**: Supprime un attachment
---
## 🗄️ Base de Données
### Modèle Mission (`prisma/schema.prisma`)
```prisma
model Mission {
id String @id @default(uuid())
name String
logo String? // Path dans Minio
oddScope String[] // Catégories ODD
niveau String // A/B/C/S
intention String // Description
missionType String // remote/onsite/hybrid
donneurDOrdre String // individual/group/organization
projection String // short/medium/long
services String[] // ["Gite", "ArtLab", "Calcul"]
participation String? // volontaire/cooptation
profils String[] // ["DataIntelligence", ...]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
creator User @relation(fields: [creatorId], references: [id])
creatorId String
attachments Attachment[]
missionUsers MissionUser[]
// Intégrations externes
leantimeProjectId String?
outlineCollectionId String?
rocketChatChannelId String?
giteaRepositoryUrl String?
penpotProjectId String?
@@index([creatorId])
}
```
### Modèle MissionUser
```prisma
model MissionUser {
id String @id @default(uuid())
role String // 'gardien-temps', 'gardien-parole', 'gardien-memoire', 'volontaire'
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
mission Mission @relation(fields: [missionId], references: [id])
missionId String
user User @relation(fields: [userId], references: [id])
userId String
@@unique([missionId, userId, role])
@@index([missionId])
@@index([userId])
}
```
### Modèle Attachment
```prisma
model Attachment {
id String @id @default(uuid())
filename String
filePath String // Path dans Minio: missions/{missionId}/attachments/{filename}
fileType String // MIME type
fileSize Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
mission Mission @relation(fields: [missionId], references: [id])
missionId String
uploader User @relation(fields: [uploaderId], references: [id])
uploaderId String
@@index([missionId])
@@index([uploaderId])
}
```
---
## 🔗 Intégrations Externes
### Service N8N (`lib/services/n8n-service.ts`)
**Webhook URL**: `https://brain.slm-lab.net/webhook/mission-created`
**Données envoyées**:
```typescript
{
name, oddScope, niveau, intention, missionType,
donneurDOrdre, projection, services, participation,
profils, guardians, volunteers, creatorId,
config: {
N8N_API_KEY,
MISSION_API_URL
}
}
```
**Workflow N8N déclenche**:
1. Création projet Leantime (si applicable)
2. Création collection Outline (si applicable)
3. Création canal RocketChat (si applicable)
4. Création repository Gitea (si applicable)
5. Création projet Penpot (si applicable)
**Retour**:
```typescript
{
success: boolean;
results?: {
leantimeProjectId?: string;
outlineCollectionId?: string;
rocketChatChannelId?: string;
giteaRepositoryUrl?: string;
penpotProjectId?: string;
failedServices?: {
gitRepo?: boolean;
leantimeProject?: boolean;
docCollection?: boolean;
rocketChatChannel?: boolean;
}
};
error?: string;
}
```
**Rollback** (non implémenté):
- Webhook: `https://brain.slm-lab.net/webhook/mission-rollback`
- Appelé lors de la suppression d'une mission
---
## 📦 Stockage de Fichiers
### Minio Configuration
**Endpoint**: `https://dome-api.slm-lab.net`
**Bucket**: `missions`
**Credentials**: Hardcodés dans `lib/mission-uploads.ts` (⚠️ à sécuriser)
### Structure des Chemins
**Logo**:
```
missions/{missionId}/logo{extension}
Exemple: missions/abc-123/logo.png
```
**Attachments**:
```
missions/{missionId}/attachments/{filename}
Exemple: missions/abc-123/attachments/document.pdf
```
### URLs Publiques
**Format**: `/api/missions/image/{path}`
**Exemples**:
- Logo: `/api/missions/image/missions/{missionId}/logo.png`
- Attachment: `/api/missions/image/missions/{missionId}/attachments/document.pdf`
### Fonctions Utilitaires (`lib/mission-uploads.ts`)
- `getMissionLogoPath()` - Génère le chemin du logo
- `getMissionAttachmentPath()` - Génère le chemin d'un attachment
- `uploadMissionLogo()` - Upload un logo vers Minio
- `uploadMissionAttachment()` - Upload un attachment vers Minio
- `deleteMissionLogo()` - Supprime un logo (TODO)
- `deleteMissionAttachment()` - Supprime un attachment
- `getMissionFileUrl()` - Construit l'URL publique
- `ensureMissionsPrefix()` - Normalise le chemin
---
## 🧩 Composants Réutilisables
### 1. `MissionsAdminPanel` (`components/missions/missions-admin-panel.tsx`)
**Fonctionnalités**:
- Formulaire multi-onglets
- Gestion des gardiens et volontaires
- Upload de fichiers
- Validation complète
- Soumission vers API
**Props**: Aucune (composant autonome)
**State**:
- `missionData` - Données de la mission
- `selectedServices` - Services sélectionnés
- `selectedProfils` - Profils sélectionnés
- `gardienDuTemps`, `gardienDeLaParole`, `gardienDeLaMemoire` - IDs des gardiens
- `volontaires` - Array d'IDs de volontaires
- `activeTab` - Onglet actif
- `isSubmitting` - État de soumission
### 2. `FileUpload` (`components/missions/file-upload.tsx`)
**Fonctionnalités**:
- Upload de logo ou attachment
- Conversion en base64
- Preview pour les images
**Props**:
- `type`: 'logo' | 'attachment'
- `isNewMission`: boolean
- `onFileSelect`: (fileData) => void
### 3. `AttachmentsList` (`components/missions/attachments-list.tsx`)
**Fonctionnalités**:
- Liste des attachments d'une mission
- Upload de nouveaux attachments
- Suppression d'attachments
**Props**:
- `missionId`: string
- `allowUpload`: boolean
- `allowDelete`: boolean
### 4. `MissionsFrame` (`components/missions/missions-frame.tsx`)
**Fonctionnalités**: Wrapper iframe (non utilisé actuellement)
---
## 🔐 Contrôles d'Accès
### Page Centrale (`/missions`)
- **Rôles requis**: `entrepreneurship` ou `admin`
- Vérifié dans `components/main-nav.tsx` via `hasRole()`
### API Routes
- **Authentification**: Session NextAuth requise
- **GET /api/missions**: Missions où user est créateur ou membre
- **GET /api/missions/all**: Toutes les missions (authentifié)
- **GET /api/missions/[id]**: Créateur ou membre
- **PUT /api/missions/[id]**: Créateur ou gardien-temps/gardien-parole
- **DELETE /api/missions/[id]**: Créateur ou admin uniquement
---
## 📊 Flux de Données Complet
### Création de Mission
```
[Frontend]
MissionsAdminPanel
↓ (soumission)
POST /api/missions
[Backend]
1. Validation
2. prisma.mission.create()
3. prisma.missionUser.createMany()
4. uploadMissionLogo() → Minio
5. uploadMissionAttachment() → Minio (pour chaque attachment)
6. prisma.attachment.create() (pour chaque attachment)
7. verifyFileExists() (vérification Minio)
8. n8nService.triggerMissionCreation()
[N8N Workflow]
- Création Leantime
- Création Outline
- Création RocketChat
- Création Gitea
- Création Penpot
[Backend]
9. Retour succès/erreur
[Frontend]
Redirection → /missions
```
### Consultation de Mission
```
[Frontend]
MissionsPage ou MissionDetailPage
fetch('/api/missions') ou fetch('/api/missions/[id]')
[Backend]
1. Vérification session
2. Query Prisma avec relations
3. Transformation des paths en URLs publiques
4. Retour JSON
[Frontend]
Affichage des données
```
### Affichage d'Image
```
[Frontend]
<img src="/api/missions/image/missions/{id}/logo.png" />
[Backend]
GET /api/missions/image/[...path]
Lecture depuis Minio
Stream vers client
```
---
## 🎨 Styles et UI
### Couleurs des Badges Niveau
- **A (Apprentissage)**: `bg-green-100 text-green-800`
- **B (Basique)**: `bg-blue-100 text-blue-800`
- **C (Complexe)**: `bg-purple-100 text-purple-800`
- **S (Spécial)**: `bg-amber-100 text-amber-800`
### Layout Sidebar CAP
- **Fond**: `bg-pink-50`
- **Bordure**: `border-pink-100`
- **Largeur**: `234px` fixe
### Grille de Missions
- **Mobile**: 1 colonne
- **Tablet**: 2 colonnes (`md:grid-cols-2`)
- **Desktop**: 3 colonnes (`lg:grid-cols-3`)
---
## 🐛 Points d'Attention
1. **Credentials Minio hardcodés** dans `lib/mission-uploads.ts` - À déplacer vers variables d'environnement
2. **Rollback N8N non implémenté** lors de la suppression
3. **Skills tab non fonctionnel** - Placeholders uniquement
4. **Presigned URLs non implémentées** - Upload direct uniquement
5. **Gestion d'erreurs N8N** - Partielle (continue même si certaines intégrations échouent)
---
## 📝 Notes Techniques
### Types TypeScript
**Mission Interface** (utilisée dans les pages):
```typescript
interface Mission {
id: string;
name: string;
logo?: string;
logoUrl?: string;
oddScope: string[];
niveau: string;
missionType: string;
projection: string;
participation?: string;
services?: string[];
profils?: string[];
intention?: string;
donneurDOrdre?: string;
createdAt: string;
creator: User;
missionUsers: MissionUser[];
attachments?: Attachment[];
}
```
### Validation
**Côté Frontend**: Validation dans `MissionsAdminPanel.validateMission()`
**Côté Backend**: Validation minimale (name et oddScope requis)
### Gestion d'Erreurs
- **Frontend**: Toast notifications via `useToast()`
- **Backend**: Retour JSON avec `error` et `details`
- **N8N**: Retour avec `success` et `failedServices` pour erreurs partielles
---
## 🔄 Évolutions Possibles
1. **Pagination** côté client pour les listes
2. **Filtres avancés** (par niveau, type, ODD, etc.)
3. **Recherche full-text** dans l'intention
4. **Export** des missions (PDF, CSV)
5. **Notifications** lors de l'assignation à une mission
6. **Statistiques** des missions
7. **Timeline** des activités d'une mission
8. **Commentaires** sur les missions
9. **États** des missions (brouillon, publiée, terminée, etc.)
10. **Permissions granulaires** par rôle de gardien
---
**Document généré le**: $(date)
**Version**: 1.0
**Auteur**: Analyse automatique du codebase

155
NGINX_HEADER_SIZE_FIX.md Normal file
View File

@ -0,0 +1,155 @@
# Fix Erreur 502 - Headers trop grands (Nginx)
## 🔍 Problème Identifié
**Erreur Nginx** :
```
upstream sent too big header while reading response header from upstream
```
**Cause** : Le cookie de session NextAuth est trop grand (> 4KB par défaut dans Nginx). Le JWT contient :
- `accessToken` (Keycloak) - ~1-2KB
- `refreshToken` (Keycloak) - ~1-2KB
- `idToken` (Keycloak) - ~1-2KB
- Données utilisateur (roles, etc.) - ~500B-1KB
- **Total** : ~4-7KB, ce qui dépasse la limite Nginx par défaut
## ✅ Solutions
### Solution 1 : Augmenter la limite Nginx (RECOMMANDÉ)
**Fichier** : Configuration Nginx (généralement `/etc/nginx/sites-available/hub.slm-lab.net` ou similaire)
**Ajouter dans le bloc `server` ou `location`** :
```nginx
server {
# ... autres configs ...
# Augmenter la taille maximale des headers
proxy_buffer_size 16k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
large_client_header_buffers 4 32k;
# Spécifiquement pour les headers de réponse
proxy_headers_hash_max_size 512;
proxy_headers_hash_bucket_size 128;
# ... reste de la config ...
}
```
**OU** pour une solution plus simple, ajouter seulement :
```nginx
server {
# ... autres configs ...
# Augmenter la limite des headers
large_client_header_buffers 4 32k;
# ... reste de la config ...
}
```
**Puis redémarrer Nginx** :
```bash
sudo nginx -t # Vérifier la config
sudo systemctl reload nginx # Ou sudo service nginx reload
```
### Solution 2 : Réduire la taille du JWT (ALTERNATIVE)
Si on ne peut pas modifier Nginx, on peut réduire la taille du JWT en ne stockant pas tous les tokens.
**Modification** : `app/api/auth/options.ts`
**Option A** : Ne pas stocker `idToken` dans le JWT (si pas nécessaire)
```typescript
// Dans JWT callback
token.idToken = account.id_token ?? ''; // ❌ Supprimer cette ligne
```
**Option B** : Stocker seulement les tokens nécessaires
```typescript
// Stocker seulement accessToken et refreshToken
// idToken peut être récupéré depuis Keycloak si nécessaire
```
**Note** : Cette solution réduit la fonctionnalité. La Solution 1 est préférable.
## 🔧 Configuration Nginx Complète Recommandée
```nginx
server {
listen 443 ssl http2;
server_name hub.slm-lab.net;
# ... SSL config ...
# Augmenter les limites pour les gros headers NextAuth
proxy_buffer_size 16k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
large_client_header_buffers 4 32k;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
location / {
proxy_pass http://172.16.0.102:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
# Headers pour NextAuth
proxy_set_header Cookie $http_cookie;
}
}
```
## 📊 Vérification
**Après modification Nginx** :
1. **Tester la config** :
```bash
sudo nginx -t
```
2. **Recharger Nginx** :
```bash
sudo systemctl reload nginx
```
3. **Tester la connexion** :
- Se connecter via Keycloak
- Vérifier que l'erreur 502 ne se produit plus
- Vérifier les logs Nginx pour confirmer
## 🎯 Cause Technique
NextAuth crée un cookie JWT qui contient :
- Le JWT encrypté avec `NEXTAUTH_SECRET`
- Le JWT contient tous les tokens Keycloak
- La taille totale peut dépasser 4KB
Nginx a une limite par défaut de 4KB pour les headers de réponse. Quand Next.js essaie de renvoyer un cookie > 4KB, Nginx rejette avec "upstream sent too big header".
## ✅ Solution Immédiate
**Action** : Modifier la configuration Nginx pour augmenter `large_client_header_buffers` à au moins `4 32k` ou `8 16k`.
**Impact** : Résout immédiatement l'erreur 502.
---
**Document créé le** : $(date)
**Priorité** : HAUTE - C'est la cause de l'erreur 502

View File

@ -85,11 +85,20 @@ export async function GET(request: Request) {
// Get total count
const totalCount = await prisma.mission.count({ where });
// Transform logo paths to public URLs
const missionsWithPublicUrls = missions.map(mission => ({
...mission,
logo: mission.logo ? `/api/missions/image/${mission.logo}` : null
}));
// Transform missions to include public URLs (same format as /api/missions)
const missionsWithPublicUrls = missions.map(mission => {
console.log('Processing mission logo:', {
missionId: mission.id,
logo: mission.logo,
constructedUrl: mission.logo ? `/api/missions/image/${mission.logo}` : null
});
return {
...mission,
logoUrl: mission.logo ? `/api/missions/image/${mission.logo}` : null,
logo: mission.logo, // Keep original logo path
};
});
return NextResponse.json({
missions: missionsWithPublicUrls,

View File

@ -0,0 +1,136 @@
<%
if (s.forceSsl) {
%>
server {
listen 80;
server_name <%-s.publicDomain%>;
# Used by Lets Encrypt
location /.well-known/acme-challenge/ {
root <%-s.staticWebRoot%>;
}
# Used by CapRover for health check
location /.well-known/captain-identifier {
root <%-s.staticWebRoot%>;
}
location / {
return 302 https://$http_host$request_uri;
}
}
<%
}
%>
server {
<%
if (!s.forceSsl) {
%>
listen 80;
<%
}
if (s.hasSsl) {
%>
listen 443 ssl;
http2 on;
ssl_certificate <%-s.crtPath%>;
ssl_certificate_key <%-s.keyPath%>;
<%
}
if (s.logAccessPath) {
%>
access_log <%-s.logAccessPath%>;
<%
}
%>
client_max_body_size 500m;
server_name <%-s.publicDomain%>;
# 127.0.0.11 is DNS set up by Docker, see:
# https://docs.docker.com/engine/userguide/networking/configure-dns/
# https://github.com/moby/moby/issues/20026
resolver 127.0.0.11 valid=10s;
# IMPORTANT!! If you are here from an old thread to set a custom port, you do not need to modify this port manually here!!
# Simply change the Container HTTP Port from the dashboard HTTP panel
set $upstream http://172.16.0.102:3000;
location / {
<%
if (s.redirectToPath) {
%>
return 302 <%-s.redirectToPath%>$request_uri;
<%
} else {
%>
<%
if (s.httpBasicAuthPath) {
%>
auth_basic "Restricted Access";
auth_basic_user_file <%-s.httpBasicAuthPath%>;
<%
}
%>
# ============================================
# FIX: Augmenter la limite des headers pour NextAuth
# Résout l'erreur "upstream sent too big header"
# ============================================
proxy_buffer_size 16k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
large_client_header_buffers 4 32k;
# Timeouts pour éviter les timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_pass $upstream;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
<%
if (s.websocketSupport) {
%>
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
<%
}
%>
<%
}
%>
}
# Used by Lets Encrypt
location /.well-known/acme-challenge/ {
root <%-s.staticWebRoot%>;
}
# Used by CapRover for health check
location /.well-known/captain-identifier {
root <%-s.staticWebRoot%>;
}
error_page 502 /captain_502_custom_error_page.html;
location = /captain_502_custom_error_page.html {
root <%-s.customErrorPagesDirectory%>;
internal;
}
}

32
nginx-config-fix.conf Normal file
View File

@ -0,0 +1,32 @@
# Configuration Nginx pour corriger l'erreur "upstream sent too big header"
# À ajouter dans votre configuration Nginx pour hub.slm-lab.net
server {
# ... votre config existante ...
# ============================================
# FIX: Augmenter la limite des headers
# ============================================
# Ces directives augmentent la taille maximale des headers
# pour permettre les gros cookies NextAuth (JWT avec tokens Keycloak)
# Taille des buffers pour les headers
proxy_buffer_size 16k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
# Limite pour les gros headers clients (et réponses)
large_client_header_buffers 4 32k;
# Hash tables pour les headers (optionnel mais recommandé)
proxy_headers_hash_max_size 512;
proxy_headers_hash_bucket_size 128;
# Timeouts (pour éviter les timeouts pendant le traitement)
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# ... reste de votre config ...
}