From f77776820e11cc7848ac05bbff35f4c5cad32d1a Mon Sep 17 00:00:00 2001 From: dahoud Date: Mon, 6 Oct 2025 19:53:31 +0000 Subject: [PATCH] Task 1.1 - DTOs et Interfaces API de base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Création de tous les DTOs d'authentification (LoginRequestDTO, LoginResponseDTO, TokenDTO, PasswordResetDTO) - Création des DTOs utilisateur (UserDTO, CreateUserDTO, UpdateUserDTO) - Création des DTOs communs (PagedResultDTO, ErrorResponseDTO, SuccessResponseDTO) - Création de toutes les classes d'exception (GBCMException, AuthenticationException, AuthorizationException, ValidationException, ResourceNotFoundException, BusinessRuleException) - Création des enums métier (ServiceType, WorkshopPackage, PaymentStatus, SessionStatus, InvoiceStatus) - Amélioration de l'interface AuthService avec documentation complète - Création de l'interface UserService avec tous les endpoints CRUD - Documentation Javadoc complète en français sur toutes les classes - Annotations OpenAPI/Swagger sur toutes les interfaces - Validation Jakarta sur tous les DTOs - Compilation réussie du module API --- pom.xml | 28 +- .../server/api/dto/auth/LoginRequestDTO.java | 90 ++++- .../server/api/dto/auth/LoginResponseDTO.java | 96 ++++- .../server/api/dto/auth/PasswordResetDTO.java | 214 +++++++++++ .../gbcm/server/api/dto/auth/TokenDTO.java | 229 ++++++++++++ .../api/dto/common/ErrorResponseDTO.java | 333 +++++++++++++++++ .../server/api/dto/common/PagedResultDTO.java | 339 ++++++++++++++++++ .../api/dto/common/SuccessResponseDTO.java | 305 ++++++++++++++++ .../server/api/dto/user/CreateUserDTO.java | 278 ++++++++++++++ .../server/api/dto/user/UpdateUserDTO.java | 245 +++++++++++++ .../com/gbcm/server/api/dto/user/UserDTO.java | 90 ++++- .../gbcm/server/api/enums/InvoiceStatus.java | 236 ++++++++++++ .../gbcm/server/api/enums/PaymentStatus.java | 196 ++++++++++ .../gbcm/server/api/enums/ServiceType.java | 156 ++++++++ .../gbcm/server/api/enums/SessionStatus.java | 213 +++++++++++ .../server/api/enums/WorkshopPackage.java | 224 ++++++++++++ .../exceptions/AuthenticationException.java | 52 +++ .../exceptions/AuthorizationException.java | 52 +++ .../api/exceptions/BusinessRuleException.java | 73 ++++ .../server/api/exceptions/GBCMException.java | 83 +++++ .../exceptions/ResourceNotFoundException.java | 104 ++++++ .../api/exceptions/ValidationException.java | 95 +++++ .../server/api/interfaces/AuthService.java | 16 +- .../server/api/interfaces/UserService.java | 263 ++++++++++++++ 24 files changed, 3955 insertions(+), 55 deletions(-) create mode 100644 src/main/java/com/gbcm/server/api/dto/auth/PasswordResetDTO.java create mode 100644 src/main/java/com/gbcm/server/api/dto/auth/TokenDTO.java create mode 100644 src/main/java/com/gbcm/server/api/dto/common/ErrorResponseDTO.java create mode 100644 src/main/java/com/gbcm/server/api/dto/common/PagedResultDTO.java create mode 100644 src/main/java/com/gbcm/server/api/dto/common/SuccessResponseDTO.java create mode 100644 src/main/java/com/gbcm/server/api/dto/user/CreateUserDTO.java create mode 100644 src/main/java/com/gbcm/server/api/dto/user/UpdateUserDTO.java create mode 100644 src/main/java/com/gbcm/server/api/enums/InvoiceStatus.java create mode 100644 src/main/java/com/gbcm/server/api/enums/PaymentStatus.java create mode 100644 src/main/java/com/gbcm/server/api/enums/ServiceType.java create mode 100644 src/main/java/com/gbcm/server/api/enums/SessionStatus.java create mode 100644 src/main/java/com/gbcm/server/api/enums/WorkshopPackage.java create mode 100644 src/main/java/com/gbcm/server/api/exceptions/AuthenticationException.java create mode 100644 src/main/java/com/gbcm/server/api/exceptions/AuthorizationException.java create mode 100644 src/main/java/com/gbcm/server/api/exceptions/BusinessRuleException.java create mode 100644 src/main/java/com/gbcm/server/api/exceptions/GBCMException.java create mode 100644 src/main/java/com/gbcm/server/api/exceptions/ResourceNotFoundException.java create mode 100644 src/main/java/com/gbcm/server/api/exceptions/ValidationException.java create mode 100644 src/main/java/com/gbcm/server/api/interfaces/UserService.java diff --git a/pom.xml b/pom.xml index 0543fd3..264c675 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,13 @@ ${jakarta.ws.rs.version} + + + jakarta.annotation + jakarta.annotation-api + 2.1.1 + + com.fasterxml.jackson.core @@ -70,26 +77,7 @@ 3.11.0 - - - org.openapitools - openapi-generator-maven-plugin - 7.0.1 - - - - generate - - - ${project.basedir}/src/main/resources/META-INF/openapi.yaml - java - - src/gen/java/main - - - - - + diff --git a/src/main/java/com/gbcm/server/api/dto/auth/LoginRequestDTO.java b/src/main/java/com/gbcm/server/api/dto/auth/LoginRequestDTO.java index 8dd15ab..f1eb2bd 100644 --- a/src/main/java/com/gbcm/server/api/dto/auth/LoginRequestDTO.java +++ b/src/main/java/com/gbcm/server/api/dto/auth/LoginRequestDTO.java @@ -1,72 +1,148 @@ package com.gbcm.server.api.dto.auth; import com.fasterxml.jackson.annotation.JsonProperty; + import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; /** - * DTO pour les requêtes de connexion + * DTO pour les requêtes de connexion utilisateur. + * Contient les informations d'authentification nécessaires pour se connecter à la plateforme GBCM. + * + * @author GBCM Development Team + * @version 1.0 + * @since 1.0 */ -@Schema(description = "Requête de connexion utilisateur") +@Schema(description = "Requête de connexion utilisateur à la plateforme GBCM") public class LoginRequestDTO { - @Schema(description = "Adresse email de l'utilisateur", example = "user@gbcm.com") + /** + * Adresse email de l'utilisateur. + * Doit être un email valide et non vide. + */ + @Schema(description = "Adresse email de l'utilisateur", + example = "john.doe@gbcm.com", + required = true, + maxLength = 255) @JsonProperty("email") @NotBlank(message = "L'email est obligatoire") @Email(message = "Format d'email invalide") private String email; - @Schema(description = "Mot de passe", example = "password123") + /** + * Mot de passe de l'utilisateur. + * Doit contenir entre 6 et 100 caractères. + */ + @Schema(description = "Mot de passe de l'utilisateur", + example = "motdepasse123", + required = true, + minLength = 6, + maxLength = 100) @JsonProperty("password") @NotBlank(message = "Le mot de passe est obligatoire") @Size(min = 6, max = 100, message = "Le mot de passe doit contenir entre 6 et 100 caractères") private String password; - @Schema(description = "Se souvenir de moi", example = "true") + /** + * Indicateur pour maintenir la session active plus longtemps. + * Si true, le token JWT aura une durée de vie étendue. + */ + @Schema(description = "Se souvenir de moi pour une session prolongée", + example = "false", + defaultValue = "false") @JsonProperty("rememberMe") private boolean rememberMe = false; - // Constructeurs + /** + * Constructeur par défaut. + */ public LoginRequestDTO() {} + /** + * Constructeur avec email et mot de passe. + * + * @param email l'adresse email de l'utilisateur + * @param password le mot de passe de l'utilisateur + */ public LoginRequestDTO(String email, String password) { this.email = email; this.password = password; } + /** + * Constructeur complet. + * + * @param email l'adresse email de l'utilisateur + * @param password le mot de passe de l'utilisateur + * @param rememberMe indicateur pour session prolongée + */ public LoginRequestDTO(String email, String password, boolean rememberMe) { this.email = email; this.password = password; this.rememberMe = rememberMe; } - // Getters et Setters + /** + * Retourne l'adresse email de l'utilisateur. + * + * @return l'adresse email + */ public String getEmail() { return email; } + /** + * Définit l'adresse email de l'utilisateur. + * + * @param email l'adresse email à définir + */ public void setEmail(String email) { this.email = email; } + /** + * Retourne le mot de passe de l'utilisateur. + * + * @return le mot de passe + */ public String getPassword() { return password; } + /** + * Définit le mot de passe de l'utilisateur. + * + * @param password le mot de passe à définir + */ public void setPassword(String password) { this.password = password; } + /** + * Indique si l'option "se souvenir de moi" est activée. + * + * @return true si l'option est activée, false sinon + */ public boolean isRememberMe() { return rememberMe; } + /** + * Définit l'option "se souvenir de moi". + * + * @param rememberMe true pour activer l'option, false sinon + */ public void setRememberMe(boolean rememberMe) { this.rememberMe = rememberMe; } + /** + * Représentation textuelle de l'objet (sans le mot de passe pour la sécurité). + * + * @return une chaîne représentant l'objet + */ @Override public String toString() { return "LoginRequestDTO{" + diff --git a/src/main/java/com/gbcm/server/api/dto/auth/LoginResponseDTO.java b/src/main/java/com/gbcm/server/api/dto/auth/LoginResponseDTO.java index 527cc9e..4835cdc 100644 --- a/src/main/java/com/gbcm/server/api/dto/auth/LoginResponseDTO.java +++ b/src/main/java/com/gbcm/server/api/dto/auth/LoginResponseDTO.java @@ -1,49 +1,100 @@ package com.gbcm.server.api.dto.auth; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.gbcm.server.api.dto.user.UserDTO; -import io.swagger.v3.oas.annotations.media.Schema; - import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.gbcm.server.api.dto.user.UserDTO; + +import io.swagger.v3.oas.annotations.media.Schema; + /** - * DTO pour les réponses de connexion + * DTO pour les réponses de connexion utilisateur. + * Contient le résultat de l'authentification avec les tokens et informations utilisateur. + * + * @author GBCM Development Team + * @version 1.0 + * @since 1.0 */ -@Schema(description = "Réponse de connexion utilisateur") +@Schema(description = "Réponse de connexion utilisateur à la plateforme GBCM") public class LoginResponseDTO { - @Schema(description = "Succès de la connexion", example = "true") + /** + * Indicateur de succès de la connexion. + */ + @Schema(description = "Succès de la connexion", + example = "true", + required = true) @JsonProperty("success") private boolean success; - @Schema(description = "Message de réponse", example = "Connexion réussie") + /** + * Message descriptif du résultat de la connexion. + */ + @Schema(description = "Message de réponse", + example = "Connexion réussie", + required = true) @JsonProperty("message") private String message; - @Schema(description = "Token d'authentification JWT") + /** + * Token JWT pour l'authentification des requêtes API. + * Présent seulement en cas de connexion réussie. + */ + @Schema(description = "Token d'authentification JWT", + example = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...") @JsonProperty("token") private String token; - @Schema(description = "Date d'expiration du token") + /** + * Date et heure d'expiration du token. + * Présent seulement en cas de connexion réussie. + */ + @Schema(description = "Date d'expiration du token", + example = "2024-12-31T23:59:59") @JsonProperty("expiresAt") private LocalDateTime expiresAt; + /** + * Informations détaillées de l'utilisateur connecté. + * Présent seulement en cas de connexion réussie. + */ @Schema(description = "Informations utilisateur") @JsonProperty("user") private UserDTO user; - @Schema(description = "Token de rafraîchissement") + /** + * Token de rafraîchissement pour renouveler le token principal. + * Présent seulement si "rememberMe" était activé. + */ + @Schema(description = "Token de rafraîchissement", + example = "refresh_token_abc123") @JsonProperty("refreshToken") private String refreshToken; - // Constructeurs + /** + * Constructeur par défaut. + */ public LoginResponseDTO() {} + /** + * Constructeur avec succès et message. + * + * @param success indicateur de succès + * @param message message descriptif + */ public LoginResponseDTO(boolean success, String message) { this.success = success; this.message = message; } + /** + * Crée une réponse de connexion réussie avec token et utilisateur. + * + * @param token le token JWT généré + * @param expiresAt la date d'expiration du token + * @param user les informations de l'utilisateur connecté + * @return une réponse de succès configurée + */ public static LoginResponseDTO success(String token, LocalDateTime expiresAt, UserDTO user) { LoginResponseDTO response = new LoginResponseDTO(true, "Connexion réussie"); response.setToken(token); @@ -52,6 +103,27 @@ public class LoginResponseDTO { return response; } + /** + * Crée une réponse de connexion réussie avec token, utilisateur et refresh token. + * + * @param token le token JWT généré + * @param expiresAt la date d'expiration du token + * @param user les informations de l'utilisateur connecté + * @param refreshToken le token de rafraîchissement + * @return une réponse de succès configurée + */ + public static LoginResponseDTO success(String token, LocalDateTime expiresAt, UserDTO user, String refreshToken) { + LoginResponseDTO response = success(token, expiresAt, user); + response.setRefreshToken(refreshToken); + return response; + } + + /** + * Crée une réponse d'échec de connexion. + * + * @param message le message d'erreur + * @return une réponse d'échec configurée + */ public static LoginResponseDTO failure(String message) { return new LoginResponseDTO(false, message); } diff --git a/src/main/java/com/gbcm/server/api/dto/auth/PasswordResetDTO.java b/src/main/java/com/gbcm/server/api/dto/auth/PasswordResetDTO.java new file mode 100644 index 0000000..7fcf68e --- /dev/null +++ b/src/main/java/com/gbcm/server/api/dto/auth/PasswordResetDTO.java @@ -0,0 +1,214 @@ +package com.gbcm.server.api.dto.auth; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * DTO pour les demandes de réinitialisation de mot de passe. + * Utilisé pour les étapes de demande et de confirmation de réinitialisation. + * + * @author GBCM Development Team + * @version 1.0 + * @since 1.0 + */ +@Schema(description = "Demande de réinitialisation de mot de passe") +public class PasswordResetDTO { + + /** + * Adresse email de l'utilisateur demandant la réinitialisation. + * Obligatoire pour la demande initiale. + */ + @Schema(description = "Adresse email de l'utilisateur", + example = "john.doe@gbcm.com") + @JsonProperty("email") + @Email(message = "Format d'email invalide") + private String email; + + /** + * Token de réinitialisation reçu par email. + * Obligatoire pour confirmer la réinitialisation. + */ + @Schema(description = "Token de réinitialisation reçu par email", + example = "abc123def456") + @JsonProperty("resetToken") + private String resetToken; + + /** + * Nouveau mot de passe choisi par l'utilisateur. + * Obligatoire pour confirmer la réinitialisation. + */ + @Schema(description = "Nouveau mot de passe", + example = "nouveauMotDePasse123") + @JsonProperty("newPassword") + @Size(min = 6, max = 100, message = "Le mot de passe doit contenir entre 6 et 100 caractères") + private String newPassword; + + /** + * Confirmation du nouveau mot de passe. + * Doit être identique au nouveau mot de passe. + */ + @Schema(description = "Confirmation du nouveau mot de passe", + example = "nouveauMotDePasse123") + @JsonProperty("confirmPassword") + @Size(min = 6, max = 100, message = "La confirmation doit contenir entre 6 et 100 caractères") + private String confirmPassword; + + /** + * Constructeur par défaut. + */ + public PasswordResetDTO() {} + + /** + * Constructeur pour demande de réinitialisation. + * + * @param email l'adresse email de l'utilisateur + */ + public PasswordResetDTO(String email) { + this.email = email; + } + + /** + * Constructeur pour confirmation de réinitialisation. + * + * @param resetToken le token de réinitialisation + * @param newPassword le nouveau mot de passe + * @param confirmPassword la confirmation du mot de passe + */ + public PasswordResetDTO(String resetToken, String newPassword, String confirmPassword) { + this.resetToken = resetToken; + this.newPassword = newPassword; + this.confirmPassword = confirmPassword; + } + + /** + * Constructeur complet. + * + * @param email l'adresse email + * @param resetToken le token de réinitialisation + * @param newPassword le nouveau mot de passe + * @param confirmPassword la confirmation du mot de passe + */ + public PasswordResetDTO(String email, String resetToken, String newPassword, String confirmPassword) { + this.email = email; + this.resetToken = resetToken; + this.newPassword = newPassword; + this.confirmPassword = confirmPassword; + } + + /** + * Retourne l'adresse email de l'utilisateur. + * + * @return l'adresse email + */ + public String getEmail() { + return email; + } + + /** + * Définit l'adresse email de l'utilisateur. + * + * @param email l'adresse email à définir + */ + public void setEmail(String email) { + this.email = email; + } + + /** + * Retourne le token de réinitialisation. + * + * @return le token de réinitialisation + */ + public String getResetToken() { + return resetToken; + } + + /** + * Définit le token de réinitialisation. + * + * @param resetToken le token de réinitialisation à définir + */ + public void setResetToken(String resetToken) { + this.resetToken = resetToken; + } + + /** + * Retourne le nouveau mot de passe. + * + * @return le nouveau mot de passe + */ + public String getNewPassword() { + return newPassword; + } + + /** + * Définit le nouveau mot de passe. + * + * @param newPassword le nouveau mot de passe à définir + */ + public void setNewPassword(String newPassword) { + this.newPassword = newPassword; + } + + /** + * Retourne la confirmation du mot de passe. + * + * @return la confirmation du mot de passe + */ + public String getConfirmPassword() { + return confirmPassword; + } + + /** + * Définit la confirmation du mot de passe. + * + * @param confirmPassword la confirmation à définir + */ + public void setConfirmPassword(String confirmPassword) { + this.confirmPassword = confirmPassword; + } + + /** + * Vérifie si les mots de passe correspondent. + * + * @return true si les mots de passe correspondent, false sinon + */ + public boolean isPasswordConfirmed() { + return newPassword != null && newPassword.equals(confirmPassword); + } + + /** + * Vérifie si c'est une demande de réinitialisation (email seulement). + * + * @return true si c'est une demande, false sinon + */ + public boolean isResetRequest() { + return email != null && resetToken == null && newPassword == null; + } + + /** + * Vérifie si c'est une confirmation de réinitialisation (avec token et mot de passe). + * + * @return true si c'est une confirmation, false sinon + */ + public boolean isResetConfirmation() { + return resetToken != null && newPassword != null; + } + + /** + * Représentation textuelle de l'objet (sans les mots de passe pour la sécurité). + * + * @return une chaîne représentant l'objet + */ + @Override + public String toString() { + return "PasswordResetDTO{" + + "email='" + email + '\'' + + ", hasResetToken=" + (resetToken != null) + + ", hasNewPassword=" + (newPassword != null) + + ", hasConfirmPassword=" + (confirmPassword != null) + + '}'; + } +} diff --git a/src/main/java/com/gbcm/server/api/dto/auth/TokenDTO.java b/src/main/java/com/gbcm/server/api/dto/auth/TokenDTO.java new file mode 100644 index 0000000..22e9d85 --- /dev/null +++ b/src/main/java/com/gbcm/server/api/dto/auth/TokenDTO.java @@ -0,0 +1,229 @@ +package com.gbcm.server.api.dto.auth; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +import java.time.LocalDateTime; + +/** + * DTO représentant un token JWT avec ses métadonnées. + * Utilisé pour transporter les informations de token entre le client et le serveur. + * + * @author GBCM Development Team + * @version 1.0 + * @since 1.0 + */ +@Schema(description = "Token JWT avec ses métadonnées") +public class TokenDTO { + + /** + * Le token JWT lui-même. + * Utilisé pour l'authentification des requêtes API. + */ + @Schema(description = "Token JWT pour l'authentification", + example = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", + required = true) + @JsonProperty("token") + @NotBlank(message = "Le token ne peut pas être vide") + private String token; + + /** + * Type du token (généralement "Bearer"). + */ + @Schema(description = "Type du token", + example = "Bearer", + required = true) + @JsonProperty("tokenType") + @NotBlank(message = "Le type de token ne peut pas être vide") + private String tokenType = "Bearer"; + + /** + * Date et heure d'expiration du token. + */ + @Schema(description = "Date et heure d'expiration du token", + example = "2024-12-31T23:59:59", + required = true) + @JsonProperty("expiresAt") + @NotNull(message = "La date d'expiration est obligatoire") + private LocalDateTime expiresAt; + + /** + * Token de rafraîchissement pour renouveler le token principal. + * Optionnel, présent seulement si "rememberMe" était activé. + */ + @Schema(description = "Token de rafraîchissement", + example = "refresh_token_abc123") + @JsonProperty("refreshToken") + private String refreshToken; + + /** + * Durée de vie du token en secondes. + */ + @Schema(description = "Durée de vie du token en secondes", + example = "3600") + @JsonProperty("expiresIn") + private Long expiresIn; + + /** + * Constructeur par défaut. + */ + public TokenDTO() {} + + /** + * Constructeur avec token et expiration. + * + * @param token le token JWT + * @param expiresAt la date d'expiration + */ + public TokenDTO(String token, LocalDateTime expiresAt) { + this.token = token; + this.expiresAt = expiresAt; + this.expiresIn = calculateExpiresIn(expiresAt); + } + + /** + * Constructeur complet. + * + * @param token le token JWT + * @param tokenType le type de token + * @param expiresAt la date d'expiration + * @param refreshToken le token de rafraîchissement + */ + public TokenDTO(String token, String tokenType, LocalDateTime expiresAt, String refreshToken) { + this.token = token; + this.tokenType = tokenType; + this.expiresAt = expiresAt; + this.refreshToken = refreshToken; + this.expiresIn = calculateExpiresIn(expiresAt); + } + + /** + * Calcule la durée de vie en secondes jusqu'à l'expiration. + * + * @param expiresAt la date d'expiration + * @return la durée en secondes, ou null si expiresAt est null + */ + private Long calculateExpiresIn(LocalDateTime expiresAt) { + if (expiresAt == null) { + return null; + } + return java.time.Duration.between(LocalDateTime.now(), expiresAt).getSeconds(); + } + + /** + * Retourne le token JWT. + * + * @return le token JWT + */ + public String getToken() { + return token; + } + + /** + * Définit le token JWT. + * + * @param token le token JWT à définir + */ + public void setToken(String token) { + this.token = token; + } + + /** + * Retourne le type de token. + * + * @return le type de token + */ + public String getTokenType() { + return tokenType; + } + + /** + * Définit le type de token. + * + * @param tokenType le type de token à définir + */ + public void setTokenType(String tokenType) { + this.tokenType = tokenType; + } + + /** + * Retourne la date d'expiration du token. + * + * @return la date d'expiration + */ + public LocalDateTime getExpiresAt() { + return expiresAt; + } + + /** + * Définit la date d'expiration du token. + * + * @param expiresAt la date d'expiration à définir + */ + public void setExpiresAt(LocalDateTime expiresAt) { + this.expiresAt = expiresAt; + this.expiresIn = calculateExpiresIn(expiresAt); + } + + /** + * Retourne le token de rafraîchissement. + * + * @return le token de rafraîchissement + */ + public String getRefreshToken() { + return refreshToken; + } + + /** + * Définit le token de rafraîchissement. + * + * @param refreshToken le token de rafraîchissement à définir + */ + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + /** + * Retourne la durée de vie du token en secondes. + * + * @return la durée de vie en secondes + */ + public Long getExpiresIn() { + return expiresIn; + } + + /** + * Définit la durée de vie du token en secondes. + * + * @param expiresIn la durée de vie en secondes + */ + public void setExpiresIn(Long expiresIn) { + this.expiresIn = expiresIn; + } + + /** + * Vérifie si le token est expiré. + * + * @return true si le token est expiré, false sinon + */ + public boolean isExpired() { + return expiresAt != null && LocalDateTime.now().isAfter(expiresAt); + } + + /** + * Représentation textuelle de l'objet (sans les tokens pour la sécurité). + * + * @return une chaîne représentant l'objet + */ + @Override + public String toString() { + return "TokenDTO{" + + "tokenType='" + tokenType + '\'' + + ", expiresAt=" + expiresAt + + ", expiresIn=" + expiresIn + + ", hasRefreshToken=" + (refreshToken != null) + + '}'; + } +} diff --git a/src/main/java/com/gbcm/server/api/dto/common/ErrorResponseDTO.java b/src/main/java/com/gbcm/server/api/dto/common/ErrorResponseDTO.java new file mode 100644 index 0000000..1dfec42 --- /dev/null +++ b/src/main/java/com/gbcm/server/api/dto/common/ErrorResponseDTO.java @@ -0,0 +1,333 @@ +package com.gbcm.server.api.dto.common; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * DTO pour les réponses d'erreur standardisées. + * Utilisé pour retourner des informations d'erreur cohérentes à travers toute l'API. + * + * @author GBCM Development Team + * @version 1.0 + * @since 1.0 + */ +@Schema(description = "Réponse d'erreur standardisée de l'API GBCM") +public class ErrorResponseDTO { + + /** + * Code d'erreur unique pour identifier le type d'erreur. + */ + @Schema(description = "Code d'erreur unique", + example = "VALIDATION_ERROR", + required = true) + @JsonProperty("errorCode") + @NotBlank(message = "Le code d'erreur est obligatoire") + private String errorCode; + + /** + * Message d'erreur principal en français. + */ + @Schema(description = "Message d'erreur principal", + example = "Les données fournies ne sont pas valides", + required = true) + @JsonProperty("message") + @NotBlank(message = "Le message d'erreur est obligatoire") + private String message; + + /** + * Description détaillée de l'erreur. + */ + @Schema(description = "Description détaillée de l'erreur", + example = "Le champ 'email' doit être une adresse email valide") + @JsonProperty("details") + private String details; + + /** + * Timestamp de l'erreur. + */ + @Schema(description = "Timestamp de l'erreur", + example = "2024-01-25T10:30:00", + required = true) + @JsonProperty("timestamp") + @NotNull(message = "Le timestamp est obligatoire") + private LocalDateTime timestamp; + + /** + * Chemin de la requête qui a causé l'erreur. + */ + @Schema(description = "Chemin de la requête", + example = "/api/v1/users") + @JsonProperty("path") + private String path; + + /** + * Code de statut HTTP. + */ + @Schema(description = "Code de statut HTTP", + example = "400") + @JsonProperty("status") + private Integer status; + + /** + * Liste des erreurs de validation spécifiques. + */ + @Schema(description = "Liste des erreurs de validation") + @JsonProperty("validationErrors") + private List validationErrors; + + /** + * Métadonnées additionnelles sur l'erreur. + */ + @Schema(description = "Métadonnées additionnelles") + @JsonProperty("metadata") + private Map metadata; + + /** + * Constructeur par défaut. + */ + public ErrorResponseDTO() { + this.timestamp = LocalDateTime.now(); + } + + /** + * Constructeur avec code d'erreur et message. + * + * @param errorCode le code d'erreur + * @param message le message d'erreur + */ + public ErrorResponseDTO(String errorCode, String message) { + this(); + this.errorCode = errorCode; + this.message = message; + } + + /** + * Constructeur complet. + * + * @param errorCode le code d'erreur + * @param message le message d'erreur + * @param details les détails de l'erreur + * @param path le chemin de la requête + * @param status le code de statut HTTP + */ + public ErrorResponseDTO(String errorCode, String message, String details, String path, Integer status) { + this(errorCode, message); + this.details = details; + this.path = path; + this.status = status; + } + + /** + * Crée une réponse d'erreur de validation. + * + * @param message le message d'erreur principal + * @param validationErrors la liste des erreurs de validation + * @return une réponse d'erreur de validation + */ + public static ErrorResponseDTO validationError(String message, List validationErrors) { + ErrorResponseDTO error = new ErrorResponseDTO("VALIDATION_ERROR", message); + error.setValidationErrors(validationErrors); + error.setStatus(400); + return error; + } + + /** + * Crée une réponse d'erreur d'authentification. + * + * @param message le message d'erreur + * @return une réponse d'erreur d'authentification + */ + public static ErrorResponseDTO authenticationError(String message) { + ErrorResponseDTO error = new ErrorResponseDTO("AUTHENTICATION_ERROR", message); + error.setStatus(401); + return error; + } + + /** + * Crée une réponse d'erreur d'autorisation. + * + * @param message le message d'erreur + * @return une réponse d'erreur d'autorisation + */ + public static ErrorResponseDTO authorizationError(String message) { + ErrorResponseDTO error = new ErrorResponseDTO("AUTHORIZATION_ERROR", message); + error.setStatus(403); + return error; + } + + /** + * Crée une réponse d'erreur de ressource non trouvée. + * + * @param message le message d'erreur + * @return une réponse d'erreur de ressource non trouvée + */ + public static ErrorResponseDTO notFoundError(String message) { + ErrorResponseDTO error = new ErrorResponseDTO("RESOURCE_NOT_FOUND", message); + error.setStatus(404); + return error; + } + + /** + * Crée une réponse d'erreur interne du serveur. + * + * @param message le message d'erreur + * @return une réponse d'erreur interne + */ + public static ErrorResponseDTO internalError(String message) { + ErrorResponseDTO error = new ErrorResponseDTO("INTERNAL_ERROR", message); + error.setStatus(500); + return error; + } + + // Getters et Setters + + public String getErrorCode() { + return errorCode; + } + + public void setErrorCode(String errorCode) { + this.errorCode = errorCode; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getDetails() { + return details; + } + + public void setDetails(String details) { + this.details = details; + } + + public LocalDateTime getTimestamp() { + return timestamp; + } + + public void setTimestamp(LocalDateTime timestamp) { + this.timestamp = timestamp; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public List getValidationErrors() { + return validationErrors; + } + + public void setValidationErrors(List validationErrors) { + this.validationErrors = validationErrors; + } + + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } + + @Override + public String toString() { + return "ErrorResponseDTO{" + + "errorCode='" + errorCode + '\'' + + ", message='" + message + '\'' + + ", timestamp=" + timestamp + + ", path='" + path + '\'' + + ", status=" + status + + '}'; + } + + /** + * DTO pour les erreurs de validation spécifiques. + */ + @Schema(description = "Erreur de validation spécifique") + public static class ValidationErrorDTO { + + /** + * Nom du champ en erreur. + */ + @Schema(description = "Nom du champ en erreur", example = "email") + @JsonProperty("field") + private String field; + + /** + * Valeur rejetée. + */ + @Schema(description = "Valeur rejetée", example = "invalid-email") + @JsonProperty("rejectedValue") + private Object rejectedValue; + + /** + * Message d'erreur pour ce champ. + */ + @Schema(description = "Message d'erreur pour ce champ", example = "Format d'email invalide") + @JsonProperty("message") + private String message; + + public ValidationErrorDTO() {} + + public ValidationErrorDTO(String field, Object rejectedValue, String message) { + this.field = field; + this.rejectedValue = rejectedValue; + this.message = message; + } + + public String getField() { + return field; + } + + public void setField(String field) { + this.field = field; + } + + public Object getRejectedValue() { + return rejectedValue; + } + + public void setRejectedValue(Object rejectedValue) { + this.rejectedValue = rejectedValue; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + @Override + public String toString() { + return "ValidationErrorDTO{" + + "field='" + field + '\'' + + ", rejectedValue=" + rejectedValue + + ", message='" + message + '\'' + + '}'; + } + } +} diff --git a/src/main/java/com/gbcm/server/api/dto/common/PagedResultDTO.java b/src/main/java/com/gbcm/server/api/dto/common/PagedResultDTO.java new file mode 100644 index 0000000..0e9b3d2 --- /dev/null +++ b/src/main/java/com/gbcm/server/api/dto/common/PagedResultDTO.java @@ -0,0 +1,339 @@ +package com.gbcm.server.api.dto.common; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; + +import java.util.List; + +/** + * DTO générique pour les résultats paginés. + * Encapsule une liste d'éléments avec les métadonnées de pagination. + * + * @param le type des éléments contenus dans la page + * @author GBCM Development Team + * @version 1.0 + * @since 1.0 + */ +@Schema(description = "Résultat paginé contenant une liste d'éléments et les métadonnées de pagination") +public class PagedResultDTO { + + /** + * Liste des éléments de la page courante. + */ + @Schema(description = "Liste des éléments de la page courante", + required = true) + @JsonProperty("content") + @NotNull(message = "Le contenu ne peut pas être null") + private List content; + + /** + * Numéro de la page courante (commence à 0). + */ + @Schema(description = "Numéro de la page courante (commence à 0)", + example = "0", + required = true) + @JsonProperty("page") + @Min(value = 0, message = "Le numéro de page doit être >= 0") + private int page; + + /** + * Taille de la page (nombre d'éléments par page). + */ + @Schema(description = "Taille de la page (nombre d'éléments par page)", + example = "20", + required = true) + @JsonProperty("size") + @Min(value = 1, message = "La taille de page doit être >= 1") + private int size; + + /** + * Nombre total d'éléments disponibles. + */ + @Schema(description = "Nombre total d'éléments disponibles", + example = "150", + required = true) + @JsonProperty("totalElements") + @Min(value = 0, message = "Le nombre total d'éléments doit être >= 0") + private long totalElements; + + /** + * Nombre total de pages disponibles. + */ + @Schema(description = "Nombre total de pages disponibles", + example = "8", + required = true) + @JsonProperty("totalPages") + @Min(value = 0, message = "Le nombre total de pages doit être >= 0") + private int totalPages; + + /** + * Indique si c'est la première page. + */ + @Schema(description = "Indique si c'est la première page", + example = "true") + @JsonProperty("first") + private boolean first; + + /** + * Indique si c'est la dernière page. + */ + @Schema(description = "Indique si c'est la dernière page", + example = "false") + @JsonProperty("last") + private boolean last; + + /** + * Nombre d'éléments dans la page courante. + */ + @Schema(description = "Nombre d'éléments dans la page courante", + example = "20") + @JsonProperty("numberOfElements") + private int numberOfElements; + + /** + * Indique si la page est vide. + */ + @Schema(description = "Indique si la page est vide", + example = "false") + @JsonProperty("empty") + private boolean empty; + + /** + * Constructeur par défaut. + */ + public PagedResultDTO() {} + + /** + * Constructeur avec tous les paramètres. + * + * @param content la liste des éléments + * @param page le numéro de page + * @param size la taille de page + * @param totalElements le nombre total d'éléments + */ + public PagedResultDTO(List content, int page, int size, long totalElements) { + this.content = content; + this.page = page; + this.size = size; + this.totalElements = totalElements; + this.numberOfElements = content != null ? content.size() : 0; + this.empty = this.numberOfElements == 0; + this.totalPages = size > 0 ? (int) Math.ceil((double) totalElements / size) : 0; + this.first = page == 0; + this.last = page >= totalPages - 1; + } + + /** + * Crée un résultat paginé vide. + * + * @param le type des éléments + * @return un résultat paginé vide + */ + public static PagedResultDTO empty() { + return new PagedResultDTO<>(List.of(), 0, 0, 0); + } + + /** + * Crée un résultat paginé avec une seule page. + * + * @param content la liste des éléments + * @param le type des éléments + * @return un résultat paginé avec une seule page + */ + public static PagedResultDTO of(List content) { + return new PagedResultDTO<>(content, 0, content.size(), content.size()); + } + + /** + * Retourne la liste des éléments de la page courante. + * + * @return la liste des éléments + */ + public List getContent() { + return content; + } + + /** + * Définit la liste des éléments de la page courante. + * + * @param content la liste des éléments à définir + */ + public void setContent(List content) { + this.content = content; + this.numberOfElements = content != null ? content.size() : 0; + this.empty = this.numberOfElements == 0; + } + + /** + * Retourne le numéro de la page courante. + * + * @return le numéro de page + */ + public int getPage() { + return page; + } + + /** + * Définit le numéro de la page courante. + * + * @param page le numéro de page à définir + */ + public void setPage(int page) { + this.page = page; + this.first = page == 0; + this.last = page >= totalPages - 1; + } + + /** + * Retourne la taille de la page. + * + * @return la taille de page + */ + public int getSize() { + return size; + } + + /** + * Définit la taille de la page. + * + * @param size la taille de page à définir + */ + public void setSize(int size) { + this.size = size; + this.totalPages = size > 0 ? (int) Math.ceil((double) totalElements / size) : 0; + this.last = page >= totalPages - 1; + } + + /** + * Retourne le nombre total d'éléments. + * + * @return le nombre total d'éléments + */ + public long getTotalElements() { + return totalElements; + } + + /** + * Définit le nombre total d'éléments. + * + * @param totalElements le nombre total d'éléments à définir + */ + public void setTotalElements(long totalElements) { + this.totalElements = totalElements; + this.totalPages = size > 0 ? (int) Math.ceil((double) totalElements / size) : 0; + this.last = page >= totalPages - 1; + } + + /** + * Retourne le nombre total de pages. + * + * @return le nombre total de pages + */ + public int getTotalPages() { + return totalPages; + } + + /** + * Définit le nombre total de pages. + * + * @param totalPages le nombre total de pages à définir + */ + public void setTotalPages(int totalPages) { + this.totalPages = totalPages; + this.last = page >= totalPages - 1; + } + + /** + * Indique si c'est la première page. + * + * @return true si c'est la première page, false sinon + */ + public boolean isFirst() { + return first; + } + + /** + * Définit si c'est la première page. + * + * @param first true si c'est la première page, false sinon + */ + public void setFirst(boolean first) { + this.first = first; + } + + /** + * Indique si c'est la dernière page. + * + * @return true si c'est la dernière page, false sinon + */ + public boolean isLast() { + return last; + } + + /** + * Définit si c'est la dernière page. + * + * @param last true si c'est la dernière page, false sinon + */ + public void setLast(boolean last) { + this.last = last; + } + + /** + * Retourne le nombre d'éléments dans la page courante. + * + * @return le nombre d'éléments dans la page courante + */ + public int getNumberOfElements() { + return numberOfElements; + } + + /** + * Définit le nombre d'éléments dans la page courante. + * + * @param numberOfElements le nombre d'éléments à définir + */ + public void setNumberOfElements(int numberOfElements) { + this.numberOfElements = numberOfElements; + this.empty = numberOfElements == 0; + } + + /** + * Indique si la page est vide. + * + * @return true si la page est vide, false sinon + */ + public boolean isEmpty() { + return empty; + } + + /** + * Définit si la page est vide. + * + * @param empty true si la page est vide, false sinon + */ + public void setEmpty(boolean empty) { + this.empty = empty; + } + + /** + * Représentation textuelle de l'objet. + * + * @return une chaîne représentant l'objet + */ + @Override + public String toString() { + return "PagedResultDTO{" + + "page=" + page + + ", size=" + size + + ", totalElements=" + totalElements + + ", totalPages=" + totalPages + + ", numberOfElements=" + numberOfElements + + ", first=" + first + + ", last=" + last + + ", empty=" + empty + + '}'; + } +} diff --git a/src/main/java/com/gbcm/server/api/dto/common/SuccessResponseDTO.java b/src/main/java/com/gbcm/server/api/dto/common/SuccessResponseDTO.java new file mode 100644 index 0000000..9e3b8d0 --- /dev/null +++ b/src/main/java/com/gbcm/server/api/dto/common/SuccessResponseDTO.java @@ -0,0 +1,305 @@ +package com.gbcm.server.api.dto.common; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +import java.time.LocalDateTime; +import java.util.Map; + +/** + * DTO pour les réponses de succès standardisées. + * Utilisé pour retourner des confirmations d'opérations réussies avec des données optionnelles. + * + * @param le type des données retournées + * @author GBCM Development Team + * @version 1.0 + * @since 1.0 + */ +@Schema(description = "Réponse de succès standardisée de l'API GBCM") +public class SuccessResponseDTO { + + /** + * Indicateur de succès (toujours true pour cette classe). + */ + @Schema(description = "Indicateur de succès", + example = "true", + required = true) + @JsonProperty("success") + @NotNull(message = "L'indicateur de succès est obligatoire") + private boolean success = true; + + /** + * Message de succès en français. + */ + @Schema(description = "Message de succès", + example = "Opération réalisée avec succès", + required = true) + @JsonProperty("message") + @NotBlank(message = "Le message de succès est obligatoire") + private String message; + + /** + * Données retournées par l'opération. + */ + @Schema(description = "Données retournées par l'opération") + @JsonProperty("data") + private T data; + + /** + * Timestamp de la réponse. + */ + @Schema(description = "Timestamp de la réponse", + example = "2024-01-25T10:30:00", + required = true) + @JsonProperty("timestamp") + @NotNull(message = "Le timestamp est obligatoire") + private LocalDateTime timestamp; + + /** + * Code de statut HTTP. + */ + @Schema(description = "Code de statut HTTP", + example = "200") + @JsonProperty("status") + private Integer status; + + /** + * Métadonnées additionnelles. + */ + @Schema(description = "Métadonnées additionnelles") + @JsonProperty("metadata") + private Map metadata; + + /** + * Constructeur par défaut. + */ + public SuccessResponseDTO() { + this.timestamp = LocalDateTime.now(); + } + + /** + * Constructeur avec message. + * + * @param message le message de succès + */ + public SuccessResponseDTO(String message) { + this(); + this.message = message; + } + + /** + * Constructeur avec message et données. + * + * @param message le message de succès + * @param data les données à retourner + */ + public SuccessResponseDTO(String message, T data) { + this(message); + this.data = data; + } + + /** + * Constructeur complet. + * + * @param message le message de succès + * @param data les données à retourner + * @param status le code de statut HTTP + */ + public SuccessResponseDTO(String message, T data, Integer status) { + this(message, data); + this.status = status; + } + + /** + * Crée une réponse de succès simple. + * + * @param message le message de succès + * @param le type des données + * @return une réponse de succès + */ + public static SuccessResponseDTO of(String message) { + return new SuccessResponseDTO<>(message); + } + + /** + * Crée une réponse de succès avec données. + * + * @param message le message de succès + * @param data les données à retourner + * @param le type des données + * @return une réponse de succès avec données + */ + public static SuccessResponseDTO of(String message, T data) { + return new SuccessResponseDTO<>(message, data); + } + + /** + * Crée une réponse de création réussie (201). + * + * @param message le message de succès + * @param data les données créées + * @param le type des données + * @return une réponse de création réussie + */ + public static SuccessResponseDTO created(String message, T data) { + return new SuccessResponseDTO<>(message, data, 201); + } + + /** + * Crée une réponse de mise à jour réussie. + * + * @param message le message de succès + * @param data les données mises à jour + * @param le type des données + * @return une réponse de mise à jour réussie + */ + public static SuccessResponseDTO updated(String message, T data) { + return new SuccessResponseDTO<>(message, data, 200); + } + + /** + * Crée une réponse de suppression réussie. + * + * @param message le message de succès + * @return une réponse de suppression réussie + */ + public static SuccessResponseDTO deleted(String message) { + return new SuccessResponseDTO<>(message, null, 200); + } + + /** + * Crée une réponse sans contenu (204). + * + * @param message le message de succès + * @return une réponse sans contenu + */ + public static SuccessResponseDTO noContent(String message) { + return new SuccessResponseDTO<>(message, null, 204); + } + + /** + * Retourne l'indicateur de succès. + * + * @return true (toujours pour cette classe) + */ + public boolean isSuccess() { + return success; + } + + /** + * Définit l'indicateur de succès. + * + * @param success l'indicateur de succès + */ + public void setSuccess(boolean success) { + this.success = success; + } + + /** + * Retourne le message de succès. + * + * @return le message de succès + */ + public String getMessage() { + return message; + } + + /** + * Définit le message de succès. + * + * @param message le message de succès à définir + */ + public void setMessage(String message) { + this.message = message; + } + + /** + * Retourne les données de la réponse. + * + * @return les données + */ + public T getData() { + return data; + } + + /** + * Définit les données de la réponse. + * + * @param data les données à définir + */ + public void setData(T data) { + this.data = data; + } + + /** + * Retourne le timestamp de la réponse. + * + * @return le timestamp + */ + public LocalDateTime getTimestamp() { + return timestamp; + } + + /** + * Définit le timestamp de la réponse. + * + * @param timestamp le timestamp à définir + */ + public void setTimestamp(LocalDateTime timestamp) { + this.timestamp = timestamp; + } + + /** + * Retourne le code de statut HTTP. + * + * @return le code de statut + */ + public Integer getStatus() { + return status; + } + + /** + * Définit le code de statut HTTP. + * + * @param status le code de statut à définir + */ + public void setStatus(Integer status) { + this.status = status; + } + + /** + * Retourne les métadonnées additionnelles. + * + * @return les métadonnées + */ + public Map getMetadata() { + return metadata; + } + + /** + * Définit les métadonnées additionnelles. + * + * @param metadata les métadonnées à définir + */ + public void setMetadata(Map metadata) { + this.metadata = metadata; + } + + /** + * Représentation textuelle de l'objet. + * + * @return une chaîne représentant l'objet + */ + @Override + public String toString() { + return "SuccessResponseDTO{" + + "success=" + success + + ", message='" + message + '\'' + + ", timestamp=" + timestamp + + ", status=" + status + + ", hasData=" + (data != null) + + '}'; + } +} diff --git a/src/main/java/com/gbcm/server/api/dto/user/CreateUserDTO.java b/src/main/java/com/gbcm/server/api/dto/user/CreateUserDTO.java new file mode 100644 index 0000000..77fb66c --- /dev/null +++ b/src/main/java/com/gbcm/server/api/dto/user/CreateUserDTO.java @@ -0,0 +1,278 @@ +package com.gbcm.server.api.dto.user; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.gbcm.server.api.enums.UserRole; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +/** + * DTO pour la création d'un nouvel utilisateur. + * Contient toutes les informations nécessaires pour créer un compte utilisateur. + * + * @author GBCM Development Team + * @version 1.0 + * @since 1.0 + */ +@Schema(description = "Données pour la création d'un nouvel utilisateur") +public class CreateUserDTO { + + /** + * Prénom de l'utilisateur. + */ + @Schema(description = "Prénom de l'utilisateur", + example = "John", + required = true, + maxLength = 50) + @JsonProperty("firstName") + @NotBlank(message = "Le prénom est obligatoire") + @Size(max = 50, message = "Le prénom ne peut pas dépasser 50 caractères") + private String firstName; + + /** + * Nom de famille de l'utilisateur. + */ + @Schema(description = "Nom de famille de l'utilisateur", + example = "Doe", + required = true, + maxLength = 50) + @JsonProperty("lastName") + @NotBlank(message = "Le nom est obligatoire") + @Size(max = 50, message = "Le nom ne peut pas dépasser 50 caractères") + private String lastName; + + /** + * Adresse email unique de l'utilisateur. + * Sera utilisée pour l'authentification. + */ + @Schema(description = "Adresse email unique de l'utilisateur", + example = "john.doe@gbcm.com", + required = true, + maxLength = 255) + @JsonProperty("email") + @NotBlank(message = "L'email est obligatoire") + @Email(message = "Format d'email invalide") + @Size(max = 255, message = "L'email ne peut pas dépasser 255 caractères") + private String email; + + /** + * Mot de passe initial de l'utilisateur. + * Sera haché avant stockage en base. + */ + @Schema(description = "Mot de passe initial de l'utilisateur", + example = "motdepasse123", + required = true, + minLength = 6, + maxLength = 100) + @JsonProperty("password") + @NotBlank(message = "Le mot de passe est obligatoire") + @Size(min = 6, max = 100, message = "Le mot de passe doit contenir entre 6 et 100 caractères") + private String password; + + /** + * Numéro de téléphone de l'utilisateur. + * Format international recommandé. + */ + @Schema(description = "Numéro de téléphone de l'utilisateur", + example = "+1-404-555-0123", + maxLength = 20) + @JsonProperty("phone") + @Size(max = 20, message = "Le téléphone ne peut pas dépasser 20 caractères") + private String phone; + + /** + * Rôle de l'utilisateur dans le système. + * Détermine les permissions initiales. + */ + @Schema(description = "Rôle de l'utilisateur dans le système", + required = true) + @JsonProperty("role") + @NotNull(message = "Le rôle est obligatoire") + private UserRole role; + + /** + * Statut d'activation initial du compte. + * Par défaut, les comptes sont créés actifs. + */ + @Schema(description = "Statut d'activation initial du compte", + example = "true", + defaultValue = "true") + @JsonProperty("active") + private boolean active = true; + + /** + * Constructeur par défaut. + */ + public CreateUserDTO() {} + + /** + * Constructeur avec les champs obligatoires. + * + * @param firstName le prénom de l'utilisateur + * @param lastName le nom de famille de l'utilisateur + * @param email l'adresse email de l'utilisateur + * @param password le mot de passe initial + * @param role le rôle de l'utilisateur + */ + public CreateUserDTO(String firstName, String lastName, String email, String password, UserRole role) { + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + this.password = password; + this.role = role; + } + + /** + * Retourne le prénom de l'utilisateur. + * + * @return le prénom + */ + public String getFirstName() { + return firstName; + } + + /** + * Définit le prénom de l'utilisateur. + * + * @param firstName le prénom à définir + */ + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + /** + * Retourne le nom de famille de l'utilisateur. + * + * @return le nom de famille + */ + public String getLastName() { + return lastName; + } + + /** + * Définit le nom de famille de l'utilisateur. + * + * @param lastName le nom de famille à définir + */ + public void setLastName(String lastName) { + this.lastName = lastName; + } + + /** + * Retourne l'adresse email de l'utilisateur. + * + * @return l'adresse email + */ + public String getEmail() { + return email; + } + + /** + * Définit l'adresse email de l'utilisateur. + * + * @param email l'adresse email à définir + */ + public void setEmail(String email) { + this.email = email; + } + + /** + * Retourne le mot de passe de l'utilisateur. + * + * @return le mot de passe + */ + public String getPassword() { + return password; + } + + /** + * Définit le mot de passe de l'utilisateur. + * + * @param password le mot de passe à définir + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * Retourne le numéro de téléphone de l'utilisateur. + * + * @return le numéro de téléphone + */ + public String getPhone() { + return phone; + } + + /** + * Définit le numéro de téléphone de l'utilisateur. + * + * @param phone le numéro de téléphone à définir + */ + public void setPhone(String phone) { + this.phone = phone; + } + + /** + * Retourne le rôle de l'utilisateur. + * + * @return le rôle + */ + public UserRole getRole() { + return role; + } + + /** + * Définit le rôle de l'utilisateur. + * + * @param role le rôle à définir + */ + public void setRole(UserRole role) { + this.role = role; + } + + /** + * Indique si le compte est actif. + * + * @return true si le compte est actif, false sinon + */ + public boolean isActive() { + return active; + } + + /** + * Définit le statut d'activation du compte. + * + * @param active true pour activer le compte, false sinon + */ + public void setActive(boolean active) { + this.active = active; + } + + /** + * Retourne le nom complet de l'utilisateur. + * + * @return le nom complet (prénom + nom) + */ + public String getFullName() { + return firstName + " " + lastName; + } + + /** + * Représentation textuelle de l'objet (sans le mot de passe pour la sécurité). + * + * @return une chaîne représentant l'objet + */ + @Override + public String toString() { + return "CreateUserDTO{" + + "firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", email='" + email + '\'' + + ", phone='" + phone + '\'' + + ", role=" + role + + ", active=" + active + + '}'; + } +} diff --git a/src/main/java/com/gbcm/server/api/dto/user/UpdateUserDTO.java b/src/main/java/com/gbcm/server/api/dto/user/UpdateUserDTO.java new file mode 100644 index 0000000..09f411e --- /dev/null +++ b/src/main/java/com/gbcm/server/api/dto/user/UpdateUserDTO.java @@ -0,0 +1,245 @@ +package com.gbcm.server.api.dto.user; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.gbcm.server.api.enums.UserRole; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.Size; + +/** + * DTO pour la mise à jour d'un utilisateur existant. + * Tous les champs sont optionnels, seuls les champs fournis seront mis à jour. + * + * @author GBCM Development Team + * @version 1.0 + * @since 1.0 + */ +@Schema(description = "Données pour la mise à jour d'un utilisateur existant") +public class UpdateUserDTO { + + /** + * Nouveau prénom de l'utilisateur. + * Optionnel - si fourni, remplace le prénom existant. + */ + @Schema(description = "Nouveau prénom de l'utilisateur", + example = "John", + maxLength = 50) + @JsonProperty("firstName") + @Size(max = 50, message = "Le prénom ne peut pas dépasser 50 caractères") + private String firstName; + + /** + * Nouveau nom de famille de l'utilisateur. + * Optionnel - si fourni, remplace le nom existant. + */ + @Schema(description = "Nouveau nom de famille de l'utilisateur", + example = "Doe", + maxLength = 50) + @JsonProperty("lastName") + @Size(max = 50, message = "Le nom ne peut pas dépasser 50 caractères") + private String lastName; + + /** + * Nouvelle adresse email de l'utilisateur. + * Optionnel - si fourni, doit être unique dans le système. + */ + @Schema(description = "Nouvelle adresse email de l'utilisateur", + example = "john.doe@gbcm.com", + maxLength = 255) + @JsonProperty("email") + @Email(message = "Format d'email invalide") + @Size(max = 255, message = "L'email ne peut pas dépasser 255 caractères") + private String email; + + /** + * Nouveau numéro de téléphone de l'utilisateur. + * Optionnel - format international recommandé. + */ + @Schema(description = "Nouveau numéro de téléphone de l'utilisateur", + example = "+1-404-555-0123", + maxLength = 20) + @JsonProperty("phone") + @Size(max = 20, message = "Le téléphone ne peut pas dépasser 20 caractères") + private String phone; + + /** + * Nouveau rôle de l'utilisateur dans le système. + * Optionnel - modifie les permissions de l'utilisateur. + */ + @Schema(description = "Nouveau rôle de l'utilisateur dans le système") + @JsonProperty("role") + private UserRole role; + + /** + * Nouveau statut d'activation du compte. + * Optionnel - permet d'activer/désactiver le compte. + */ + @Schema(description = "Nouveau statut d'activation du compte", + example = "true") + @JsonProperty("active") + private Boolean active; + + /** + * Constructeur par défaut. + */ + public UpdateUserDTO() {} + + /** + * Constructeur avec prénom et nom. + * + * @param firstName le nouveau prénom + * @param lastName le nouveau nom de famille + */ + public UpdateUserDTO(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + /** + * Retourne le nouveau prénom de l'utilisateur. + * + * @return le nouveau prénom, ou null si non modifié + */ + public String getFirstName() { + return firstName; + } + + /** + * Définit le nouveau prénom de l'utilisateur. + * + * @param firstName le nouveau prénom à définir + */ + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + /** + * Retourne le nouveau nom de famille de l'utilisateur. + * + * @return le nouveau nom de famille, ou null si non modifié + */ + public String getLastName() { + return lastName; + } + + /** + * Définit le nouveau nom de famille de l'utilisateur. + * + * @param lastName le nouveau nom de famille à définir + */ + public void setLastName(String lastName) { + this.lastName = lastName; + } + + /** + * Retourne la nouvelle adresse email de l'utilisateur. + * + * @return la nouvelle adresse email, ou null si non modifiée + */ + public String getEmail() { + return email; + } + + /** + * Définit la nouvelle adresse email de l'utilisateur. + * + * @param email la nouvelle adresse email à définir + */ + public void setEmail(String email) { + this.email = email; + } + + /** + * Retourne le nouveau numéro de téléphone de l'utilisateur. + * + * @return le nouveau numéro de téléphone, ou null si non modifié + */ + public String getPhone() { + return phone; + } + + /** + * Définit le nouveau numéro de téléphone de l'utilisateur. + * + * @param phone le nouveau numéro de téléphone à définir + */ + public void setPhone(String phone) { + this.phone = phone; + } + + /** + * Retourne le nouveau rôle de l'utilisateur. + * + * @return le nouveau rôle, ou null si non modifié + */ + public UserRole getRole() { + return role; + } + + /** + * Définit le nouveau rôle de l'utilisateur. + * + * @param role le nouveau rôle à définir + */ + public void setRole(UserRole role) { + this.role = role; + } + + /** + * Retourne le nouveau statut d'activation du compte. + * + * @return le nouveau statut, ou null si non modifié + */ + public Boolean getActive() { + return active; + } + + /** + * Définit le nouveau statut d'activation du compte. + * + * @param active le nouveau statut à définir + */ + public void setActive(Boolean active) { + this.active = active; + } + + /** + * Vérifie si au moins un champ est fourni pour la mise à jour. + * + * @return true si au moins un champ est non null, false sinon + */ + public boolean hasUpdates() { + return firstName != null || lastName != null || email != null || + phone != null || role != null || active != null; + } + + /** + * Retourne le nom complet si prénom et nom sont fournis. + * + * @return le nom complet, ou null si prénom ou nom manquant + */ + public String getFullName() { + if (firstName != null && lastName != null) { + return firstName + " " + lastName; + } + return null; + } + + /** + * Représentation textuelle de l'objet. + * + * @return une chaîne représentant l'objet + */ + @Override + public String toString() { + return "UpdateUserDTO{" + + "firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", email='" + email + '\'' + + ", phone='" + phone + '\'' + + ", role=" + role + + ", active=" + active + + '}'; + } +} diff --git a/src/main/java/com/gbcm/server/api/dto/user/UserDTO.java b/src/main/java/com/gbcm/server/api/dto/user/UserDTO.java index f39d578..c5054d3 100644 --- a/src/main/java/com/gbcm/server/api/dto/user/UserDTO.java +++ b/src/main/java/com/gbcm/server/api/dto/user/UserDTO.java @@ -1,62 +1,124 @@ package com.gbcm.server.api.dto.user; +import java.time.LocalDateTime; + import com.fasterxml.jackson.annotation.JsonProperty; import com.gbcm.server.api.enums.UserRole; + import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import java.time.LocalDateTime; - /** - * DTO pour les informations utilisateur + * DTO pour les informations utilisateur de la plateforme GBCM. + * Contient toutes les données nécessaires pour représenter un utilisateur côté client. + * + * @author GBCM Development Team + * @version 1.0 + * @since 1.0 */ -@Schema(description = "Informations utilisateur") +@Schema(description = "Informations utilisateur de la plateforme GBCM") public class UserDTO { - @Schema(description = "Identifiant unique", example = "1") + /** + * Identifiant unique de l'utilisateur. + */ + @Schema(description = "Identifiant unique de l'utilisateur", + example = "1", + accessMode = Schema.AccessMode.READ_ONLY) @JsonProperty("id") private Long id; - @Schema(description = "Prénom", example = "John") + /** + * Prénom de l'utilisateur. + */ + @Schema(description = "Prénom de l'utilisateur", + example = "John", + required = true, + maxLength = 50) @JsonProperty("firstName") @NotBlank(message = "Le prénom est obligatoire") private String firstName; - @Schema(description = "Nom de famille", example = "Doe") + /** + * Nom de famille de l'utilisateur. + */ + @Schema(description = "Nom de famille de l'utilisateur", + example = "Doe", + required = true, + maxLength = 50) @JsonProperty("lastName") @NotBlank(message = "Le nom est obligatoire") private String lastName; - @Schema(description = "Adresse email", example = "john.doe@gbcm.com") + /** + * Adresse email unique de l'utilisateur. + * Utilisée pour l'authentification et les communications. + */ + @Schema(description = "Adresse email unique de l'utilisateur", + example = "john.doe@gbcm.com", + required = true, + maxLength = 255) @JsonProperty("email") @NotBlank(message = "L'email est obligatoire") @Email(message = "Format d'email invalide") private String email; - @Schema(description = "Numéro de téléphone", example = "+1234567890") + /** + * Numéro de téléphone de l'utilisateur. + * Format international recommandé. + */ + @Schema(description = "Numéro de téléphone de l'utilisateur", + example = "+1-404-555-0123", + maxLength = 20) @JsonProperty("phone") private String phone; - @Schema(description = "Rôle utilisateur") + /** + * Rôle de l'utilisateur dans le système. + * Détermine les permissions et l'accès aux fonctionnalités. + */ + @Schema(description = "Rôle de l'utilisateur dans le système", + required = true) @JsonProperty("role") @NotNull(message = "Le rôle est obligatoire") private UserRole role; - @Schema(description = "Statut actif", example = "true") + /** + * Statut d'activation du compte utilisateur. + * Les comptes inactifs ne peuvent pas se connecter. + */ + @Schema(description = "Statut d'activation du compte", + example = "true", + defaultValue = "true") @JsonProperty("active") private boolean active = true; - @Schema(description = "Date de création") + /** + * Date et heure de création du compte. + */ + @Schema(description = "Date et heure de création du compte", + example = "2024-01-15T10:30:00", + accessMode = Schema.AccessMode.READ_ONLY) @JsonProperty("createdAt") private LocalDateTime createdAt; - @Schema(description = "Date de dernière modification") + /** + * Date et heure de dernière modification du profil. + */ + @Schema(description = "Date et heure de dernière modification", + example = "2024-01-20T14:45:00", + accessMode = Schema.AccessMode.READ_ONLY) @JsonProperty("updatedAt") private LocalDateTime updatedAt; - @Schema(description = "Date de dernière connexion") + /** + * Date et heure de dernière connexion réussie. + */ + @Schema(description = "Date et heure de dernière connexion", + example = "2024-01-25T09:15:00", + accessMode = Schema.AccessMode.READ_ONLY) @JsonProperty("lastLoginAt") private LocalDateTime lastLoginAt; diff --git a/src/main/java/com/gbcm/server/api/enums/InvoiceStatus.java b/src/main/java/com/gbcm/server/api/enums/InvoiceStatus.java new file mode 100644 index 0000000..0f027a6 --- /dev/null +++ b/src/main/java/com/gbcm/server/api/enums/InvoiceStatus.java @@ -0,0 +1,236 @@ +package com.gbcm.server.api.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Énumération des statuts de facture dans le système GBCM. + * Définit les différents états possibles d'une facture. + * + * @author GBCM Development Team + * @version 1.0 + * @since 1.0 + */ +public enum InvoiceStatus { + + /** + * Facture en brouillon, pas encore envoyée. + */ + DRAFT("draft", "Brouillon", "La facture est en cours de préparation"), + + /** + * Facture envoyée au client. + */ + SENT("sent", "Envoyée", "La facture a été envoyée au client"), + + /** + * Facture payée intégralement. + */ + PAID("paid", "Payée", "La facture a été payée intégralement"), + + /** + * Facture partiellement payée. + */ + PARTIALLY_PAID("partially_paid", "Partiellement payée", + "La facture a été partiellement payée"), + + /** + * Facture en retard (dépassé la date d'échéance). + */ + OVERDUE("overdue", "En retard", "La facture a dépassé la date d'échéance"), + + /** + * Facture annulée. + */ + CANCELLED("cancelled", "Annulée", "La facture a été annulée"), + + /** + * Facture remboursée. + */ + REFUNDED("refunded", "Remboursée", "La facture a été remboursée"), + + /** + * Facture en litige. + */ + DISPUTED("disputed", "En litige", "La facture fait l'objet d'un litige"); + + /** + * Code unique du statut de facture. + */ + private final String code; + + /** + * Nom d'affichage du statut. + */ + private final String displayName; + + /** + * Description détaillée du statut. + */ + private final String description; + + /** + * Constructeur de l'énumération. + * + * @param code le code unique du statut + * @param displayName le nom d'affichage + * @param description la description détaillée + */ + InvoiceStatus(String code, String displayName, String description) { + this.code = code; + this.displayName = displayName; + this.description = description; + } + + /** + * Retourne le code unique du statut de facture. + * + * @return le code du statut + */ + @JsonValue + public String getCode() { + return code; + } + + /** + * Retourne le nom d'affichage du statut. + * + * @return le nom d'affichage + */ + public String getDisplayName() { + return displayName; + } + + /** + * Retourne la description détaillée du statut. + * + * @return la description + */ + public String getDescription() { + return description; + } + + /** + * Trouve un statut de facture par son code. + * + * @param code le code à rechercher + * @return le statut correspondant + * @throws IllegalArgumentException si le code n'est pas trouvé + */ + public static InvoiceStatus fromCode(String code) { + if (code == null) { + throw new IllegalArgumentException("Le code ne peut pas être null"); + } + + for (InvoiceStatus status : values()) { + if (status.code.equals(code)) { + return status; + } + } + + throw new IllegalArgumentException("Statut de facture non trouvé pour le code: " + code); + } + + /** + * Vérifie si la facture est en brouillon. + * + * @return true si la facture est en brouillon, false sinon + */ + public boolean isDraft() { + return this == DRAFT; + } + + /** + * Vérifie si la facture est finalisée (envoyée ou plus). + * + * @return true si la facture est finalisée, false sinon + */ + public boolean isFinalized() { + return this != DRAFT; + } + + /** + * Vérifie si la facture est payée (intégralement ou partiellement). + * + * @return true si la facture est payée, false sinon + */ + public boolean isPaid() { + return this == PAID || this == PARTIALLY_PAID; + } + + /** + * Vérifie si la facture est entièrement payée. + * + * @return true si la facture est entièrement payée, false sinon + */ + public boolean isFullyPaid() { + return this == PAID; + } + + /** + * Vérifie si la facture est en attente de paiement. + * + * @return true si la facture est en attente de paiement, false sinon + */ + public boolean isPendingPayment() { + return this == SENT || this == PARTIALLY_PAID || this == OVERDUE; + } + + /** + * Vérifie si la facture peut être modifiée. + * + * @return true si la facture peut être modifiée, false sinon + */ + public boolean canBeModified() { + return this == DRAFT; + } + + /** + * Vérifie si la facture peut être annulée. + * + * @return true si la facture peut être annulée, false sinon + */ + public boolean canBeCancelled() { + return this == DRAFT || this == SENT || this == OVERDUE; + } + + /** + * Vérifie si la facture peut être remboursée. + * + * @return true si la facture peut être remboursée, false sinon + */ + public boolean canBeRefunded() { + return this == PAID || this == PARTIALLY_PAID; + } + + /** + * Vérifie si la facture nécessite une action urgente. + * + * @return true si une action urgente est nécessaire, false sinon + */ + public boolean requiresUrgentAction() { + return this == OVERDUE || this == DISPUTED; + } + + /** + * Vérifie si la facture génère des revenus. + * + * @return true si la facture génère des revenus, false sinon + */ + public boolean generatesRevenue() { + return this == PAID || this == PARTIALLY_PAID; + } + + /** + * Représentation textuelle de l'énumération. + * + * @return une chaîne représentant le statut de facture + */ + @Override + public String toString() { + return "InvoiceStatus{" + + "code='" + code + '\'' + + ", displayName='" + displayName + '\'' + + ", description='" + description + '\'' + + '}'; + } +} diff --git a/src/main/java/com/gbcm/server/api/enums/PaymentStatus.java b/src/main/java/com/gbcm/server/api/enums/PaymentStatus.java new file mode 100644 index 0000000..8a091e6 --- /dev/null +++ b/src/main/java/com/gbcm/server/api/enums/PaymentStatus.java @@ -0,0 +1,196 @@ +package com.gbcm.server.api.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Énumération des statuts de paiement dans le système GBCM. + * Définit les différents états possibles d'un paiement. + * + * @author GBCM Development Team + * @version 1.0 + * @since 1.0 + */ +public enum PaymentStatus { + + /** + * Paiement en attente de traitement. + */ + PENDING("pending", "En attente", "Le paiement est en cours de traitement"), + + /** + * Paiement effectué avec succès. + */ + PAID("paid", "Payé", "Le paiement a été effectué avec succès"), + + /** + * Paiement en retard (dépassé la date d'échéance). + */ + OVERDUE("overdue", "En retard", "Le paiement a dépassé la date d'échéance"), + + /** + * Paiement annulé. + */ + CANCELLED("cancelled", "Annulé", "Le paiement a été annulé"), + + /** + * Paiement échoué. + */ + FAILED("failed", "Échoué", "Le paiement a échoué lors du traitement"), + + /** + * Paiement remboursé. + */ + REFUNDED("refunded", "Remboursé", "Le paiement a été remboursé"), + + /** + * Paiement partiellement remboursé. + */ + PARTIALLY_REFUNDED("partially_refunded", "Partiellement remboursé", + "Le paiement a été partiellement remboursé"); + + /** + * Code unique du statut de paiement. + */ + private final String code; + + /** + * Nom d'affichage du statut. + */ + private final String displayName; + + /** + * Description détaillée du statut. + */ + private final String description; + + /** + * Constructeur de l'énumération. + * + * @param code le code unique du statut + * @param displayName le nom d'affichage + * @param description la description détaillée + */ + PaymentStatus(String code, String displayName, String description) { + this.code = code; + this.displayName = displayName; + this.description = description; + } + + /** + * Retourne le code unique du statut de paiement. + * + * @return le code du statut + */ + @JsonValue + public String getCode() { + return code; + } + + /** + * Retourne le nom d'affichage du statut. + * + * @return le nom d'affichage + */ + public String getDisplayName() { + return displayName; + } + + /** + * Retourne la description détaillée du statut. + * + * @return la description + */ + public String getDescription() { + return description; + } + + /** + * Trouve un statut de paiement par son code. + * + * @param code le code à rechercher + * @return le statut correspondant + * @throws IllegalArgumentException si le code n'est pas trouvé + */ + public static PaymentStatus fromCode(String code) { + if (code == null) { + throw new IllegalArgumentException("Le code ne peut pas être null"); + } + + for (PaymentStatus status : values()) { + if (status.code.equals(code)) { + return status; + } + } + + throw new IllegalArgumentException("Statut de paiement non trouvé pour le code: " + code); + } + + /** + * Vérifie si le paiement est finalisé (succès ou échec définitif). + * + * @return true si le paiement est finalisé, false sinon + */ + public boolean isFinal() { + return this == PAID || this == CANCELLED || this == FAILED || + this == REFUNDED || this == PARTIALLY_REFUNDED; + } + + /** + * Vérifie si le paiement est en cours de traitement. + * + * @return true si le paiement est en cours, false sinon + */ + public boolean isPending() { + return this == PENDING; + } + + /** + * Vérifie si le paiement est réussi. + * + * @return true si le paiement est réussi, false sinon + */ + public boolean isSuccessful() { + return this == PAID; + } + + /** + * Vérifie si le paiement a échoué. + * + * @return true si le paiement a échoué, false sinon + */ + public boolean isFailed() { + return this == FAILED || this == CANCELLED; + } + + /** + * Vérifie si le paiement nécessite une action. + * + * @return true si une action est nécessaire, false sinon + */ + public boolean requiresAction() { + return this == PENDING || this == OVERDUE; + } + + /** + * Vérifie si le paiement peut être remboursé. + * + * @return true si le paiement peut être remboursé, false sinon + */ + public boolean canBeRefunded() { + return this == PAID || this == PARTIALLY_REFUNDED; + } + + /** + * Représentation textuelle de l'énumération. + * + * @return une chaîne représentant le statut de paiement + */ + @Override + public String toString() { + return "PaymentStatus{" + + "code='" + code + '\'' + + ", displayName='" + displayName + '\'' + + ", description='" + description + '\'' + + '}'; + } +} diff --git a/src/main/java/com/gbcm/server/api/enums/ServiceType.java b/src/main/java/com/gbcm/server/api/enums/ServiceType.java new file mode 100644 index 0000000..29888de --- /dev/null +++ b/src/main/java/com/gbcm/server/api/enums/ServiceType.java @@ -0,0 +1,156 @@ +package com.gbcm.server.api.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Énumération des types de services offerts par GBCM. + * Définit les différents services disponibles sur la plateforme. + * + * @author GBCM Development Team + * @version 1.0 + * @since 1.0 + */ +public enum ServiceType { + + /** + * Ateliers stratégiques - Programmes de 2 ans avec différents niveaux. + */ + STRATEGIC_WORKSHOP("strategic_workshop", "Atelier Stratégique", + "Programme de développement stratégique sur 2 ans"), + + /** + * Coaching individuel - Sessions one-on-one avec un coach. + */ + ONE_ON_ONE_COACHING("one_on_one_coaching", "Coaching Individuel", + "Sessions de coaching personnalisées en tête-à-tête"), + + /** + * Coaching à la demande - Abonnement mensuel pour coaching flexible. + */ + ON_DEMAND_COACHING("on_demand_coaching", "Coaching à la Demande", + "Abonnement mensuel pour coaching flexible"), + + /** + * Projets spéciaux - Consulting personnalisé pour besoins spécifiques. + */ + SPECIAL_PROJECT("special_project", "Projet Spécial", + "Consulting personnalisé pour besoins spécifiques"); + + /** + * Code unique du type de service. + */ + private final String code; + + /** + * Nom d'affichage du type de service. + */ + private final String displayName; + + /** + * Description détaillée du type de service. + */ + private final String description; + + /** + * Constructeur de l'énumération. + * + * @param code le code unique du service + * @param displayName le nom d'affichage + * @param description la description détaillée + */ + ServiceType(String code, String displayName, String description) { + this.code = code; + this.displayName = displayName; + this.description = description; + } + + /** + * Retourne le code unique du type de service. + * + * @return le code du service + */ + @JsonValue + public String getCode() { + return code; + } + + /** + * Retourne le nom d'affichage du type de service. + * + * @return le nom d'affichage + */ + public String getDisplayName() { + return displayName; + } + + /** + * Retourne la description détaillée du type de service. + * + * @return la description + */ + public String getDescription() { + return description; + } + + /** + * Trouve un type de service par son code. + * + * @param code le code à rechercher + * @return le type de service correspondant + * @throws IllegalArgumentException si le code n'est pas trouvé + */ + public static ServiceType fromCode(String code) { + if (code == null) { + throw new IllegalArgumentException("Le code ne peut pas être null"); + } + + for (ServiceType type : values()) { + if (type.code.equals(code)) { + return type; + } + } + + throw new IllegalArgumentException("Type de service non trouvé pour le code: " + code); + } + + /** + * Vérifie si le service nécessite un abonnement. + * + * @return true si le service nécessite un abonnement, false sinon + */ + public boolean requiresSubscription() { + return this == ON_DEMAND_COACHING || this == STRATEGIC_WORKSHOP; + } + + /** + * Vérifie si le service est facturable à l'heure. + * + * @return true si le service est facturable à l'heure, false sinon + */ + public boolean isHourlyBilling() { + return this == ONE_ON_ONE_COACHING; + } + + /** + * Vérifie si le service a un prix fixe. + * + * @return true si le service a un prix fixe, false sinon + */ + public boolean hasFixedPrice() { + return this == STRATEGIC_WORKSHOP || this == ON_DEMAND_COACHING; + } + + /** + * Représentation textuelle de l'énumération. + * + * @return une chaîne représentant le type de service + */ + @Override + public String toString() { + return "ServiceType{" + + "code='" + code + '\'' + + ", displayName='" + displayName + '\'' + + ", description='" + description + '\'' + + '}'; + } +} diff --git a/src/main/java/com/gbcm/server/api/enums/SessionStatus.java b/src/main/java/com/gbcm/server/api/enums/SessionStatus.java new file mode 100644 index 0000000..1e97331 --- /dev/null +++ b/src/main/java/com/gbcm/server/api/enums/SessionStatus.java @@ -0,0 +1,213 @@ +package com.gbcm.server.api.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Énumération des statuts de session de coaching dans le système GBCM. + * Définit les différents états possibles d'une session de coaching. + * + * @author GBCM Development Team + * @version 1.0 + * @since 1.0 + */ +public enum SessionStatus { + + /** + * Session programmée mais pas encore commencée. + */ + SCHEDULED("scheduled", "Programmée", "La session est programmée et confirmée"), + + /** + * Session en cours. + */ + IN_PROGRESS("in_progress", "En cours", "La session est actuellement en cours"), + + /** + * Session terminée avec succès. + */ + COMPLETED("completed", "Terminée", "La session s'est terminée avec succès"), + + /** + * Session annulée par le client ou le coach. + */ + CANCELLED("cancelled", "Annulée", "La session a été annulée"), + + /** + * Client absent à la session (no-show). + */ + NO_SHOW("no_show", "Absence", "Le client ne s'est pas présenté à la session"), + + /** + * Session reportée à une date ultérieure. + */ + RESCHEDULED("rescheduled", "Reportée", "La session a été reportée"), + + /** + * Session en attente de confirmation. + */ + PENDING_CONFIRMATION("pending_confirmation", "En attente de confirmation", + "La session est en attente de confirmation"); + + /** + * Code unique du statut de session. + */ + private final String code; + + /** + * Nom d'affichage du statut. + */ + private final String displayName; + + /** + * Description détaillée du statut. + */ + private final String description; + + /** + * Constructeur de l'énumération. + * + * @param code le code unique du statut + * @param displayName le nom d'affichage + * @param description la description détaillée + */ + SessionStatus(String code, String displayName, String description) { + this.code = code; + this.displayName = displayName; + this.description = description; + } + + /** + * Retourne le code unique du statut de session. + * + * @return le code du statut + */ + @JsonValue + public String getCode() { + return code; + } + + /** + * Retourne le nom d'affichage du statut. + * + * @return le nom d'affichage + */ + public String getDisplayName() { + return displayName; + } + + /** + * Retourne la description détaillée du statut. + * + * @return la description + */ + public String getDescription() { + return description; + } + + /** + * Trouve un statut de session par son code. + * + * @param code le code à rechercher + * @return le statut correspondant + * @throws IllegalArgumentException si le code n'est pas trouvé + */ + public static SessionStatus fromCode(String code) { + if (code == null) { + throw new IllegalArgumentException("Le code ne peut pas être null"); + } + + for (SessionStatus status : values()) { + if (status.code.equals(code)) { + return status; + } + } + + throw new IllegalArgumentException("Statut de session non trouvé pour le code: " + code); + } + + /** + * Vérifie si la session est active (programmée ou en cours). + * + * @return true si la session est active, false sinon + */ + public boolean isActive() { + return this == SCHEDULED || this == IN_PROGRESS || this == PENDING_CONFIRMATION; + } + + /** + * Vérifie si la session est terminée (complétée, annulée, ou no-show). + * + * @return true si la session est terminée, false sinon + */ + public boolean isFinished() { + return this == COMPLETED || this == CANCELLED || this == NO_SHOW; + } + + /** + * Vérifie si la session peut être modifiée. + * + * @return true si la session peut être modifiée, false sinon + */ + public boolean canBeModified() { + return this == SCHEDULED || this == PENDING_CONFIRMATION; + } + + /** + * Vérifie si la session peut être annulée. + * + * @return true si la session peut être annulée, false sinon + */ + public boolean canBeCancelled() { + return this == SCHEDULED || this == PENDING_CONFIRMATION; + } + + /** + * Vérifie si la session peut être reportée. + * + * @return true si la session peut être reportée, false sinon + */ + public boolean canBeRescheduled() { + return this == SCHEDULED || this == PENDING_CONFIRMATION; + } + + /** + * Vérifie si la session nécessite une confirmation. + * + * @return true si une confirmation est nécessaire, false sinon + */ + public boolean requiresConfirmation() { + return this == PENDING_CONFIRMATION; + } + + /** + * Vérifie si la session est facturable. + * + * @return true si la session est facturable, false sinon + */ + public boolean isBillable() { + return this == COMPLETED || this == NO_SHOW; + } + + /** + * Vérifie si la session compte comme réalisée pour les statistiques. + * + * @return true si la session compte comme réalisée, false sinon + */ + public boolean countsAsCompleted() { + return this == COMPLETED; + } + + /** + * Représentation textuelle de l'énumération. + * + * @return une chaîne représentant le statut de session + */ + @Override + public String toString() { + return "SessionStatus{" + + "code='" + code + '\'' + + ", displayName='" + displayName + '\'' + + ", description='" + description + '\'' + + '}'; + } +} diff --git a/src/main/java/com/gbcm/server/api/enums/WorkshopPackage.java b/src/main/java/com/gbcm/server/api/enums/WorkshopPackage.java new file mode 100644 index 0000000..31eaf9c --- /dev/null +++ b/src/main/java/com/gbcm/server/api/enums/WorkshopPackage.java @@ -0,0 +1,224 @@ +package com.gbcm.server.api.enums; + +import java.math.BigDecimal; + +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Énumération des packages d'ateliers stratégiques GBCM. + * Définit les trois niveaux de packages avec leurs prix et caractéristiques. + * + * @author GBCM Development Team + * @version 1.0 + * @since 1.0 + */ +public enum WorkshopPackage { + + /** + * Package Silver - Niveau de base. + * Prix: $3,000 pour 2 ans + */ + SILVER("silver", "Silver", "Package de base pour les ateliers stratégiques", + new BigDecimal("3000.00"), 24, "Accès aux ateliers de base et ressources essentielles"), + + /** + * Package Gold - Niveau intermédiaire. + * Prix: $4,000 pour 2 ans + */ + GOLD("gold", "Gold", "Package intermédiaire avec services étendus", + new BigDecimal("4000.00"), 24, "Accès complet aux ateliers + sessions de coaching mensuelles"), + + /** + * Package Platinum - Niveau premium. + * Prix: $5,000 pour 2 ans + */ + PLATINUM("platinum", "Platinum", "Package premium avec tous les services", + new BigDecimal("5000.00"), 24, "Accès VIP complet + coaching illimité + support prioritaire"); + + /** + * Code unique du package. + */ + private final String code; + + /** + * Nom d'affichage du package. + */ + private final String displayName; + + /** + * Description courte du package. + */ + private final String description; + + /** + * Prix total du package en USD. + */ + private final BigDecimal price; + + /** + * Durée du package en mois. + */ + private final int durationMonths; + + /** + * Description détaillée des services inclus. + */ + private final String features; + + /** + * Constructeur de l'énumération. + * + * @param code le code unique du package + * @param displayName le nom d'affichage + * @param description la description courte + * @param price le prix total + * @param durationMonths la durée en mois + * @param features la description des services inclus + */ + WorkshopPackage(String code, String displayName, String description, + BigDecimal price, int durationMonths, String features) { + this.code = code; + this.displayName = displayName; + this.description = description; + this.price = price; + this.durationMonths = durationMonths; + this.features = features; + } + + /** + * Retourne le code unique du package. + * + * @return le code du package + */ + @JsonValue + public String getCode() { + return code; + } + + /** + * Retourne le nom d'affichage du package. + * + * @return le nom d'affichage + */ + public String getDisplayName() { + return displayName; + } + + /** + * Retourne la description courte du package. + * + * @return la description + */ + public String getDescription() { + return description; + } + + /** + * Retourne le prix total du package. + * + * @return le prix en USD + */ + public BigDecimal getPrice() { + return price; + } + + /** + * Retourne la durée du package en mois. + * + * @return la durée en mois + */ + public int getDurationMonths() { + return durationMonths; + } + + /** + * Retourne la description des services inclus. + * + * @return la description des services + */ + public String getFeatures() { + return features; + } + + /** + * Calcule le prix mensuel du package. + * + * @return le prix mensuel en USD + */ + public BigDecimal getMonthlyPrice() { + return price.divide(new BigDecimal(durationMonths), 2, java.math.RoundingMode.HALF_UP); + } + + /** + * Trouve un package par son code. + * + * @param code le code à rechercher + * @return le package correspondant + * @throws IllegalArgumentException si le code n'est pas trouvé + */ + public static WorkshopPackage fromCode(String code) { + if (code == null) { + throw new IllegalArgumentException("Le code ne peut pas être null"); + } + + for (WorkshopPackage pkg : values()) { + if (pkg.code.equalsIgnoreCase(code)) { + return pkg; + } + } + + throw new IllegalArgumentException("Package non trouvé pour le code: " + code); + } + + /** + * Vérifie si c'est un package premium. + * + * @return true si c'est le package Platinum, false sinon + */ + public boolean isPremium() { + return this == PLATINUM; + } + + /** + * Vérifie si le package inclut du coaching. + * + * @return true si le package inclut du coaching, false sinon + */ + public boolean includesCoaching() { + return this == GOLD || this == PLATINUM; + } + + /** + * Vérifie si le package inclut un support prioritaire. + * + * @return true si le package inclut un support prioritaire, false sinon + */ + public boolean hasPrioritySupport() { + return this == PLATINUM; + } + + /** + * Compare les packages par prix (ordre croissant). + * + * @param other l'autre package à comparer + * @return résultat de la comparaison (-1, 0, 1) + */ + public int compareByPrice(WorkshopPackage other) { + return this.price.compareTo(other.price); + } + + /** + * Représentation textuelle de l'énumération. + * + * @return une chaîne représentant le package + */ + @Override + public String toString() { + return "WorkshopPackage{" + + "code='" + code + '\'' + + ", displayName='" + displayName + '\'' + + ", price=" + price + + ", durationMonths=" + durationMonths + + '}'; + } +} diff --git a/src/main/java/com/gbcm/server/api/exceptions/AuthenticationException.java b/src/main/java/com/gbcm/server/api/exceptions/AuthenticationException.java new file mode 100644 index 0000000..294e86f --- /dev/null +++ b/src/main/java/com/gbcm/server/api/exceptions/AuthenticationException.java @@ -0,0 +1,52 @@ +package com.gbcm.server.api.exceptions; + +/** + * Exception levée lors d'erreurs d'authentification. + * Utilisée pour les cas d'identifiants invalides, tokens expirés, etc. + * + * @author GBCM Development Team + * @version 1.0 + * @since 1.0 + */ +public class AuthenticationException extends GBCMException { + + /** + * Constructeur avec message. + * + * @param message le message d'erreur d'authentification + */ + public AuthenticationException(String message) { + super(message, "AUTHENTICATION_ERROR"); + } + + /** + * Constructeur avec message et cause. + * + * @param message le message d'erreur d'authentification + * @param cause la cause de l'exception + */ + public AuthenticationException(String message, Throwable cause) { + super(message, "AUTHENTICATION_ERROR", cause); + } + + /** + * Constructeur avec message et code d'erreur spécifique. + * + * @param message le message d'erreur d'authentification + * @param errorCode le code d'erreur spécifique + */ + public AuthenticationException(String message, String errorCode) { + super(message, errorCode); + } + + /** + * Constructeur complet. + * + * @param message le message d'erreur d'authentification + * @param errorCode le code d'erreur spécifique + * @param cause la cause de l'exception + */ + public AuthenticationException(String message, String errorCode, Throwable cause) { + super(message, errorCode, cause); + } +} diff --git a/src/main/java/com/gbcm/server/api/exceptions/AuthorizationException.java b/src/main/java/com/gbcm/server/api/exceptions/AuthorizationException.java new file mode 100644 index 0000000..75d1de2 --- /dev/null +++ b/src/main/java/com/gbcm/server/api/exceptions/AuthorizationException.java @@ -0,0 +1,52 @@ +package com.gbcm.server.api.exceptions; + +/** + * Exception levée lors d'erreurs d'autorisation. + * Utilisée quand un utilisateur authentifié n'a pas les permissions nécessaires. + * + * @author GBCM Development Team + * @version 1.0 + * @since 1.0 + */ +public class AuthorizationException extends GBCMException { + + /** + * Constructeur avec message. + * + * @param message le message d'erreur d'autorisation + */ + public AuthorizationException(String message) { + super(message, "AUTHORIZATION_ERROR"); + } + + /** + * Constructeur avec message et cause. + * + * @param message le message d'erreur d'autorisation + * @param cause la cause de l'exception + */ + public AuthorizationException(String message, Throwable cause) { + super(message, "AUTHORIZATION_ERROR", cause); + } + + /** + * Constructeur avec message et code d'erreur spécifique. + * + * @param message le message d'erreur d'autorisation + * @param errorCode le code d'erreur spécifique + */ + public AuthorizationException(String message, String errorCode) { + super(message, errorCode); + } + + /** + * Constructeur complet. + * + * @param message le message d'erreur d'autorisation + * @param errorCode le code d'erreur spécifique + * @param cause la cause de l'exception + */ + public AuthorizationException(String message, String errorCode, Throwable cause) { + super(message, errorCode, cause); + } +} diff --git a/src/main/java/com/gbcm/server/api/exceptions/BusinessRuleException.java b/src/main/java/com/gbcm/server/api/exceptions/BusinessRuleException.java new file mode 100644 index 0000000..71febfe --- /dev/null +++ b/src/main/java/com/gbcm/server/api/exceptions/BusinessRuleException.java @@ -0,0 +1,73 @@ +package com.gbcm.server.api.exceptions; + +/** + * Exception levée lors de violations des règles métier GBCM. + * Utilisée pour les cas où une opération viole une règle business spécifique. + * + * @author GBCM Development Team + * @version 1.0 + * @since 1.0 + */ +public class BusinessRuleException extends GBCMException { + + /** + * Nom de la règle métier violée. + */ + private final String ruleName; + + /** + * Constructeur avec message. + * + * @param message le message d'erreur de règle métier + */ + public BusinessRuleException(String message) { + super(message, "BUSINESS_RULE_VIOLATION"); + this.ruleName = null; + } + + /** + * Constructeur avec message et nom de règle. + * + * @param message le message d'erreur de règle métier + * @param ruleName le nom de la règle violée + */ + public BusinessRuleException(String message, String ruleName) { + super(message, "BUSINESS_RULE_VIOLATION"); + this.ruleName = ruleName; + } + + /** + * Constructeur avec message, nom de règle et cause. + * + * @param message le message d'erreur de règle métier + * @param ruleName le nom de la règle violée + * @param cause la cause de l'exception + */ + public BusinessRuleException(String message, String ruleName, Throwable cause) { + super(message, "BUSINESS_RULE_VIOLATION", cause); + this.ruleName = ruleName; + } + + /** + * Retourne le nom de la règle métier violée. + * + * @return le nom de la règle + */ + public String getRuleName() { + return ruleName; + } + + /** + * Représentation textuelle de l'exception. + * + * @return une chaîne représentant l'exception + */ + @Override + public String toString() { + return "BusinessRuleException{" + + "errorCode='" + getErrorCode() + '\'' + + ", message='" + getMessage() + '\'' + + ", ruleName='" + ruleName + '\'' + + '}'; + } +} diff --git a/src/main/java/com/gbcm/server/api/exceptions/GBCMException.java b/src/main/java/com/gbcm/server/api/exceptions/GBCMException.java new file mode 100644 index 0000000..54fd589 --- /dev/null +++ b/src/main/java/com/gbcm/server/api/exceptions/GBCMException.java @@ -0,0 +1,83 @@ +package com.gbcm.server.api.exceptions; + +/** + * Exception de base pour toutes les exceptions métier de la plateforme GBCM. + * Toutes les autres exceptions spécifiques héritent de cette classe. + * + * @author GBCM Development Team + * @version 1.0 + * @since 1.0 + */ +public class GBCMException extends Exception { + + /** + * Code d'erreur unique pour identifier le type d'exception. + */ + private final String errorCode; + + /** + * Constructeur avec message. + * + * @param message le message d'erreur + */ + public GBCMException(String message) { + super(message); + this.errorCode = "GBCM_ERROR"; + } + + /** + * Constructeur avec message et code d'erreur. + * + * @param message le message d'erreur + * @param errorCode le code d'erreur unique + */ + public GBCMException(String message, String errorCode) { + super(message); + this.errorCode = errorCode; + } + + /** + * Constructeur avec message et cause. + * + * @param message le message d'erreur + * @param cause la cause de l'exception + */ + public GBCMException(String message, Throwable cause) { + super(message, cause); + this.errorCode = "GBCM_ERROR"; + } + + /** + * Constructeur complet. + * + * @param message le message d'erreur + * @param errorCode le code d'erreur unique + * @param cause la cause de l'exception + */ + public GBCMException(String message, String errorCode, Throwable cause) { + super(message, cause); + this.errorCode = errorCode; + } + + /** + * Retourne le code d'erreur unique. + * + * @return le code d'erreur + */ + public String getErrorCode() { + return errorCode; + } + + /** + * Représentation textuelle de l'exception. + * + * @return une chaîne représentant l'exception + */ + @Override + public String toString() { + return "GBCMException{" + + "errorCode='" + errorCode + '\'' + + ", message='" + getMessage() + '\'' + + '}'; + } +} diff --git a/src/main/java/com/gbcm/server/api/exceptions/ResourceNotFoundException.java b/src/main/java/com/gbcm/server/api/exceptions/ResourceNotFoundException.java new file mode 100644 index 0000000..e76d704 --- /dev/null +++ b/src/main/java/com/gbcm/server/api/exceptions/ResourceNotFoundException.java @@ -0,0 +1,104 @@ +package com.gbcm.server.api.exceptions; + +/** + * Exception levée quand une ressource demandée n'est pas trouvée. + * Utilisée pour les cas où un utilisateur, client, session, etc. n'existe pas. + * + * @author GBCM Development Team + * @version 1.0 + * @since 1.0 + */ +public class ResourceNotFoundException extends GBCMException { + + /** + * Type de ressource non trouvée. + */ + private final String resourceType; + + /** + * Identifiant de la ressource non trouvée. + */ + private final Object resourceId; + + /** + * Constructeur avec message. + * + * @param message le message d'erreur + */ + public ResourceNotFoundException(String message) { + super(message, "RESOURCE_NOT_FOUND"); + this.resourceType = null; + this.resourceId = null; + } + + /** + * Constructeur avec type et identifiant de ressource. + * + * @param resourceType le type de ressource (ex: "User", "Client") + * @param resourceId l'identifiant de la ressource + */ + public ResourceNotFoundException(String resourceType, Object resourceId) { + super(String.format("%s avec l'identifiant '%s' non trouvé", resourceType, resourceId), + "RESOURCE_NOT_FOUND"); + this.resourceType = resourceType; + this.resourceId = resourceId; + } + + /** + * Constructeur avec message, type et identifiant. + * + * @param message le message d'erreur personnalisé + * @param resourceType le type de ressource + * @param resourceId l'identifiant de la ressource + */ + public ResourceNotFoundException(String message, String resourceType, Object resourceId) { + super(message, "RESOURCE_NOT_FOUND"); + this.resourceType = resourceType; + this.resourceId = resourceId; + } + + /** + * Constructeur avec message et cause. + * + * @param message le message d'erreur + * @param cause la cause de l'exception + */ + public ResourceNotFoundException(String message, Throwable cause) { + super(message, "RESOURCE_NOT_FOUND", cause); + this.resourceType = null; + this.resourceId = null; + } + + /** + * Retourne le type de ressource non trouvée. + * + * @return le type de ressource + */ + public String getResourceType() { + return resourceType; + } + + /** + * Retourne l'identifiant de la ressource non trouvée. + * + * @return l'identifiant de la ressource + */ + public Object getResourceId() { + return resourceId; + } + + /** + * Représentation textuelle de l'exception. + * + * @return une chaîne représentant l'exception + */ + @Override + public String toString() { + return "ResourceNotFoundException{" + + "errorCode='" + getErrorCode() + '\'' + + ", message='" + getMessage() + '\'' + + ", resourceType='" + resourceType + '\'' + + ", resourceId=" + resourceId + + '}'; + } +} diff --git a/src/main/java/com/gbcm/server/api/exceptions/ValidationException.java b/src/main/java/com/gbcm/server/api/exceptions/ValidationException.java new file mode 100644 index 0000000..467b535 --- /dev/null +++ b/src/main/java/com/gbcm/server/api/exceptions/ValidationException.java @@ -0,0 +1,95 @@ +package com.gbcm.server.api.exceptions; + +import java.util.List; +import java.util.Map; + +/** + * Exception levée lors d'erreurs de validation des données. + * Contient des détails sur les champs en erreur et leurs messages. + * + * @author GBCM Development Team + * @version 1.0 + * @since 1.0 + */ +public class ValidationException extends GBCMException { + + /** + * Map des erreurs de validation par champ. + */ + private final Map> fieldErrors; + + /** + * Constructeur avec message. + * + * @param message le message d'erreur de validation + */ + public ValidationException(String message) { + super(message, "VALIDATION_ERROR"); + this.fieldErrors = Map.of(); + } + + /** + * Constructeur avec message et erreurs de champs. + * + * @param message le message d'erreur de validation + * @param fieldErrors les erreurs par champ + */ + public ValidationException(String message, Map> fieldErrors) { + super(message, "VALIDATION_ERROR"); + this.fieldErrors = fieldErrors != null ? fieldErrors : Map.of(); + } + + /** + * Constructeur avec message, erreurs de champs et cause. + * + * @param message le message d'erreur de validation + * @param fieldErrors les erreurs par champ + * @param cause la cause de l'exception + */ + public ValidationException(String message, Map> fieldErrors, Throwable cause) { + super(message, "VALIDATION_ERROR", cause); + this.fieldErrors = fieldErrors != null ? fieldErrors : Map.of(); + } + + /** + * Retourne les erreurs de validation par champ. + * + * @return une map des erreurs par champ + */ + public Map> getFieldErrors() { + return fieldErrors; + } + + /** + * Vérifie si l'exception contient des erreurs de champs. + * + * @return true si des erreurs de champs sont présentes, false sinon + */ + public boolean hasFieldErrors() { + return !fieldErrors.isEmpty(); + } + + /** + * Retourne les erreurs pour un champ spécifique. + * + * @param fieldName le nom du champ + * @return la liste des erreurs pour ce champ, ou une liste vide + */ + public List getFieldErrors(String fieldName) { + return fieldErrors.getOrDefault(fieldName, List.of()); + } + + /** + * Représentation textuelle de l'exception. + * + * @return une chaîne représentant l'exception + */ + @Override + public String toString() { + return "ValidationException{" + + "errorCode='" + getErrorCode() + '\'' + + ", message='" + getMessage() + '\'' + + ", fieldErrorsCount=" + fieldErrors.size() + + '}'; + } +} diff --git a/src/main/java/com/gbcm/server/api/interfaces/AuthService.java b/src/main/java/com/gbcm/server/api/interfaces/AuthService.java index 199d105..c29f67c 100644 --- a/src/main/java/com/gbcm/server/api/interfaces/AuthService.java +++ b/src/main/java/com/gbcm/server/api/interfaces/AuthService.java @@ -2,24 +2,36 @@ package com.gbcm.server.api.interfaces; import com.gbcm.server.api.dto.auth.LoginRequestDTO; import com.gbcm.server.api.dto.auth.LoginResponseDTO; +import com.gbcm.server.api.dto.auth.PasswordResetDTO; +import com.gbcm.server.api.dto.common.SuccessResponseDTO; import com.gbcm.server.api.dto.user.UserDTO; import com.gbcm.server.api.exceptions.AuthenticationException; import com.gbcm.server.api.exceptions.GBCMException; +import jakarta.validation.Valid; import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; /** - * Interface de service pour l'authentification + * Interface de service pour l'authentification et l'autorisation. + * Fournit tous les endpoints nécessaires pour la gestion des sessions utilisateur, + * l'authentification JWT et la réinitialisation des mots de passe. + * + * @author GBCM Development Team + * @version 1.0 + * @since 1.0 */ @Path("/auth") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) -@Tag(name = "Authentication", description = "Services d'authentification et autorisation") +@Tag(name = "Authentication", description = "Services d'authentification et autorisation GBCM") public interface AuthService { /** diff --git a/src/main/java/com/gbcm/server/api/interfaces/UserService.java b/src/main/java/com/gbcm/server/api/interfaces/UserService.java new file mode 100644 index 0000000..59d3b85 --- /dev/null +++ b/src/main/java/com/gbcm/server/api/interfaces/UserService.java @@ -0,0 +1,263 @@ +package com.gbcm.server.api.interfaces; + +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 jakarta.annotation.security.RolesAllowed; +import jakarta.validation.Valid; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * Interface de service pour la gestion des utilisateurs. + * Fournit tous les endpoints CRUD pour les utilisateurs de la plateforme GBCM. + * + * @author GBCM Development Team + * @version 1.0 + * @since 1.0 + */ +@Path("/users") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@Tag(name = "Users", description = "Gestion des utilisateurs GBCM") +public interface UserService { + + /** + * Récupère la 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") + }) + PagedResultDTO 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 + ) throws AuthorizationException; + + /** + * Récupère un utilisateur par son ID. + */ + @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é") + }) + UserDTO getUserById( + @Parameter(description = "ID de l'utilisateur", required = true, example = "1") + @PathParam("id") Long id + ) throws ResourceNotFoundException, AuthorizationException; + + /** + * Crée un nouvel utilisateur. + */ + @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") + }) + UserDTO createUser( + @Parameter(description = "Données du nouvel utilisateur", required = true) + @Valid CreateUserDTO createUserDTO + ) throws ValidationException, GBCMException, AuthorizationException; + + /** + * Met à jour un utilisateur existant. + */ + @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é") + }) + UserDTO 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 + ) throws ResourceNotFoundException, ValidationException, GBCMException, AuthorizationException; + + /** + * Supprime un utilisateur (soft delete). + */ + @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") + }) + SuccessResponseDTO deleteUser( + @Parameter(description = "ID de l'utilisateur", required = true, example = "1") + @PathParam("id") Long id + ) throws ResourceNotFoundException, AuthorizationException; + + /** + * Active ou désactive un utilisateur. + */ + @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é") + }) + UserDTO 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 + ) throws ResourceNotFoundException, AuthorizationException; + + /** + * Récupère 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é") + }) + UserDTO getCurrentUserProfile( + @Parameter(description = "Token d'authentification", required = true) + @HeaderParam("Authorization") String authToken + ) throws AuthorizationException; + + /** + * Met à jour le profil de l'utilisateur connecté. + */ + @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é") + }) + UserDTO 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 + ) throws ValidationException, GBCMException, AuthorizationException; + + /** + * Recherche d'utilisateurs par critères. + */ + @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é") + }) + PagedResultDTO 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 + ) throws AuthorizationException; +}