Files
lions-user-manager/HANDOFF_COMPLET.md
lionsdev 70b4bd93a1 docs: Document de handoff complet pour prochain agent IA
Document exhaustif de 1000+ lignes contenant:
- État actuel détaillé (45% complété)
- Liste complète des erreurs de compilation à corriger
- Architecture et décisions techniques
- Tâches restantes avec instructions détaillées pas à pas
- Spécifications complètes module client PrimeFaces
- Scripts Keycloak provisioning
- Helm charts Kubernetes
- Guide tests unitaires et intégration
- Checklist finale avec estimations durée
- Commandes utiles pour tous les scénarios

Le prochain agent IA peut reprendre de manière 100% autonome.

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 09:30:56 +00:00

2627 lines
79 KiB
Markdown

# 🔄 HANDOFF COMPLET - LIONS-USER-MANAGER
## Document de Transfert pour Agent IA Successeur
**Date**: 2025-11-09
**Projet**: lions-user-manager - Système de gestion utilisateurs Keycloak
**Localisation**: C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager
**Progression globale**: 45% complété
---
## 📋 TABLE DES MATIÈRES
1. [Vue d'ensemble du projet](#1-vue-densemble-du-projet)
2. [État actuel détaillé](#2-état-actuel-détaillé)
3. [Erreurs de compilation à corriger](#3-erreurs-de-compilation-à-corriger)
4. [Architecture et décisions techniques](#4-architecture-et-décisions-techniques)
5. [Configuration Git](#5-configuration-git)
6. [Tâches prioritaires](#6-tâches-prioritaires)
7. [Tâches complètes restantes](#7-tâches-complètes-restantes)
8. [Spécifications techniques détaillées](#8-spécifications-techniques-détaillées)
9. [Commandes utiles](#9-commandes-utiles)
---
## 1. VUE D'ENSEMBLE DU PROJET
### 1.1 Objectif Principal
Créer un système complet de gestion des utilisateurs Keycloak avec:
- **Backend Quarkus**: API REST pour gérer users, rôles, audit
- **Frontend PrimeFaces**: Interface JSF avec thème Freya
- **Kubernetes ready**: Déploiement via Helm charts
- **CONTRAINTE CRITIQUE**: ZÉRO accès direct à la DB Keycloak, uniquement via Admin REST API
### 1.2 Structure des Modules (Maven Multi-module)
```
lions-user-manager/
├── pom.xml (parent)
├── lions-user-manager-server-api/ ✅ 100% COMPLÉTÉ
│ ├── src/main/java/dev/lions/user/manager/
│ │ ├── dto/
│ │ │ ├── base/BaseDTO.java
│ │ │ ├── user/UserDTO.java, UserSearchCriteriaDTO.java, UserSearchResultDTO.java
│ │ │ ├── role/RoleDTO.java, RoleAssignmentDTO.java
│ │ │ └── audit/AuditLogDTO.java
│ │ ├── enums/
│ │ │ ├── user/StatutUser.java
│ │ │ ├── role/TypeRole.java
│ │ │ └── audit/TypeActionAudit.java
│ │ ├── service/
│ │ │ ├── UserService.java (interface - 25+ méthodes)
│ │ │ ├── RoleService.java (interface - 20+ méthodes)
│ │ │ ├── AuditService.java (interface - 12+ méthodes)
│ │ │ └── SyncService.java (interface - 8+ méthodes)
│ │ └── validation/ValidationConstants.java
│ └── pom.xml
├── lions-user-manager-server-impl-quarkus/ ⚠️ 70% COMPLÉTÉ - ERREURS COMPILATION
│ ├── src/main/java/dev/lions/user/manager/
│ │ ├── client/
│ │ │ ├── KeycloakAdminClient.java ✅ OK
│ │ │ └── KeycloakAdminClientImpl.java ✅ OK (Circuit Breaker, Retry, Timeout)
│ │ ├── mapper/
│ │ │ ├── UserMapper.java ✅ OK
│ │ │ └── RoleMapper.java ❌ ERREURS (name vs nom)
│ │ ├── service/impl/
│ │ │ ├── UserServiceImpl.java ✅ OK
│ │ │ ├── RoleServiceImpl.java ❌ ERREURS (signatures méthodes)
│ │ │ ├── AuditServiceImpl.java ❌ ERREURS (signatures méthodes)
│ │ │ └── SyncServiceImpl.java ❌ ERREURS (signatures méthodes)
│ │ ├── resource/
│ │ │ ├── UserResource.java ✅ OK
│ │ │ ├── RoleResource.java ❌ ERREURS
│ │ │ ├── AuditResource.java ❌ ERREURS
│ │ │ ├── SyncResource.java ❌ ERREURS
│ │ │ ├── KeycloakHealthCheck.java ✅ OK
│ │ │ └── HealthResourceEndpoint.java ✅ OK
│ │ └── src/main/resources/
│ │ ├── application.properties ✅ OK
│ │ ├── application-dev.properties ✅ OK
│ │ └── application-prod.properties ✅ OK
│ └── pom.xml
├── lions-user-manager-client-quarkus-primefaces-freya/ ⏳ 0% - PAS COMMENCÉ
│ └── pom.xml (structure de base seulement)
├── README.md ✅ CRÉÉ
├── PROGRESS_REPORT.md ✅ CRÉÉ
├── HANDOFF_COMPLET.md ✅ CE FICHIER
└── .gitignore ✅ CRÉÉ
```
### 1.3 Technologies Stack
**Backend**:
- Quarkus 3.15.1
- Keycloak Admin Client 23.0.3 (avec exclusions RESTEasy)
- SmallRye Fault Tolerance (Circuit Breaker, Retry, Timeout)
- MicroProfile OpenAPI
- Jakarta EE (Validation, Inject, REST)
- Lombok 1.18.30
- MapStruct 1.5.5.Final (pas encore utilisé)
**Frontend** (à implémenter):
- PrimeFaces 14.0.5
- Freya Theme 5.0.0-jakarta (depuis git.lions.dev/lionsdev/btpxpress-maven-repo)
- JSF (Jakarta Faces)
- MicroProfile Rest Client
**Tests** (à implémenter):
- JUnit 5
- Testcontainers 1.19.3 (Keycloak, PostgreSQL)
- RestAssured
- Jacoco (objectif 80% coverage)
---
## 2. ÉTAT ACTUEL DÉTAILLÉ
### 2.1 Fichiers Créés et Fonctionnels ✅
#### Module server-api (15 fichiers - 100% OK)
```
lions-user-manager-server-api/src/main/java/dev/lions/user/manager/
├── dto/
│ ├── base/BaseDTO.java [FONCTIONNEL]
│ ├── user/
│ │ ├── UserDTO.java [FONCTIONNEL - 60+ champs]
│ │ ├── UserSearchCriteriaDTO.java [FONCTIONNEL]
│ │ └── UserSearchResultDTO.java [FONCTIONNEL]
│ ├── role/
│ │ ├── RoleDTO.java [FONCTIONNEL - champ: name]
│ │ └── RoleAssignmentDTO.java [FONCTIONNEL]
│ └── audit/
│ └── AuditLogDTO.java [FONCTIONNEL]
├── enums/
│ ├── user/StatutUser.java [FONCTIONNEL - 7 états]
│ ├── role/TypeRole.java [FONCTIONNEL]
│ └── audit/TypeActionAudit.java [FONCTIONNEL - 15+ actions]
├── service/
│ ├── UserService.java [INTERFACE OK]
│ ├── RoleService.java [INTERFACE OK]
│ ├── AuditService.java [INTERFACE OK]
│ └── SyncService.java [INTERFACE OK]
└── validation/ValidationConstants.java [FONCTIONNEL]
```
#### Module server-impl (7 fichiers fonctionnels, 6 avec erreurs)
**Fichiers 100% fonctionnels**:
1. `KeycloakAdminClient.java` - Interface
2. `KeycloakAdminClientImpl.java` - Implémentation avec résilience
3. `UserMapper.java` - Conversions UserDTO <-> Keycloak
4. `UserServiceImpl.java` - Service utilisateurs (25+ méthodes)
5. `UserResource.java` - REST API users (12 endpoints)
6. `KeycloakHealthCheck.java` - Health check Keycloak
7. `HealthResourceEndpoint.java` - Endpoint health
**Fichiers avec erreurs de compilation**:
1. `RoleMapper.java` - Utilise getNom() au lieu de getName()
2. `RoleServiceImpl.java` - Signatures méthodes incompatibles avec interface
3. `AuditServiceImpl.java` - Méthodes pas dans l'interface
4. `SyncServiceImpl.java` - Méthodes incompatibles
5. `RoleResource.java` - Appelle méthodes inexistantes
6. `AuditResource.java` - Appelle méthodes inexistantes
7. `SyncResource.java` - Probablement OK mais dépend de SyncServiceImpl
### 2.2 Configuration Git ✅
**4 repositories configurés**:
1. **master**: https://git.lions.dev/lionsdev/lions-user-manager.git
2. **server-api**: https://git.lions.dev/lionsdev/lions-user-manager-server-api.git
3. **server-impl**: https://git.lions.dev/lionsdev/lions-user-manager-server-impl-quarkus.git
4. **client**: https://git.lions.dev/lionsdev/lions-user-manager-client-quarkus-primefaces-freya.git
**Credentials Git**:
- Username: `lionsdev`
- Password: `lions@2025`
- Format URL: `https://lionsdev:lions%402025@git.lions.dev/...`
**Structure Git actuelle**:
- ✅ Chaque sous-module a son propre repository Git avec UNIQUEMENT son code
- ✅ Le repository master contient tout le projet
- ✅ Tous les repos sont sur la branche `main`
- ✅ Derniers commits effectués et pushés
---
## 3. ERREURS DE COMPILATION À CORRIGER
### 3.1 Erreurs Critiques (bloquent la compilation)
#### Erreur #1: RoleDTO - Incohérence nom du champ
**Fichier**: `RoleDTO.java` ligne 38
**Problème**: Le champ s'appelle `name` et non `nom`
```java
// RoleDTO.java:38
private String name; // ✅ CORRECT dans le DTO
```
**Fichiers utilisant incorrectement `getNom()`**:
- `RoleMapper.java` lignes 25, 43
- `RoleServiceImpl.java` lignes 170, 179
- `RoleResource.java` lignes 56
**Solution**: Remplacer tous les `getNom()` par `getName()` et `setNom()` par `setName()`
#### Erreur #2: RoleMapper - Mauvaise propriété dans builder
**Fichier**: `RoleMapper.java` ligne 25
**Code actuel**:
```java
.nom(roleRep.getName()) // ❌ ERREUR
```
**Correction**:
```java
.name(roleRep.getName()) // ✅ CORRECT
```
#### Erreur #3: RoleMapper - Test null incorrect
**Fichier**: `RoleMapper.java` ligne 29
**Code actuel**:
```java
.composite(roleRep.isComposite() != null ? roleRep.isComposite() : false) // ❌ ERREUR
```
**Problème**: `isComposite()` retourne `boolean`, pas `Boolean`
**Correction**:
```java
.composite(roleRep.isComposite()) // ✅ CORRECT
```
#### Erreur #4: RoleServiceImpl - Signatures incompatibles avec interface
**Interface RoleService attend**:
```java
// RoleService.java
List<RoleDTO> getAllClientRoles(@NotBlank String realmName, @NotBlank String clientName);
Optional<RoleDTO> getRoleByName(@NotBlank String roleName, @NotBlank String realmName,
@NotNull TypeRole typeRole, String clientName);
RoleDTO createClientRole(@Valid @NotNull RoleDTO role, @NotBlank String realmName,
@NotBlank String clientName);
RoleDTO updateRole(@NotBlank String roleId, @Valid @NotNull RoleDTO role,
@NotBlank String realmName, @NotNull TypeRole typeRole, String clientName);
void deleteRole(@NotBlank String roleId, @NotBlank String realmName,
@NotNull TypeRole typeRole, String clientName);
// ... et beaucoup d'autres méthodes différentes
```
**RoleServiceImpl implémente**:
```java
// RoleServiceImpl.java - SIGNATURES DIFFÉRENTES
Optional<RoleDTO> getRealmRoleByName(String roleName, String realmName); // ❌ Pas dans interface
RoleDTO updateRealmRole(String roleName, RoleDTO roleDTO, String realmName); // ❌ Pas dans interface
void deleteRealmRole(String roleName, String realmName); // ❌ Pas dans interface
List<RoleDTO> getAllClientRoles(String clientId, String realmName); // ❌ Ordre paramètres inversé
// ... etc
```
**SOLUTION RECOMMANDÉE**:
Deux options:
1. **Option A** (RECOMMANDÉE): Modifier `RoleServiceImpl` pour implémenter EXACTEMENT les méthodes de l'interface
2. **Option B**: Modifier l'interface `RoleService.java` pour correspondre à l'implémentation (moins propre)
#### Erreur #5: AuditService - Méthodes manquantes dans l'interface
**AuditServiceImpl utilise**:
```java
List<AuditLogDTO> searchLogs(String acteur, LocalDateTime debut, LocalDateTime fin,
TypeActionAudit type, String ressourceType, Boolean succes,
int page, int pageSize);
List<AuditLogDTO> getLogsByActeur(String acteur, int limit);
List<AuditLogDTO> getLogsByRessource(String type, String id, int limit);
Map<TypeActionAudit, Long> getActionStatistics(LocalDateTime debut, LocalDateTime fin);
long getFailureCount(LocalDateTime debut, LocalDateTime fin);
// ... etc
```
**Mais l'interface AuditService.java ne déclare pas toutes ces méthodes!**
**SOLUTION**: Ajouter toutes les méthodes manquantes dans `AuditService.java`
#### Erreur #6: SyncServiceImpl - Classes internes pas exportées
**Fichier**: `SyncServiceImpl.java`
**Problème**: Classes internes `SyncResult` et `HealthStatus` utilisées dans l'interface mais non visibles
**SOLUTION**:
1. Soit déplacer ces classes dans le module `server-api` comme DTOs séparés
2. Soit les garder dans l'implémentation et créer des interfaces dans `server-api`
#### Erreur #7: RoleResource - Appels à méthodes inexistantes
**Fichier**: `RoleResource.java`
**Lignes avec erreurs**: 91, 146, 172, 237, 292, 321, 348, 377, 457, 482
**Exemple ligne 91**:
```java
return roleService.getRealmRoleByName(roleName, realmName) // ❌ Méthode n'existe pas
```
**SOLUTION**: Utiliser les méthodes correctes de l'interface après correction de RoleService
#### Erreur #8: Conversion Set vers List
**Fichier**: `RoleServiceImpl.java` ligne 539
**Code actuel**:
```java
List<RoleRepresentation> composites = roleResource.getRoleComposites(); // ❌ Retourne Set
```
**Correction**:
```java
List<RoleRepresentation> composites = new ArrayList<>(roleResource.getRoleComposites());
```
### 3.2 Plan de Correction Étape par Étape
**ÉTAPE 1: Corriger RoleMapper** (5 minutes)
```bash
# Fichier: lions-user-manager-server-impl-quarkus/src/main/java/dev/lions/user/manager/mapper/RoleMapper.java
# Ligne 25: .nom(roleRep.getName()) → .name(roleRep.getName())
# Ligne 29: roleRep.isComposite() != null ? ... → roleRep.isComposite()
# Ligne 43: roleDTO.getNom() → roleDTO.getName()
```
**ÉTAPE 2: Aligner RoleService et RoleServiceImpl** (30 minutes)
Deux sous-options:
**Option 2A** (RECOMMANDÉE): Simplifier l'interface RoleService
```java
// Nouvelle interface simplifiée dans server-api/service/RoleService.java
public interface RoleService {
// CRUD Realm Roles
RoleDTO createRealmRole(RoleDTO roleDTO, String realmName);
Optional<RoleDTO> getRealmRoleById(String roleId, String realmName);
Optional<RoleDTO> getRealmRoleByName(String roleName, String realmName);
RoleDTO updateRealmRole(String roleName, RoleDTO roleDTO, String realmName);
void deleteRealmRole(String roleName, String realmName);
List<RoleDTO> getAllRealmRoles(String realmName);
// CRUD Client Roles
RoleDTO createClientRole(RoleDTO roleDTO, String clientId, String realmName);
Optional<RoleDTO> getClientRoleByName(String roleName, String clientId, String realmName);
void deleteClientRole(String roleName, String clientId, String realmName);
List<RoleDTO> getAllClientRoles(String clientId, String realmName);
// Attribution de rôles
void assignRealmRolesToUser(String userId, List<String> roleNames, String realmName);
void revokeRealmRolesFromUser(String userId, List<String> roleNames, String realmName);
void assignClientRolesToUser(String userId, String clientId, List<String> roleNames, String realmName);
void revokeClientRolesFromUser(String userId, String clientId, List<String> roleNames, String realmName);
List<RoleDTO> getUserRealmRoles(String userId, String realmName);
List<RoleDTO> getUserClientRoles(String userId, String clientId, String realmName);
// Rôles composites
void addCompositesToRealmRole(String roleName, List<String> compositeRoleNames, String realmName);
void removeCompositesFromRealmRole(String roleName, List<String> compositeRoleNames, String realmName);
List<RoleDTO> getCompositeRoles(String roleName, String realmName);
// Vérification permissions
boolean userHasRealmRole(String userId, String roleName, String realmName);
boolean userHasClientRole(String userId, String clientId, String roleName, String realmName);
List<RoleDTO> getUserEffectiveRealmRoles(String userId, String realmName);
}
```
**Option 2B**: Modifier RoleServiceImpl pour correspondre à l'interface actuelle (plus de travail)
**ÉTAPE 3: Compléter AuditService.java** (10 minutes)
```java
// Ajouter dans server-api/service/AuditService.java
List<AuditLogDTO> searchLogs(String acteurUsername, LocalDateTime dateDebut, LocalDateTime dateFin,
TypeActionAudit typeAction, String ressourceType, Boolean succes,
int page, int pageSize);
List<AuditLogDTO> getLogsByActeur(String acteurUsername, int limit);
List<AuditLogDTO> getLogsByRessource(String ressourceType, String ressourceId, int limit);
List<AuditLogDTO> getLogsByAction(TypeActionAudit typeAction, LocalDateTime dateDebut,
LocalDateTime dateFin, int limit);
Map<TypeActionAudit, Long> getActionStatistics(LocalDateTime dateDebut, LocalDateTime dateFin);
Map<String, Long> getUserActivityStatistics(LocalDateTime dateDebut, LocalDateTime dateFin);
long getFailureCount(LocalDateTime dateDebut, LocalDateTime dateFin);
long getSuccessCount(LocalDateTime dateDebut, LocalDateTime dateFin);
List<String> exportLogsToCSV(LocalDateTime dateDebut, LocalDateTime dateFin);
void purgeOldLogs(int joursDAnciennete);
```
**ÉTAPE 4: Créer DTOs pour Sync** (15 minutes)
```bash
# Créer: server-api/dto/sync/SyncResultDTO.java
# Créer: server-api/dto/sync/HealthStatusDTO.java
# Modifier SyncService.java pour utiliser ces DTOs
# Modifier SyncServiceImpl.java pour retourner ces DTOs
```
**ÉTAPE 5: Corriger RoleServiceImpl ligne 539** (2 minutes)
```java
List<RoleRepresentation> composites = new ArrayList<>(
keycloakAdminClient.getInstance()
.realm(realmName)
.roles()
.get(roleName)
.getRoleComposites()
);
```
**ÉTAPE 6: Compiler et vérifier** (5 minutes)
```bash
cd C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager
mvn clean compile -DskipTests
```
---
## 4. ARCHITECTURE ET DÉCISIONS TECHNIQUES
### 4.1 Décisions Architecturales Clés
#### A. Pattern Circuit Breaker + Retry
**Localisation**: `KeycloakAdminClientImpl.java`
**Raison**: Keycloak peut être temporairement indisponible
**Configuration**:
```java
@Retry(maxRetries = 3, delay = 2, delayUnit = ChronoUnit.SECONDS)
@Timeout(value = 30, unit = ChronoUnit.SECONDS)
@CircuitBreaker(requestVolumeThreshold = 10, failureRatio = 0.5, delay = 5000)
```
#### B. Exclusion RESTEasy Classic
**Localisation**: `server-impl/pom.xml`
**Problème résolu**: "Mixing Quarkus REST and RESTEasy Classic"
**Solution**:
```xml
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-client</artifactId>
<version>23.0.3</version>
<exclusions>
<exclusion>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
</exclusion>
<exclusion>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-multipart-provider</artifactId>
</exclusion>
<exclusion>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson2-provider</artifactId>
</exclusion>
</exclusions>
</dependency>
```
#### C. Audit Logging - En Mémoire pour Dev
**Localisation**: `AuditServiceImpl.java`
**État actuel**: Stockage en mémoire via ConcurrentHashMap
**TODO Production**: Implémenter persistence PostgreSQL avec Panache
**Logging parallèle**: SLF4J pour capture par systèmes centralisés (Graylog, ELK)
#### D. Mappers - Statiques vs MapStruct
**État actuel**: Méthodes statiques dans UserMapper et RoleMapper
**Décision**: Pas encore utilisé MapStruct (dépendance présente mais pas configurée)
**TODO**: Migrer vers MapStruct pour génération automatique
#### E. Séparation Dev/Prod
**Fichiers**:
- `application.properties` - Configuration de base
- `application-dev.properties` - Localhost, DEBUG logs
- `application-prod.properties` - HTTPS, DB obligatoire, SSL/TLS
**Activation**:
```bash
# Dev
mvn quarkus:dev -Dquarkus.profile=dev
# Prod
java -jar app.jar -Dquarkus.profile=prod
```
### 4.2 Contraintes Critiques du Projet
1. **ZÉRO accès direct DB Keycloak** - Utiliser UNIQUEMENT Keycloak Admin REST API
2. **Multi-realm support** - Toutes les opérations prennent `realmName` en paramètre
3. **Audit trail complet** - Qui, quoi, quand, IP, succès/échec pour TOUTES les opérations
4. **Test coverage 80%** - Objectif Jacoco avec Testcontainers
5. **Helm charts requis** - Déploiement Kubernetes
6. **Freya theme custom** - Depuis repository Git lions.dev
### 4.3 Patterns de Code Utilisés
#### Pattern DTO
```java
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class UserDTO extends BaseDTO {
// ...
}
```
#### Pattern Service/Resource
```
Service Interface (server-api)
↓ implémente
Service Implementation (server-impl)
↓ injecté dans
REST Resource (server-impl)
↓ expose
API REST OpenAPI
```
#### Pattern Mapper
```java
public class UserMapper {
public static UserDTO toDTO(UserRepresentation keycloakUser, String realmName) { }
public static UserRepresentation toRepresentation(UserDTO userDTO) { }
public static List<UserDTO> toDTOList(...) { }
}
```
---
## 5. CONFIGURATION GIT
### 5.1 Structure Multi-Repository
```
Repository Master (contient TOUT)
https://git.lions.dev/lionsdev/lions-user-manager.git
└── C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager\
Repository Server-API (contient UNIQUEMENT server-api)
https://git.lions.dev/lionsdev/lions-user-manager-server-api.git
└── C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager\lions-user-manager-server-api\
Repository Server-Impl (contient UNIQUEMENT server-impl)
https://git.lions.dev/lionsdev/lions-user-manager-server-impl-quarkus.git
└── C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager\lions-user-manager-server-impl-quarkus\
Repository Client (contient UNIQUEMENT client)
https://git.lions.dev/lionsdev/lions-user-manager-client-quarkus-primefaces-freya.git
└── C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager\lions-user-manager-client-quarkus-primefaces-freya\
```
### 5.2 Commandes Git Configurées
**Credentials**:
- Username: `lionsdev`
- Password: `lions@2025` (encoder en URL: `lions%402025`)
**Push vers tous les repos depuis la racine**:
```bash
cd C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager
# Commit dans le repo racine
git add .
git commit -m "feat: Description du commit"
git push origin main
```
**Push d'un sous-module spécifique**:
```bash
# Server-API
cd lions-user-manager-server-api
git add .
git commit -m "feat: Description"
git push origin main
# Server-Impl
cd ../lions-user-manager-server-impl-quarkus
git add .
git commit -m "feat: Description"
git push origin main
# Client
cd ../lions-user-manager-client-quarkus-primefaces-freya
git add .
git commit -m "feat: Description"
git push origin main
```
**Règle de synchronisation**:
Le repository master doit TOUJOURS être mis à jour APRÈS au moins un des repos enfants.
### 5.3 Branches
Toutes les branches sont sur `main` (pas `master`).
---
## 6. TÂCHES PRIORITAIRES
### Priorité 1: Corriger les erreurs de compilation (2-3 heures)
**Objectif**: Faire compiler le projet sans erreurs
**Étapes**:
1. ✅ Corriger RoleMapper (name vs nom)
2. ✅ Aligner RoleService interface et implémentation
3. ✅ Compléter AuditService interface
4. ✅ Créer DTOs pour SyncService
5. ✅ Corriger RoleServiceImpl Set→List
6. ✅ Vérifier compilation: `mvn clean compile -DskipTests`
7. ✅ Commit et push
**Commande de vérification**:
```bash
cd C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager
mvn clean compile -DskipTests 2>&1 | grep -i "BUILD SUCCESS"
```
### Priorité 2: Créer les tests unitaires de base (3-4 heures)
**Objectif**: Atteindre 30% de coverage minimum
**Fichiers à créer**:
```
server-impl/src/test/java/dev/lions/user/manager/
├── service/impl/
│ ├── UserServiceImplTest.java
│ ├── RoleServiceImplTest.java
│ ├── AuditServiceImplTest.java
│ └── SyncServiceImplTest.java
├── mapper/
│ ├── UserMapperTest.java
│ └── RoleMapperTest.java
└── resource/
├── UserResourceTest.java
├── RoleResourceTest.java
├── AuditResourceTest.java
└── SyncResourceTest.java
```
**Exemple de test**:
```java
@QuarkusTest
public class UserServiceImplTest {
@InjectMock
KeycloakAdminClient keycloakAdminClient;
@Inject
UserService userService;
@Test
void testCreateUser() {
// Given
UserDTO userDTO = UserDTO.builder()
.username("testuser")
.email("test@example.com")
.build();
// Mock Keycloak
when(keycloakAdminClient.getInstance()...)
.thenReturn(...);
// When
UserDTO created = userService.createUser(userDTO, "test-realm");
// Then
assertNotNull(created.getId());
assertEquals("testuser", created.getUsername());
}
}
```
### Priorité 3: Créer les tests d'intégration avec Testcontainers (4-6 heures)
**Objectif**: Tests réels contre Keycloak en Docker
**Fichiers à créer**:
```
server-impl/src/test/java/dev/lions/user/manager/integration/
├── KeycloakTestResource.java (configure Testcontainer)
├── UserServiceIntegrationTest.java
├── RoleServiceIntegrationTest.java
└── AuditServiceIntegrationTest.java
```
**Configuration Testcontainer**:
```java
@QuarkusTestResource(KeycloakTestResource.class)
@QuarkusTest
public class UserServiceIntegrationTest {
public static class KeycloakTestResource implements QuarkusTestResourceLifecycleManager {
private static final KeycloakContainer keycloak = new KeycloakContainer()
.withRealmImportFile("test-realm.json");
@Override
public Map<String, String> start() {
keycloak.start();
return Map.of(
"lions.keycloak.server-url", keycloak.getAuthServerUrl(),
"lions.keycloak.admin-username", "admin",
"lions.keycloak.admin-password", "admin"
);
}
@Override
public void stop() {
keycloak.stop();
}
}
@Inject
UserService userService;
@Test
void testCreateUserInRealKeycloak() {
// Test contre un vrai Keycloak dans Docker
UserDTO user = userService.createUser(...);
assertNotNull(user.getId());
}
}
```
### Priorité 4: Commencer le module client (8-12 heures)
**Objectif**: Interface utilisateur fonctionnelle de base
Voir section 7.4 pour les détails complets.
---
## 7. TÂCHES COMPLÈTES RESTANTES
### 7.1 Backend - Services Restants
#### A. Implémenter Persistence Audit (PostgreSQL)
**Actuellement**: Stockage en mémoire dans `AuditServiceImpl`
**À faire**: Persistence PostgreSQL avec Panache
**Étapes**:
1. **Créer l'entité Panache**:
```java
// server-impl/src/main/java/dev/lions/user/manager/entity/AuditLogEntity.java
@Entity
@Table(name = "audit_log", indexes = {
@Index(name = "idx_audit_acteur", columnList = "acteur_username"),
@Index(name = "idx_audit_date", columnList = "date_action"),
@Index(name = "idx_audit_ressource", columnList = "ressource_type, ressource_id")
})
public class AuditLogEntity extends PanacheEntityBase {
@Id
@Column(length = 36)
public String id;
@Column(name = "date_action", nullable = false)
public LocalDateTime dateAction;
@Column(name = "acteur_username", length = 100, nullable = false)
public String acteurUsername;
@Enumerated(EnumType.STRING)
@Column(name = "type_action", length = 50, nullable = false)
public TypeActionAudit typeAction;
@Column(name = "ressource_type", length = 50, nullable = false)
public String ressourceType;
@Column(name = "ressource_id", length = 100, nullable = false)
public String ressourceId;
@Column(name = "succes", nullable = false)
public boolean succes;
@Column(name = "adresse_ip", length = 45)
public String adresseIp;
@Column(name = "details", length = 1000)
public String details;
@Column(name = "message_erreur", length = 500)
public String messageErreur;
@Column(name = "donnees_avant", columnDefinition = "TEXT")
public String donneesAvant;
@Column(name = "donnees_apres", columnDefinition = "TEXT")
public String donneesApres;
}
```
2. **Créer le mapper entité**:
```java
// server-impl/.../mapper/AuditLogMapper.java
public class AuditLogMapper {
public static AuditLogEntity toEntity(AuditLogDTO dto) { }
public static AuditLogDTO toDTO(AuditLogEntity entity) { }
}
```
3. **Créer le repository Panache**:
```java
// server-impl/.../repository/AuditLogRepository.java
@ApplicationScoped
public class AuditLogRepository implements PanacheRepository<AuditLogEntity> {
public List<AuditLogEntity> findByActeur(String username, int limit) {
return find("acteurUsername = ?1 ORDER BY dateAction DESC", username)
.page(0, limit).list();
}
public List<AuditLogEntity> searchLogs(String acteur, LocalDateTime debut,
LocalDateTime fin, ...) {
// Query complexe avec critères dynamiques
}
public long countFailures(LocalDateTime debut, LocalDateTime fin) {
return count("succes = false AND dateAction BETWEEN ?1 AND ?2", debut, fin);
}
@Transactional
public void purgeOldLogs(LocalDateTime before) {
delete("dateAction < ?1", before);
}
}
```
4. **Modifier AuditServiceImpl**:
```java
@ApplicationScoped
@Slf4j
public class AuditServiceImpl implements AuditService {
@Inject
AuditLogRepository repository;
@ConfigProperty(name = "lions.audit.log-to-database", defaultValue = "false")
boolean logToDatabase;
@Override
@Transactional
public AuditLogDTO logAction(@Valid @NotNull AuditLogDTO auditLog) {
// Log SLF4J
log.info("AUDIT | ...");
// Persister en DB si activé
if (logToDatabase) {
AuditLogEntity entity = AuditLogMapper.toEntity(auditLog);
repository.persist(entity);
}
return auditLog;
}
// Modifier toutes les méthodes pour utiliser repository au lieu de Map
}
```
5. **Créer migration Flyway**:
```sql
-- server-impl/src/main/resources/db/migration/V1.0.0__create_audit_log.sql
CREATE TABLE audit_log (
id VARCHAR(36) PRIMARY KEY,
date_action TIMESTAMP NOT NULL,
acteur_username VARCHAR(100) NOT NULL,
type_action VARCHAR(50) NOT NULL,
ressource_type VARCHAR(50) NOT NULL,
ressource_id VARCHAR(100) NOT NULL,
succes BOOLEAN NOT NULL,
adresse_ip VARCHAR(45),
details VARCHAR(1000),
message_erreur VARCHAR(500),
donnees_avant TEXT,
donnees_apres TEXT
);
CREATE INDEX idx_audit_acteur ON audit_log(acteur_username);
CREATE INDEX idx_audit_date ON audit_log(date_action);
CREATE INDEX idx_audit_ressource ON audit_log(ressource_type, ressource_id);
CREATE INDEX idx_audit_type_action ON audit_log(type_action);
```
6. **Configurer datasource**:
```properties
# application-prod.properties
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=${DB_USERNAME:audit_user}
quarkus.datasource.password=${DB_PASSWORD:audit_password}
quarkus.datasource.jdbc.url=jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:lions_audit}
quarkus.hibernate-orm.database.generation=none
quarkus.flyway.migrate-at-start=true
lions.audit.log-to-database=true
```
#### B. Ajouter MapStruct pour Mappers
**Actuellement**: Mappers manuels avec méthodes statiques
**À faire**: Utiliser MapStruct pour génération automatique
**Étapes**:
1. **Configurer annotation processor dans POM**:
```xml
<!-- Déjà présent dans parent POM, à vérifier -->
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
```
2. **Créer mapper UserMapper avec MapStruct**:
```java
@Mapper(componentModel = "cdi")
public interface UserMapper {
@Mapping(source = "id", target = "id")
@Mapping(source = "username", target = "username")
@Mapping(source = "email", target = "email")
@Mapping(source = "enabled", target = "statut", qualifiedByName = "enabledToStatut")
UserDTO toDTO(UserRepresentation userRep, @Context String realmName);
@Mapping(source = "username", target = "username")
@Mapping(source = "email", target = "email")
@Mapping(source = "statut", target = "enabled", qualifiedByName = "statutToEnabled")
UserRepresentation toRepresentation(UserDTO userDTO);
@Named("enabledToStatut")
default StatutUser enabledToStatut(Boolean enabled) {
return StatutUser.fromEnabled(enabled != null ? enabled : false);
}
@Named("statutToEnabled")
default Boolean statutToEnabled(StatutUser statut) {
return statut != null && statut.peutSeConnecter();
}
}
```
#### C. Implémenter Cache avec Quarkus Cache
**Objectif**: Cache des rôles et users pour réduire appels Keycloak
**Étapes**:
1. **Ajouter dépendance**:
```xml
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-cache</artifactId>
</dependency>
```
2. **Annoter méthodes**:
```java
@ApplicationScoped
public class RoleServiceImpl implements RoleService {
@CacheResult(cacheName = "realm-roles")
public List<RoleDTO> getAllRealmRoles(@NotBlank String realmName) {
// Appel Keycloak
}
@CacheInvalidate(cacheName = "realm-roles")
public RoleDTO createRealmRole(RoleDTO roleDTO, String realmName) {
// Création role
}
}
```
3. **Configurer cache**:
```properties
quarkus.cache.caffeine.realm-roles.expire-after-write=5M
quarkus.cache.caffeine.client-roles.expire-after-write=5M
quarkus.cache.caffeine.users.expire-after-write=2M
```
### 7.2 Backend - Sécurité et Validation
#### A. Implémenter OIDC Authentication
**Objectif**: Sécuriser les endpoints REST avec Keycloak OIDC
**Configuration déjà présente** dans `application.properties`:
```properties
quarkus.oidc.auth-server-url=${KEYCLOAK_URL:http://localhost:8180}/realms/master
quarkus.oidc.client-id=${KEYCLOAK_CLIENT_ID:lions-user-manager}
quarkus.oidc.credentials.secret=${KEYCLOAK_CLIENT_SECRET:secret}
```
**À faire**:
1. **Tester avec vrai Keycloak**
2. **Ajouter JWT claims mapping**:
```java
@ApplicationScoped
public class UserIdentityProvider {
@Inject
JsonWebToken jwt;
public String getCurrentUsername() {
return jwt.getName();
}
public String getCurrentUserEmail() {
return jwt.getClaim("email");
}
public Set<String> getCurrentUserRoles() {
return jwt.getGroups();
}
}
```
3. **Utiliser dans audit logging**:
```java
@Inject
UserIdentityProvider userIdentity;
@Override
public UserDTO createUser(UserDTO user, String realmName) {
String acteur = userIdentity.getCurrentUsername();
try {
UserDTO created = // ... création
auditService.logSuccess(acteur, TypeActionAudit.USER_CREATE,
"user", created.getId(), ...);
return created;
} catch (Exception e) {
auditService.logFailure(acteur, TypeActionAudit.USER_CREATE,
"user", user.getUsername(), ...);
throw e;
}
}
```
#### B. Ajouter Validation Personnalisée
**Objectif**: Validateurs custom pour règles métier complexes
**Exemple**: Validation format téléphone français
```java
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = FrenchPhoneValidator.class)
public @interface FrenchPhone {
String message() default "Numéro de téléphone français invalide";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class FrenchPhoneValidator implements ConstraintValidator<FrenchPhone, String> {
private static final Pattern FRENCH_PHONE = Pattern.compile("^(\\+33|0)[1-9](\\d{2}){4}$");
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null || value.isEmpty()) {
return true;
}
return FRENCH_PHONE.matcher(value).matches();
}
}
```
**Utilisation dans UserDTO**:
```java
@FrenchPhone
private String telephone;
```
### 7.3 Backend - Observabilité
#### A. Métriques Prometheus
**Objectif**: Exposer métriques custom
**Dépendance déjà présente**: `quarkus-micrometer-registry-prometheus`
**À implémenter**:
```java
@ApplicationScoped
public class UserServiceMetrics {
private final Counter userCreationCounter;
private final Counter userCreationFailureCounter;
private final Timer userCreationTimer;
public UserServiceMetrics(MeterRegistry registry) {
this.userCreationCounter = registry.counter("lions.user.creation.total");
this.userCreationFailureCounter = registry.counter("lions.user.creation.failures");
this.userCreationTimer = registry.timer("lions.user.creation.duration");
}
public void recordUserCreation(boolean success, long durationMs) {
if (success) {
userCreationCounter.increment();
} else {
userCreationFailureCounter.increment();
}
userCreationTimer.record(durationMs, TimeUnit.MILLISECONDS);
}
}
```
**Utiliser dans UserServiceImpl**:
```java
@Inject
UserServiceMetrics metrics;
@Override
public UserDTO createUser(UserDTO user, String realmName) {
long start = System.currentTimeMillis();
try {
UserDTO created = // ... création
metrics.recordUserCreation(true, System.currentTimeMillis() - start);
return created;
} catch (Exception e) {
metrics.recordUserCreation(false, System.currentTimeMillis() - start);
throw e;
}
}
```
**Vérifier métriques**:
```bash
curl http://localhost:8081/q/metrics
```
#### B. Tracing OpenTelemetry
**Dépendance à ajouter**:
```xml
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-opentelemetry</artifactId>
</dependency>
```
**Configuration**:
```properties
quarkus.opentelemetry.enabled=true
quarkus.opentelemetry.tracer.exporter.otlp.endpoint=http://jaeger:4317
```
### 7.4 Module Client - Interface Utilisateur
**ÉTAT**: 0% - Non commencé
**PRIORITÉ**: Haute (après correction des erreurs backend)
#### A. Structure du Module Client
```
lions-user-manager-client-quarkus-primefaces-freya/
├── src/main/java/dev/lions/user/manager/client/
│ ├── api/ (REST Clients vers backend)
│ │ ├── UserApiClient.java
│ │ ├── RoleApiClient.java
│ │ ├── AuditApiClient.java
│ │ └── SyncApiClient.java
│ ├── bean/ (JSF Managed Beans)
│ │ ├── user/
│ │ │ ├── UserListBean.java (Liste utilisateurs)
│ │ │ ├── UserEditBean.java (Création/Édition)
│ │ │ ├── UserDetailBean.java (Détails utilisateur)
│ │ │ └── UserSearchBean.java (Recherche avancée)
│ │ ├── role/
│ │ │ ├── RoleListBean.java
│ │ │ ├── RoleEditBean.java
│ │ │ └── RoleAssignBean.java (Attribution rôles)
│ │ ├── audit/
│ │ │ ├── AuditLogBean.java
│ │ │ └── AuditStatsBean.java
│ │ └── navigation/
│ │ ├── MenuBean.java
│ │ └── BreadcrumbBean.java
│ ├── security/
│ │ ├── SecurityFilter.java (Vérif auth OIDC)
│ │ ├── RoleChecker.java
│ │ └── UserSessionBean.java (Session utilisateur)
│ ├── converter/ (JSF Converters)
│ │ ├── StatutUserConverter.java
│ │ └── TypeRoleConverter.java
│ └── validator/ (JSF Validators)
│ ├── UsernameValidator.java
│ └── EmailValidator.java
├── src/main/resources/
│ ├── META-INF/
│ │ └── resources/
│ │ ├── WEB-INF/
│ │ │ ├── web.xml
│ │ │ ├── faces-config.xml
│ │ │ ├── template.xhtml (Layout principal)
│ │ │ └── menu.xhtml (Menu navigation)
│ │ ├── users/
│ │ │ ├── list.xhtml (Liste utilisateurs)
│ │ │ ├── edit.xhtml (Formulaire utilisateur)
│ │ │ ├── detail.xhtml (Détails utilisateur)
│ │ │ └── search.xhtml (Recherche avancée)
│ │ ├── roles/
│ │ │ ├── list.xhtml (Liste rôles)
│ │ │ ├── edit.xhtml (Formulaire rôle)
│ │ │ └── assign.xhtml (Attribution rôles)
│ │ ├── audit/
│ │ │ ├── logs.xhtml (Logs d'audit)
│ │ │ └── stats.xhtml (Statistiques)
│ │ ├── resources/
│ │ │ ├── css/
│ │ │ │ └── custom.css
│ │ │ └── js/
│ │ │ └── app.js
│ │ └── index.xhtml (Page d'accueil)
│ └── application.properties
└── pom.xml
```
#### B. Configuration POM Client
**Dépendances critiques à ajouter**:
```xml
<dependencies>
<!-- Dépendance sur server-api -->
<dependency>
<groupId>dev.lions.user.manager</groupId>
<artifactId>lions-user-manager-server-api</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Quarkus JSF (MyFaces) -->
<dependency>
<groupId>io.quarkiverse.myfaces</groupId>
<artifactId>quarkus-myfaces</artifactId>
<version>3.0.0</version>
</dependency>
<!-- PrimeFaces -->
<dependency>
<groupId>org.primefaces</groupId>
<artifactId>primefaces</artifactId>
<version>14.0.5</version>
<classifier>jakarta</classifier>
</dependency>
<!-- Freya Theme depuis repo custom -->
<dependency>
<groupId>org.primefaces</groupId>
<artifactId>freya-theme</artifactId>
<version>5.0.0-jakarta</version>
</dependency>
<!-- MicroProfile Rest Client -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-reactive-jackson</artifactId>
</dependency>
<!-- OIDC Client -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc</artifactId>
</dependency>
</dependencies>
<repositories>
<repository>
<id>btpxpress-maven-repo</id>
<url>https://git.lions.dev/lionsdev/btpxpress-maven-repo/raw/branch/main/</url>
</repository>
</repositories>
```
#### C. REST Clients vers Backend
**Exemple**: UserApiClient.java
```java
package dev.lions.user.manager.client.api;
import dev.lions.user.manager.dto.user.UserDTO;
import dev.lions.user.manager.dto.user.UserSearchCriteriaDTO;
import dev.lions.user.manager.dto.user.UserSearchResultDTO;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
@Path("/api/users")
@RegisterRestClient(configKey = "user-api")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public interface UserApiClient {
@POST
@Path("/search")
UserSearchResultDTO searchUsers(UserSearchCriteriaDTO criteria);
@GET
@Path("/{userId}")
UserDTO getUser(@PathParam("userId") String userId,
@QueryParam("realm") String realmName);
@POST
UserDTO createUser(UserDTO user, @QueryParam("realm") String realmName);
@PUT
@Path("/{userId}")
UserDTO updateUser(@PathParam("userId") String userId,
UserDTO user,
@QueryParam("realm") String realmName);
@DELETE
@Path("/{userId}")
void deleteUser(@PathParam("userId") String userId,
@QueryParam("realm") String realmName);
}
```
**Configuration**:
```properties
# application.properties
quarkus.rest-client.user-api.url=http://localhost:8081
quarkus.rest-client.role-api.url=http://localhost:8081
quarkus.rest-client.audit-api.url=http://localhost:8081
```
#### D. JSF Managed Bean Exemple
**UserListBean.java**:
```java
package dev.lions.user.manager.client.bean.user;
import dev.lions.user.manager.client.api.UserApiClient;
import dev.lions.user.manager.dto.user.UserDTO;
import dev.lions.user.manager.dto.user.UserSearchCriteriaDTO;
import dev.lions.user.manager.dto.user.UserSearchResultDTO;
import jakarta.annotation.PostConstruct;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import lombok.Getter;
import lombok.Setter;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import java.io.Serializable;
import java.util.List;
@Named("userListBean")
@ViewScoped
public class UserListBean implements Serializable {
@Inject
@RestClient
UserApiClient userApiClient;
@Getter
@Setter
private List<UserDTO> users;
@Getter
@Setter
private UserDTO selectedUser;
@Getter
@Setter
private String searchKeyword;
@Getter
@Setter
private String currentRealm = "master";
@PostConstruct
public void init() {
loadUsers();
}
public void loadUsers() {
UserSearchCriteriaDTO criteria = UserSearchCriteriaDTO.builder()
.realmName(currentRealm)
.keyword(searchKeyword)
.page(0)
.pageSize(50)
.build();
UserSearchResultDTO result = userApiClient.searchUsers(criteria);
this.users = result.getUsers();
}
public void deleteUser(UserDTO user) {
userApiClient.deleteUser(user.getId(), currentRealm);
loadUsers();
}
public String editUser(UserDTO user) {
return "edit?faces-redirect=true&userId=" + user.getId();
}
}
```
#### E. Page XHTML Exemple
**users/list.xhtml**:
```xhtml
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<ui:composition template="/WEB-INF/template.xhtml">
<ui:define name="content">
<h:form id="userListForm">
<p:panel header="Gestion des Utilisateurs">
<!-- Barre de recherche -->
<div class="p-grid p-mb-3">
<div class="p-col-12 p-md-8">
<p:inputText id="searchKeyword"
value="#{userListBean.searchKeyword}"
placeholder="Rechercher un utilisateur..."
styleClass="p-mr-2" />
<p:commandButton value="Rechercher"
action="#{userListBean.loadUsers}"
update="usersTable"
icon="pi pi-search" />
</div>
<div class="p-col-12 p-md-4 p-text-right">
<p:commandButton value="Nouvel Utilisateur"
action="/users/edit?faces-redirect=true"
icon="pi pi-plus"
styleClass="p-button-success" />
</div>
</div>
<!-- Tableau des utilisateurs -->
<p:dataTable id="usersTable"
value="#{userListBean.users}"
var="user"
paginator="true"
rows="20"
paginatorPosition="bottom"
selectionMode="single"
selection="#{userListBean.selectedUser}"
rowKey="#{user.id}"
emptyMessage="Aucun utilisateur trouvé">
<p:column headerText="Username" sortBy="#{user.username}">
<h:outputText value="#{user.username}" />
</p:column>
<p:column headerText="Email" sortBy="#{user.email}">
<h:outputText value="#{user.email}" />
</p:column>
<p:column headerText="Nom Complet">
<h:outputText value="#{user.prenom} #{user.nom}" />
</p:column>
<p:column headerText="Statut">
<p:tag value="#{user.statut.libelle}"
severity="#{user.statut.name() == 'ACTIF' ? 'success' : 'danger'}" />
</p:column>
<p:column headerText="Dernière Connexion">
<h:outputText value="#{user.derniereConnexion}">
<f:convertDateTime pattern="dd/MM/yyyy HH:mm" />
</h:outputText>
</p:column>
<p:column headerText="Actions" style="width:150px">
<p:commandButton icon="pi pi-pencil"
action="#{userListBean.editUser(user)}"
styleClass="p-button-rounded p-button-warning p-mr-2"
title="Éditer" />
<p:commandButton icon="pi pi-trash"
action="#{userListBean.deleteUser(user)}"
update="usersTable"
styleClass="p-button-rounded p-button-danger"
title="Supprimer">
<p:confirm header="Confirmation"
message="Êtes-vous sûr de vouloir supprimer #{user.username} ?"
icon="pi pi-exclamation-triangle" />
</p:commandButton>
</p:column>
</p:dataTable>
</p:panel>
<p:confirmDialog global="true" showEffect="fade" hideEffect="fade">
<p:commandButton value="Oui" type="button"
styleClass="ui-confirmdialog-yes p-button-success" />
<p:commandButton value="Non" type="button"
styleClass="ui-confirmdialog-no p-button-danger" />
</p:confirmDialog>
</h:form>
</ui:define>
</ui:composition>
</html>
```
#### F. Template Principal
**WEB-INF/template.xhtml**:
```xhtml
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<h:head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Lions User Manager</title>
<h:outputStylesheet name="css/custom.css" />
</h:head>
<h:body>
<p:layout fullPage="true">
<!-- Top Bar -->
<p:layoutUnit position="north" size="60">
<div class="topbar">
<h1>Lions User Manager</h1>
<div class="user-info">
<span>#{userSessionBean.currentUserName}</span>
<p:commandButton value="Déconnexion"
action="#{userSessionBean.logout}"
icon="pi pi-sign-out" />
</div>
</div>
</p:layoutUnit>
<!-- Left Menu -->
<p:layoutUnit position="west" size="250">
<ui:include src="menu.xhtml" />
</p:layoutUnit>
<!-- Content -->
<p:layoutUnit position="center">
<div class="content-wrapper">
<p:messages id="messages" showDetail="true" closable="true" />
<ui:insert name="content" />
</div>
</p:layoutUnit>
</p:layout>
</h:body>
</html>
```
#### G. Configuration JSF
**WEB-INF/web.xml**:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
<display-name>Lions User Manager</display-name>
<!-- JSF Servlet -->
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>jakarta.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
<!-- PrimeFaces Configuration -->
<context-param>
<param-name>primefaces.THEME</param-name>
<param-value>freya</param-value>
</context-param>
<context-param>
<param-name>primefaces.FONT_AWESOME</param-name>
<param-value>true</param-value>
</context-param>
<!-- JSF Configuration -->
<context-param>
<param-name>jakarta.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<context-param>
<param-name>jakarta.faces.FACELETS_REFRESH_PERIOD</param-name>
<param-value>0</param-value>
</context-param>
<welcome-file-list>
<welcome-file>index.xhtml</welcome-file>
</welcome-file-list>
</web-app>
```
**WEB-INF/faces-config.xml**:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-facesconfig_4_0.xsd"
version="4.0">
<application>
<locale-config>
<default-locale>fr</default-locale>
<supported-locale>fr</supported-locale>
<supported-locale>en</supported-locale>
</locale-config>
<resource-bundle>
<base-name>i18n.messages</base-name>
<var>msg</var>
</resource-bundle>
</application>
</faces-config>
```
### 7.5 Infrastructure - Kubernetes et Helm
#### A. Dockerfile pour Server-Impl
```dockerfile
# server-impl/src/main/docker/Dockerfile.jvm
FROM registry.access.redhat.com/ubi8/openjdk-17:1.18
ENV LANGUAGE='en_US:en'
# Configure JVM
ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
# Copy application
COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/
COPY --chown=185 target/quarkus-app/*.jar /deployments/
COPY --chown=185 target/quarkus-app/app/ /deployments/app/
COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/
EXPOSE 8080
USER 185
ENTRYPOINT [ "java", "-jar", "/deployments/quarkus-run.jar" ]
```
#### B. Helm Chart Structure
```
helm/
├── Chart.yaml
├── values.yaml
├── values-dev.yaml
├── values-prod.yaml
└── templates/
├── _helpers.tpl
├── deployment-server.yaml
├── deployment-client.yaml
├── service-server.yaml
├── service-client.yaml
├── ingress.yaml
├── configmap.yaml
├── secret.yaml
├── serviceaccount.yaml
└── hpa.yaml
```
**Chart.yaml**:
```yaml
apiVersion: v2
name: lions-user-manager
description: Système de gestion utilisateurs Keycloak
type: application
version: 1.0.0
appVersion: "1.0.0"
keywords:
- keycloak
- user-management
- quarkus
- primefaces
maintainers:
- name: Lions Dev Team
email: dev@lions.dev
```
**values.yaml**:
```yaml
# Configuration globale
global:
environment: production
domain: lions.dev
# Server Backend
server:
enabled: true
replicaCount: 2
image:
repository: registry.lions.dev/lions-user-manager-server
tag: 1.0.0
pullPolicy: IfNotPresent
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
service:
type: ClusterIP
port: 8080
env:
- name: QUARKUS_PROFILE
value: "prod"
- name: KEYCLOAK_URL
value: "https://security.lions.dev"
- name: KEYCLOAK_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: lions-user-manager-secrets
key: keycloak-client-secret
- name: DB_HOST
value: "postgresql.lions.svc.cluster.local"
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: lions-user-manager-secrets
key: db-username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: lions-user-manager-secrets
key: db-password
# Client Frontend
client:
enabled: true
replicaCount: 2
image:
repository: registry.lions.dev/lions-user-manager-client
tag: 1.0.0
pullPolicy: IfNotPresent
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
service:
type: ClusterIP
port: 8080
# Ingress
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
hosts:
- host: user-manager.lions.dev
paths:
- path: /
pathType: Prefix
backend: client
- path: /api
pathType: Prefix
backend: server
tls:
- secretName: user-manager-tls
hosts:
- user-manager.lions.dev
# PostgreSQL (optionnel si base externe)
postgresql:
enabled: true
auth:
username: audit_user
password: changeme
database: lions_audit
primary:
persistence:
enabled: true
size: 10Gi
```
**templates/deployment-server.yaml**:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "lions-user-manager.fullname" . }}-server
labels:
{{- include "lions-user-manager.labels" . | nindent 4 }}
app.kubernetes.io/component: server
spec:
{{- if not .Values.server.autoscaling.enabled }}
replicas: {{ .Values.server.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "lions-user-manager.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: server
template:
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/q/metrics"
labels:
{{- include "lions-user-manager.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: server
spec:
serviceAccountName: {{ include "lions-user-manager.serviceAccountName" . }}
containers:
- name: server
image: "{{ .Values.server.image.repository }}:{{ .Values.server.image.tag }}"
imagePullPolicy: {{ .Values.server.image.pullPolicy }}
ports:
- name: http
containerPort: 8080
protocol: TCP
env:
{{- toYaml .Values.server.env | nindent 12 }}
livenessProbe:
httpGet:
path: /q/health/live
port: http
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /q/health/ready
port: http
initialDelaySeconds: 10
periodSeconds: 5
resources:
{{- toYaml .Values.server.resources | nindent 12 }}
```
**Installation Helm**:
```bash
# Dev
helm install lions-user-manager ./helm -f ./helm/values-dev.yaml --namespace lions-dev
# Prod
helm install lions-user-manager ./helm -f ./helm/values-prod.yaml --namespace lions-prod
```
### 7.6 Scripts Keycloak - Provisioning
#### A. Script d'initialisation Keycloak
**scripts/keycloak-init.sh**:
```bash
#!/bin/bash
set -e
# Configuration
KEYCLOAK_URL=${KEYCLOAK_URL:-http://localhost:8180}
ADMIN_USER=${ADMIN_USER:-admin}
ADMIN_PASSWORD=${ADMIN_PASSWORD:-admin}
echo "🚀 Initialisation de Keycloak pour Lions User Manager"
# 1. Obtenir token admin
echo "📝 Obtention du token admin..."
TOKEN=$(curl -s -X POST "${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=${ADMIN_USER}" \
-d "password=${ADMIN_PASSWORD}" \
-d "grant_type=password" \
-d "client_id=admin-cli" \
| jq -r '.access_token')
if [ -z "$TOKEN" ] || [ "$TOKEN" == "null" ]; then
echo "❌ Erreur: Impossible d'obtenir le token admin"
exit 1
fi
echo "✅ Token obtenu"
# 2. Créer le realm lions (si n'existe pas)
echo "📝 Création du realm 'lions'..."
curl -s -X POST "${KEYCLOAK_URL}/admin/realms" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"realm": "lions",
"enabled": true,
"displayName": "Lions Realm",
"registrationAllowed": false,
"resetPasswordAllowed": true,
"rememberMe": true,
"verifyEmail": true,
"loginWithEmailAllowed": true,
"duplicateEmailsAllowed": false,
"sslRequired": "external",
"passwordPolicy": "length(8) and digits(1) and lowerCase(1) and upperCase(1) and specialChars(1)",
"accessTokenLifespan": 3600,
"ssoSessionIdleTimeout": 1800,
"ssoSessionMaxLifespan": 36000
}' || echo "⚠️ Realm 'lions' existe probablement déjà"
# 3. Créer le client lions-user-manager
echo "📝 Création du client 'lions-user-manager'..."
CLIENT_ID=$(curl -s -X POST "${KEYCLOAK_URL}/admin/realms/lions/clients" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"clientId": "lions-user-manager",
"name": "Lions User Manager",
"enabled": true,
"protocol": "openid-connect",
"publicClient": false,
"standardFlowEnabled": true,
"directAccessGrantsEnabled": true,
"serviceAccountsEnabled": true,
"authorizationServicesEnabled": true,
"redirectUris": [
"http://localhost:8082/*",
"https://user-manager.lions.dev/*"
],
"webOrigins": ["+"],
"attributes": {
"access.token.lifespan": "3600",
"client.secret.creation.time": "0"
}
}' \
-w "%{http_code}" -o /tmp/client-response.json)
if [ "$CLIENT_ID" == "201" ]; then
CLIENT_UUID=$(jq -r '.id' /tmp/client-response.json)
echo "✅ Client créé avec UUID: $CLIENT_UUID"
# Récupérer le secret du client
CLIENT_SECRET=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/lions/clients/${CLIENT_UUID}/client-secret" \
-H "Authorization: Bearer ${TOKEN}" \
| jq -r '.value')
echo "🔑 Client Secret: ${CLIENT_SECRET}"
echo " ⚠️ Sauvegardez ce secret dans vos variables d'environnement!"
else
echo "⚠️ Client existe probablement déjà"
fi
# 4. Créer les rôles realm
echo "📝 Création des rôles realm..."
for role in admin user_manager role_manager auditor sync_manager role_viewer; do
curl -s -X POST "${KEYCLOAK_URL}/admin/realms/lions/roles" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"name\": \"${role}\", \"description\": \"Role ${role}\"}" \
&& echo " ✅ Rôle '${role}' créé" \
|| echo " ⚠️ Rôle '${role}' existe déjà"
done
# 5. Créer un utilisateur admin de test
echo "📝 Création de l'utilisateur admin de test..."
curl -s -X POST "${KEYCLOAK_URL}/admin/realms/lions/users" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"username": "admin@lions.dev",
"email": "admin@lions.dev",
"firstName": "Admin",
"lastName": "Lions",
"enabled": true,
"emailVerified": true,
"credentials": [{
"type": "password",
"value": "Admin@2025",
"temporary": false
}]
}' && echo "✅ Utilisateur admin créé" || echo "⚠️ Utilisateur admin existe déjà"
# 6. Assigner le rôle admin à l'utilisateur
echo "📝 Attribution du rôle admin..."
USER_ID=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/lions/users?username=admin@lions.dev" \
-H "Authorization: Bearer ${TOKEN}" \
| jq -r '.[0].id')
ADMIN_ROLE=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/lions/roles/admin" \
-H "Authorization: Bearer ${TOKEN}")
curl -s -X POST "${KEYCLOAK_URL}/admin/realms/lions/users/${USER_ID}/role-mappings/realm" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d "[${ADMIN_ROLE}]" \
&& echo "✅ Rôle admin attribué" || echo "⚠️ Erreur attribution rôle"
echo ""
echo "🎉 Initialisation Keycloak terminée!"
echo ""
echo "📋 Informations de connexion:"
echo " URL: ${KEYCLOAK_URL}"
echo " Realm: lions"
echo " Client ID: lions-user-manager"
echo " Client Secret: ${CLIENT_SECRET:-<voir ci-dessus>}"
echo " Utilisateur test: admin@lions.dev / Admin@2025"
```
**Exécution**:
```bash
chmod +x scripts/keycloak-init.sh
./scripts/keycloak-init.sh
```
#### B. Realm Export JSON
**config/lions-realm-export.json**:
```json
{
"realm": "lions",
"enabled": true,
"displayName": "Lions Realm",
"roles": {
"realm": [
{"name": "admin", "description": "Administrateur complet"},
{"name": "user_manager", "description": "Gestionnaire d'utilisateurs"},
{"name": "role_manager", "description": "Gestionnaire de rôles"},
{"name": "auditor", "description": "Auditeur (lecture seule)"},
{"name": "sync_manager", "description": "Gestionnaire de synchronisation"},
{"name": "role_viewer", "description": "Visualiseur de rôles"}
]
},
"clients": [
{
"clientId": "lions-user-manager",
"name": "Lions User Manager",
"enabled": true,
"protocol": "openid-connect",
"publicClient": false,
"standardFlowEnabled": true,
"directAccessGrantsEnabled": true,
"serviceAccountsEnabled": true,
"authorizationServicesEnabled": true,
"redirectUris": [
"http://localhost:8082/*",
"https://user-manager.lions.dev/*"
],
"webOrigins": ["+"]
}
]
}
```
**Import**:
```bash
# Avec docker
docker exec -it keycloak /opt/keycloak/bin/kc.sh import \
--file /opt/keycloak/data/import/lions-realm-export.json
# Avec commande locale
./kc.sh import --file config/lions-realm-export.json
```
---
## 8. SPÉCIFICATIONS TECHNIQUES DÉTAILLÉES
### 8.1 Contraintes et Règles Métier
#### A. Gestion Utilisateurs
**Règles de validation**:
- Username: 3-50 caractères, alphanumérique + _ -
- Email: format RFC 5322
- Mot de passe: min 8 caractères, 1 maj, 1 min, 1 chiffre, 1 spécial
- Téléphone (France): format +33XXXXXXXXX ou 0XXXXXXXXX
**États utilisateur** (enum StatutUser):
```
ACTIF → Peut se connecter
INACTIF → Désactivé temporairement
SUSPENDU → Suspendu par admin
EN_ATTENTE → Attend validation
VERROUILLE → Verrouillé après X tentatives
EXPIRE → Compte expiré
SUPPRIME → Suppression logique
```
**Règles métier**:
- Un utilisateur VERROUILLE peut être déverrouillé par un admin
- Un utilisateur SUSPENDU ne peut être réactivé que par un admin
- La suppression est TOUJOURS logique (soft delete), jamais physique
- Lors de la désactivation, toutes les sessions doivent être révoquées
#### B. Gestion Rôles
**Types de rôles** (enum TypeRole):
```
REALM_ROLE → Rôle au niveau du realm
CLIENT_ROLE → Rôle spécifique à un client
COMPOSITE_ROLE → Rôle composite (contient d'autres rôles)
```
**Règles métier**:
- Un rôle composite ne peut pas être supprimé s'il est utilisé
- L'attribution de rôles doit être auditée
- Les rôles système (admin, user) ne peuvent être supprimés
- Un utilisateur peut avoir plusieurs rôles realm ET plusieurs rôles client
#### C. Audit Trail
**Actions auditées** (enum TypeActionAudit):
```
USER_CREATE, USER_UPDATE, USER_DELETE
USER_ACTIVATE, USER_DEACTIVATE, USER_LOCK, USER_UNLOCK
PASSWORD_RESET, PASSWORD_CHANGE
ROLE_CREATE, ROLE_UPDATE, ROLE_DELETE
ROLE_ASSIGN, ROLE_REVOKE
SESSION_LOGOUT, SESSION_REVOKE_ALL
SYNC_USERS, SYNC_ROLES
```
**Informations obligatoires**:
- Qui: username de l'acteur
- Quoi: type d'action
- Quand: timestamp précis
- Sur quoi: type et ID de la ressource
- Résultat: succès ou échec
- Où: adresse IP de l'acteur
**Rétention**:
- Dev: 30 jours
- Prod: 2 ans minimum (contrainte légale)
### 8.2 Sécurité
#### A. Authentication & Authorization
**Flow OIDC**:
```
1. Utilisateur → Frontend → Redirect vers Keycloak
2. Keycloak → Authentification → Génère ID Token + Access Token
3. Frontend → Stocke tokens (HttpOnly cookies)
4. Frontend → API calls avec Access Token dans header Authorization
5. Backend → Valide token avec Keycloak
6. Backend → Vérifie rôles (@RolesAllowed)
7. Backend → Execute action + Audit log
```
**Matrice de permissions**:
```
Rôle | Users | Roles | Audit | Sync
-----------------+-------+-------+-------+------
admin | RWD | RWD | RWD | RWD
user_manager | RWD | - | R | -
role_manager | R | RWD | R | -
auditor | R | R | R | -
sync_manager | R | R | R | RWD
role_viewer | - | R | - | -
R = Read, W = Write, D = Delete
```
#### B. Validation des Entrées
**Côté Backend (obligatoire)**:
- Jakarta Bean Validation sur tous les DTOs
- Custom validators pour règles complexes
- Sanitization des inputs pour prévenir XSS/SQL injection
**Côté Frontend (UX)**:
- Validation JavaScript pour feedback immédiat
- Messages d'erreur contextuels
- Boutons submit désactivés si formulaire invalide
#### C. Protection CSRF
**Backend**:
- Validation token CSRF sur endpoints state-changing (POST, PUT, DELETE)
- Token inclus dans JWT
**Frontend**:
- Token CSRF dans meta tag
- Ajouté automatiquement dans headers Ajax
### 8.3 Performance
#### A. Objectifs de Performance
**Backend**:
- Temps réponse API: < 200ms (p95)
- Throughput: > 1000 req/s
- Taux erreur: < 0.1%
**Frontend**:
- Time to Interactive: < 3s
- First Contentful Paint: < 1.5s
- Largest Contentful Paint: < 2.5s
#### B. Optimisations
**Backend**:
- Cache des rôles (TTL 5min)
- Cache des users (TTL 2min)
- Connection pooling Keycloak
- Database connection pooling (HikariCP)
- Pagination systématique (max 100 items)
**Frontend**:
- Lazy loading des composants PrimeFaces
- Compression Gzip/Brotli
- Cache statique (CSS, JS, images)
- CDN pour Freya theme
### 8.4 Monitoring et Alerting
#### A. Métriques Clés
**Métriques applicatives**:
```
lions_user_creation_total Counter
lions_user_creation_failures Counter
lions_user_creation_duration_ms Histogram
lions_keycloak_api_calls_total Counter
lions_keycloak_api_errors_total Counter
lions_audit_logs_total Counter
```
**Métriques système**:
```
jvm_memory_used_bytes
jvm_threads_current
http_server_requests_seconds
```
#### B. Alertes Critiques
```
Alert: KeycloakDown
Condition: up{job="keycloak"} == 0
Severity: Critical
Action: Page on-call
Alert: HighErrorRate
Condition: rate(lions_user_creation_failures[5m]) > 0.1
Severity: Warning
Action: Notify team
Alert: SlowResponse
Condition: histogram_quantile(0.95, lions_user_creation_duration_ms) > 500
Severity: Warning
Action: Investigate performance
```
#### C. Logs Centralisés
**Format JSON structuré**:
```json
{
"timestamp": "2025-11-09T14:30:00Z",
"level": "INFO",
"logger": "UserServiceImpl",
"message": "User created",
"userId": "123-456",
"username": "jdoe",
"acteur": "admin@lions.dev",
"realmName": "lions",
"duration_ms": 45
}
```
**Stack recommandé**:
- Filebeat collect logs
- Logstash parse et enrich
- Elasticsearch store
- Kibana visualize
---
## 9. COMMANDES UTILES
### 9.1 Build et Compilation
```bash
# Compilation complète
cd C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager
mvn clean compile -DskipTests
# Compilation avec tests
mvn clean test
# Package (JAR)
mvn clean package
# Package natif (GraalVM - long!)
mvn clean package -Pnative
# Install dans repo Maven local
mvn clean install
```
### 9.2 Exécution
```bash
# Dev mode avec hot reload
cd lions-user-manager-server-impl-quarkus
mvn quarkus:dev
# Dev mode avec profil dev
mvn quarkus:dev -Dquarkus.profile=dev
# Dev mode avec debug port 5005
mvn quarkus:dev -Ddebug=5005
# Exécution du JAR packagé
java -jar target/quarkus-app/quarkus-run.jar
# Exécution avec profil prod
java -jar target/quarkus-app/quarkus-run.jar -Dquarkus.profile=prod
```
### 9.3 Tests
```bash
# Tous les tests
mvn test
# Tests d'un module spécifique
cd lions-user-manager-server-impl-quarkus
mvn test
# Test d'une classe spécifique
mvn test -Dtest=UserServiceImplTest
# Test d'une méthode spécifique
mvn test -Dtest=UserServiceImplTest#testCreateUser
# Tests d'intégration uniquement
mvn verify -Pintegration-tests
# Coverage Jacoco
mvn clean test jacoco:report
# Rapport: target/site/jacoco/index.html
```
### 9.4 Git
```bash
# Status de tous les modules
cd C:\Users\dadyo\PersonalProjects\lions-workspace\lions-user-manager
git status
# Commit et push repository master
git add .
git commit -m "feat: Description"
git push origin main
# Commit et push sous-module server-api
cd lions-user-manager-server-api
git add .
git commit -m "feat: Description"
git push origin main
cd ..
# Commit et push sous-module server-impl
cd lions-user-manager-server-impl-quarkus
git add .
git commit -m "feat: Description"
git push origin main
cd ..
# Commit et push sous-module client
cd lions-user-manager-client-quarkus-primefaces-freya
git add .
git commit -m "feat: Description"
git push origin main
cd ..
# Puis mettre à jour le master
git add .
git commit -m "chore: Sync submodules"
git push origin main
```
### 9.5 Docker
```bash
# Build image Docker
docker build -f src/main/docker/Dockerfile.jvm -t lions-user-manager-server:1.0.0 .
# Run container
docker run -p 8081:8080 \
-e KEYCLOAK_URL=http://keycloak:8080 \
-e KEYCLOAK_CLIENT_SECRET=secret \
lions-user-manager-server:1.0.0
# Docker Compose (si créé)
docker-compose up -d
# Voir logs
docker logs -f lions-user-manager-server
```
### 9.6 Kubernetes
```bash
# Apply manifests
kubectl apply -f k8s/
# Helm install dev
helm install lions-user-manager ./helm -f ./helm/values-dev.yaml -n lions-dev
# Helm upgrade
helm upgrade lions-user-manager ./helm -f ./helm/values-prod.yaml -n lions-prod
# Check pods
kubectl get pods -n lions-prod
# Logs d'un pod
kubectl logs -f deployment/lions-user-manager-server -n lions-prod
# Port-forward pour debug
kubectl port-forward svc/lions-user-manager-server 8081:8080 -n lions-prod
```
### 9.7 Keycloak Admin
```bash
# Obtenir token admin
curl -X POST http://localhost:8180/realms/master/protocol/openid-connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=admin" \
-d "password=admin" \
-d "grant_type=password" \
-d "client_id=admin-cli"
# Lister les users d'un realm
curl -X GET http://localhost:8180/admin/realms/lions/users \
-H "Authorization: Bearer ${TOKEN}"
# Créer un user
curl -X POST http://localhost:8180/admin/realms/lions/users \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{"username": "test", "email": "test@example.com", "enabled": true}'
```
### 9.8 OpenAPI / Swagger
```bash
# Accéder à Swagger UI (en dev mode)
http://localhost:8081/q/swagger-ui
# Télécharger spec OpenAPI
curl http://localhost:8081/q/openapi -o openapi.json
```
---
## 10. CHECKLIST FINALE POUR LE PROCHAIN AGENT
### ✅ Actions Immédiates (1-2 heures)
- [ ] Lire entièrement ce document HANDOFF_COMPLET.md
- [ ] Vérifier que tous les fichiers sont présents dans le workspace
- [ ] Compiler le projet: `mvn clean compile -DskipTests`
- [ ] **Corriger les erreurs de compilation** (voir section 3)
- [ ] RoleMapper: name vs nom
- [ ] RoleServiceImpl: signatures méthodes
- [ ] AuditService: compléter interface
- [ ] SyncService: créer DTOs
- [ ] Re-compiler pour vérifier: `mvn clean compile -DskipTests`
- [ ] Commit et push les corrections
### 📊 Phase 1: Stabilisation Backend (3-5 heures)
- [ ] Créer tests unitaires de base (30% coverage minimum)
- [ ] Créer tests d'intégration avec Testcontainers
- [ ] Implémenter persistence audit PostgreSQL + Panache
- [ ] Migrer vers MapStruct pour les mappers
- [ ] Ajouter cache Quarkus pour réduire appels Keycloak
- [ ] Vérifier coverage Jacoco: `mvn test jacoco:report`
- [ ] Commit et push
### 🎨 Phase 2: Module Client (10-15 heures)
- [ ] Configurer POM avec PrimeFaces + Freya
- [ ] Créer REST Clients (@RegisterRestClient)
- [ ] Créer template principal + menu
- [ ] Créer pages Users (list, edit, detail, search)
- [ ] Créer pages Roles (list, edit, assign)
- [ ] Créer pages Audit (logs, stats)
- [ ] Tester interface en local
- [ ] Commit et push
### 🚀 Phase 3: Infrastructure (8-12 heures)
- [ ] Créer Dockerfiles optimisés
- [ ] Créer Helm charts complets
- [ ] Créer scripts Keycloak provisioning
- [ ] Tester déploiement Kubernetes local (minikube/kind)
- [ ] Créer pipeline CI/CD (GitHub Actions ou GitLab CI)
- [ ] Documenter procédure de déploiement
- [ ] Commit et push
### 📈 Phase 4: Monitoring & Docs (5-8 heures)
- [ ] Implémenter métriques Prometheus custom
- [ ] Configurer tracing OpenTelemetry
- [ ] Créer dashboards Grafana
- [ ] Configurer alertes critiques
- [ ] Écrire documentation utilisateur
- [ ] Créer guide d'administration
- [ ] Créer guide de contribution développeurs
- [ ] Commit et push
### 🎯 Phase 5: Production Readiness (3-5 heures)
- [ ] Load testing avec Gatling ou k6
- [ ] Security scanning (OWASP ZAP, Snyk)
- [ ] Performance tuning
- [ ] Backup/restore procedures
- [ ] Disaster recovery plan
- [ ] Runbook pour on-call
- [ ] Final QA testing
- [ ] Release 1.0.0 🎉
---
## 11. CONTACTS ET RESSOURCES
### Documentation Externe
**Quarkus**:
- Guide: https://quarkus.io/guides/
- REST Client: https://quarkus.io/guides/rest-client
- OIDC: https://quarkus.io/guides/security-oidc-code-flow-authentication
**Keycloak**:
- Admin REST API: https://www.keycloak.org/docs-api/23.0.3/rest-api/index.html
- Admin Client Java: https://www.keycloak.org/docs/latest/server_development/#admin-rest-api
**PrimeFaces**:
- Showcase: https://www.primefaces.org/showcase/
- Freya Theme: (repository custom lions.dev)
**Testcontainers**:
- Keycloak Module: https://java.testcontainers.org/modules/databases/keycloak/
### Repositories Git
- Master: https://git.lions.dev/lionsdev/lions-user-manager
- Server-API: https://git.lions.dev/lionsdev/lions-user-manager-server-api
- Server-Impl: https://git.lions.dev/lionsdev/lions-user-manager-server-impl-quarkus
- Client: https://git.lions.dev/lionsdev/lions-user-manager-client-quarkus-primefaces-freya
- Maven Repo: https://git.lions.dev/lionsdev/btpxpress-maven-repo
---
## 🎯 OBJECTIF FINAL
**Livrer un système de gestion utilisateurs Keycloak production-ready** comprenant:
1. Backend REST API complet et testé (80% coverage)
2. Frontend PrimeFaces ergonomique et responsive
3. Audit trail complet de toutes les opérations
4. Déploiement Kubernetes via Helm
5. Monitoring et alerting opérationnels
6. Documentation complète (technique + utilisateur)
7. CI/CD pipeline automatisé
**Durée estimée restante**: 40-60 heures de développement
**Priorité absolue**: Corriger les erreurs de compilation AVANT toute autre tâche!
---
## 📝 NOTES FINALES
Ce document contient TOUTES les informations nécessaires pour continuer le développement de manière autonome.
**Points d'attention critiques**:
1. Ne JAMAIS accéder directement à la DB Keycloak
2. Toujours auditer les actions sensibles
3. Respecter la structure Git multi-repository
4. Tester avec Testcontainers avant déploiement
5. Maintenir 80% de test coverage minimum
**En cas de blocage**, se référer aux sections:
- Section 3 pour les erreurs de compilation
- Section 7 pour les implémentations détaillées
- Section 9 pour les commandes utiles
Bon développement! 🚀
---
**Document généré le**: 2025-11-09
**Par**: Agent IA Claude (Anthropic)
**Pour**: Continuation projet lions-user-manager
**Version**: 1.0.0