feat: WebSocket temps réel + Finance Workflow + corrections
- Task #6: WebSocket /ws/dashboard + Kafka events (5 topics) * Backend: KafkaEventProducer, KafkaEventConsumer * Mobile: WebSocketService (reconnection, heartbeat, typed events) * DashboardBloc: Auto-refresh depuis WebSocket events - Finance Workflow: approbations + budgets (backend + mobile) * Backend: entities, services, resources, migrations Flyway V6 * Mobile: features finance_workflow complète avec BLoC - Corrections DI: interfaces IRepository partout * IProfileRepository, IOrganizationRepository, IMembreRepository * GetIt configuré avec @injectable - Spec-Kit: constitution + templates mis à jour * .specify/memory/constitution.md enrichie * Templates agent, plan, spec, tasks, checklist - Nettoyage: fichiers temporaires supprimés Signed-off-by: lions dev Team
This commit is contained in:
401
unionflow/docs/CORRECTION_ERREUR_404_MEMBRE.md
Normal file
401
unionflow/docs/CORRECTION_ERREUR_404_MEMBRE.md
Normal file
@@ -0,0 +1,401 @@
|
||||
# Correction Erreur 404 - Membre Non Trouvé
|
||||
|
||||
> **Date**: 2026-03-02
|
||||
> **Système**: UnionFlow - Frontend & Backend
|
||||
> **Statut**: ✅ **CORRIGÉ**
|
||||
|
||||
---
|
||||
|
||||
## Problème Initial
|
||||
|
||||
### Erreur rencontrée
|
||||
|
||||
```
|
||||
2026-03-02 17:08:52,964 SEVERE [RestClientExceptionMapper] Erreur backend - HTTP 404 (Not Found)
|
||||
Auto-détection du membre connecté: membre.mukefi@unionflow.test
|
||||
NotFoundException: Ressource non trouvée
|
||||
```
|
||||
|
||||
### Cause racine
|
||||
|
||||
Le frontend utilisait l'endpoint `/api/membres/search` (recherche générale) pour trouver le membre connecté par email :
|
||||
|
||||
```java
|
||||
List<MembreResponse> membresRecherche = membreService.rechercher(
|
||||
null, null, username, null, null, null, 0, 1
|
||||
);
|
||||
```
|
||||
|
||||
**Problème** : Cet endpoint retourne HTTP 404 si aucun membre n'est trouvé, au lieu de retourner une liste vide.
|
||||
|
||||
---
|
||||
|
||||
## Solution Implémentée
|
||||
|
||||
### 1. Création endpoint dédié `/api/membres/me`
|
||||
|
||||
**Backend** : `MembreResource.java`
|
||||
|
||||
#### A. Injection SecurityIdentity
|
||||
|
||||
```java
|
||||
@Inject
|
||||
io.quarkus.security.identity.SecurityIdentity securityIdentity;
|
||||
```
|
||||
|
||||
**Ligne** : ~62
|
||||
|
||||
---
|
||||
|
||||
#### B. Endpoint `/me`
|
||||
|
||||
```java
|
||||
@GET
|
||||
@Path("/me")
|
||||
@RolesAllowed({ "MEMBRE", "ADMIN", "SUPER_ADMIN" })
|
||||
@Operation(summary = "Récupérer le membre connecté")
|
||||
@APIResponse(responseCode = "200", description = "Membre connecté trouvé")
|
||||
@APIResponse(responseCode = "404", description = "Membre non trouvé")
|
||||
public Response obtenirMembreConnecte() {
|
||||
String email = securityIdentity.getPrincipal().getName();
|
||||
LOG.infof("Récupération du membre connecté: %s", email);
|
||||
|
||||
Membre membre = membreService.trouverParEmail(email)
|
||||
.filter(m -> m.getActif() == null || m.getActif())
|
||||
.orElseThrow(() -> new NotFoundException("Membre non trouvé pour l'email: " + email));
|
||||
|
||||
return Response.ok(membreService.convertToResponse(membre)).build();
|
||||
}
|
||||
```
|
||||
|
||||
**Ligne** : ~100
|
||||
|
||||
**Caractéristiques** :
|
||||
- ✅ Accès direct via SecurityIdentity (auto-détection)
|
||||
- ✅ Pas de paramètre requis
|
||||
- ✅ Retourne directement le membre connecté
|
||||
- ✅ Erreur 404 claire si membre inexistant
|
||||
- ✅ Filtrage par actif
|
||||
|
||||
---
|
||||
|
||||
### 2. Déclaration interface REST Client
|
||||
|
||||
**Frontend** : `MembreService.java` (interface)
|
||||
|
||||
```java
|
||||
@GET
|
||||
@Path("/me")
|
||||
MembreResponse obtenirMembreConnecte();
|
||||
```
|
||||
|
||||
**Ligne** : ~30 (après `obtenirParNumero`)
|
||||
|
||||
**Ajouté** : Méthode dans l'interface REST Client
|
||||
|
||||
---
|
||||
|
||||
### 3. Mise à jour MembreCotisationBean
|
||||
|
||||
**Avant** (code problématique) :
|
||||
|
||||
```java
|
||||
// Rechercher le membre par email
|
||||
List<MembreResponse> membresRecherche = retryService.executeWithRetrySupplier(
|
||||
() -> membreService.rechercher(null, null, username, null, null, null, 0, 1),
|
||||
"recherche du membre connecté par email"
|
||||
);
|
||||
|
||||
if (membresRecherche != null && !membresRecherche.isEmpty()) {
|
||||
MembreResponse membreConnecte = membresRecherche.get(0);
|
||||
membreId = membreConnecte.getId();
|
||||
numeroMembre = membreConnecte.getNumeroMembre();
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Après** (code corrigé) :
|
||||
|
||||
```java
|
||||
// Récupérer directement le membre connecté via l'endpoint /me
|
||||
MembreResponse membreConnecte = retryService.executeWithRetrySupplier(
|
||||
() -> membreService.obtenirMembreConnecte(),
|
||||
"récupération du membre connecté"
|
||||
);
|
||||
|
||||
if (membreConnecte != null) {
|
||||
membreId = membreConnecte.getId();
|
||||
numeroMembre = membreConnecte.getNumeroMembre();
|
||||
LOG.infof("Membre connecté détecté: %s (%s)", numeroMembre, membreId);
|
||||
}
|
||||
```
|
||||
|
||||
**Fichier** : `MembreCotisationBean.java` (lignes 148-165)
|
||||
|
||||
---
|
||||
|
||||
### 4. Mise à jour MesCotisationsPaiementBean
|
||||
|
||||
**Même modification appliquée** :
|
||||
|
||||
```java
|
||||
// Récupérer directement le membre connecté via l'endpoint /me
|
||||
membre = retryService.executeWithRetrySupplier(
|
||||
() -> membreService.obtenirMembreConnecte(),
|
||||
"récupération du membre connecté"
|
||||
);
|
||||
|
||||
if (membre != null) {
|
||||
membreId = membre.getId();
|
||||
numeroMembre = membre.getNumeroMembre();
|
||||
LOG.infof("Membre connecté détecté: %s (%s)", numeroMembre, membreId);
|
||||
}
|
||||
```
|
||||
|
||||
**Fichier** : `MesCotisationsPaiementBean.java` (lignes 116-126)
|
||||
|
||||
---
|
||||
|
||||
## Avantages de la Solution
|
||||
|
||||
### 1. Performance
|
||||
|
||||
- ✅ **1 requête HTTP** au lieu de recherche avec filtres
|
||||
- ✅ **Requête directe** : `GET /api/membres/me`
|
||||
- ✅ **Pas de filtrage côté client** (liste de résultats)
|
||||
|
||||
---
|
||||
|
||||
### 2. Sécurité
|
||||
|
||||
- ✅ **Auto-détection via JWT** : `securityIdentity.getPrincipal().getName()`
|
||||
- ✅ **Pas de manipulation d'email** : le backend valide le token JWT
|
||||
- ✅ **Autorisation granulaire** : `@RolesAllowed`
|
||||
|
||||
---
|
||||
|
||||
### 3. Simplicité
|
||||
|
||||
**Avant** :
|
||||
```java
|
||||
// 1. Extraire email du SecurityIdentity
|
||||
String username = securityIdentity.getPrincipal().getName();
|
||||
|
||||
// 2. Rechercher avec filtres
|
||||
List<MembreResponse> results = membreService.rechercher(
|
||||
null, null, username, null, null, null, 0, 1
|
||||
);
|
||||
|
||||
// 3. Vérifier liste vide
|
||||
if (results != null && !results.isEmpty()) {
|
||||
MembreResponse membre = results.get(0);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Après** :
|
||||
```java
|
||||
// 1 appel direct
|
||||
MembreResponse membre = membreService.obtenirMembreConnecte();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Réutilisabilité
|
||||
|
||||
Cet endpoint `/me` peut être réutilisé partout où on a besoin du membre connecté :
|
||||
- ✅ DashboardMembreBean
|
||||
- ✅ MembreCotisationBean
|
||||
- ✅ MesCotisationsPaiementBean
|
||||
- ✅ Profil utilisateur
|
||||
- ✅ Toutes les pages personnelles
|
||||
|
||||
---
|
||||
|
||||
## Tests de Validation
|
||||
|
||||
### 1. Test Backend
|
||||
|
||||
**Swagger UI** : `http://localhost:8085/q/swagger-ui`
|
||||
|
||||
```bash
|
||||
GET /api/membres/me
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
**Réponse attendue** :
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"numeroMembre": "M-2026-001",
|
||||
"email": "membre.mukefi@unionflow.test",
|
||||
"nom": "Mukefi",
|
||||
"prenom": "Jean",
|
||||
"statut": "ACTIF",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Test Frontend
|
||||
|
||||
**Navigation** : `/pages/secure/membre/cotisations.xhtml`
|
||||
|
||||
**Logs attendus** :
|
||||
```
|
||||
INFO [MembreCotisationBean] Auto-détection du membre connecté: membre.mukefi@unionflow.test
|
||||
INFO [MembreCotisationBean] Membre connecté détecté: M-2026-001 (uuid)
|
||||
```
|
||||
|
||||
**Résultat** :
|
||||
- ✅ Aucune erreur 404
|
||||
- ✅ Page cotisations chargée
|
||||
- ✅ Données personnelles affichées
|
||||
|
||||
---
|
||||
|
||||
## Checklist Validation
|
||||
|
||||
- [x] **Endpoint `/me` créé** → ✅ MembreResource.java
|
||||
- [x] **SecurityIdentity injecté** → ✅ Backend
|
||||
- [x] **Interface REST Client mise à jour** → ✅ MembreService.java (frontend)
|
||||
- [x] **MembreCotisationBean modifié** → ✅ Appel `obtenirMembreConnecte()`
|
||||
- [x] **MesCotisationsPaiementBean modifié** → ✅ Appel `obtenirMembreConnecte()`
|
||||
- [x] **Compilation backend** → ✅ BUILD SUCCESS
|
||||
- [x] **Compilation frontend** → ✅ BUILD SUCCESS
|
||||
- [ ] **Tests fonctionnels** → ⏳ À faire (redémarrer serveurs)
|
||||
- [ ] **Validation navigateur** → ⏳ À faire
|
||||
|
||||
---
|
||||
|
||||
## Prochaines Étapes
|
||||
|
||||
### 1. Redémarrer les serveurs
|
||||
|
||||
**Backend** :
|
||||
```bash
|
||||
# Arrêter Quarkus (Ctrl+C)
|
||||
cd unionflow/unionflow-server-impl-quarkus
|
||||
mvn quarkus:dev
|
||||
```
|
||||
|
||||
**Frontend** :
|
||||
```bash
|
||||
# Arrêter Quarkus (Ctrl+C)
|
||||
cd unionflow/unionflow-client-quarkus-primefaces-freya
|
||||
mvn quarkus:dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Vider le cache navigateur
|
||||
|
||||
- ✅ Ctrl+Shift+Delete → Vider cache et cookies
|
||||
- ✅ Ou navigation privée
|
||||
|
||||
---
|
||||
|
||||
### 3. Tester l'application
|
||||
|
||||
1. **Se connecter** : `http://localhost:8090/`
|
||||
2. **Naviguer** : Menu Cotisations → Mes Cotisations
|
||||
3. **Vérifier** :
|
||||
- ✅ Aucune erreur 404
|
||||
- ✅ Page se charge correctement
|
||||
- ✅ Données personnelles affichées
|
||||
|
||||
---
|
||||
|
||||
### 4. Vérifier les logs
|
||||
|
||||
**Backend** :
|
||||
```
|
||||
INFO [MembreResource] Récupération du membre connecté: membre.mukefi@unionflow.test
|
||||
```
|
||||
|
||||
**Frontend** :
|
||||
```
|
||||
INFO [MembreCotisationBean] Membre connecté détecté: M-2026-001 (uuid)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pattern Réutilisable
|
||||
|
||||
### Endpoint `/me` - Standard REST
|
||||
|
||||
Ce pattern est un **standard REST** pour récupérer l'utilisateur connecté :
|
||||
|
||||
```
|
||||
GET /api/users/me → Twitter, GitHub, LinkedIn
|
||||
GET /api/membres/me → UnionFlow
|
||||
GET /api/auth/me → Alternative
|
||||
GET /api/profile → Alternative
|
||||
```
|
||||
|
||||
### Utilisation dans d'autres beans
|
||||
|
||||
**DashboardMembreBean** (exemple d'utilisation) :
|
||||
|
||||
```java
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
try {
|
||||
// Auto-détection membre connecté
|
||||
MembreResponse membre = retryService.executeWithRetrySupplier(
|
||||
() -> membreService.obtenirMembreConnecte(),
|
||||
"récupération du membre connecté"
|
||||
);
|
||||
|
||||
if (membre != null) {
|
||||
this.membreId = membre.getId();
|
||||
this.numeroMembre = membre.getNumeroMembre();
|
||||
chargerDonnees();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur auto-détection membre");
|
||||
errorHandler.handleException(e, "lors du chargement du dashboard", null);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Sécurité
|
||||
|
||||
### Validation JWT
|
||||
|
||||
L'endpoint `/me` valide automatiquement le JWT via Quarkus OIDC :
|
||||
|
||||
1. ✅ **Token validé** : signature, expiration, issuer
|
||||
2. ✅ **Email extrait** : `securityIdentity.getPrincipal().getName()`
|
||||
3. ✅ **Membre trouvé** : `membreService.trouverParEmail(email)`
|
||||
4. ✅ **Filtre actif** : Seuls les membres actifs sont retournés
|
||||
|
||||
### Autorisation
|
||||
|
||||
```java
|
||||
@RolesAllowed({ "MEMBRE", "ADMIN", "SUPER_ADMIN" })
|
||||
```
|
||||
|
||||
- ✅ Accessible à tous les membres authentifiés
|
||||
- ❌ Interdit aux utilisateurs non authentifiés (401)
|
||||
- ❌ Interdit aux tokens expirés (401)
|
||||
|
||||
---
|
||||
|
||||
## Contact
|
||||
|
||||
**Fichiers modifiés** :
|
||||
- `MembreResource.java` (+20 lignes)
|
||||
- `MembreService.java` (interface frontend, +4 lignes)
|
||||
- `MembreCotisationBean.java` (~10 lignes modifiées)
|
||||
- `MesCotisationsPaiementBean.java` (~10 lignes modifiées)
|
||||
|
||||
**Endpoint ajouté** : `GET /api/membres/me`
|
||||
|
||||
**Documentation liée** :
|
||||
- `docs/IMPLEMENTATION_TESTS_SERVICES.md`
|
||||
- `docs/IMPLEMENTATION_SERVICES_COTISATIONS_COMPLET.md`
|
||||
- `docs/IMPLEMENTATION_ENDPOINTS_BACKEND.md`
|
||||
Reference in New Issue
Block a user