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:
dahoud
2026-03-15 02:12:17 +00:00
parent bbc409de9d
commit e8ad874015
635 changed files with 58160 additions and 20674 deletions

View 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`