- 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
9.5 KiB
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 :
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
@Inject
io.quarkus.security.identity.SecurityIdentity securityIdentity;
Ligne : ~62
B. Endpoint /me
@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)
@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) :
// 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é) :
// 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 :
// 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 :
// 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 :
// 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
GET /api/membres/me
Authorization: Bearer {token}
Réponse attendue :
{
"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
- Endpoint
/mecréé → ✅ MembreResource.java - SecurityIdentity injecté → ✅ Backend
- Interface REST Client mise à jour → ✅ MembreService.java (frontend)
- MembreCotisationBean modifié → ✅ Appel
obtenirMembreConnecte() - MesCotisationsPaiementBean modifié → ✅ Appel
obtenirMembreConnecte() - Compilation backend → ✅ BUILD SUCCESS
- Compilation frontend → ✅ BUILD SUCCESS
- Tests fonctionnels → ⏳ À faire (redémarrer serveurs)
- Validation navigateur → ⏳ À faire
Prochaines Étapes
1. Redémarrer les serveurs
Backend :
# Arrêter Quarkus (Ctrl+C)
cd unionflow/unionflow-server-impl-quarkus
mvn quarkus:dev
Frontend :
# 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
- Se connecter :
http://localhost:8090/ - Naviguer : Menu Cotisations → Mes Cotisations
- 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) :
@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 :
- ✅ Token validé : signature, expiration, issuer
- ✅ Email extrait :
securityIdentity.getPrincipal().getName() - ✅ Membre trouvé :
membreService.trouverParEmail(email) - ✅ Filtre actif : Seuls les membres actifs sont retournés
Autorisation
@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.mddocs/IMPLEMENTATION_SERVICES_COTISATIONS_COMPLET.mddocs/IMPLEMENTATION_ENDPOINTS_BACKEND.md