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;
+}