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>
79 KiB
🔄 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
- Vue d'ensemble du projet
- État actuel détaillé
- Erreurs de compilation à corriger
- Architecture et décisions techniques
- Configuration Git
- Tâches prioritaires
- Tâches complètes restantes
- Spécifications techniques détaillées
- 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:
KeycloakAdminClient.java- InterfaceKeycloakAdminClientImpl.java- Implémentation avec résilienceUserMapper.java- Conversions UserDTO <-> KeycloakUserServiceImpl.java- Service utilisateurs (25+ méthodes)UserResource.java- REST API users (12 endpoints)KeycloakHealthCheck.java- Health check KeycloakHealthResourceEndpoint.java- Endpoint health
Fichiers avec erreurs de compilation:
RoleMapper.java- Utilise getNom() au lieu de getName()RoleServiceImpl.java- Signatures méthodes incompatibles avec interfaceAuditServiceImpl.java- Méthodes pas dans l'interfaceSyncServiceImpl.java- Méthodes incompatiblesRoleResource.java- Appelle méthodes inexistantesAuditResource.java- Appelle méthodes inexistantesSyncResource.java- Probablement OK mais dépend de SyncServiceImpl
2.2 Configuration Git ✅
4 repositories configurés:
- master: https://git.lions.dev/lionsdev/lions-user-manager.git
- server-api: https://git.lions.dev/lionsdev/lions-user-manager-server-api.git
- server-impl: https://git.lions.dev/lionsdev/lions-user-manager-server-impl-quarkus.git
- 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
// RoleDTO.java:38
private String name; // ✅ CORRECT dans le DTO
Fichiers utilisant incorrectement getNom():
RoleMapper.javalignes 25, 43RoleServiceImpl.javalignes 170, 179RoleResource.javalignes 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:
.nom(roleRep.getName()) // ❌ ERREUR
Correction:
.name(roleRep.getName()) // ✅ CORRECT
Erreur #3: RoleMapper - Test null incorrect
Fichier: RoleMapper.java ligne 29
Code actuel:
.composite(roleRep.isComposite() != null ? roleRep.isComposite() : false) // ❌ ERREUR
Problème: isComposite() retourne boolean, pas Boolean
Correction:
.composite(roleRep.isComposite()) // ✅ CORRECT
Erreur #4: RoleServiceImpl - Signatures incompatibles avec interface
Interface RoleService attend:
// 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:
// 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:
- Option A (RECOMMANDÉE): Modifier
RoleServiceImplpour implémenter EXACTEMENT les méthodes de l'interface - Option B: Modifier l'interface
RoleService.javapour correspondre à l'implémentation (moins propre)
Erreur #5: AuditService - Méthodes manquantes dans l'interface
AuditServiceImpl utilise:
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:
- Soit déplacer ces classes dans le module
server-apicomme DTOs séparés - 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:
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:
List<RoleRepresentation> composites = roleResource.getRoleComposites(); // ❌ Retourne Set
Correction:
List<RoleRepresentation> composites = new ArrayList<>(roleResource.getRoleComposites());
3.2 Plan de Correction Étape par Étape
ÉTAPE 1: Corriger RoleMapper (5 minutes)
# 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
// 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)
// 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)
# 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)
List<RoleRepresentation> composites = new ArrayList<>(
keycloakAdminClient.getInstance()
.realm(realmName)
.roles()
.get(roleName)
.getRoleComposites()
);
ÉTAPE 6: Compiler et vérifier (5 minutes)
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:
@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:
<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 baseapplication-dev.properties- Localhost, DEBUG logsapplication-prod.properties- HTTPS, DB obligatoire, SSL/TLS
Activation:
# Dev
mvn quarkus:dev -Dquarkus.profile=dev
# Prod
java -jar app.jar -Dquarkus.profile=prod
4.2 Contraintes Critiques du Projet
- ZÉRO accès direct DB Keycloak - Utiliser UNIQUEMENT Keycloak Admin REST API
- Multi-realm support - Toutes les opérations prennent
realmNameen paramètre - Audit trail complet - Qui, quoi, quand, IP, succès/échec pour TOUTES les opérations
- Test coverage 80% - Objectif Jacoco avec Testcontainers
- Helm charts requis - Déploiement Kubernetes
- Freya theme custom - Depuis repository Git lions.dev
4.3 Patterns de Code Utilisés
Pattern DTO
@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
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:
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:
# 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:
- ✅ Corriger RoleMapper (name vs nom)
- ✅ Aligner RoleService interface et implémentation
- ✅ Compléter AuditService interface
- ✅ Créer DTOs pour SyncService
- ✅ Corriger RoleServiceImpl Set→List
- ✅ Vérifier compilation:
mvn clean compile -DskipTests - ✅ Commit et push
Commande de vérification:
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:
@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:
@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:
- Créer l'entité Panache:
// 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;
}
- Créer le mapper entité:
// server-impl/.../mapper/AuditLogMapper.java
public class AuditLogMapper {
public static AuditLogEntity toEntity(AuditLogDTO dto) { }
public static AuditLogDTO toDTO(AuditLogEntity entity) { }
}
- Créer le repository Panache:
// 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);
}
}
- Modifier AuditServiceImpl:
@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
}
- Créer migration Flyway:
-- 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);
- Configurer datasource:
# 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:
- Configurer annotation processor dans POM:
<!-- Déjà présent dans parent POM, à vérifier -->
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
- Créer mapper UserMapper avec MapStruct:
@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:
- Ajouter dépendance:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-cache</artifactId>
</dependency>
- Annoter méthodes:
@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
}
}
- Configurer cache:
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:
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:
- Tester avec vrai Keycloak
- Ajouter JWT claims mapping:
@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();
}
}
- Utiliser dans audit logging:
@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
@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:
@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:
@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:
@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:
curl http://localhost:8081/q/metrics
B. Tracing OpenTelemetry
Dépendance à ajouter:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-opentelemetry</artifactId>
</dependency>
Configuration:
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:
<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
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:
# 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:
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:
<!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:
<!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 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 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
# 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:
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:
# 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:
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:
# 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:
#!/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:
chmod +x scripts/keycloak-init.sh
./scripts/keycloak-init.sh
B. Realm Export JSON
config/lions-realm-export.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:
# 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é:
{
"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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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:
- ✅ Backend REST API complet et testé (80% coverage)
- ✅ Frontend PrimeFaces ergonomique et responsive
- ✅ Audit trail complet de toutes les opérations
- ✅ Déploiement Kubernetes via Helm
- ✅ Monitoring et alerting opérationnels
- ✅ Documentation complète (technique + utilisateur)
- ✅ 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:
- Ne JAMAIS accéder directement à la DB Keycloak
- Toujours auditer les actions sensibles
- Respecter la structure Git multi-repository
- Tester avec Testcontainers avant déploiement
- 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