# API Documentation - Campus Safe API

## 1) Vue d'ensemble

- Préfixe API: `/api/v1`
- Healthcheck: `GET /health`
- OpenAPI JSON: `/openapi.json` (fallback: `/api/v1/openapi.json`)
- Swagger UI: `/api/v1/docs`
- ReDoc: `/api/v1/redoc`

## 2) Authentification

La majorité des endpoints demandent un Bearer token.

En-tête attendu :

```http
Authorization: Bearer <access_token>
```

Le token est obtenu via `POST /api/v1/auth/login`.

## 3) Format des réponses

### 3.1 Réponse standard (succès)

```json
{
  "data": {},
  "message": "OK",
  "statusCode": 200,
  "timestamp": "2026-03-19T10:00:00Z"
}
```

### 3.2 Réponse paginée

```json
{
  "data": {
    "content": [],
    "number": 0,
    "size": 20,
    "totalElements": 125,
    "totalPages": 7,
    "first": true,
    "last": false
  },
  "message": "OK",
  "statusCode": 200,
  "timestamp": "2026-03-19T10:00:00Z"
}
```

### 3.3 Réponse d'erreur

```json
{
  "error": "Unauthorized",
  "message": "Token invalide ou expire",
  "statusCode": 401,
  "timestamp": "2026-03-19T10:00:00Z",
  "path": "/api/v1/signalements/me"
}
```

## 4) Rôles utilisés dans l'API

- `UTILISATEUR`
- `GESTIONNAIRE`
- `ADMIN_SYSTEME`

Règles utiles :

- Gestionnaire+ = `GESTIONNAIRE | ADMIN_SYSTEME`
- Admin only = `ADMIN_SYSTEME`
- Compatibilité legacy: si une base contient encore `SUPERVISEUR` ou `MODERATEUR`,
  elles doivent être migrées vers `ADMIN_SYSTEME` et `GESTIONNAIRE`.

## 5) Endpoints REST

Tous les chemins ci-dessous sont relatifs à `/api/v1`.

---

### 5.1 Auth (`/auth`)

| Méthode | Path | Auth | Body | Réponse data |
|---|---|---|---|---|
| POST | `/auth/signup` | Non | `SignupRequest` | Compte créé |
| POST | `/auth/login` | Non | `LoginRequest` | Tokens + session |
| POST | `/auth/logout` | Oui | - | `null` |
| POST | `/auth/refresh` | Non | `RefreshRequest` | Nouveaux tokens |
| GET | `/auth/validate` | Oui | - | `{ valid, email, typeUtilisateur }` |
| POST | `/auth/forgot-password` | Non | `{ email }` | `null` |

Schémas principaux :

`SignupRequest`

```json
{
  "email": "user@exemple.com",
  "motDePasse": "secret",
  "nom": "Dupont",
  "prenom": "Aminata",
  "bureauGenreId": "BG-001"
}
```

`LoginRequest`

```json
{
  "email": "user@exemple.com",
  "motDePasse": "secret",
  "appareil": "iPhone 15",
  "localisation": "Campus Nord"
}
```

`LoginResponse` (dans `data`)

```json
{
  "token": "...",
  "refreshToken": "...",
  "sessionId": "uuid",
  "email": "user@exemple.com",
  "typeUtilisateur": "UTILISATEUR",
  "expiresIn": 3600
}
```

---

### 5.2 Comptes (`/comptes`)

| Méthode | Path | Auth | Rôle minimal | Body | Réponse data |
|---|---|---|---|---|---|
| GET | `/comptes/me` | Oui | Utilisateur connecté | - | Profil courant |
| PUT | `/comptes/me` | Oui | Utilisateur connecté | `CompteUpdate` | Profil mis à jour |
| PUT | `/comptes/me/password` | Oui | Utilisateur connecté | `ChangePasswordRequest` | `null` |
| POST | `/comptes/` | Oui | Admin | `CompteCreate` | Compte créé |
| GET | `/comptes/` | Oui | Gestionnaire+ | Query `page,size` | Liste paginée |
| GET | `/comptes/{compte_id}` | Oui | Utilisateur connecté | - | Compte |
| PUT | `/comptes/{compte_id}` | Oui | Utilisateur connecté | `CompteUpdate` | Compte mis à jour |
| PUT | `/comptes/{compte_id}/reset-password` | Oui | Admin | `ResetPasswordAdminRequest` | `null` |
| PUT | `/comptes/{compte_id}/deactivate` | Oui | Admin | - | `null` |
| PUT | `/comptes/{compte_id}/reactivate` | Oui | Admin | - | `null` |
| POST | `/comptes/invitations` | Oui | Admin | `{ email, typeUtilisateur }` | Invitation |
| GET | `/comptes/invitations` | Oui | Admin | Query `page,size,statut` | Invitations paginées |

---

### 5.3 Signalements (`/signalements`)

| Méthode | Path | Auth | Rôle minimal | Body | Réponse data |
|---|---|---|---|---|---|
| POST | `/signalements/` | JWT **ou** Guest | Utilisateur connecté / invité | `SignalementCreate` | Signalement |
| GET | `/signalements/me` | Oui | Utilisateur connecté | Query `page,size` | Mes signalements paginés |
| GET | `/signalements/guest/me` | Guest (`X-Guest-Id`) | Invité | Query `page,size` | Mes signalements invités paginés |
| GET | `/signalements/non-traites` | Oui | Gestionnaire+ | Query `page,size` | Liste paginée |
| GET | `/signalements/{signalement_id}` | JWT **ou** Guest | Participant autorisé | - | Signalement détail |
| PUT | `/signalements/{signalement_id}/statut` | Oui | Gestionnaire+ | `{ statut }` | Signalement |
| PUT | `/signalements/{signalement_id}/assigner` | Oui | Gestionnaire+ | `{ gestionnaireId }` | Signalement |
| POST | `/signalements/{signalement_id}/notes` | Oui | Gestionnaire+ | `{ contenu }` | Note interne |
| GET | `/signalements/{signalement_id}/timeline` | JWT **ou** Guest | Participant autorisé | - | Timeline[] |

Champs additionnels dans les réponses de liste/détail :

- `hasUnreadUpdates`: booléen indiquant s'il reste des messages non lus sur le dossier.
- `messagesNonLusCount`: compteur exact des messages non lus sur le dossier.
- `sourceActeur`: `AUTH_USER` ou `GUEST`.
- `acteurId`: identifiant de l'acteur source (userId ou guestId).
- `estFusionne` (optionnel): indique si la donnée guest est déjà rattachée à un compte.

`SignalementCreate` (exemple)

```json
{
  "titre": "Harcèlement verbal",
  "description": "Description détaillée...",
  "type": "HARCELEMENT",
  "modeIdentification": "ANONYME",
  "dateIncident": "2026-03-18T14:30:00Z",
  "lieuIncident": "Bâtiment A",
  "auteurPresume": "Nom ou description",
  "aTemoins": true,
  "descriptionTemoins": "2 personnes",
  "bureauGenreId": "BG-001"
}
```

---

### 5.4 Discussions et messages (`/discussions`)

| Méthode | Path | Auth | Body | Réponse data |
|---|---|---|---|---|
| POST | `/discussions/` | JWT **ou** Guest | `DiscussionCreate` | Discussion |
| GET | `/discussions/` | Oui | Query `page,size` | Discussions paginées |
| GET | `/discussions/guest/me` | Guest (`X-Guest-Id`) | Query `page,size` | Discussions invité paginées |
| GET | `/discussions/{discussion_id}` | JWT **ou** Guest | - | Discussion |
| POST | `/discussions/{discussion_id}/messages` | JWT **ou** Guest | `MessageCreate` | Message |
| GET | `/discussions/{discussion_id}/messages` | JWT **ou** Guest | Query `page,size` | Messages paginés |
| PUT | `/discussions/{discussion_id}/messages/lus` | JWT **ou** Guest | - | `null` |

`DiscussionCreate`

```json
{
  "titre": "Conversation avec support",
  "signalementId": "uuid-optionnel"
}
```

`MessageCreate`

```json
{
  "discussionId": "uuid",
  "contenu": "Bonjour",
  "type": "USER",
  "piecesJointes": ["file-id-1", "file-id-2"]
}
```

Réponses discussion/message incluent aussi:

- `sourceActeur`
- `acteurId`
- `estFusionne` (optionnel)

---

### 5.5 Sessions invitées, fusion et suivi (`/guest`)

Headers utiles:

```http
X-Guest-Id: <guest_uuid>
Idempotency-Key: <unique_key_for_merge>
```

| Méthode | Path | Auth | Body/Query | Réponse data |
|---|---|---|---|---|
| POST | `/guest/sessions` | Non | `{ telephone, deviceFingerprint? }` | Session invitée |
| GET | `/guest/sessions/{guestId}` | Non | - | Session invitée |
| PATCH | `/guest/sessions/{guestId}/telephone` | Non | `{ telephone }` | Session invitée |
| GET | `/guest/sessions/{guestId}/merge-preview` | JWT optionnel | Query `userId` optionnel si JWT | Aperçu fusion + conflits |
| POST | `/guest/sessions/{guestId}/merge` | Oui | Header `Idempotency-Key`, body `GuestMergeRequest` | Résultat de fusion |
| GET | `/guest/suivi` | Guest (`X-Guest-Id`) | - | Suivi agrégé invité |
| GET | `/guest/suivi/{reference}` | Guest (`X-Guest-Id`) | - | Détail suivi invité |

`GuestMergeRequest`

```json
{
  "acceptPolicy": "MERGE_ALL",
  "resolutions": [
    {
      "level": "SIGNALEMENT",
      "guestEntityId": "uuid",
      "targetEntityId": "uuid",
      "action": "MERGE_FIELDS"
    }
  ]
}
```

### 5.5.1 Gestion de conflit de fusion (2 niveaux)

Niveau 1: `SIGNALEMENT`

- Détection principale: même `reference`.
- Détection secondaire: empreinte métier (`type`, `dateIncident`, `lieuIncident`, description normalisée).
- Actions: `KEEP_TARGET`, `KEEP_GUEST`, `MERGE_FIELDS`, `SKIP`.

Niveau 2: `DISCUSSION`

- Détection: discussion liée au même signalement, même référence de signalement, ou titre normalisé identique.
- Messages: déduplication par signature (auteur, contenu normalisé, type, pièces jointes).
- Consolidation: fusion vers discussion cible selon action, renumérotation de `numeroSequence`, recalcul résumé discussion.

### 5.5.2 Idempotence

- `POST /guest/sessions/{guestId}/merge` exige `Idempotency-Key`.
- Rejeu de la même clé: réponse `200` avec le résultat précédemment persisté.

### 5.5.3 Codes d'erreur guest

- `GUEST_SESSION_NOT_FOUND`
- `GUEST_SESSION_INVALID`
- `GUEST_SESSION_MERGED`
- `GUEST_ACCESS_DENIED`
- `GUEST_MERGE_CONFLICT`
- `GUEST_MERGE_IDEMPOTENT_REPLAY`
- `IDEMPOTENCY_KEY_REQUIRED`

---

### 5.6 Calendrier (`/calendriers`)

| Méthode | Path | Auth | Rôle minimal | Body | Réponse data |
|---|---|---|---|---|---|
| POST | `/calendriers/` | Oui | Gestionnaire+ | `CalendrierCreate` | Événement |
| GET | `/calendriers/` | Oui | Utilisateur connecté | Query `page,size` | Liste paginée |
| GET | `/calendriers/date/{target_date}` | Oui | Utilisateur connecté | - | Événements[] |
| GET | `/calendriers/range?debut=YYYY-MM-DD&fin=YYYY-MM-DD` | Oui | Utilisateur connecté | - | Événements[] |
| GET | `/calendriers/{evenement_id}` | Oui | Utilisateur connecté | - | Événement |
| PUT | `/calendriers/{evenement_id}` | Oui | Gestionnaire+ | `CalendrierUpdate` | Événement |
| DELETE | `/calendriers/{evenement_id}` | Oui | Gestionnaire+ | - | (204 no content) |
| POST | `/calendriers/{evenement_id}/inscription` | Oui | Utilisateur connecté | - | Confirmation |
| DELETE | `/calendriers/{evenement_id}/inscription` | Oui | Utilisateur connecté | - | (204 no content) |
| GET | `/calendriers/{evenement_id}/participants` | Oui | Gestionnaire+ | Query `page,size` | Participants paginés |
| PUT | `/calendriers/participants/{participant_id}/statut` | Oui | Gestionnaire+ | `{ statut }` | Participant |

`CalendrierCreate` (exemple)

```json
{
  "titre": "Atelier prevention",
  "description": "Sensibilisation",
  "type": "ATELIER",
  "dateDebut": "2026-04-01T09:00:00Z",
  "dateFin": "2026-04-01T12:00:00Z",
  "lieu": "Salle B12",
  "estEnLigne": false,
  "lienEnLigne": null,
  "nbMaxParticipants": 80,
  "imageUrl": "https://...",
  "tags": ["prevention", "campus"],
  "publicCible": ["ETUDIANT", "PERSONNEL"]
}
```

---

### 5.7 Postes, commentaires, reactions (`/postes`)

| Méthode | Path | Auth | Rôle minimal | Body | Réponse data |
|---|---|---|---|---|---|
| POST | `/postes/` | Oui | Utilisateur connecté | `PosteCreate` | Poste |
| GET | `/postes/` | Oui | Utilisateur connecté | Query `page,size` | Liste paginée |
| GET | `/postes/approuves` | Oui | Utilisateur connecté | Query `page,size` | Liste paginée des postes approuvés |
| GET | `/postes/{poste_id}` | Oui | Utilisateur connecté | - | Poste |
| PUT | `/postes/{poste_id}/epingle` | Oui | Gestionnaire+ | - | Poste |
| DELETE | `/postes/{poste_id}/epingle` | Oui | Gestionnaire+ | - | Poste |
| DELETE | `/postes/{poste_id}` | Oui | Auteur ou admin | - | (204 no content) |
| PATCH | `/postes/{poste_id}/statut` | Oui | Gestionnaire+ | `{ statut }` | Poste |
| POST | `/postes/{poste_id}/commentaires` | Oui | Utilisateur connecté | `{ contenu }` | Commentaire |
| GET | `/postes/{poste_id}/commentaires` | Oui | Utilisateur connecté | Query `page,size` | Liste paginée |
| DELETE | `/postes/{poste_id}/commentaires/{commentaire_id}` | Oui | Auteur ou admin | - | (204 no content) |
| POST | `/postes/{poste_id}/reactions` | Oui | Utilisateur connecté | `{ type }` | Réaction |
| GET | `/postes/{poste_id}/reactions` | Oui | Utilisateur connecté | - | Réactions[] |
| DELETE | `/postes/{poste_id}/reactions/{reaction_id}` | Oui | Auteur ou admin | - | (204 no content) |

Statuts de modération des postes (backend actuel) :

- `NOUVEAU`
- `APPROUVER_IA`
- `REJETER_IA`
- `APPROUVER`
- `REJETER`
- `ARCHIVER`

Payload `PATCH /postes/{poste_id}/statut` :

```json
{
  "statut": "APPROUVER"
}
```

Notes:

- `GET /postes/approuves` retourne les postes dont le statut est `APPROUVER` ou `APPROUVER_IA`.
- Quand le statut devient `APPROUVER`, le backend renseigne `approuverParId` avec l'ID du modérateur.

---

### 5.8 Préférences (`/preferences`)

| Méthode | Path | Auth | Body | Réponse data |
|---|---|---|---|---|
| GET | `/preferences/` | Oui | - | Préférences utilisateur |
| PUT | `/preferences/` | Oui | `PreferenceUpdate` | Préférences mises à jour |

`PreferenceUpdate` (exemple)

```json
{
  "notificationsActivees": true,
  "notificationsEmail": true,
  "notificationsPush": false,
  "langue": "fr",
  "theme": "light",
  "parametres": {
    "digestHebdo": true
  }
}
```

---

### 5.9 Rôles (`/roles`)

| Méthode | Path | Auth | Rôle minimal | Réponse data |
|---|---|---|---|---|
| GET | `/roles/` | Oui | Admin | Rôles[] |
| GET | `/roles/{nom}/permissions` | Oui | Admin | `{ role, permissions }` |

---

### 5.10 Statistiques (`/stats`)

| Méthode | Path | Auth | Rôle minimal | Réponse data |
|---|---|---|---|---|
| GET | `/stats/dashboard` | Oui | Utilisateur connecté | Dashboard stats (global pour gestionnaire+, personnel pour utilisateur) |
| GET | `/stats/signalements` | Oui | Utilisateur connecté | Rapport des signalements (global pour gestionnaire+, personnel pour utilisateur) |
| POST | `/stats/signalements/export` | Oui | Gestionnaire+ | `{ exportId, exportedAt, downloadUrl }` |
| GET | `/stats/dashboard/team` | Oui | Gestionnaire+ | Statistiques équipe |
| GET | `/stats/dashboard/alerts` | Oui | Utilisateur connecté | Alertes (global pour gestionnaire+, personnelles pour utilisateur) |
| GET | `/stats/dashboard/cases/recent` | Oui | Utilisateur connecté | Dossiers récents (global pour gestionnaire+, personnels pour utilisateur) |
| GET | `/stats/dashboard/distribution/categories` | Oui | Gestionnaire+ | Distribution par catégorie |
| GET | `/stats/dashboard/trends/cases` | Oui | Gestionnaire+ | Tendances de création |
| GET | `/stats/dashboard/trends/resolution` | Oui | Gestionnaire+ | Tendances de résolution |
| GET | `/stats/dashboard/quick-actions` | Oui | Gestionnaire+ | Actions rapides |
| PUT | `/stats/dashboard/alerts/{alert_id}/dismiss` | Oui | Gestionnaire+ | `{ id, dismissed }` |
| GET | `/stats/dashboard/invitations/pending` | Oui | Admin | `{ invitationsEnAttente, pendingInvitations }` |

Notes:

- Les endpoints `/stats/dashboard`, `/stats/signalements`, `/stats/dashboard/alerts` et `/stats/dashboard/cases/recent` sont accessibles à tout utilisateur authentifié.
- Pour un `UTILISATEUR`, les données sont automatiquement filtrées sur ses propres signalements.
- Pour `GESTIONNAIRE` et `ADMIN_SYSTEME`, les données retournées restent globales.
- `PUT /stats/dashboard/alerts/{alert_id}/dismiss` transforme une alerte issue d'un signalement (`NOUVEAU` -> `ASSIGNE`, `EN_ATTENTE` -> `EN_COURS`).
- `GET /stats/dashboard/invitations/pending` est réservé à `ADMIN_SYSTEME`.

---

### 5.10 Aide (`/aide`)

| Méthode | Path | Auth | Réponse data |
|---|---|---|---|
| GET | `/aide/faqs` | Oui | FAQ[] publiées |
| GET | `/aide/contacts-urgence` | Oui | Contacts urgence[] actifs |

---

### 5.11 Fichiers (`/fichiers`)

| Méthode | Path | Auth | Body | Réponse data |
|---|---|---|---|---|
| POST | `/fichiers/upload` | Oui | `multipart/form-data` champ `file` | Meta fichier |
| GET | `/fichiers/{file_id}` | Oui | - | `{ url, fileId }` |
| DELETE | `/fichiers/{file_id}` | Oui | - | (204 no content) |

Contraintes d'upload :

- Taille max: `10 MB` (paramétrable via `max_file_size_mb`)
- Erreur attendue si dépassement: `413`

---

### 5.12 Ressources (`/ressources`)

| Méthode | Path | Auth | Body | Réponse data |
|---|---|---|---|---|
| POST | `/ressources/upload` | Oui | `multipart/form-data` (`file`) + optionnels `categorie,titre,description` | Upload + metadata ressource |
| POST | `/ressources/` | Oui | `RessourceCreate` | Ressource metadata |
| GET | `/ressources/` | Oui | Query `page,size,categorie,search,sort,order` | Ressources paginées |
| GET | `/ressources/{resource_id}` | Oui | - | Ressource |
| DELETE | `/ressources/{resource_id}` | Oui | - | `{ id, deleted }` |

`RessourceCreate` (exemple)

```json
{
  "titre": "Guide de prévention",
  "description": "Version PDF",
  "categorie": "DOCUMENT",
  "bucketId": "resources",
  "fileId": "file_01JXYZ...",
  "publicUrl": "https://storage.exemple.com/view/file_01JXYZ...",
  "downloadUrl": "https://storage.exemple.com/download/file_01JXYZ...",
  "fileName": "guide-prevention.pdf",
  "mimeType": "application/pdf",
  "sizeBytes": 245760
}
```

Réponse upload (exemple)

```json
{
  "resourceId": "uuid",
  "fileId": "file_01JXYZ...",
  "bucketId": "resources",
  "fileName": "guide-prevention.pdf",
  "mimeType": "application/pdf",
  "sizeBytes": 245760,
  "publicUrl": "https://storage.exemple.com/view/file_01JXYZ...",
  "downloadUrl": "https://storage.exemple.com/download/file_01JXYZ...",
  "createdAt": "2026-03-25T09:00:00Z"
}
```

Contraintes ressources :

- Taille max: `20 MB` (paramétrable via `resource_max_file_size_mb`)
- MIME autorisés par défaut: `application/pdf`, `image/jpeg`, `image/png`, `image/webp`, `video/mp4`
- Si `appwrite_enabled=false`, l'upload passe par le file-server de fallback backend.

## 6) Realtime (WebSocket)

### 6.1 Endpoint de connexion

```text
ws://<host>/api/v1/ws/{canal}?token=<access_token>
```

Exemple:

```text
ws://localhost:8000/api/v1/ws/signalements?token=eyJ...
```

Handshake:

- Si token invalide: fermeture socket `code=4001`
- Si accès canal refusé: fermeture socket `code=4003`
- Si connexion OK: événement `connected` envoyé immédiatement

Événement initial :

```json
{
  "type": "connected",
  "canal": "signalements",
  "payload": {
    "userId": "uuid",
    "canal": "signalements"
  },
  "timestamp": "2026-03-19T10:00:00Z"
}
```

Keepalive simple:

- Client envoie: `ping`
- Serveur répond: `pong`

### 6.2 Canaux disponibles

| Canal | Rôle requis | Utilisation |
|---|---|---|
| `admin` | `ADMIN_SYSTEME` | Alertes système et events admin |
| `signalements` | Gestionnaire+ | Flux global des signalements |
| `signalement:{signalementId}` | Utilisateur authentifie | Mises a jour d'un signalement |
| `discussion:{discussionId}` | Utilisateur authentifie | Nouveaux messages / lecture |
| `poste:{posteId}` | Utilisateur authentifie | Commentaires/reactions d'un poste |
| `calendriers` | Utilisateur authentifié | Mises à jour d'événements |
| `dashboard` | Gestionnaire+ | Mise à jour du dashboard |
| `user:{userId}` | Utilisateur authentifié | Notifications personnelles |

Note importante:

- L'événement `discussion_created` est actuellement diffusé sur le canal `discussions`.
- Le canal `discussions` n'est pas autorisé dans la vérification d'accès websocket actuelle.
- Conséquence front: ne pas dépendre de `discussions` en l'état sans correctif backend.

### 6.3 Structure d'un événement WS

```json
{
  "type": "message_created",
  "canal": "discussion:9e4f...",
  "payload": {},
  "timestamp": "2026-03-19T10:00:00Z"
}
```

Champs:

- `type`: type d'événement
- `canal`: canal de diffusion
- `payload`: données métier
- `timestamp`: date UTC ISO8601

### 6.4 Types d'événements émis

#### Connexion

- `connected`
- `error` (enum présent, émission non visible dans les services actuels)

#### Signalements

- `signalement_created` (canal `signalements`)
- `signalement_statut_updated` (canaux `signalement:{id}` et `signalements`)
- `signalement_assigned` (canaux `signalement:{id}` et `signalements`)
- `signalement_note_added` (canal `signalement:{id}`)
- `signalement_timeline_updated` (enum présent, émission non visible actuellement)

#### Discussions / messages

- `discussion_created` (canal `discussions`)
- `message_created` (canal `discussion:{id}`)
- `message_read` (canal `discussion:{id}`)

#### Forum

- `poste_created` (enum présent, émission non visible actuellement)
- `commentaire_created` (canal `poste:{id}`)
- `reaction_created` (canal `poste:{id}`)
- `reaction_removed` (canal `poste:{id}`)

#### Calendrier

- `event_created` (canal `calendriers`)
- `event_updated` (canal `calendriers`)
- `event_cancelled` (canal `calendriers`)

#### Dashboard / admin

- `stats_updated` (canal `dashboard`, helper présent)
- `alert_created` (enum présent, émission non visible actuellement)
- `user_deactivated` (helper present, canal `admin` + notif user)
- `system_alert` (helper present, canal `admin`)

Déclenchements `stats_updated` déjà observés côté services :

- après `PUT /signalements/{signalement_id}/statut`
- après `PATCH /postes/{poste_id}/statut`
- après `PUT /stats/dashboard/alerts/{alert_id}/dismiss`

### 6.5 Payloads WS observés

Exemples concrets de payload:

- `message_created`: objet Message (id, discussionId, contenu, auteurId, auteurNom, type, estLu, numeroSequence, piecesJointes, dateCreation)
- `message_read`: `{ "messageId": "uuid", "estLu": true }`
- `signalement_created`: objet Signalement
- `signalement_statut_updated`: objet Signalement (après update)
- `signalement_assigned`: objet Signalement (après assignation)
- `signalement_note_added`: `{ "id", "auteurId", "contenu", "dateCreation" }`
- `commentaire_created`: objet Commentaire
- `reaction_created`: objet Reaction
- `reaction_removed`: `{ "reactionId": "uuid" }`
- `event_created` / `event_updated`: objet Calendrier
- `event_cancelled`: `{ "id": "evenement_id" }`

## 7) Recommandations front

- Centraliser la gestion du token access + refresh.
- Traiter toutes les réponses selon le même envelope (`data`, `message`, `statusCode`, `timestamp`).
- Sur websocket, reconnecter automatiquement avec backoff exponentiel.
- Ouvrir les sockets par vue métier (ex: détail signalement => `signalement:{id}`).
- Ignorer proprement les types WS inconnus pour rester compatible avec de futurs events.

## 8) Checklist intégration rapide

1. Implémenter login + stockage sécurisé des tokens.
2. Ajouter interceptor HTTP pour `Authorization`.
3. Mapper les DTOs principaux (Signalement, Discussion, Message, Calendrier, Poste).
4. Intégrer la pagination uniforme (`data.content`, `data.totalElements`, etc.).
5. Connecter websocket par canal selon écran.
6. Implémenter la gestion des erreurs standards (`error`, `message`, `statusCode`).

## 9) Compléments pratiques (endpoints exposés)

Les endpoints ci-dessous sont déjà exposés et utilisés pour l'intégration admin/export.

### 9.1 Participants événement (admin)

| Méthode | Path | Auth | Rôle minimal | Body | Réponse data |
|---|---|---|---|---|---|
| GET | `/calendriers/{evenement_id}/participants` | Oui | Gestionnaire+ | - | Participants paginés |
| PUT | `/calendriers/participants/{participant_id}/statut` | Oui | Gestionnaire+ | `{ statut }` | Participant mis à jour |

Payload de mise à jour statut :

```json
{
  "statut": "CONFIRMED"
}
```

Réponse attendue (exemple) :

```json
{
  "id": "participant-id",
  "eventId": "event-id",
  "userId": "user-id",
  "statut": "CONFIRMED",
  "dateInscription": "2026-03-21T10:00:00Z",
  "dateModification": "2026-03-21T10:10:00Z"
}
```

Statuts participants recommandés :

- `PENDING`
- `CONFIRMED`
- `CANCELLED`
- `WAITLISTED`

### 9.2 Export stats dédié

| Méthode | Path | Auth | Rôle minimal | Body | Réponse data |
|---|---|---|---|---|---|
| POST | `/stats/signalements/export` | Oui | Gestionnaire+ | Paramètres d'export (optionnels) | `{ exportId, exportedAt, downloadUrl }` |

Exemple de réponse :

```json
{
  "exportId": "exp_01JXYZ...",
  "exportedAt": "2026-03-21T10:15:00Z",
  "downloadUrl": "https://api.exemple.com/downloads/exp_01JXYZ.csv"
}
```

### 9.3 Invitation équipe dédiée

| Méthode | Path | Auth | Rôle minimal | Body | Réponse data |
|---|---|---|---|---|---|
| POST | `/comptes/invitations` | Oui | Admin | `{ email, typeUtilisateur }` | Statut invitation + expiration |

Exemple body:

```json
{
  "email": "nouvel.agent@campus.org",
  "typeUtilisateur": "GESTIONNAIRE"
}
```

Exemple reponse:

```json
{
  "invitationId": "inv_01JXYZ...",
  "email": "nouvel.agent@campus.org",
  "typeUtilisateur": "GESTIONNAIRE",
  "statut": "PENDING",
  "expireLe": "2026-03-28T10:15:00Z"
}
```

### 9.4 Liste des invitations (admin)

| Méthode | Path | Auth | Rôle minimal | Query | Réponse data |
|---|---|---|---|---|---|
| GET | `/comptes/invitations` | Oui | Admin | `page,size,statut` | Invitations paginées |

Notes:

- Le filtre `statut` est optionnel (exemple: `PENDING`).
- Le format paginé suit `data.items`, `data.total`, `data.page`, `data.size`, `data.pages`.

### 9.5 Pending invitations dashboard (admin)

| Méthode | Path | Auth | Rôle minimal | Réponse data |
|---|---|---|---|---|
| GET | `/stats/dashboard/invitations/pending` | Oui | Admin | `{ invitationsEnAttente, pendingInvitations }` |

Note:

- Le compteur inclut les invitations `PENDING` non expirées.

## 10) Pagination (état actuel)

Format majoritaire des endpoints paginés :

```json
{
  "data": {
    "items": [],
    "total": 125,
    "page": 0,
    "size": 20,
    "pages": 7
  },
  "message": "OK",
  "statusCode": 200,
  "timestamp": "2026-03-21T10:00:00Z"
}
```

Règle recommandée côté front :

- Utiliser uniquement `data.items`, `data.total`, `data.page`, `data.size`, `data.pages`.
- Éviter tout mix `content`/`items` selon les endpoints.

Compatibilité défensive (front) :

- `items = data.items ?? data.content ?? []`
- `total = data.total ?? data.totalElements ?? 0`
- `page = data.page ?? data.number ?? 0`
- `pages = data.pages ?? data.totalPages ?? 0`

## 11) Mapping statuts/priorités

Objectif: avoir une convention stable et unique entre dossiers/signalements/postes/calendriers.

### 11.1 Statuts canoniques

- `PENDING`
- `IN_PROGRESS`
- `RESOLVED`
- `CANCELLED`

### 11.2 Priorités canoniques

- `LOW`
- `MEDIUM`
- `HIGH`
- `CRITICAL`

### 11.3 Mapping backend existant vers canonique

Signalements:

- `NOUVEAU` -> `PENDING`
- `ASSIGNE` -> `IN_PROGRESS`
- `EN_COURS` -> `IN_PROGRESS`
- `EN_ATTENTE` -> `PENDING`
- `TRAITE` -> `RESOLVED`
- `CLOTURE` -> `RESOLVED`

Calendriers:

- `PUBLIE` -> `IN_PROGRESS`
- `ANNULE` -> `CANCELLED`

Participants événement :

- `INSCRIT` -> `CONFIRMED` (vue métier participants)

## 12) Contrat WebSocket

### 12.1 Canaux autorisés

- `admin` (Admin only)
- `signalements` (Gestionnaire+)
- `signalement:{signalementId}` (utilisateur authentifie)
- `discussion:{discussionId}` (utilisateur authentifie)
- `poste:{posteId}` (utilisateur authentifie)
- `calendriers` (utilisateur authentifie)
- `dashboard` (Gestionnaire+)
- `user:{userId}` (utilisateur authentifie)

### 12.2 Schéma d'événement courant

```json
{
  "eventId": "01JXYZABCDEFG",
  "type": "signalement.updated",
  "timestamp": "2026-03-21T10:00:00Z",
  "payload": {}
}
```

Notes de compatibilité :

- Le backend actuel émet `type`, `canal`, `payload`, `timestamp`.
- Le champ `eventId` n'est pas garanti sur tous les events actuels.
- Côté client, accepter les deux formes (avec ou sans `eventId`).

## 13) Refresh token et session (comportement formalisé)

Comportement attendu:

1. Login crée une session active (`sessionId`) + `token` + `refreshToken`.
2. `POST /auth/refresh` invalide l'ancienne session (`sessionId` precedent).
3. Le refresh retourne une nouvelle session (`sessionId` nouveau) avec nouveaux tokens.
4. Côté client, il faut remplacer atomiquement `token`, `refreshToken`, `sessionId`.
5. Si refresh échoue (401), forcer logout local et renvoyer vers login.

Ce comportement correspond déjà à l'implémentation actuelle du service d'authentification.

## 14) Mises à jour récentes explicites (2026-04-01)

Cette section résume les ajouts backend récents déjà actifs.

1. Modération des postes:
- ajout `GET /postes/approuves`
- ajout `PATCH /postes/{poste_id}/statut`
- statuts supportés: `NOUVEAU`, `APPROUVER_IA`, `REJETER_IA`, `APPROUVER`, `REJETER`, `ARCHIVER`
- champ `approuverParId` exposé dans les réponses de poste

2. Ressources:
- nouveau module REST complet sous `/ressources`
- upload direct (`/ressources/upload`) + création metadata (`/ressources/`)
- listing/recherche/tri + détail + suppression logique

3. Dashboard / stats:
- ajout `PUT /stats/dashboard/alerts/{alert_id}/dismiss`
- ajout `GET /stats/dashboard/invitations/pending` (Admin)
- émission websocket `stats_updated` sur les mutations clés de dashboard/modération

4. Invitations équipe:
- ajout listing admin `GET /comptes/invitations` avec filtre `statut`
- conservation du endpoint de création `POST /comptes/invitations`

5. Signalements:
- ajout des indicateurs non lus dans les payloads:
  `hasUnreadUpdates`, `messagesNonLusCount`
