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