Files
unionflow-server-api/unionflow/docs/CORRECTION_ERREUR_404_MEMBRE.md
dahoud e8ad874015 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
2026-03-15 02:12:17 +00:00

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 /me créé 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

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

@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

@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