package dev.lions.btpxpress.adapter.http; import dev.lions.btpxpress.application.service.UserService; import dev.lions.btpxpress.domain.core.entity.User; import dev.lions.btpxpress.domain.core.entity.UserRole; import dev.lions.btpxpress.domain.core.entity.UserStatus; import io.quarkus.security.Authenticated; import jakarta.inject.Inject; 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 java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.UUID; import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; import org.eclipse.microprofile.openapi.annotations.security.SecurityRequirement; import org.eclipse.microprofile.openapi.annotations.tags.Tag; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * API REST pour la gestion des utilisateurs BTP * Expose les fonctionnalités de création, consultation et administration des utilisateurs */ @Path("/api/v1/users") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @Tag(name = "Utilisateurs", description = "Gestion des utilisateurs du système") @SecurityRequirement(name = "JWT") // @Authenticated - Désactivé pour les tests public class UserResource { private static final Logger logger = LoggerFactory.getLogger(UserResource.class); @Inject UserService userService; // =================================== // CONSULTATION DES UTILISATEURS // =================================== @GET @Operation(summary = "Récupère tous les utilisateurs") @APIResponse(responseCode = "200", description = "Liste des utilisateurs") @APIResponse(responseCode = "401", description = "Non authentifié") @APIResponse(responseCode = "403", description = "Accès refusé") public Response getAllUsers( @Parameter(description = "Numéro de page (0-indexed)") @QueryParam("page") @DefaultValue("0") int page, @Parameter(description = "Taille de la page") @QueryParam("size") @DefaultValue("20") int size, @Parameter(description = "Terme de recherche") @QueryParam("search") String search, @Parameter(description = "Filtrer par rôle") @QueryParam("role") String role, @Parameter(description = "Filtrer par statut") @QueryParam("status") String status) { try { List users; if (search != null && !search.isEmpty()) { users = userService.searchUsers(search, page, size); } else if (role != null && !role.isEmpty()) { UserRole userRole = UserRole.valueOf(role.toUpperCase()); users = userService.findByRole(userRole, page, size); } else if (status != null && !status.isEmpty()) { UserStatus userStatus = UserStatus.valueOf(status.toUpperCase()); users = userService.findByStatus(userStatus, page, size); } else { users = userService.findAll(page, size); } // Convertir en DTO pour éviter d'exposer les données sensibles List userResponses = users.stream().map(this::toUserResponse).toList(); return Response.ok(userResponses).build(); } catch (Exception e) { logger.error("Erreur lors de la récupération des utilisateurs", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", "Erreur lors de la récupération des utilisateurs")) .build(); } } @GET @Path("/{id}") @Operation(summary = "Récupère un utilisateur par ID") @APIResponse(responseCode = "200", description = "Utilisateur trouvé") @APIResponse(responseCode = "404", description = "Utilisateur non trouvé") @APIResponse(responseCode = "403", description = "Accès refusé") public Response getUserById( @Parameter(description = "ID de l'utilisateur") @PathParam("id") String id) { try { UUID userId = UUID.fromString(id); return userService .findById(userId) .map(user -> Response.ok(toUserResponse(user)).build()) .orElse( Response.status(Response.Status.NOT_FOUND) .entity(Map.of("error", "Utilisateur non trouvé avec l'ID: " + id)) .build()); } catch (IllegalArgumentException e) { return Response.status(Response.Status.BAD_REQUEST) .entity(Map.of("error", "ID d'utilisateur invalide: " + id)) .build(); } catch (Exception e) { logger.error("Erreur lors de la récupération de l'utilisateur {}", id, e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", "Erreur lors de la récupération de l'utilisateur")) .build(); } } @GET @Path("/count") @Operation(summary = "Compter le nombre d'utilisateurs") @APIResponse(responseCode = "200", description = "Nombre d'utilisateurs retourné avec succès") @APIResponse(responseCode = "403", description = "Accès refusé") public Response countUsers( @Parameter(description = "Filtrer par statut") @QueryParam("status") String status, @Parameter(description = "Token d'authentification") @HeaderParam("Authorization") String authorizationHeader) { try { long count; if (status != null && !status.isEmpty()) { UserStatus userStatus = UserStatus.valueOf(status.toUpperCase()); count = userService.countByStatus(userStatus); } else { count = userService.count(); } return Response.ok(new CountResponse(count)).build(); } catch (SecurityException e) { return Response.status(Response.Status.FORBIDDEN) .entity("Accès refusé: " + e.getMessage()) .build(); } catch (Exception e) { logger.error("Erreur lors du comptage des utilisateurs", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity("Erreur lors du comptage des utilisateurs: " + e.getMessage()) .build(); } } @GET @Path("/pending") @Operation(summary = "Récupérer les utilisateurs en attente de validation") @APIResponse( responseCode = "200", description = "Liste des utilisateurs en attente récupérée avec succès") @APIResponse(responseCode = "403", description = "Accès refusé") public Response getPendingUsers( @Parameter(description = "Token d'authentification") @HeaderParam("Authorization") String authorizationHeader) { try { List pendingUsers = userService.findByStatus(UserStatus.PENDING, 0, 100); List userResponses = pendingUsers.stream().map(this::toUserResponse).toList(); return Response.ok(userResponses).build(); } catch (SecurityException e) { return Response.status(Response.Status.FORBIDDEN) .entity("Accès refusé: " + e.getMessage()) .build(); } catch (Exception e) { logger.error("Erreur lors de la récupération des utilisateurs en attente", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity("Erreur lors de la récupération des utilisateurs en attente: " + e.getMessage()) .build(); } } @GET @Path("/stats") @Operation(summary = "Récupère les statistiques des utilisateurs") @APIResponse(responseCode = "200", description = "Statistiques des utilisateurs") @APIResponse(responseCode = "403", description = "Accès refusé") public Response getUserStats() { try { Object stats = userService.getStatistics(); return Response.ok(stats).build(); } catch (Exception e) { logger.error("Erreur lors de la génération des statistiques utilisateurs", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", "Erreur lors de la génération des statistiques")) .build(); } } // =================================== // GESTION DES UTILISATEURS // =================================== @POST @Operation(summary = "Crée un nouvel utilisateur") @APIResponse(responseCode = "201", description = "Utilisateur créé avec succès") @APIResponse(responseCode = "400", description = "Données invalides") @APIResponse(responseCode = "409", description = "Email déjà utilisé") @APIResponse(responseCode = "403", description = "Accès refusé") public Response createUser( @Parameter(description = "Données du nouvel utilisateur") @Valid @NotNull CreateUserRequest request) { try { User user = userService.createUser( request.email, request.password, request.nom, request.prenom, request.role, request.status); return Response.status(Response.Status.CREATED).entity(toUserResponse(user)).build(); } catch (IllegalArgumentException e) { return Response.status(Response.Status.BAD_REQUEST) .entity(Map.of("error", "Données invalides: " + e.getMessage())) .build(); } catch (Exception e) { logger.error("Erreur lors de la création de l'utilisateur", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", "Erreur lors de la création de l'utilisateur")) .build(); } } @PUT @Path("/{id}") @Operation(summary = "Met à jour un utilisateur") @APIResponse(responseCode = "200", description = "Utilisateur mis à jour avec succès") @APIResponse(responseCode = "400", description = "Données invalides") @APIResponse(responseCode = "404", description = "Utilisateur non trouvé") @APIResponse(responseCode = "403", description = "Accès refusé") public Response updateUser( @Parameter(description = "ID de l'utilisateur") @PathParam("id") String id, @Parameter(description = "Nouvelles données utilisateur") @Valid @NotNull UpdateUserRequest request) { try { UUID userId = UUID.fromString(id); User user = userService.updateUser(userId, request.nom, request.prenom, request.email); return Response.ok(toUserResponse(user)).build(); } catch (IllegalArgumentException e) { return Response.status(Response.Status.BAD_REQUEST) .entity(Map.of("error", "Données invalides: " + e.getMessage())) .build(); } catch (Exception e) { logger.error("Erreur lors de la modification de l'utilisateur {}", id, e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", "Erreur lors de la modification de l'utilisateur")) .build(); } } @PUT @Path("/{id}/status") @Operation(summary = "Met à jour le statut d'un utilisateur") @APIResponse(responseCode = "200", description = "Statut mis à jour avec succès") @APIResponse(responseCode = "400", description = "Statut invalide") @APIResponse(responseCode = "404", description = "Utilisateur non trouvé") @APIResponse(responseCode = "403", description = "Accès refusé") public Response updateUserStatus( @Parameter(description = "ID de l'utilisateur") @PathParam("id") String id, @Parameter(description = "Nouveau statut") @Valid @NotNull UpdateStatusRequest request) { try { UUID userId = UUID.fromString(id); UserStatus status = UserStatus.valueOf(request.status.toUpperCase()); User user = userService.updateStatus(userId, status); return Response.ok(toUserResponse(user)).build(); } catch (IllegalArgumentException e) { return Response.status(Response.Status.BAD_REQUEST) .entity(Map.of("error", "Statut invalide: " + e.getMessage())) .build(); } catch (Exception e) { logger.error("Erreur lors de la modification du statut utilisateur {}", id, e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", "Erreur lors de la modification du statut")) .build(); } } @PUT @Path("/{id}/role") @Operation(summary = "Met à jour le rôle d'un utilisateur") @APIResponse(responseCode = "200", description = "Rôle mis à jour avec succès") @APIResponse(responseCode = "400", description = "Rôle invalide") @APIResponse(responseCode = "404", description = "Utilisateur non trouvé") @APIResponse(responseCode = "403", description = "Accès refusé") public Response updateUserRole( @Parameter(description = "ID de l'utilisateur") @PathParam("id") String id, @Parameter(description = "Nouveau rôle") @Valid @NotNull UpdateRoleRequest request) { try { UUID userId = UUID.fromString(id); UserRole role = UserRole.valueOf(request.role.toUpperCase()); User user = userService.updateRole(userId, role); return Response.ok(toUserResponse(user)).build(); } catch (IllegalArgumentException e) { return Response.status(Response.Status.BAD_REQUEST) .entity(Map.of("error", "Rôle invalide: " + e.getMessage())) .build(); } catch (Exception e) { logger.error("Erreur lors de la modification du rôle utilisateur {}", id, e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", "Erreur lors de la modification du rôle")) .build(); } } @POST @Path("/{id}/approve") @Operation(summary = "Approuve un utilisateur en attente") @APIResponse(responseCode = "200", description = "Utilisateur approuvé avec succès") @APIResponse(responseCode = "404", description = "Utilisateur non trouvé") @APIResponse(responseCode = "403", description = "Accès refusé") public Response approveUser( @Parameter(description = "ID de l'utilisateur") @PathParam("id") String id) { try { UUID userId = UUID.fromString(id); User user = userService.approveUser(userId); return Response.ok(toUserResponse(user)).build(); } catch (IllegalArgumentException e) { return Response.status(Response.Status.BAD_REQUEST) .entity(Map.of("error", "Données invalides: " + e.getMessage())) .build(); } catch (Exception e) { logger.error("Erreur lors de l'approbation de l'utilisateur {}", id, e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", "Erreur lors de l'approbation de l'utilisateur")) .build(); } } @POST @Path("/{id}/reject") @Operation(summary = "Rejette un utilisateur en attente") @APIResponse(responseCode = "200", description = "Utilisateur rejeté avec succès") @APIResponse(responseCode = "404", description = "Utilisateur non trouvé") @APIResponse(responseCode = "403", description = "Accès refusé") public Response rejectUser( @Parameter(description = "ID de l'utilisateur") @PathParam("id") String id, @Parameter(description = "Raison du rejet") @Valid @NotNull RejectUserRequest request) { try { UUID userId = UUID.fromString(id); userService.rejectUser(userId, request.reason); return Response.ok(Map.of("message", "Utilisateur rejeté avec succès")).build(); } catch (IllegalArgumentException e) { return Response.status(Response.Status.BAD_REQUEST) .entity(Map.of("error", "Données invalides: " + e.getMessage())) .build(); } catch (Exception e) { logger.error("Erreur lors du rejet de l'utilisateur {}", id, e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", "Erreur lors du rejet de l'utilisateur")) .build(); } } @DELETE @Path("/{id}") @Operation(summary = "Supprime un utilisateur") @APIResponse(responseCode = "204", description = "Utilisateur supprimé avec succès") @APIResponse(responseCode = "404", description = "Utilisateur non trouvé") @APIResponse(responseCode = "403", description = "Accès refusé") public Response deleteUser( @Parameter(description = "ID de l'utilisateur") @PathParam("id") String id) { try { UUID userId = UUID.fromString(id); userService.deleteUser(userId); return Response.noContent().build(); } catch (IllegalArgumentException e) { return Response.status(Response.Status.BAD_REQUEST) .entity(Map.of("error", "ID invalide: " + e.getMessage())) .build(); } catch (Exception e) { logger.error("Erreur lors de la suppression de l'utilisateur {}", id, e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", "Erreur lors de la suppression de l'utilisateur")) .build(); } } // === MÉTHODES UTILITAIRES === private UserResponse toUserResponse(User user) { return new UserResponse( user.getId(), user.getEmail(), user.getNom(), user.getPrenom(), user.getRole().toString(), user.getStatus().toString(), user.getDateCreation(), user.getDateModification(), user.getDerniereConnexion(), user.getActif()); } // === CLASSES UTILITAIRES === public static record CountResponse(long count) {} public static record CreateUserRequest( @Parameter(description = "Email de l'utilisateur") String email, @Parameter(description = "Mot de passe") String password, @Parameter(description = "Nom de famille") String nom, @Parameter(description = "Prénom") String prenom, @Parameter(description = "Rôle (USER, ADMIN, MANAGER)") String role, @Parameter(description = "Statut (ACTIF, INACTIF, SUSPENDU)") String status) {} public static record UpdateUserRequest( @Parameter(description = "Nouveau nom") String nom, @Parameter(description = "Nouveau prénom") String prenom, @Parameter(description = "Nouvel email") String email) {} public static record UpdateStatusRequest( @Parameter(description = "Nouveau statut") String status) {} public static record UpdateRoleRequest(@Parameter(description = "Nouveau rôle") String role) {} public static record RejectUserRequest( @Parameter(description = "Raison du rejet") String reason) {} public static record UserResponse( UUID id, String email, String nom, String prenom, String role, String status, LocalDateTime dateCreation, LocalDateTime dateModification, LocalDateTime derniereConnexion, Boolean actif) {} }