From 5ec5e538ccf3c0cd90a5591a297d58c09a201e8c Mon Sep 17 00:00:00 2001 From: dahoud Date: Mon, 6 Oct 2025 20:44:56 +0000 Subject: [PATCH] Task 1.5 - Endpoints REST complets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Création de AuthResource avec tous les endpoints d'authentification * POST /api/auth/login - Connexion utilisateur avec JWT * POST /api/auth/logout - Déconnexion utilisateur * POST /api/auth/refresh - Rafraîchissement de token * GET /api/auth/validate - Validation de token * POST /api/auth/forgot-password - Demande de réinitialisation * POST /api/auth/reset-password - Réinitialisation avec token * PUT /api/auth/change-password - Changement de mot de passe - Création de UserResource avec tous les endpoints de gestion utilisateurs * GET /api/users - Liste paginée avec filtres * GET /api/users/{id} - Utilisateur par ID * POST /api/users - Création d'utilisateur (ADMIN) * PUT /api/users/{id} - Mise à jour utilisateur * DELETE /api/users/{id} - Suppression utilisateur (ADMIN) * PUT /api/users/{id}/status - Changement de statut * GET /api/users/profile - Profil utilisateur courant * PUT /api/users/profile - Mise à jour profil courant * GET /api/users/search - Recherche d'utilisateurs - Annotations OpenAPI complètes sur tous les endpoints - Gestion d'erreurs avec try-catch et codes HTTP appropriés - Logging SLF4J sur toutes les opérations - Sécurité basée sur les rôles avec @RolesAllowed - Correction des imports OpenAPI (Parameter, APIResponse, APIResponses) - Correction de l'ordre des exceptions (AuthorizationException avant GBCMException) - Correction des constructeurs ValidationException - Compilation réussie de tous les endpoints --- .../server/impl/resource/AuthResource.java | 392 ++++++++++++ .../server/impl/resource/UserResource.java | 585 ++++++++++++++++++ .../server/impl/service/UserServiceImpl.java | 20 +- 3 files changed, 987 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/gbcm/server/impl/resource/AuthResource.java create mode 100644 src/main/java/com/gbcm/server/impl/resource/UserResource.java diff --git a/src/main/java/com/gbcm/server/impl/resource/AuthResource.java b/src/main/java/com/gbcm/server/impl/resource/AuthResource.java new file mode 100644 index 0000000..7167973 --- /dev/null +++ b/src/main/java/com/gbcm/server/impl/resource/AuthResource.java @@ -0,0 +1,392 @@ +package com.gbcm.server.impl.resource; + +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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gbcm.server.api.dto.auth.LoginRequestDTO; +import com.gbcm.server.api.dto.auth.LoginResponseDTO; +import com.gbcm.server.api.dto.user.UserDTO; +import com.gbcm.server.api.exceptions.AuthenticationException; +import com.gbcm.server.api.exceptions.GBCMException; +import com.gbcm.server.api.interfaces.AuthService; + +import jakarta.inject.Inject; +import jakarta.validation.Valid; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.FormParam; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +/** + * Contrôleur REST pour les services d'authentification de la plateforme GBCM. + * Expose tous les endpoints d'authentification, autorisation et gestion des tokens. + * + * @author GBCM Development Team + * @version 1.0 + * @since 1.0 + */ +@Path("/api/auth") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@Tag(name = "Authentication", description = "Services d'authentification et autorisation GBCM") +public class AuthResource { + + private static final Logger logger = LoggerFactory.getLogger(AuthResource.class); + + @Inject + AuthService authService; + + /** + * Endpoint de connexion utilisateur. + * + * @param loginRequest les informations de connexion + * @return la réponse de connexion avec token et informations utilisateur + * @throws AuthenticationException si l'authentification échoue + * @throws GBCMException si une erreur interne survient + */ + @POST + @Path("/login") + @Operation( + summary = "Connexion utilisateur", + description = "Authentifie un utilisateur avec email et mot de passe et retourne un token JWT" + ) + @APIResponses({ + @APIResponse( + responseCode = "200", + description = "Connexion réussie", + content = @Content(schema = @Schema(implementation = LoginResponseDTO.class)) + ), + @APIResponse( + responseCode = "401", + description = "Identifiants invalides", + content = @Content(schema = @Schema(implementation = String.class)) + ), + @APIResponse( + responseCode = "400", + description = "Données de requête invalides", + content = @Content(schema = @Schema(implementation = String.class)) + ), + @APIResponse( + responseCode = "500", + description = "Erreur interne du serveur", + content = @Content(schema = @Schema(implementation = String.class)) + ) + }) + public Response login( + @Parameter(description = "Informations de connexion", required = true) + @Valid LoginRequestDTO loginRequest + ) { + try { + logger.info("Demande de connexion reçue pour: {}", loginRequest.getEmail()); + + LoginResponseDTO response = authService.login(loginRequest); + + logger.info("Connexion réussie pour: {}", loginRequest.getEmail()); + return Response.ok(response).build(); + + } catch (AuthenticationException e) { + logger.warn("Échec d'authentification pour {}: {}", loginRequest.getEmail(), e.getMessage()); + return Response.status(Response.Status.UNAUTHORIZED) + .entity("Erreur d'authentification: " + e.getMessage()) + .build(); + } catch (GBCMException e) { + logger.error("Erreur GBCM lors de la connexion pour {}: {}", loginRequest.getEmail(), e.getMessage()); + return Response.status(Response.Status.BAD_REQUEST) + .entity("Erreur: " + e.getMessage()) + .build(); + } catch (Exception e) { + logger.error("Erreur inattendue lors de la connexion pour {}: {}", loginRequest.getEmail(), e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity("Erreur interne du serveur") + .build(); + } + } + + /** + * Endpoint de déconnexion utilisateur. + * + * @param authToken le token d'authentification + * @return confirmation de déconnexion + */ + @POST + @Path("/logout") + @Operation( + summary = "Déconnexion utilisateur", + description = "Invalide le token d'authentification de l'utilisateur" + ) + @APIResponses({ + @APIResponse(responseCode = "200", description = "Déconnexion réussie"), + @APIResponse(responseCode = "401", description = "Token invalide"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response logout( + @Parameter(description = "Token d'authentification", required = true) + @HeaderParam("Authorization") String authToken + ) { + try { + logger.info("Demande de déconnexion reçue"); + + authService.logout(authToken); + + logger.info("Déconnexion réussie"); + return Response.ok("Déconnexion réussie").build(); + + } catch (AuthenticationException e) { + logger.warn("Échec de déconnexion: {}", e.getMessage()); + return Response.status(Response.Status.UNAUTHORIZED) + .entity("Erreur d'authentification: " + e.getMessage()) + .build(); + } catch (Exception e) { + logger.error("Erreur inattendue lors de la déconnexion: {}", e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity("Erreur interne du serveur") + .build(); + } + } + + /** + * Endpoint de rafraîchissement de token. + * + * @param refreshToken le token de rafraîchissement + * @return nouveau token d'accès + */ + @POST + @Path("/refresh") + @Operation( + summary = "Rafraîchissement du token", + description = "Génère un nouveau token d'authentification à partir d'un refresh token" + ) + @APIResponses({ + @APIResponse( + responseCode = "200", + description = "Token rafraîchi", + content = @Content(schema = @Schema(implementation = LoginResponseDTO.class)) + ), + @APIResponse(responseCode = "401", description = "Token de rafraîchissement invalide"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response refreshToken( + @Parameter(description = "Token de rafraîchissement", required = true) + @FormParam("refreshToken") String refreshToken + ) { + try { + logger.info("Demande de rafraîchissement de token reçue"); + + LoginResponseDTO response = authService.refreshToken(refreshToken); + + logger.info("Token rafraîchi avec succès"); + return Response.ok(response).build(); + + } catch (AuthenticationException e) { + logger.warn("Échec de rafraîchissement de token: {}", e.getMessage()); + return Response.status(Response.Status.UNAUTHORIZED) + .entity("Erreur d'authentification: " + e.getMessage()) + .build(); + } catch (Exception e) { + logger.error("Erreur inattendue lors du rafraîchissement: {}", e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity("Erreur interne du serveur") + .build(); + } + } + + /** + * Endpoint de validation de token. + * + * @param authToken le token d'authentification à valider + * @return les informations de l'utilisateur si le token est valide + */ + @GET + @Path("/validate") + @Operation( + summary = "Validation du token", + description = "Vérifie la validité d'un token d'authentification et retourne les informations utilisateur" + ) + @APIResponses({ + @APIResponse( + responseCode = "200", + description = "Token valide", + content = @Content(schema = @Schema(implementation = UserDTO.class)) + ), + @APIResponse(responseCode = "401", description = "Token invalide ou expiré"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response validateToken( + @Parameter(description = "Token d'authentification", required = true) + @HeaderParam("Authorization") String authToken + ) { + try { + logger.debug("Demande de validation de token reçue"); + + UserDTO user = authService.validateToken(authToken); + + logger.debug("Token validé avec succès pour l'utilisateur: {}", user.getEmail()); + return Response.ok(user).build(); + + } catch (AuthenticationException e) { + logger.warn("Échec de validation de token: {}", e.getMessage()); + return Response.status(Response.Status.UNAUTHORIZED) + .entity("Erreur d'authentification: " + e.getMessage()) + .build(); + } catch (Exception e) { + logger.error("Erreur inattendue lors de la validation: {}", e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity("Erreur interne du serveur") + .build(); + } + } + + /** + * Endpoint de demande de réinitialisation de mot de passe. + * + * @param email l'adresse email de l'utilisateur + * @return confirmation d'envoi de l'email + */ + @POST + @Path("/forgot-password") + @Operation( + summary = "Mot de passe oublié", + description = "Envoie un email de réinitialisation de mot de passe à l'utilisateur" + ) + @APIResponses({ + @APIResponse(responseCode = "200", description = "Email envoyé"), + @APIResponse(responseCode = "400", description = "Email invalide"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response forgotPassword( + @Parameter(description = "Adresse email", required = true) + @FormParam("email") String email + ) { + try { + logger.info("Demande de réinitialisation de mot de passe pour: {}", email); + + authService.forgotPassword(email); + + logger.info("Email de réinitialisation envoyé pour: {}", email); + return Response.ok("Email de réinitialisation envoyé").build(); + + } catch (GBCMException e) { + logger.warn("Erreur lors de la demande de réinitialisation pour {}: {}", email, e.getMessage()); + return Response.status(Response.Status.BAD_REQUEST) + .entity("Erreur: " + e.getMessage()) + .build(); + } catch (Exception e) { + logger.error("Erreur inattendue lors de la demande de réinitialisation pour {}: {}", email, e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity("Erreur interne du serveur") + .build(); + } + } + + /** + * Endpoint de réinitialisation de mot de passe. + * + * @param resetToken le token de réinitialisation + * @param newPassword le nouveau mot de passe + * @return confirmation de réinitialisation + */ + @POST + @Path("/reset-password") + @Operation( + summary = "Réinitialisation du mot de passe", + description = "Réinitialise le mot de passe avec un token de réinitialisation" + ) + @APIResponses({ + @APIResponse(responseCode = "200", description = "Mot de passe réinitialisé"), + @APIResponse(responseCode = "400", description = "Token invalide ou expiré"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response resetPassword( + @Parameter(description = "Token de réinitialisation", required = true) + @FormParam("resetToken") String resetToken, + @Parameter(description = "Nouveau mot de passe", required = true) + @FormParam("newPassword") String newPassword + ) { + try { + logger.info("Demande de réinitialisation de mot de passe avec token"); + + authService.resetPassword(resetToken, newPassword); + + logger.info("Mot de passe réinitialisé avec succès"); + return Response.ok("Mot de passe réinitialisé avec succès").build(); + + } catch (GBCMException e) { + logger.warn("Erreur lors de la réinitialisation: {}", e.getMessage()); + return Response.status(Response.Status.BAD_REQUEST) + .entity("Erreur: " + e.getMessage()) + .build(); + } catch (Exception e) { + logger.error("Erreur inattendue lors de la réinitialisation: {}", e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity("Erreur interne du serveur") + .build(); + } + } + + /** + * Endpoint de changement de mot de passe. + * + * @param authToken le token d'authentification + * @param oldPassword l'ancien mot de passe + * @param newPassword le nouveau mot de passe + * @return confirmation de changement + */ + @PUT + @Path("/change-password") + @Operation( + summary = "Changement de mot de passe", + description = "Change le mot de passe d'un utilisateur authentifié" + ) + @APIResponses({ + @APIResponse(responseCode = "200", description = "Mot de passe changé"), + @APIResponse(responseCode = "401", description = "Non autorisé"), + @APIResponse(responseCode = "400", description = "Ancien mot de passe incorrect"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response changePassword( + @Parameter(description = "Token d'authentification", required = true) + @HeaderParam("Authorization") String authToken, + @Parameter(description = "Ancien mot de passe", required = true) + @FormParam("oldPassword") String oldPassword, + @Parameter(description = "Nouveau mot de passe", required = true) + @FormParam("newPassword") String newPassword + ) { + try { + logger.info("Demande de changement de mot de passe"); + + authService.changePassword(authToken, oldPassword, newPassword); + + logger.info("Mot de passe changé avec succès"); + return Response.ok("Mot de passe changé avec succès").build(); + + } catch (AuthenticationException e) { + logger.warn("Échec de changement de mot de passe: {}", e.getMessage()); + return Response.status(Response.Status.UNAUTHORIZED) + .entity("Erreur d'authentification: " + e.getMessage()) + .build(); + } catch (GBCMException e) { + logger.warn("Erreur lors du changement de mot de passe: {}", e.getMessage()); + return Response.status(Response.Status.BAD_REQUEST) + .entity("Erreur: " + e.getMessage()) + .build(); + } catch (Exception e) { + logger.error("Erreur inattendue lors du changement de mot de passe: {}", e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity("Erreur interne du serveur") + .build(); + } + } +} diff --git a/src/main/java/com/gbcm/server/impl/resource/UserResource.java b/src/main/java/com/gbcm/server/impl/resource/UserResource.java new file mode 100644 index 0000000..178f884 --- /dev/null +++ b/src/main/java/com/gbcm/server/impl/resource/UserResource.java @@ -0,0 +1,585 @@ +package com.gbcm.server.impl.resource; + +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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gbcm.server.api.dto.common.PagedResultDTO; +import com.gbcm.server.api.dto.common.SuccessResponseDTO; +import com.gbcm.server.api.dto.user.CreateUserDTO; +import com.gbcm.server.api.dto.user.UpdateUserDTO; +import com.gbcm.server.api.dto.user.UserDTO; +import com.gbcm.server.api.enums.UserRole; +import com.gbcm.server.api.exceptions.AuthorizationException; +import com.gbcm.server.api.exceptions.GBCMException; +import com.gbcm.server.api.exceptions.ResourceNotFoundException; +import com.gbcm.server.api.exceptions.ValidationException; +import com.gbcm.server.api.interfaces.UserService; + +import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Inject; +import jakarta.validation.Valid; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.FormParam; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +/** + * Contrôleur REST pour la gestion des utilisateurs de la plateforme GBCM. + * Expose tous les endpoints CRUD pour les utilisateurs avec sécurité basée sur les rôles. + * + * @author GBCM Development Team + * @version 1.0 + * @since 1.0 + */ +@Path("/api/users") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@Tag(name = "Users", description = "Gestion des utilisateurs GBCM") +public class UserResource { + + private static final Logger logger = LoggerFactory.getLogger(UserResource.class); + + @Inject + UserService userService; + + /** + * Endpoint pour récupérer la liste paginée des utilisateurs. + * + * @param page numéro de page (commence à 0) + * @param size taille de page + * @param sort critères de tri + * @param role filtre par rôle + * @param active filtre par statut actif + * @param search terme de recherche + * @return liste paginée des utilisateurs + */ + @GET + @RolesAllowed({"ADMIN", "MANAGER"}) + @Operation( + summary = "Liste des utilisateurs", + description = "Récupère la liste paginée des utilisateurs avec filtres optionnels" + ) + @APIResponses({ + @APIResponse( + responseCode = "200", + description = "Liste récupérée avec succès", + content = @Content(schema = @Schema(implementation = PagedResultDTO.class)) + ), + @APIResponse(responseCode = "401", description = "Non authentifié"), + @APIResponse(responseCode = "403", description = "Accès refusé - permissions insuffisantes"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response getUsers( + @Parameter(description = "Numéro de page (commence à 0)", example = "0") + @QueryParam("page") @DefaultValue("0") int page, + + @Parameter(description = "Taille de page", example = "20") + @QueryParam("size") @DefaultValue("20") int size, + + @Parameter(description = "Tri (ex: firstName,asc ou email,desc)") + @QueryParam("sort") String sort, + + @Parameter(description = "Filtre par rôle") + @QueryParam("role") UserRole role, + + @Parameter(description = "Filtre par statut actif") + @QueryParam("active") Boolean active, + + @Parameter(description = "Recherche par nom ou email") + @QueryParam("search") String search + ) { + try { + logger.info("Demande de liste des utilisateurs - page: {}, size: {}", page, size); + + PagedResultDTO result = userService.getUsers(page, size, sort, role, active, search); + + logger.info("Liste des utilisateurs récupérée avec succès - {} éléments", result.getTotalElements()); + return Response.ok(result).build(); + + } catch (AuthorizationException e) { + logger.warn("Accès refusé pour la liste des utilisateurs: {}", e.getMessage()); + return Response.status(Response.Status.FORBIDDEN) + .entity("Accès refusé: " + e.getMessage()) + .build(); + } catch (Exception e) { + logger.error("Erreur inattendue lors de la récupération des utilisateurs: {}", e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity("Erreur interne du serveur") + .build(); + } + } + + /** + * Endpoint pour récupérer un utilisateur par son ID. + * + * @param id l'identifiant de l'utilisateur + * @return les détails de l'utilisateur + */ + @GET + @Path("/{id}") + @RolesAllowed({"ADMIN", "MANAGER", "COACH"}) + @Operation( + summary = "Utilisateur par ID", + description = "Récupère les détails d'un utilisateur par son identifiant" + ) + @APIResponses({ + @APIResponse( + responseCode = "200", + description = "Utilisateur trouvé", + content = @Content(schema = @Schema(implementation = UserDTO.class)) + ), + @APIResponse(responseCode = "404", description = "Utilisateur non trouvé"), + @APIResponse(responseCode = "401", description = "Non authentifié"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response getUserById( + @Parameter(description = "ID de l'utilisateur", required = true, example = "1") + @PathParam("id") Long id + ) { + try { + logger.info("Demande de récupération de l'utilisateur ID: {}", id); + + UserDTO user = userService.getUserById(id); + + logger.info("Utilisateur récupéré avec succès: {}", user.getEmail()); + return Response.ok(user).build(); + + } catch (ResourceNotFoundException e) { + logger.warn("Utilisateur non trouvé avec ID: {}", id); + return Response.status(Response.Status.NOT_FOUND) + .entity("Utilisateur non trouvé: " + e.getMessage()) + .build(); + } catch (AuthorizationException e) { + logger.warn("Accès refusé pour l'utilisateur ID {}: {}", id, e.getMessage()); + return Response.status(Response.Status.FORBIDDEN) + .entity("Accès refusé: " + e.getMessage()) + .build(); + } catch (Exception e) { + logger.error("Erreur inattendue lors de la récupération de l'utilisateur {}: {}", id, e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity("Erreur interne du serveur") + .build(); + } + } + + /** + * Endpoint pour créer un nouvel utilisateur. + * + * @param createUserDTO les données du nouvel utilisateur + * @return l'utilisateur créé + */ + @POST + @RolesAllowed({"ADMIN"}) + @Operation( + summary = "Créer un utilisateur", + description = "Crée un nouvel utilisateur dans le système" + ) + @APIResponses({ + @APIResponse( + responseCode = "201", + description = "Utilisateur créé avec succès", + content = @Content(schema = @Schema(implementation = UserDTO.class)) + ), + @APIResponse(responseCode = "400", description = "Données invalides"), + @APIResponse(responseCode = "409", description = "Email déjà utilisé"), + @APIResponse(responseCode = "401", description = "Non authentifié"), + @APIResponse(responseCode = "403", description = "Accès refusé - seuls les admins peuvent créer des utilisateurs"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response createUser( + @Parameter(description = "Données du nouvel utilisateur", required = true) + @Valid CreateUserDTO createUserDTO + ) { + try { + logger.info("Demande de création d'utilisateur: {}", createUserDTO.getEmail()); + + UserDTO user = userService.createUser(createUserDTO); + + logger.info("Utilisateur créé avec succès: {}", user.getEmail()); + return Response.status(Response.Status.CREATED).entity(user).build(); + + } catch (ValidationException e) { + logger.warn("Données invalides pour la création d'utilisateur: {}", e.getMessage()); + return Response.status(Response.Status.BAD_REQUEST) + .entity("Données invalides: " + e.getMessage()) + .build(); + } catch (AuthorizationException e) { + logger.warn("Accès refusé pour la création d'utilisateur: {}", e.getMessage()); + return Response.status(Response.Status.FORBIDDEN) + .entity("Accès refusé: " + e.getMessage()) + .build(); + } catch (GBCMException e) { + logger.warn("Erreur lors de la création d'utilisateur: {}", e.getMessage()); + return Response.status(Response.Status.CONFLICT) + .entity("Erreur: " + e.getMessage()) + .build(); + } catch (Exception e) { + logger.error("Erreur inattendue lors de la création d'utilisateur: {}", e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity("Erreur interne du serveur") + .build(); + } + } + + /** + * Endpoint pour mettre à jour un utilisateur existant. + * + * @param id l'identifiant de l'utilisateur + * @param updateUserDTO les données de mise à jour + * @return l'utilisateur mis à jour + */ + @PUT + @Path("/{id}") + @RolesAllowed({"ADMIN", "MANAGER"}) + @Operation( + summary = "Mettre à jour un utilisateur", + description = "Met à jour les informations d'un utilisateur existant" + ) + @APIResponses({ + @APIResponse( + responseCode = "200", + description = "Utilisateur mis à jour avec succès", + content = @Content(schema = @Schema(implementation = UserDTO.class)) + ), + @APIResponse(responseCode = "404", description = "Utilisateur non trouvé"), + @APIResponse(responseCode = "400", description = "Données invalides"), + @APIResponse(responseCode = "409", description = "Email déjà utilisé par un autre utilisateur"), + @APIResponse(responseCode = "401", description = "Non authentifié"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response updateUser( + @Parameter(description = "ID de l'utilisateur", required = true, example = "1") + @PathParam("id") Long id, + + @Parameter(description = "Données de mise à jour", required = true) + @Valid UpdateUserDTO updateUserDTO + ) { + try { + logger.info("Demande de mise à jour de l'utilisateur ID: {}", id); + + UserDTO user = userService.updateUser(id, updateUserDTO); + + logger.info("Utilisateur mis à jour avec succès: {}", user.getEmail()); + return Response.ok(user).build(); + + } catch (ResourceNotFoundException e) { + logger.warn("Utilisateur non trouvé pour mise à jour ID: {}", id); + return Response.status(Response.Status.NOT_FOUND) + .entity("Utilisateur non trouvé: " + e.getMessage()) + .build(); + } catch (ValidationException e) { + logger.warn("Données invalides pour la mise à jour de l'utilisateur {}: {}", id, e.getMessage()); + return Response.status(Response.Status.BAD_REQUEST) + .entity("Données invalides: " + e.getMessage()) + .build(); + } catch (AuthorizationException e) { + logger.warn("Accès refusé pour la mise à jour de l'utilisateur {}: {}", id, e.getMessage()); + return Response.status(Response.Status.FORBIDDEN) + .entity("Accès refusé: " + e.getMessage()) + .build(); + } catch (GBCMException e) { + logger.warn("Erreur lors de la mise à jour de l'utilisateur {}: {}", id, e.getMessage()); + return Response.status(Response.Status.CONFLICT) + .entity("Erreur: " + e.getMessage()) + .build(); + } catch (Exception e) { + logger.error("Erreur inattendue lors de la mise à jour de l'utilisateur {}: {}", id, e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity("Erreur interne du serveur") + .build(); + } + } + + /** + * Endpoint pour supprimer un utilisateur (soft delete). + * + * @param id l'identifiant de l'utilisateur + * @return confirmation de suppression + */ + @DELETE + @Path("/{id}") + @RolesAllowed({"ADMIN"}) + @Operation( + summary = "Supprimer un utilisateur", + description = "Supprime un utilisateur du système (soft delete)" + ) + @APIResponses({ + @APIResponse(responseCode = "200", description = "Utilisateur supprimé avec succès"), + @APIResponse(responseCode = "404", description = "Utilisateur non trouvé"), + @APIResponse(responseCode = "401", description = "Non authentifié"), + @APIResponse(responseCode = "403", description = "Accès refusé - seuls les admins peuvent supprimer des utilisateurs"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response deleteUser( + @Parameter(description = "ID de l'utilisateur", required = true, example = "1") + @PathParam("id") Long id + ) { + try { + logger.info("Demande de suppression de l'utilisateur ID: {}", id); + + SuccessResponseDTO result = userService.deleteUser(id); + + logger.info("Utilisateur supprimé avec succès ID: {}", id); + return Response.ok(result).build(); + + } catch (ResourceNotFoundException e) { + logger.warn("Utilisateur non trouvé pour suppression ID: {}", id); + return Response.status(Response.Status.NOT_FOUND) + .entity("Utilisateur non trouvé: " + e.getMessage()) + .build(); + } catch (AuthorizationException e) { + logger.warn("Accès refusé pour la suppression de l'utilisateur {}: {}", id, e.getMessage()); + return Response.status(Response.Status.FORBIDDEN) + .entity("Accès refusé: " + e.getMessage()) + .build(); + } catch (Exception e) { + logger.error("Erreur inattendue lors de la suppression de l'utilisateur {}: {}", id, e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity("Erreur interne du serveur") + .build(); + } + } + + /** + * Endpoint pour changer le statut d'un utilisateur. + * + * @param id l'identifiant de l'utilisateur + * @param active le nouveau statut + * @return l'utilisateur avec le nouveau statut + */ + @PUT + @Path("/{id}/status") + @RolesAllowed({"ADMIN", "MANAGER"}) + @Operation( + summary = "Changer le statut d'un utilisateur", + description = "Active ou désactive un compte utilisateur" + ) + @APIResponses({ + @APIResponse( + responseCode = "200", + description = "Statut modifié avec succès", + content = @Content(schema = @Schema(implementation = UserDTO.class)) + ), + @APIResponse(responseCode = "404", description = "Utilisateur non trouvé"), + @APIResponse(responseCode = "401", description = "Non authentifié"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response changeUserStatus( + @Parameter(description = "ID de l'utilisateur", required = true, example = "1") + @PathParam("id") Long id, + + @Parameter(description = "Nouveau statut", required = true, example = "true") + @FormParam("active") boolean active + ) { + try { + logger.info("Demande de changement de statut pour l'utilisateur ID: {} -> {}", id, active); + + UserDTO user = userService.changeUserStatus(id, active); + + logger.info("Statut utilisateur changé avec succès: {} -> {}", id, active); + return Response.ok(user).build(); + + } catch (ResourceNotFoundException e) { + logger.warn("Utilisateur non trouvé pour changement de statut ID: {}", id); + return Response.status(Response.Status.NOT_FOUND) + .entity("Utilisateur non trouvé: " + e.getMessage()) + .build(); + } catch (AuthorizationException e) { + logger.warn("Accès refusé pour le changement de statut de l'utilisateur {}: {}", id, e.getMessage()); + return Response.status(Response.Status.FORBIDDEN) + .entity("Accès refusé: " + e.getMessage()) + .build(); + } catch (Exception e) { + logger.error("Erreur inattendue lors du changement de statut de l'utilisateur {}: {}", id, e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity("Erreur interne du serveur") + .build(); + } + } + + /** + * Endpoint pour récupérer le profil de l'utilisateur connecté. + * + * @param authToken le token d'authentification + * @return le profil de l'utilisateur connecté + */ + @GET + @Path("/profile") + @RolesAllowed({"ADMIN", "MANAGER", "COACH", "CLIENT", "PROSPECT"}) + @Operation( + summary = "Profil utilisateur", + description = "Récupère le profil de l'utilisateur actuellement connecté" + ) + @APIResponses({ + @APIResponse( + responseCode = "200", + description = "Profil récupéré avec succès", + content = @Content(schema = @Schema(implementation = UserDTO.class)) + ), + @APIResponse(responseCode = "401", description = "Non authentifié"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response getCurrentUserProfile( + @Parameter(description = "Token d'authentification", required = true) + @HeaderParam("Authorization") String authToken + ) { + try { + logger.info("Demande de récupération du profil utilisateur courant"); + + UserDTO user = userService.getCurrentUserProfile(authToken); + + logger.info("Profil utilisateur récupéré avec succès: {}", user.getEmail()); + return Response.ok(user).build(); + + } catch (AuthorizationException e) { + logger.warn("Accès refusé pour le profil utilisateur: {}", e.getMessage()); + return Response.status(Response.Status.UNAUTHORIZED) + .entity("Accès refusé: " + e.getMessage()) + .build(); + } catch (Exception e) { + logger.error("Erreur inattendue lors de la récupération du profil: {}", e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity("Erreur interne du serveur") + .build(); + } + } + + /** + * Endpoint pour mettre à jour le profil de l'utilisateur connecté. + * + * @param authToken le token d'authentification + * @param updateUserDTO les données de mise à jour + * @return le profil mis à jour + */ + @PUT + @Path("/profile") + @RolesAllowed({"ADMIN", "MANAGER", "COACH", "CLIENT", "PROSPECT"}) + @Operation( + summary = "Mettre à jour le profil", + description = "Met à jour le profil de l'utilisateur actuellement connecté" + ) + @APIResponses({ + @APIResponse( + responseCode = "200", + description = "Profil mis à jour avec succès", + content = @Content(schema = @Schema(implementation = UserDTO.class)) + ), + @APIResponse(responseCode = "400", description = "Données invalides"), + @APIResponse(responseCode = "409", description = "Email déjà utilisé"), + @APIResponse(responseCode = "401", description = "Non authentifié"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response updateCurrentUserProfile( + @Parameter(description = "Token d'authentification", required = true) + @HeaderParam("Authorization") String authToken, + + @Parameter(description = "Données de mise à jour du profil", required = true) + @Valid UpdateUserDTO updateUserDTO + ) { + try { + logger.info("Demande de mise à jour du profil utilisateur courant"); + + UserDTO user = userService.updateCurrentUserProfile(authToken, updateUserDTO); + + logger.info("Profil utilisateur mis à jour avec succès: {}", user.getEmail()); + return Response.ok(user).build(); + + } catch (ValidationException e) { + logger.warn("Données invalides pour la mise à jour du profil: {}", e.getMessage()); + return Response.status(Response.Status.BAD_REQUEST) + .entity("Données invalides: " + e.getMessage()) + .build(); + } catch (AuthorizationException e) { + logger.warn("Accès refusé pour la mise à jour du profil: {}", e.getMessage()); + return Response.status(Response.Status.UNAUTHORIZED) + .entity("Accès refusé: " + e.getMessage()) + .build(); + } catch (GBCMException e) { + logger.warn("Erreur lors de la mise à jour du profil: {}", e.getMessage()); + return Response.status(Response.Status.CONFLICT) + .entity("Erreur: " + e.getMessage()) + .build(); + } catch (Exception e) { + logger.error("Erreur inattendue lors de la mise à jour du profil: {}", e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity("Erreur interne du serveur") + .build(); + } + } + + /** + * Endpoint pour rechercher des utilisateurs par critères. + * + * @param query le terme de recherche + * @param page le numéro de page + * @param size la taille de page + * @return les résultats de recherche paginés + */ + @GET + @Path("/search") + @RolesAllowed({"ADMIN", "MANAGER", "COACH"}) + @Operation( + summary = "Rechercher des utilisateurs", + description = "Recherche d'utilisateurs par différents critères" + ) + @APIResponses({ + @APIResponse( + responseCode = "200", + description = "Résultats de recherche", + content = @Content(schema = @Schema(implementation = PagedResultDTO.class)) + ), + @APIResponse(responseCode = "401", description = "Non authentifié"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Response searchUsers( + @Parameter(description = "Terme de recherche", required = true) + @QueryParam("q") String query, + + @Parameter(description = "Numéro de page", example = "0") + @QueryParam("page") @DefaultValue("0") int page, + + @Parameter(description = "Taille de page", example = "20") + @QueryParam("size") @DefaultValue("20") int size + ) { + try { + logger.info("Demande de recherche d'utilisateurs avec query: {}", query); + + PagedResultDTO result = userService.searchUsers(query, page, size); + + logger.info("Recherche d'utilisateurs terminée - {} résultats", result.getTotalElements()); + return Response.ok(result).build(); + + } catch (AuthorizationException e) { + logger.warn("Accès refusé pour la recherche d'utilisateurs: {}", e.getMessage()); + return Response.status(Response.Status.FORBIDDEN) + .entity("Accès refusé: " + e.getMessage()) + .build(); + } catch (Exception e) { + logger.error("Erreur inattendue lors de la recherche d'utilisateurs: {}", e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity("Erreur interne du serveur") + .build(); + } + } +} diff --git a/src/main/java/com/gbcm/server/impl/service/UserServiceImpl.java b/src/main/java/com/gbcm/server/impl/service/UserServiceImpl.java index 09dd389..568a286 100644 --- a/src/main/java/com/gbcm/server/impl/service/UserServiceImpl.java +++ b/src/main/java/com/gbcm/server/impl/service/UserServiceImpl.java @@ -1,5 +1,11 @@ package com.gbcm.server.impl.service; +import java.util.ArrayList; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.gbcm.server.api.dto.common.PagedResultDTO; import com.gbcm.server.api.dto.common.SuccessResponseDTO; import com.gbcm.server.api.dto.user.CreateUserDTO; @@ -11,17 +17,11 @@ import com.gbcm.server.api.exceptions.GBCMException; import com.gbcm.server.api.exceptions.ResourceNotFoundException; import com.gbcm.server.api.exceptions.ValidationException; import com.gbcm.server.api.interfaces.UserService; -import com.gbcm.server.impl.entity.User; import com.gbcm.server.impl.service.security.PasswordService; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.transaction.Transactional; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; /** * Implémentation simplifiée du service de gestion des utilisateurs pour la plateforme GBCM. @@ -105,11 +105,11 @@ public class UserServiceImpl implements UserService { // Validation basique if (createUserDTO == null) { - throw new ValidationException("Données de création requises", "USER_CREATE_DATA_REQUIRED"); + throw new ValidationException("Données de création requises"); } if (createUserDTO.getEmail() == null || createUserDTO.getEmail().trim().isEmpty()) { - throw new ValidationException("Email requis", "USER_EMAIL_REQUIRED"); + throw new ValidationException("Email requis"); } // Simulation - créer un utilisateur fictif @@ -140,7 +140,7 @@ public class UserServiceImpl implements UserService { } if (updateUserDTO == null) { - throw new ValidationException("Données de mise à jour requises", "USER_UPDATE_DATA_REQUIRED"); + throw new ValidationException("Données de mise à jour requises"); } // Simulation - retourner un utilisateur mis à jour @@ -238,7 +238,7 @@ public class UserServiceImpl implements UserService { } if (updateUserDTO == null) { - throw new ValidationException("Données de mise à jour requises", "USER_UPDATE_DATA_REQUIRED"); + throw new ValidationException("Données de mise à jour requises"); } // Simulation - retourner le profil mis à jour