feat(server-api): ajout interfaces API JAX-RS, nouveaux DTOs (Sync, Common, Auth) et enrichissement des existants

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
lionsdev
2026-02-18 03:27:41 +00:00
parent e45a95505a
commit dcae4756ac
20 changed files with 902 additions and 115 deletions

109
.gitignore vendored
View File

@@ -1,109 +0,0 @@
# ============================================================================
# Lions User Manager - Server API - .gitignore
# ============================================================================
# Maven
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
# Build artifacts
*.jar
*.war
*.ear
*.class
*.idx
# Eclipse
.project
.classpath
.settings/
.metadata/
bin/
# IntelliJ IDEA
.idea/
*.iml
*.iws
*.ipr
out/
# NetBeans
nbproject/
nbbuild/
nbdist/
.nb-gradle/
# VS Code
.vscode/
*.code-workspace
# Mac
.DS_Store
# Windows
Thumbs.db
ehthumbs.db
Desktop.ini
# Logs
logs/
*.log
*.log.*
hs_err_pid*.log
# Temporary files
*.tmp
*.bak
*.swp
*~
*.orig
# Test files and reports
test_output*.txt
surefire-reports/
failsafe-reports/
*.dump
*.dumpstream
# Test coverage
.jacoco/
jacoco.exec
coverage/
target/site/jacoco/
# Generated sources
generated-sources/
generated-test-sources/
# Maven status
maven-status/
# Build metrics
build-metrics.json
# IDE specific
*.sublime-project
*.sublime-workspace
# OS specific
.DS_Store?
._*
.Spotlight-V100
.Trashes
# Lombok configuration (généré automatiquement)
lombok.config
# Environment files
.env
.env.local
.env.*.local

6
lombok.config Normal file
View File

@@ -0,0 +1,6 @@
# This file configures Lombok for the project
# See https://projectlombok.org/features/configuration
# Add @Generated annotation to all generated code
# This allows JaCoCo to automatically exclude Lombok-generated code from coverage
lombok.addLombokGeneratedAnnotation = true

View File

@@ -0,0 +1,111 @@
package dev.lions.user.manager.api;
import dev.lions.user.manager.dto.audit.AuditLogDTO;
import dev.lions.user.manager.dto.common.CountDTO;
import dev.lions.user.manager.enums.audit.TypeActionAudit;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.util.List;
import java.util.Map;
/**
* Interface JAX-RS pour l'API d'audit.
*/
@Path("/api/audit")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Audit", description = "Consultation des logs d'audit et statistiques")
public interface AuditResourceApi {
@POST
@Path("/search")
@Operation(summary = "Rechercher des logs d'audit", description = "Recherche avancée de logs selon critères")
@APIResponses({
@APIResponse(responseCode = "200", description = "Résultats de recherche", content = @Content(schema = @Schema(implementation = AuditLogDTO.class, type = org.eclipse.microprofile.openapi.annotations.enums.SchemaType.ARRAY))),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
List<AuditLogDTO> searchLogs(
@QueryParam("acteur") String acteurUsername,
@QueryParam("dateDebut") String dateDebut,
@QueryParam("dateFin") String dateFin,
@QueryParam("typeAction") TypeActionAudit typeAction,
@QueryParam("ressourceType") String ressourceType,
@QueryParam("succes") Boolean succes,
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("pageSize") @DefaultValue("50") int pageSize);
@GET
@Path("/actor/{acteurUsername}")
@Operation(summary = "Récupérer les logs d'un acteur", description = "Liste les derniers logs d'un utilisateur")
List<AuditLogDTO> getLogsByActor(
@Parameter(description = "Username de l'acteur") @PathParam("acteurUsername") String acteurUsername,
@Parameter(description = "Nombre de logs à retourner") @QueryParam("limit") @DefaultValue("100") int limit);
@GET
@Path("/resource/{ressourceType}/{ressourceId}")
@Operation(summary = "Récupérer les logs d'une ressource", description = "Liste les derniers logs d'une ressource spécifique")
List<AuditLogDTO> getLogsByResource(
@PathParam("ressourceType") String ressourceType,
@PathParam("ressourceId") String ressourceId,
@QueryParam("limit") @DefaultValue("100") int limit);
@GET
@Path("/action/{typeAction}")
@Operation(summary = "Récupérer les logs par type d'action", description = "Liste les logs d'un type d'action spécifique")
List<AuditLogDTO> getLogsByAction(
@PathParam("typeAction") TypeActionAudit typeAction,
@QueryParam("dateDebut") String dateDebut,
@QueryParam("dateFin") String dateFin,
@QueryParam("limit") @DefaultValue("100") int limit);
@GET
@Path("/stats/actions")
@Operation(summary = "Statistiques par type d'action", description = "Retourne le nombre de logs par type d'action")
Map<TypeActionAudit, Long> getActionStatistics(
@QueryParam("dateDebut") String dateDebut,
@QueryParam("dateFin") String dateFin);
@GET
@Path("/stats/users")
@Operation(summary = "Statistiques par utilisateur", description = "Retourne le nombre d'actions par utilisateur")
Map<String, Long> getUserActivityStatistics(
@QueryParam("dateDebut") String dateDebut,
@QueryParam("dateFin") String dateFin);
@GET
@Path("/stats/failures")
@Operation(summary = "Comptage des échecs", description = "Retourne le nombre d'échecs sur une période")
CountDTO getFailureCount(
@QueryParam("dateDebut") String dateDebut,
@QueryParam("dateFin") String dateFin);
@GET
@Path("/stats/success")
@Operation(summary = "Comptage des succès", description = "Retourne le nombre de succès sur une période")
CountDTO getSuccessCount(
@QueryParam("dateDebut") String dateDebut,
@QueryParam("dateFin") String dateFin);
@GET
@Path("/export/csv")
@Produces(MediaType.TEXT_PLAIN)
@Operation(summary = "Exporter les logs en CSV", description = "Génère un fichier CSV des logs d'audit")
Response exportLogsToCSV(
@QueryParam("dateDebut") String dateDebut,
@QueryParam("dateFin") String dateFin);
@DELETE
@Path("/purge")
@Operation(summary = "Purger les anciens logs", description = "Supprime les logs de plus de X jours")
void purgeOldLogs(
@QueryParam("joursAnciennete") @DefaultValue("90") int joursAnciennete);
}

View File

@@ -0,0 +1,103 @@
package dev.lions.user.manager.api;
import dev.lions.user.manager.dto.realm.AuthorizedRealmsDTO;
import dev.lions.user.manager.dto.realm.RealmAccessCheckDTO;
import dev.lions.user.manager.dto.realm.RealmAssignmentDTO;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.util.List;
/**
* Interface JAX-RS pour l'API de gestion des affectations de realms.
*/
@Path("/api/realm-assignments")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Realm Assignments", description = "Gestion des affectations de realms (contrôle d'accès multi-tenant)")
public interface RealmAssignmentResourceApi {
@GET
@Operation(summary = "Lister toutes les affectations", description = "Liste toutes les affectations de realms")
List<RealmAssignmentDTO> getAllAssignments();
@GET
@Path("/user/{userId}")
@Operation(summary = "Affectations par utilisateur", description = "Liste les realms assignés à un utilisateur")
List<RealmAssignmentDTO> getAssignmentsByUser(
@Parameter(description = "ID de l'utilisateur") @PathParam("userId") String userId);
@GET
@Path("/realm/{realmName}")
@Operation(summary = "Affectations par realm", description = "Liste les utilisateurs ayant accès à un realm")
List<RealmAssignmentDTO> getAssignmentsByRealm(
@Parameter(description = "Nom du realm") @PathParam("realmName") String realmName);
@GET
@Path("/{assignmentId}")
@Operation(summary = "Récupérer une affectation", description = "Récupère une affectation par son ID")
RealmAssignmentDTO getAssignmentById(
@Parameter(description = "ID de l'affectation") @PathParam("assignmentId") String assignmentId);
@GET
@Path("/check")
@Operation(summary = "Vérifier l'accès", description = "Vérifie si un utilisateur peut gérer un realm")
RealmAccessCheckDTO canManageRealm(
@Parameter(description = "ID de l'utilisateur") @QueryParam("userId") String userId,
@Parameter(description = "Nom du realm") @QueryParam("realmName") String realmName);
@GET
@Path("/authorized-realms/{userId}")
@Operation(summary = "Realms autorisés", description = "Liste les realms qu'un utilisateur peut gérer")
AuthorizedRealmsDTO getAuthorizedRealms(
@Parameter(description = "ID de l'utilisateur") @PathParam("userId") String userId);
@POST
@Operation(summary = "Assigner un realm", description = "Assigne un realm à un utilisateur")
@APIResponses({
@APIResponse(responseCode = "201", description = "Affectation créée", content = @Content(schema = @Schema(implementation = RealmAssignmentDTO.class)))
})
Response assignRealmToUser(@Valid @NotNull RealmAssignmentDTO assignment);
@DELETE
@Path("/user/{userId}/realm/{realmName}")
@Operation(summary = "Révoquer un realm", description = "Retire l'accès d'un utilisateur à un realm")
void revokeRealmFromUser(
@Parameter(description = "ID de l'utilisateur") @PathParam("userId") String userId,
@Parameter(description = "Nom du realm") @PathParam("realmName") String realmName);
@DELETE
@Path("/user/{userId}")
@Operation(summary = "Révoquer tous les realms", description = "Retire tous les accès d'un utilisateur")
void revokeAllRealmsFromUser(
@Parameter(description = "ID de l'utilisateur") @PathParam("userId") String userId);
@PUT
@Path("/{assignmentId}/deactivate")
@Operation(summary = "Désactiver une affectation", description = "Désactive une affectation sans la supprimer")
void deactivateAssignment(
@Parameter(description = "ID de l'affectation") @PathParam("assignmentId") String assignmentId);
@PUT
@Path("/{assignmentId}/activate")
@Operation(summary = "Activer une affectation", description = "Réactive une affectation")
void activateAssignment(
@Parameter(description = "ID de l'affectation") @PathParam("assignmentId") String assignmentId);
@PUT
@Path("/super-admin/{userId}")
@Operation(summary = "Définir super admin", description = "Définit ou retire le statut de super admin")
void setSuperAdmin(
@Parameter(description = "ID de l'utilisateur") @PathParam("userId") String userId,
@Parameter(description = "Super admin (true/false)") @QueryParam("superAdmin") @NotNull Boolean superAdmin);
}

View File

@@ -0,0 +1,44 @@
package dev.lions.user.manager.api;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.util.List;
/**
* Interface JAX-RS pour la gestion des realms Keycloak.
*/
@Path("/api/realms")
@Produces(MediaType.APPLICATION_JSON)
@Tag(name = "Realms", description = "Gestion des realms Keycloak")
public interface RealmResourceApi {
@GET
@Path("/list")
@Operation(summary = "Lister tous les realms", description = "Récupère la liste de tous les realms disponibles dans Keycloak")
@APIResponses({
@APIResponse(responseCode = "200", description = "Liste des realms", content = @Content(schema = @Schema(type = org.eclipse.microprofile.openapi.annotations.enums.SchemaType.ARRAY, implementation = String.class))),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
List<String> getAllRealms();
@GET
@Path("/{realm}/clients")
@Operation(summary = "Lister les clients d'un realm", description = "Récupère la liste des clientId de tous les clients d'un realm")
@APIResponses({
@APIResponse(responseCode = "200", description = "Liste des clients"),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
List<String> getRealmClients(
@Parameter(description = "Nom du realm") @PathParam("realm") String realmName);
}

View File

@@ -0,0 +1,163 @@
package dev.lions.user.manager.api;
import dev.lions.user.manager.dto.role.RoleAssignmentRequestDTO;
import dev.lions.user.manager.dto.role.RoleDTO;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.util.List;
/**
* Interface JAX-RS pour la gestion des rôles Keycloak.
*/
@Path("/api/roles")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Roles", description = "Gestion des rôles Keycloak (realm et client)")
public interface RoleResourceApi {
// ==================== Endpoints Realm Roles ====================
@POST
@Path("/realm")
@Operation(summary = "Créer un rôle realm", description = "Crée un nouveau rôle au niveau du realm")
@APIResponses({
@APIResponse(responseCode = "201", description = "Rôle créé", content = @Content(schema = @Schema(implementation = RoleDTO.class)))
})
Response createRealmRole(
@Valid @NotNull RoleDTO roleDTO,
@Parameter(description = "Nom du realm") @QueryParam("realm") String realmName);
@GET
@Path("/realm/{roleName}")
@Operation(summary = "Récupérer un rôle realm par nom", description = "Récupère les détails d'un rôle realm")
RoleDTO getRealmRole(
@Parameter(description = "Nom du rôle") @PathParam("roleName") String roleName,
@Parameter(description = "Nom du realm") @QueryParam("realm") String realmName);
@GET
@Path("/realm")
@Operation(summary = "Lister tous les rôles realm", description = "Liste tous les rôles du realm")
List<RoleDTO> getAllRealmRoles(
@Parameter(description = "Nom du realm") @QueryParam("realm") String realmName);
@PUT
@Path("/realm/{roleName}")
@Operation(summary = "Mettre à jour un rôle realm", description = "Met à jour les informations d'un rôle realm")
RoleDTO updateRealmRole(
@PathParam("roleName") String roleName,
@Valid @NotNull RoleDTO roleDTO,
@QueryParam("realm") String realmName);
@DELETE
@Path("/realm/{roleName}")
@Operation(summary = "Supprimer un rôle realm", description = "Supprime un rôle realm")
void deleteRealmRole(
@PathParam("roleName") String roleName,
@QueryParam("realm") String realmName);
// ==================== Endpoints Client Roles ====================
@POST
@Path("/client/{clientId}")
@Operation(summary = "Créer un rôle client", description = "Crée un nouveau rôle pour un client spécifique")
@APIResponses({
@APIResponse(responseCode = "201", description = "Rôle créé", content = @Content(schema = @Schema(implementation = RoleDTO.class)))
})
Response createClientRole(
@PathParam("clientId") String clientId,
@Valid @NotNull RoleDTO roleDTO,
@QueryParam("realm") String realmName);
@GET
@Path("/client/{clientId}/{roleName}")
@Operation(summary = "Récupérer un rôle client par nom", description = "Récupère les détails d'un rôle client")
RoleDTO getClientRole(
@PathParam("clientId") String clientId,
@PathParam("roleName") String roleName,
@QueryParam("realm") String realmName);
@GET
@Path("/client/{clientId}")
@Operation(summary = "Lister tous les rôles d'un client", description = "Liste tous les rôles d'un client spécifique")
List<RoleDTO> getAllClientRoles(
@PathParam("clientId") String clientId,
@QueryParam("realm") String realmName);
@DELETE
@Path("/client/{clientId}/{roleName}")
@Operation(summary = "Supprimer un rôle client", description = "Supprime un rôle d'un client")
void deleteClientRole(
@PathParam("clientId") String clientId,
@PathParam("roleName") String roleName,
@QueryParam("realm") String realmName);
// ==================== Endpoints Attribution de rôles ====================
@POST
@Path("/assign/realm/{userId}")
@Operation(summary = "Attribuer des rôles realm à un utilisateur", description = "Assigne un ou plusieurs rôles realm à un utilisateur")
void assignRealmRoles(
@PathParam("userId") String userId,
@QueryParam("realm") String realmName,
@NotNull RoleAssignmentRequestDTO request);
@POST
@Path("/revoke/realm/{userId}")
@Operation(summary = "Révoquer des rôles realm d'un utilisateur", description = "Révoque un ou plusieurs rôles realm d'un utilisateur")
void revokeRealmRoles(
@PathParam("userId") String userId,
@QueryParam("realm") String realmName,
@NotNull RoleAssignmentRequestDTO request);
@POST
@Path("/assign/client/{clientId}/{userId}")
@Operation(summary = "Attribuer des rôles client à un utilisateur", description = "Assigne un ou plusieurs rôles client à un utilisateur")
void assignClientRoles(
@PathParam("clientId") String clientId,
@PathParam("userId") String userId,
@QueryParam("realm") String realmName,
@NotNull RoleAssignmentRequestDTO request);
@GET
@Path("/user/realm/{userId}")
@Operation(summary = "Récupérer les rôles realm d'un utilisateur", description = "Liste tous les rôles realm d'un utilisateur")
List<RoleDTO> getUserRealmRoles(
@PathParam("userId") String userId,
@QueryParam("realm") String realmName);
@GET
@Path("/user/client/{clientId}/{userId}")
@Operation(summary = "Récupérer les rôles client d'un utilisateur", description = "Liste tous les rôles client d'un utilisateur")
List<RoleDTO> getUserClientRoles(
@PathParam("clientId") String clientId,
@PathParam("userId") String userId,
@QueryParam("realm") String realmName);
// ==================== Endpoints Rôles composites ====================
@POST
@Path("/composite/{roleName}/add")
@Operation(summary = "Ajouter des rôles composites", description = "Ajoute des rôles composites à un rôle")
void addComposites(
@PathParam("roleName") String roleName,
@QueryParam("realm") String realmName,
@NotNull RoleAssignmentRequestDTO request);
@GET
@Path("/composite/{roleName}")
@Operation(summary = "Récupérer les rôles composites", description = "Liste tous les rôles composites d'un rôle")
List<RoleDTO> getComposites(
@PathParam("roleName") String roleName,
@QueryParam("realm") String realmName);
}

View File

@@ -0,0 +1,54 @@
package dev.lions.user.manager.api;
import dev.lions.user.manager.dto.sync.HealthStatusDTO;
import dev.lions.user.manager.dto.sync.SyncConsistencyDTO;
import dev.lions.user.manager.dto.sync.SyncHistoryDTO;
import dev.lions.user.manager.dto.sync.SyncResultDTO;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
/**
* Interface JAX-RS définissant le contrat de l'API de synchronisation.
* Cette interface doit être implémentée par le serveur et utilisée par le
* client (via MicroProfile Rest Client).
*/
@Path("/api/sync")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Sync", description = "Synchronisation avec Keycloak et health checks")
public interface SyncResourceApi {
@GET
@Path("/health/keycloak")
@Operation(summary = "Vérifier la santé de Keycloak", description = "Retourne le statut de santé de Keycloak")
HealthStatusDTO checkKeycloakHealth();
@POST
@Path("/users")
@Operation(summary = "Synchroniser les utilisateurs", description = "Synchronise tous les utilisateurs depuis Keycloak")
SyncResultDTO syncUsers(@QueryParam("realm") String realmName);
@POST
@Path("/roles")
@Operation(summary = "Synchroniser les rôles", description = "Synchronise les rôles (realm et/ou client)")
SyncResultDTO syncRoles(
@QueryParam("realm") String realmName,
@QueryParam("clientName") String clientName);
@GET
@Path("/consistency")
@Operation(summary = "Vérifier la cohérence des données", description = "Compare le cache local et Keycloak pour un realm")
SyncConsistencyDTO checkDataConsistency(@QueryParam("realm") String realmName);
@GET
@Path("/status")
@Operation(summary = "Statut de la dernière synchro", description = "Retourne le statut de la dernière synchronisation du realm")
SyncHistoryDTO getLastSyncStatus(@QueryParam("realm") String realmName);
@POST
@Path("/force")
@Operation(summary = "Forcer une synchro", description = "Lance une synchronisation complète du realm (users + roles)")
SyncHistoryDTO forceSyncRealm(@QueryParam("realm") String realmName);
}

View File

@@ -0,0 +1,32 @@
package dev.lions.user.manager.api;
import dev.lions.user.manager.dto.common.UserSessionStatsDTO;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
/**
* API de métriques liées aux utilisateurs (sessions, utilisateurs en ligne, etc.).
*/
@Path("/api/metrics/users")
@Produces(MediaType.APPLICATION_JSON)
@Tag(name = "User Metrics", description = "Statistiques sur les utilisateurs et leurs sessions")
public interface UserMetricsResourceApi {
@GET
@Path("/sessions")
@Operation(summary = "Statistiques de sessions utilisateurs",
description = "Retourne des statistiques agrégées sur les utilisateurs et leurs sessions pour un realm")
@APIResponses({
@APIResponse(responseCode = "200", description = "Statistiques retournées avec succès"),
@APIResponse(responseCode = "500", description = "Erreur serveur")
})
UserSessionStatsDTO getUserSessionStats(@QueryParam("realm") String realmName);
}

View File

@@ -0,0 +1,134 @@
package dev.lions.user.manager.api;
import dev.lions.user.manager.dto.importexport.ImportResultDTO;
import dev.lions.user.manager.dto.user.*;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.util.List;
/**
* Interface JAX-RS pour la gestion des utilisateurs.
*/
@Path("/api/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Users", description = "Gestion des utilisateurs Keycloak")
public interface UserResourceApi {
@POST
@Path("/search")
@Operation(summary = "Rechercher des utilisateurs", description = "Recherche d'utilisateurs selon des critères")
UserSearchResultDTO searchUsers(@Valid @NotNull UserSearchCriteriaDTO criteria);
@GET
@Path("/{userId}")
@Operation(summary = "Récupérer un utilisateur par ID", description = "Récupère les détails d'un utilisateur")
UserDTO getUserById(
@Parameter(description = "ID de l'utilisateur") @PathParam("userId") String userId,
@Parameter(description = "Nom du realm") @QueryParam("realm") String realmName);
@GET
@Operation(summary = "Lister tous les utilisateurs", description = "Liste paginée de tous les utilisateurs")
UserSearchResultDTO getAllUsers(
@QueryParam("realm") String realmName,
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("pageSize") @DefaultValue("20") int pageSize);
@POST
@Operation(summary = "Créer un utilisateur", description = "Crée un nouvel utilisateur dans Keycloak")
@APIResponses({
@APIResponse(responseCode = "201", description = "Utilisateur créé", content = @Content(schema = @Schema(implementation = UserDTO.class)))
})
Response createUser(
@Valid @NotNull UserDTO user,
@QueryParam("realm") String realmName);
@PUT
@Path("/{userId}")
@Operation(summary = "Mettre à jour un utilisateur", description = "Met à jour les informations d'un utilisateur")
UserDTO updateUser(
@PathParam("userId") String userId,
@Valid @NotNull UserDTO user,
@QueryParam("realm") String realmName);
@DELETE
@Path("/{userId}")
@Operation(summary = "Supprimer un utilisateur", description = "Supprime un utilisateur (soft ou hard delete)")
void deleteUser(
@PathParam("userId") String userId,
@QueryParam("realm") String realmName,
@QueryParam("hardDelete") @DefaultValue("false") boolean hardDelete);
@POST
@Path("/{userId}/activate")
@Operation(summary = "Activer un utilisateur", description = "Active un utilisateur désactivé")
void activateUser(
@PathParam("userId") String userId,
@QueryParam("realm") String realmName);
@POST
@Path("/{userId}/deactivate")
@Operation(summary = "Désactiver un utilisateur", description = "Désactive un utilisateur")
void deactivateUser(
@PathParam("userId") String userId,
@QueryParam("realm") String realmName,
@QueryParam("raison") String raison);
@POST
@Path("/{userId}/reset-password")
@Operation(summary = "Réinitialiser le mot de passe", description = "Définit un nouveau mot de passe pour l'utilisateur")
void resetPassword(
@PathParam("userId") String userId,
@QueryParam("realm") String realmName,
@NotNull PasswordResetRequestDTO request);
@POST
@Path("/{userId}/send-verification-email")
@Operation(summary = "Envoyer email de vérification", description = "Envoie un email de vérification à l'utilisateur")
void sendVerificationEmail(
@PathParam("userId") String userId,
@QueryParam("realm") String realmName);
@POST
@Path("/{userId}/logout-sessions")
@Operation(summary = "Déconnecter toutes les sessions", description = "Révoque toutes les sessions actives de l'utilisateur")
SessionsRevokedDTO logoutAllSessions(
@PathParam("userId") String userId,
@QueryParam("realm") String realmName);
@GET
@Path("/{userId}/sessions")
@Operation(summary = "Récupérer les sessions actives", description = "Liste les sessions actives de l'utilisateur")
@APIResponses({
@APIResponse(responseCode = "200", description = "Liste des sessions", content = @Content(schema = @Schema(type = org.eclipse.microprofile.openapi.annotations.enums.SchemaType.ARRAY, implementation = String.class)))
})
List<String> getActiveSessions(
@PathParam("userId") String userId,
@QueryParam("realm") String realmName);
@GET
@Path("/export/csv")
@Produces("text/csv")
@Operation(summary = "Exporter les utilisateurs en CSV", description = "Exporte la liste des utilisateurs du realm au format CSV")
Response exportUsersToCSV(@QueryParam("realm") String realmName);
@POST
@Path("/import/csv")
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Importer des utilisateurs depuis un CSV", description = "Importe des utilisateurs à partir d'un fichier CSV")
ImportResultDTO importUsersFromCSV(
@QueryParam("realm") String realmName,
String csvContent);
}

View File

@@ -0,0 +1,21 @@
package dev.lions.user.manager.dto.common;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.io.Serializable;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@RegisterForReflection
@Schema(description = "Réponse d'erreur standard")
public class ApiErrorDTO implements Serializable {
@Schema(description = "Message d'erreur")
private String message;
}

View File

@@ -0,0 +1,21 @@
package dev.lions.user.manager.dto.common;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.io.Serializable;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@RegisterForReflection
@Schema(description = "Réponse de comptage générique")
public class CountDTO implements Serializable {
@Schema(description = "Nombre d'éléments")
private long count;
}

View File

@@ -0,0 +1,32 @@
package dev.lions.user.manager.dto.common;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.io.Serializable;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@RegisterForReflection
@Schema(description = "Statistiques de sessions et d'utilisateurs en ligne pour un realm")
public class UserSessionStatsDTO implements Serializable {
@Schema(description = "Nom du realm concerné")
private String realmName;
@Schema(description = "Nombre total d'utilisateurs dans le realm")
private long totalUsers;
@Schema(description = "Nombre total de sessions actives (approximation)")
private long activeSessions;
@Schema(description = "Nombre d'utilisateurs considérés comme en ligne (approximation)")
private long onlineUsers;
}

View File

@@ -0,0 +1,25 @@
package dev.lions.user.manager.dto.realm;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.io.Serializable;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@RegisterForReflection
@Schema(description = "Liste des realms autorisés pour un utilisateur")
public class AuthorizedRealmsDTO implements Serializable {
@Schema(description = "Liste des realms (peut être vide si super admin)")
private List<String> realms;
@Schema(description = "L'utilisateur est super admin")
private boolean isSuperAdmin;
}

View File

@@ -0,0 +1,27 @@
package dev.lions.user.manager.dto.realm;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.io.Serializable;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@RegisterForReflection
@Schema(description = "Réponse de vérification d'accès au realm")
public class RealmAccessCheckDTO implements Serializable {
@Schema(description = "L'utilisateur peut gérer le realm")
private boolean canManage;
@Schema(description = "ID de l'utilisateur")
private String userId;
@Schema(description = "Nom du realm")
private String realmName;
}

View File

@@ -0,0 +1,22 @@
package dev.lions.user.manager.dto.role;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.io.Serializable;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@RegisterForReflection
@Schema(description = "Requête d'attribution ou de révocation de rôles")
public class RoleAssignmentRequestDTO implements Serializable {
@Schema(description = "Liste des noms de rôles", required = true)
private List<String> roleNames;
}

View File

@@ -0,0 +1,29 @@
package dev.lions.user.manager.dto.sync;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Set;
/**
* DTO rapport de cohérence entre données Keycloak et cache local.
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SyncConsistencyDTO {
private String realmName;
private String status; // OK, MISMATCH, ERROR
private Integer usersKeycloakCount;
private Integer usersLocalCount;
private Set<String> missingUsersInLocal;
private Set<String> missingUsersInKeycloak;
private Integer rolesKeycloakCount;
private Integer rolesLocalCount;
private Set<String> missingRolesInLocal;
private Set<String> missingRolesInKeycloak;
private String error;
}

View File

@@ -0,0 +1,23 @@
package dev.lions.user.manager.dto.sync;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SyncHistoryDTO {
private String id;
private String realmName;
private LocalDateTime syncDate;
private String syncType; // FULL, PARTIAL, USER, ROLE
private String status; // SUCCESS, FAILURE
private Integer itemsProcessed;
private Long durationMs;
private String errorMessage;
}

View File

@@ -0,0 +1,25 @@
package dev.lions.user.manager.dto.user;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.io.Serializable;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@RegisterForReflection
@Schema(description = "Requête de réinitialisation de mot de passe")
public class PasswordResetRequestDTO implements Serializable {
@Schema(description = "Nouveau mot de passe", required = true)
private String password;
@Schema(description = "Indique si le mot de passe est temporaire", defaultValue = "true")
@Builder.Default
private boolean temporary = true;
}

View File

@@ -0,0 +1,21 @@
package dev.lions.user.manager.dto.user;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.io.Serializable;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@RegisterForReflection
@Schema(description = "Réponse de révocation de sessions")
public class SessionsRevokedDTO implements Serializable {
@Schema(description = "Nombre de sessions révoquées")
private int count;
}

View File

@@ -24,6 +24,7 @@ public enum TypeActionAudit {
USER_PASSWORD_RESET("Réinitialisation mot de passe", "USER", "PASSWORD_RESET"),
USER_EMAIL_VERIFY("Vérification email", "USER", "EMAIL_VERIFY"),
USER_FORCE_LOGOUT("Déconnexion forcée", "USER", "FORCE_LOGOUT"),
USER_IMPORT("Import utilisateurs CSV", "USER", "IMPORT"), // Ajout
// Actions Rôle
ROLE_CREATE("Création rôle", "ROLE", "CREATE"),
@@ -41,8 +42,10 @@ public enum TypeActionAudit {
GROUP_ADD_MEMBER("Ajout membre groupe", "GROUP", "ADD_MEMBER"),
GROUP_REMOVE_MEMBER("Retrait membre groupe", "GROUP", "REMOVE_MEMBER"),
// Actions Realm
REALM_SYNC("Synchronisation realm", "REALM", "SYNC"),
// Actions Realm & Sync
REALM_SYNC("Synchronisation realm (Général)", "REALM", "SYNC"),
SYNC_USERS("Synchronisation utilisateurs", "REALM", "SYNC_USERS"), // Ajout
SYNC_ROLES("Synchronisation rôles", "REALM", "SYNC_ROLES"), // Ajout
REALM_EXPORT("Export realm", "REALM", "EXPORT"),
REALM_IMPORT("Import realm", "REALM", "IMPORT"),
REALM_ASSIGN("Assignation realm", "REALM", "ASSIGN"),
@@ -105,9 +108,9 @@ public enum TypeActionAudit {
*/
public boolean isCritical() {
return this == USER_DELETE
|| this == ROLE_DELETE
|| this == USER_SUSPEND
|| this == SESSION_REVOKE_ALL
|| this == SYSTEM_RESTORE;
|| this == ROLE_DELETE
|| this == USER_SUSPEND
|| this == SESSION_REVOKE_ALL
|| this == SYSTEM_RESTORE;
}
}