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

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

  1. Vue d'ensemble du projet
  2. État actuel détaillé
  3. Erreurs de compilation à corriger
  4. Architecture et décisions techniques
  5. Configuration Git
  6. Tâches prioritaires
  7. Tâches complètes restantes
  8. Spécifications techniques détaillées
  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

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

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

  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:

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:

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 base
  • application-dev.properties - Localhost, DEBUG logs
  • application-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

  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

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

  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:

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:

  1. 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;
}
  1. 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) { }
}
  1. 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);
    }
}
  1. 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
}
  1. 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);
  1. 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:

  1. 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>
  1. 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:

  1. Ajouter dépendance:
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-cache</artifactId>
</dependency>
  1. 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
    }
}
  1. 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:

  1. Tester avec vrai Keycloak
  2. 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();
    }
}
  1. 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:

Keycloak:

PrimeFaces:

Testcontainers:

Repositories Git


🎯 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