Task 1.2 - Entités JPA fondamentales
- Création de BaseEntity avec audit trail et soft delete - Création de l'entité User avec Quarkus Security JPA - Création de l'entité Client avec informations d'entreprise - Création de l'entité Coach avec informations professionnelles - Relations JPA one-to-one entre User-Client et User-Coach - Migrations Flyway V1, V2, V3 pour les tables - Données de test dans import.sql - Compilation réussie du module d'implémentation
This commit is contained in:
@@ -1,180 +0,0 @@
|
||||
package com.gbcm.server.entities;
|
||||
|
||||
import com.gbcm.server.api.enums.UserRole;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
|
||||
import io.quarkus.security.jpa.Password;
|
||||
import io.quarkus.security.jpa.Roles;
|
||||
import io.quarkus.security.jpa.UserDefinition;
|
||||
import io.quarkus.security.jpa.Username;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Entité utilisateur pour l'authentification et la gestion des profils
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "users", indexes = {
|
||||
@Index(name = "idx_user_email", columnList = "email", unique = true),
|
||||
@Index(name = "idx_user_role", columnList = "role"),
|
||||
@Index(name = "idx_user_active", columnList = "active")
|
||||
})
|
||||
@UserDefinition
|
||||
public class User extends PanacheEntityBase {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
public Long id;
|
||||
|
||||
@Column(name = "first_name", nullable = false, length = 100)
|
||||
@NotBlank(message = "Le prénom est obligatoire")
|
||||
public String firstName;
|
||||
|
||||
@Column(name = "last_name", nullable = false, length = 100)
|
||||
@NotBlank(message = "Le nom est obligatoire")
|
||||
public String lastName;
|
||||
|
||||
@Column(name = "email", nullable = false, unique = true, length = 255)
|
||||
@NotBlank(message = "L'email est obligatoire")
|
||||
@Email(message = "Format d'email invalide")
|
||||
@Username
|
||||
public String email;
|
||||
|
||||
@Column(name = "password_hash", nullable = false)
|
||||
@NotBlank(message = "Le mot de passe est obligatoire")
|
||||
@Password
|
||||
public String passwordHash;
|
||||
|
||||
@Column(name = "phone", length = 20)
|
||||
public String phone;
|
||||
|
||||
@Column(name = "role", nullable = false, length = 20)
|
||||
@Enumerated(EnumType.STRING)
|
||||
@NotNull(message = "Le rôle est obligatoire")
|
||||
@Roles
|
||||
public UserRole role;
|
||||
|
||||
@Column(name = "active", nullable = false)
|
||||
public boolean active = true;
|
||||
|
||||
@Column(name = "email_verified", nullable = false)
|
||||
public boolean emailVerified = false;
|
||||
|
||||
@Column(name = "created_at", nullable = false)
|
||||
public LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "updated_at")
|
||||
public LocalDateTime updatedAt;
|
||||
|
||||
@Column(name = "last_login_at")
|
||||
public LocalDateTime lastLoginAt;
|
||||
|
||||
@Column(name = "password_reset_token")
|
||||
public String passwordResetToken;
|
||||
|
||||
@Column(name = "password_reset_expires_at")
|
||||
public LocalDateTime passwordResetExpiresAt;
|
||||
|
||||
@Column(name = "email_verification_token")
|
||||
public String emailVerificationToken;
|
||||
|
||||
// Constructeurs
|
||||
public User() {
|
||||
this.createdAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public User(String firstName, String lastName, String email, String passwordHash, UserRole role) {
|
||||
this();
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
this.email = email;
|
||||
this.passwordHash = passwordHash;
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
// Méthodes utilitaires
|
||||
public String getFullName() {
|
||||
return firstName + " " + lastName;
|
||||
}
|
||||
|
||||
public String getInitials() {
|
||||
String firstInitial = firstName != null && !firstName.isEmpty() ?
|
||||
firstName.substring(0, 1).toUpperCase() : "";
|
||||
String lastInitial = lastName != null && !lastName.isEmpty() ?
|
||||
lastName.substring(0, 1).toUpperCase() : "";
|
||||
return firstInitial + lastInitial;
|
||||
}
|
||||
|
||||
public boolean isPasswordResetTokenValid() {
|
||||
return passwordResetToken != null &&
|
||||
passwordResetExpiresAt != null &&
|
||||
passwordResetExpiresAt.isAfter(LocalDateTime.now());
|
||||
}
|
||||
|
||||
public void updateLastLogin() {
|
||||
this.lastLoginAt = LocalDateTime.now();
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
// Callbacks JPA
|
||||
@PreUpdate
|
||||
public void preUpdate() {
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
// Méthodes de recherche Panache
|
||||
public static User findByEmail(String email) {
|
||||
return find("email", email).firstResult();
|
||||
}
|
||||
|
||||
public static User findByEmailAndActive(String email, boolean active) {
|
||||
return find("email = ?1 and active = ?2", email, active).firstResult();
|
||||
}
|
||||
|
||||
public static User findByPasswordResetToken(String token) {
|
||||
return find("passwordResetToken", token).firstResult();
|
||||
}
|
||||
|
||||
public static User findByEmailVerificationToken(String token) {
|
||||
return find("emailVerificationToken", token).firstResult();
|
||||
}
|
||||
|
||||
public static long countByRole(UserRole role) {
|
||||
return count("role", role);
|
||||
}
|
||||
|
||||
public static long countActiveUsers() {
|
||||
return count("active", true);
|
||||
}
|
||||
|
||||
// equals et hashCode
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
User user = (User) o;
|
||||
return Objects.equals(id, user.id) && Objects.equals(email, user.email);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, email);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "User{" +
|
||||
"id=" + id +
|
||||
", firstName='" + firstName + '\'' +
|
||||
", lastName='" + lastName + '\'' +
|
||||
", email='" + email + '\'' +
|
||||
", role=" + role +
|
||||
", active=" + active +
|
||||
", createdAt=" + createdAt +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
271
src/main/java/com/gbcm/server/impl/entity/BaseEntity.java
Normal file
271
src/main/java/com/gbcm/server/impl/entity/BaseEntity.java
Normal file
@@ -0,0 +1,271 @@
|
||||
package com.gbcm.server.impl.entity;
|
||||
|
||||
import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
|
||||
import jakarta.persistence.*;
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
import org.hibernate.annotations.UpdateTimestamp;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Entité de base pour toutes les entités GBCM.
|
||||
* Fournit l'audit trail (createdAt, updatedAt, createdBy, updatedBy) et le soft delete.
|
||||
*
|
||||
* @author GBCM Development Team
|
||||
* @version 1.0
|
||||
* @since 1.0
|
||||
*/
|
||||
@MappedSuperclass
|
||||
public abstract class BaseEntity extends PanacheEntityBase {
|
||||
|
||||
/**
|
||||
* Date et heure de création de l'enregistrement.
|
||||
* Automatiquement définie lors de la persistance.
|
||||
*/
|
||||
@CreationTimestamp
|
||||
@Column(name = "created_at", nullable = false, updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/**
|
||||
* Date et heure de dernière mise à jour de l'enregistrement.
|
||||
* Automatiquement mise à jour lors des modifications.
|
||||
*/
|
||||
@UpdateTimestamp
|
||||
@Column(name = "updated_at", nullable = false)
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
/**
|
||||
* Identifiant de l'utilisateur qui a créé l'enregistrement.
|
||||
* Doit être défini manuellement lors de la création.
|
||||
*/
|
||||
@Column(name = "created_by", length = 100)
|
||||
private String createdBy;
|
||||
|
||||
/**
|
||||
* Identifiant de l'utilisateur qui a effectué la dernière mise à jour.
|
||||
* Doit être défini manuellement lors des modifications.
|
||||
*/
|
||||
@Column(name = "updated_by", length = 100)
|
||||
private String updatedBy;
|
||||
|
||||
/**
|
||||
* Indicateur de suppression logique (soft delete).
|
||||
* true = supprimé, false = actif.
|
||||
*/
|
||||
@Column(name = "deleted", nullable = false)
|
||||
private boolean deleted = false;
|
||||
|
||||
/**
|
||||
* Date et heure de suppression logique.
|
||||
* Définie lors du soft delete.
|
||||
*/
|
||||
@Column(name = "deleted_at")
|
||||
private LocalDateTime deletedAt;
|
||||
|
||||
/**
|
||||
* Identifiant de l'utilisateur qui a effectué la suppression logique.
|
||||
*/
|
||||
@Column(name = "deleted_by", length = 100)
|
||||
private String deletedBy;
|
||||
|
||||
/**
|
||||
* Constructeur par défaut.
|
||||
*/
|
||||
protected BaseEntity() {}
|
||||
|
||||
/**
|
||||
* Méthode appelée avant la persistance pour définir les champs d'audit.
|
||||
*/
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
if (createdAt == null) {
|
||||
createdAt = LocalDateTime.now();
|
||||
}
|
||||
if (updatedAt == null) {
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode appelée avant la mise à jour pour mettre à jour les champs d'audit.
|
||||
*/
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Effectue une suppression logique de l'entité.
|
||||
*
|
||||
* @param deletedBy l'identifiant de l'utilisateur effectuant la suppression
|
||||
*/
|
||||
public void softDelete(String deletedBy) {
|
||||
this.deleted = true;
|
||||
this.deletedAt = LocalDateTime.now();
|
||||
this.deletedBy = deletedBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restaure une entité supprimée logiquement.
|
||||
*/
|
||||
public void restore() {
|
||||
this.deleted = false;
|
||||
this.deletedAt = null;
|
||||
this.deletedBy = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'entité est supprimée logiquement.
|
||||
*
|
||||
* @return true si l'entité est supprimée, false sinon
|
||||
*/
|
||||
public boolean isDeleted() {
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'entité est active (non supprimée).
|
||||
*
|
||||
* @return true si l'entité est active, false sinon
|
||||
*/
|
||||
public boolean isActive() {
|
||||
return !deleted;
|
||||
}
|
||||
|
||||
// Getters et Setters
|
||||
|
||||
/**
|
||||
* Retourne la date de création.
|
||||
*
|
||||
* @return la date de création
|
||||
*/
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Définit la date de création.
|
||||
*
|
||||
* @param createdAt la date de création
|
||||
*/
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne la date de dernière mise à jour.
|
||||
*
|
||||
* @return la date de dernière mise à jour
|
||||
*/
|
||||
public LocalDateTime getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Définit la date de dernière mise à jour.
|
||||
*
|
||||
* @param updatedAt la date de dernière mise à jour
|
||||
*/
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne l'identifiant de l'utilisateur créateur.
|
||||
*
|
||||
* @return l'identifiant du créateur
|
||||
*/
|
||||
public String getCreatedBy() {
|
||||
return createdBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Définit l'identifiant de l'utilisateur créateur.
|
||||
*
|
||||
* @param createdBy l'identifiant du créateur
|
||||
*/
|
||||
public void setCreatedBy(String createdBy) {
|
||||
this.createdBy = createdBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne l'identifiant de l'utilisateur ayant effectué la dernière mise à jour.
|
||||
*
|
||||
* @return l'identifiant du modificateur
|
||||
*/
|
||||
public String getUpdatedBy() {
|
||||
return updatedBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Définit l'identifiant de l'utilisateur ayant effectué la dernière mise à jour.
|
||||
*
|
||||
* @param updatedBy l'identifiant du modificateur
|
||||
*/
|
||||
public void setUpdatedBy(String updatedBy) {
|
||||
this.updatedBy = updatedBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Définit le statut de suppression logique.
|
||||
*
|
||||
* @param deleted true pour marquer comme supprimé, false sinon
|
||||
*/
|
||||
public void setDeleted(boolean deleted) {
|
||||
this.deleted = deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne la date de suppression logique.
|
||||
*
|
||||
* @return la date de suppression
|
||||
*/
|
||||
public LocalDateTime getDeletedAt() {
|
||||
return deletedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Définit la date de suppression logique.
|
||||
*
|
||||
* @param deletedAt la date de suppression
|
||||
*/
|
||||
public void setDeletedAt(LocalDateTime deletedAt) {
|
||||
this.deletedAt = deletedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne l'identifiant de l'utilisateur ayant effectué la suppression.
|
||||
*
|
||||
* @return l'identifiant du suppresseur
|
||||
*/
|
||||
public String getDeletedBy() {
|
||||
return deletedBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Définit l'identifiant de l'utilisateur ayant effectué la suppression.
|
||||
*
|
||||
* @param deletedBy l'identifiant du suppresseur
|
||||
*/
|
||||
public void setDeletedBy(String deletedBy) {
|
||||
this.deletedBy = deletedBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Représentation textuelle de l'entité de base.
|
||||
*
|
||||
* @return une chaîne représentant l'entité
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BaseEntity{" +
|
||||
"createdAt=" + createdAt +
|
||||
", updatedAt=" + updatedAt +
|
||||
", createdBy='" + createdBy + '\'' +
|
||||
", updatedBy='" + updatedBy + '\'' +
|
||||
", deleted=" + deleted +
|
||||
", deletedAt=" + deletedAt +
|
||||
", deletedBy='" + deletedBy + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
531
src/main/java/com/gbcm/server/impl/entity/Client.java
Normal file
531
src/main/java/com/gbcm/server/impl/entity/Client.java
Normal file
@@ -0,0 +1,531 @@
|
||||
package com.gbcm.server.impl.entity;
|
||||
|
||||
import com.gbcm.server.api.enums.ServiceType;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Entité représentant un client de la plateforme GBCM.
|
||||
* Un client est associé à un utilisateur et peut avoir plusieurs services.
|
||||
*
|
||||
* @author GBCM Development Team
|
||||
* @version 1.0
|
||||
* @since 1.0
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "clients", indexes = {
|
||||
@Index(name = "idx_clients_user_id", columnList = "user_id", unique = true),
|
||||
@Index(name = "idx_clients_company", columnList = "company_name"),
|
||||
@Index(name = "idx_clients_status", columnList = "status"),
|
||||
@Index(name = "idx_clients_deleted", columnList = "deleted")
|
||||
})
|
||||
@NamedQueries({
|
||||
@NamedQuery(name = "Client.findByUserId",
|
||||
query = "SELECT c FROM Client c WHERE c.user.id = :userId AND c.deleted = false"),
|
||||
@NamedQuery(name = "Client.findByStatus",
|
||||
query = "SELECT c FROM Client c WHERE c.status = :status AND c.deleted = false"),
|
||||
@NamedQuery(name = "Client.findByCompanyName",
|
||||
query = "SELECT c FROM Client c WHERE LOWER(c.companyName) LIKE LOWER(:companyName) AND c.deleted = false"),
|
||||
@NamedQuery(name = "Client.findActiveClients",
|
||||
query = "SELECT c FROM Client c WHERE c.status = 'ACTIVE' AND c.deleted = false")
|
||||
})
|
||||
public class Client extends BaseEntity {
|
||||
|
||||
/**
|
||||
* Identifiant unique du client.
|
||||
*/
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* Utilisateur associé à ce client.
|
||||
* Relation one-to-one obligatoire.
|
||||
*/
|
||||
@OneToOne(fetch = FetchType.LAZY, optional = false)
|
||||
@JoinColumn(name = "user_id", nullable = false, unique = true,
|
||||
foreignKey = @ForeignKey(name = "fk_clients_user_id"))
|
||||
@NotNull(message = "L'utilisateur associé est obligatoire")
|
||||
private User user;
|
||||
|
||||
/**
|
||||
* Nom de l'entreprise du client.
|
||||
*/
|
||||
@Column(name = "company_name", nullable = false, length = 200)
|
||||
@NotBlank(message = "Le nom de l'entreprise est obligatoire")
|
||||
@Size(max = 200, message = "Le nom de l'entreprise ne peut pas dépasser 200 caractères")
|
||||
private String companyName;
|
||||
|
||||
/**
|
||||
* Secteur d'activité de l'entreprise.
|
||||
*/
|
||||
@Column(name = "industry", length = 100)
|
||||
@Size(max = 100, message = "Le secteur d'activité ne peut pas dépasser 100 caractères")
|
||||
private String industry;
|
||||
|
||||
/**
|
||||
* Taille de l'entreprise (nombre d'employés).
|
||||
*/
|
||||
@Column(name = "company_size")
|
||||
private Integer companySize;
|
||||
|
||||
/**
|
||||
* Chiffre d'affaires annuel de l'entreprise.
|
||||
*/
|
||||
@Column(name = "annual_revenue", precision = 15, scale = 2)
|
||||
private BigDecimal annualRevenue;
|
||||
|
||||
/**
|
||||
* Adresse de l'entreprise - ligne 1.
|
||||
*/
|
||||
@Column(name = "address_line1", length = 255)
|
||||
@Size(max = 255, message = "L'adresse ligne 1 ne peut pas dépasser 255 caractères")
|
||||
private String addressLine1;
|
||||
|
||||
/**
|
||||
* Adresse de l'entreprise - ligne 2.
|
||||
*/
|
||||
@Column(name = "address_line2", length = 255)
|
||||
@Size(max = 255, message = "L'adresse ligne 2 ne peut pas dépasser 255 caractères")
|
||||
private String addressLine2;
|
||||
|
||||
/**
|
||||
* Ville de l'entreprise.
|
||||
*/
|
||||
@Column(name = "city", length = 100)
|
||||
@Size(max = 100, message = "La ville ne peut pas dépasser 100 caractères")
|
||||
private String city;
|
||||
|
||||
/**
|
||||
* État/Province de l'entreprise.
|
||||
*/
|
||||
@Column(name = "state", length = 100)
|
||||
@Size(max = 100, message = "L'état ne peut pas dépasser 100 caractères")
|
||||
private String state;
|
||||
|
||||
/**
|
||||
* Code postal de l'entreprise.
|
||||
*/
|
||||
@Column(name = "postal_code", length = 20)
|
||||
@Size(max = 20, message = "Le code postal ne peut pas dépasser 20 caractères")
|
||||
private String postalCode;
|
||||
|
||||
/**
|
||||
* Pays de l'entreprise.
|
||||
*/
|
||||
@Column(name = "country", length = 100)
|
||||
@Size(max = 100, message = "Le pays ne peut pas dépasser 100 caractères")
|
||||
private String country;
|
||||
|
||||
/**
|
||||
* Site web de l'entreprise.
|
||||
*/
|
||||
@Column(name = "website", length = 255)
|
||||
@Size(max = 255, message = "Le site web ne peut pas dépasser 255 caractères")
|
||||
private String website;
|
||||
|
||||
/**
|
||||
* Statut du client.
|
||||
*/
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "status", nullable = false, length = 20)
|
||||
@NotNull(message = "Le statut est obligatoire")
|
||||
private ClientStatus status = ClientStatus.PROSPECT;
|
||||
|
||||
/**
|
||||
* Date de conversion de prospect à client.
|
||||
*/
|
||||
@Column(name = "converted_at")
|
||||
private LocalDateTime convertedAt;
|
||||
|
||||
/**
|
||||
* Type de service principal du client.
|
||||
*/
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "primary_service_type", length = 30)
|
||||
private ServiceType primaryServiceType;
|
||||
|
||||
/**
|
||||
* Valeur totale des contrats du client.
|
||||
*/
|
||||
@Column(name = "total_contract_value", precision = 15, scale = 2)
|
||||
private BigDecimal totalContractValue = BigDecimal.ZERO;
|
||||
|
||||
/**
|
||||
* Date de début de la relation client.
|
||||
*/
|
||||
@Column(name = "relationship_start_date")
|
||||
private LocalDate relationshipStartDate;
|
||||
|
||||
/**
|
||||
* Notes internes sur le client.
|
||||
*/
|
||||
@Column(name = "notes", columnDefinition = "TEXT")
|
||||
private String notes;
|
||||
|
||||
/**
|
||||
* Source d'acquisition du client.
|
||||
*/
|
||||
@Column(name = "acquisition_source", length = 100)
|
||||
@Size(max = 100, message = "La source d'acquisition ne peut pas dépasser 100 caractères")
|
||||
private String acquisitionSource;
|
||||
|
||||
/**
|
||||
* Constructeur par défaut.
|
||||
*/
|
||||
public Client() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructeur avec les champs obligatoires.
|
||||
*
|
||||
* @param user l'utilisateur associé
|
||||
* @param companyName le nom de l'entreprise
|
||||
*/
|
||||
public Client(User user, String companyName) {
|
||||
this();
|
||||
this.user = user;
|
||||
this.companyName = companyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convertit un prospect en client.
|
||||
*/
|
||||
public void convertToClient() {
|
||||
if (this.status == ClientStatus.PROSPECT) {
|
||||
this.status = ClientStatus.ACTIVE;
|
||||
this.convertedAt = LocalDateTime.now();
|
||||
this.relationshipStartDate = LocalDate.now();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Désactive le client.
|
||||
*/
|
||||
public void deactivate() {
|
||||
this.status = ClientStatus.INACTIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Réactive le client.
|
||||
*/
|
||||
public void reactivate() {
|
||||
this.status = ClientStatus.ACTIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute une valeur au contrat total.
|
||||
*
|
||||
* @param amount le montant à ajouter
|
||||
*/
|
||||
public void addContractValue(BigDecimal amount) {
|
||||
if (amount != null && amount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
this.totalContractValue = this.totalContractValue.add(amount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne l'adresse complète formatée.
|
||||
*
|
||||
* @return l'adresse complète
|
||||
*/
|
||||
public String getFullAddress() {
|
||||
StringBuilder address = new StringBuilder();
|
||||
|
||||
if (addressLine1 != null && !addressLine1.trim().isEmpty()) {
|
||||
address.append(addressLine1);
|
||||
}
|
||||
|
||||
if (addressLine2 != null && !addressLine2.trim().isEmpty()) {
|
||||
if (address.length() > 0) address.append(", ");
|
||||
address.append(addressLine2);
|
||||
}
|
||||
|
||||
if (city != null && !city.trim().isEmpty()) {
|
||||
if (address.length() > 0) address.append(", ");
|
||||
address.append(city);
|
||||
}
|
||||
|
||||
if (state != null && !state.trim().isEmpty()) {
|
||||
if (address.length() > 0) address.append(", ");
|
||||
address.append(state);
|
||||
}
|
||||
|
||||
if (postalCode != null && !postalCode.trim().isEmpty()) {
|
||||
if (address.length() > 0) address.append(" ");
|
||||
address.append(postalCode);
|
||||
}
|
||||
|
||||
if (country != null && !country.trim().isEmpty()) {
|
||||
if (address.length() > 0) address.append(", ");
|
||||
address.append(country);
|
||||
}
|
||||
|
||||
return address.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si le client est actif.
|
||||
*
|
||||
* @return true si le client est actif, false sinon
|
||||
*/
|
||||
public boolean isActiveClient() {
|
||||
return status == ClientStatus.ACTIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si le client est un prospect.
|
||||
*
|
||||
* @return true si c'est un prospect, false sinon
|
||||
*/
|
||||
public boolean isProspect() {
|
||||
return status == ClientStatus.PROSPECT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode de recherche par ID utilisateur.
|
||||
*
|
||||
* @param userId l'ID de l'utilisateur
|
||||
* @return le client trouvé ou null
|
||||
*/
|
||||
public static Client findByUserId(Long userId) {
|
||||
return find("#Client.findByUserId", userId).firstResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode de recherche par statut.
|
||||
*
|
||||
* @param status le statut à rechercher
|
||||
* @return la liste des clients avec ce statut
|
||||
*/
|
||||
public static List<Client> findByStatus(ClientStatus status) {
|
||||
return find("#Client.findByStatus", status).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode de recherche par nom d'entreprise.
|
||||
*
|
||||
* @param companyName le nom d'entreprise à rechercher
|
||||
* @return la liste des clients correspondants
|
||||
*/
|
||||
public static List<Client> findByCompanyName(String companyName) {
|
||||
String searchPattern = "%" + companyName + "%";
|
||||
return find("#Client.findByCompanyName", searchPattern).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode de recherche des clients actifs.
|
||||
*
|
||||
* @return la liste des clients actifs
|
||||
*/
|
||||
public static List<Client> findActiveClients() {
|
||||
return find("#Client.findActiveClients").list();
|
||||
}
|
||||
|
||||
// Getters et Setters
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public String getCompanyName() {
|
||||
return companyName;
|
||||
}
|
||||
|
||||
public void setCompanyName(String companyName) {
|
||||
this.companyName = companyName;
|
||||
}
|
||||
|
||||
public String getIndustry() {
|
||||
return industry;
|
||||
}
|
||||
|
||||
public void setIndustry(String industry) {
|
||||
this.industry = industry;
|
||||
}
|
||||
|
||||
public Integer getCompanySize() {
|
||||
return companySize;
|
||||
}
|
||||
|
||||
public void setCompanySize(Integer companySize) {
|
||||
this.companySize = companySize;
|
||||
}
|
||||
|
||||
public BigDecimal getAnnualRevenue() {
|
||||
return annualRevenue;
|
||||
}
|
||||
|
||||
public void setAnnualRevenue(BigDecimal annualRevenue) {
|
||||
this.annualRevenue = annualRevenue;
|
||||
}
|
||||
|
||||
public String getAddressLine1() {
|
||||
return addressLine1;
|
||||
}
|
||||
|
||||
public void setAddressLine1(String addressLine1) {
|
||||
this.addressLine1 = addressLine1;
|
||||
}
|
||||
|
||||
public String getAddressLine2() {
|
||||
return addressLine2;
|
||||
}
|
||||
|
||||
public void setAddressLine2(String addressLine2) {
|
||||
this.addressLine2 = addressLine2;
|
||||
}
|
||||
|
||||
public String getCity() {
|
||||
return city;
|
||||
}
|
||||
|
||||
public void setCity(String city) {
|
||||
this.city = city;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(String state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public String getPostalCode() {
|
||||
return postalCode;
|
||||
}
|
||||
|
||||
public void setPostalCode(String postalCode) {
|
||||
this.postalCode = postalCode;
|
||||
}
|
||||
|
||||
public String getCountry() {
|
||||
return country;
|
||||
}
|
||||
|
||||
public void setCountry(String country) {
|
||||
this.country = country;
|
||||
}
|
||||
|
||||
public String getWebsite() {
|
||||
return website;
|
||||
}
|
||||
|
||||
public void setWebsite(String website) {
|
||||
this.website = website;
|
||||
}
|
||||
|
||||
public ClientStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(ClientStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public LocalDateTime getConvertedAt() {
|
||||
return convertedAt;
|
||||
}
|
||||
|
||||
public void setConvertedAt(LocalDateTime convertedAt) {
|
||||
this.convertedAt = convertedAt;
|
||||
}
|
||||
|
||||
public ServiceType getPrimaryServiceType() {
|
||||
return primaryServiceType;
|
||||
}
|
||||
|
||||
public void setPrimaryServiceType(ServiceType primaryServiceType) {
|
||||
this.primaryServiceType = primaryServiceType;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalContractValue() {
|
||||
return totalContractValue;
|
||||
}
|
||||
|
||||
public void setTotalContractValue(BigDecimal totalContractValue) {
|
||||
this.totalContractValue = totalContractValue;
|
||||
}
|
||||
|
||||
public LocalDate getRelationshipStartDate() {
|
||||
return relationshipStartDate;
|
||||
}
|
||||
|
||||
public void setRelationshipStartDate(LocalDate relationshipStartDate) {
|
||||
this.relationshipStartDate = relationshipStartDate;
|
||||
}
|
||||
|
||||
public String getNotes() {
|
||||
return notes;
|
||||
}
|
||||
|
||||
public void setNotes(String notes) {
|
||||
this.notes = notes;
|
||||
}
|
||||
|
||||
public String getAcquisitionSource() {
|
||||
return acquisitionSource;
|
||||
}
|
||||
|
||||
public void setAcquisitionSource(String acquisitionSource) {
|
||||
this.acquisitionSource = acquisitionSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Client{" +
|
||||
"id=" + id +
|
||||
", companyName='" + companyName + '\'' +
|
||||
", industry='" + industry + '\'' +
|
||||
", status=" + status +
|
||||
", totalContractValue=" + totalContractValue +
|
||||
", relationshipStartDate=" + relationshipStartDate +
|
||||
'}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Énumération des statuts de client.
|
||||
*/
|
||||
public enum ClientStatus {
|
||||
/**
|
||||
* Prospect - pas encore client.
|
||||
*/
|
||||
PROSPECT,
|
||||
|
||||
/**
|
||||
* Client actif.
|
||||
*/
|
||||
ACTIVE,
|
||||
|
||||
/**
|
||||
* Client inactif.
|
||||
*/
|
||||
INACTIVE,
|
||||
|
||||
/**
|
||||
* Ancien client.
|
||||
*/
|
||||
FORMER
|
||||
}
|
||||
}
|
||||
550
src/main/java/com/gbcm/server/impl/entity/Coach.java
Normal file
550
src/main/java/com/gbcm/server/impl/entity/Coach.java
Normal file
@@ -0,0 +1,550 @@
|
||||
package com.gbcm.server.impl.entity;
|
||||
|
||||
import com.gbcm.server.api.enums.ServiceType;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Entité représentant un coach de la plateforme GBCM.
|
||||
* Un coach est associé à un utilisateur et peut offrir différents services.
|
||||
*
|
||||
* @author GBCM Development Team
|
||||
* @version 1.0
|
||||
* @since 1.0
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "coaches", indexes = {
|
||||
@Index(name = "idx_coaches_user_id", columnList = "user_id", unique = true),
|
||||
@Index(name = "idx_coaches_status", columnList = "status"),
|
||||
@Index(name = "idx_coaches_specialization", columnList = "specialization"),
|
||||
@Index(name = "idx_coaches_deleted", columnList = "deleted")
|
||||
})
|
||||
@NamedQueries({
|
||||
@NamedQuery(name = "Coach.findByUserId",
|
||||
query = "SELECT c FROM Coach c WHERE c.user.id = :userId AND c.deleted = false"),
|
||||
@NamedQuery(name = "Coach.findByStatus",
|
||||
query = "SELECT c FROM Coach c WHERE c.status = :status AND c.deleted = false"),
|
||||
@NamedQuery(name = "Coach.findBySpecialization",
|
||||
query = "SELECT c FROM Coach c WHERE c.specialization = :specialization AND c.deleted = false"),
|
||||
@NamedQuery(name = "Coach.findAvailableCoaches",
|
||||
query = "SELECT c FROM Coach c WHERE c.status = 'ACTIVE' AND c.availableForBooking = true AND c.deleted = false")
|
||||
})
|
||||
public class Coach extends BaseEntity {
|
||||
|
||||
/**
|
||||
* Identifiant unique du coach.
|
||||
*/
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* Utilisateur associé à ce coach.
|
||||
* Relation one-to-one obligatoire.
|
||||
*/
|
||||
@OneToOne(fetch = FetchType.LAZY, optional = false)
|
||||
@JoinColumn(name = "user_id", nullable = false, unique = true,
|
||||
foreignKey = @ForeignKey(name = "fk_coaches_user_id"))
|
||||
@NotNull(message = "L'utilisateur associé est obligatoire")
|
||||
private User user;
|
||||
|
||||
/**
|
||||
* Spécialisation principale du coach.
|
||||
*/
|
||||
@Column(name = "specialization", nullable = false, length = 100)
|
||||
@NotBlank(message = "La spécialisation est obligatoire")
|
||||
@Size(max = 100, message = "La spécialisation ne peut pas dépasser 100 caractères")
|
||||
private String specialization;
|
||||
|
||||
/**
|
||||
* Biographie professionnelle du coach.
|
||||
*/
|
||||
@Column(name = "bio", columnDefinition = "TEXT")
|
||||
private String bio;
|
||||
|
||||
/**
|
||||
* Années d'expérience du coach.
|
||||
*/
|
||||
@Column(name = "years_of_experience")
|
||||
private Integer yearsOfExperience;
|
||||
|
||||
/**
|
||||
* Certifications du coach.
|
||||
*/
|
||||
@Column(name = "certifications", columnDefinition = "TEXT")
|
||||
private String certifications;
|
||||
|
||||
/**
|
||||
* Langues parlées par le coach.
|
||||
*/
|
||||
@Column(name = "languages", length = 255)
|
||||
@Size(max = 255, message = "Les langues ne peuvent pas dépasser 255 caractères")
|
||||
private String languages;
|
||||
|
||||
/**
|
||||
* Tarif horaire du coach pour les sessions individuelles.
|
||||
*/
|
||||
@Column(name = "hourly_rate", precision = 10, scale = 2)
|
||||
private BigDecimal hourlyRate;
|
||||
|
||||
/**
|
||||
* Statut du coach.
|
||||
*/
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "status", nullable = false, length = 20)
|
||||
@NotNull(message = "Le statut est obligatoire")
|
||||
private CoachStatus status = CoachStatus.ACTIVE;
|
||||
|
||||
/**
|
||||
* Disponibilité pour les réservations.
|
||||
*/
|
||||
@Column(name = "available_for_booking", nullable = false)
|
||||
private boolean availableForBooking = true;
|
||||
|
||||
/**
|
||||
* Types de services offerts par le coach.
|
||||
*/
|
||||
@ElementCollection(targetClass = ServiceType.class)
|
||||
@CollectionTable(name = "coach_service_types",
|
||||
joinColumns = @JoinColumn(name = "coach_id"),
|
||||
foreignKey = @ForeignKey(name = "fk_coach_service_types_coach_id"))
|
||||
@Column(name = "service_type", length = 30)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private Set<ServiceType> serviceTypes;
|
||||
|
||||
/**
|
||||
* Heures de travail - début.
|
||||
*/
|
||||
@Column(name = "working_hours_start")
|
||||
private LocalTime workingHoursStart;
|
||||
|
||||
/**
|
||||
* Heures de travail - fin.
|
||||
*/
|
||||
@Column(name = "working_hours_end")
|
||||
private LocalTime workingHoursEnd;
|
||||
|
||||
/**
|
||||
* Jours de travail (format: MONDAY,TUESDAY,WEDNESDAY...).
|
||||
*/
|
||||
@Column(name = "working_days", length = 100)
|
||||
@Size(max = 100, message = "Les jours de travail ne peuvent pas dépasser 100 caractères")
|
||||
private String workingDays;
|
||||
|
||||
/**
|
||||
* Fuseau horaire du coach.
|
||||
*/
|
||||
@Column(name = "timezone", length = 50)
|
||||
@Size(max = 50, message = "Le fuseau horaire ne peut pas dépasser 50 caractères")
|
||||
private String timezone;
|
||||
|
||||
/**
|
||||
* Date de début d'activité comme coach.
|
||||
*/
|
||||
@Column(name = "start_date")
|
||||
private LocalDate startDate;
|
||||
|
||||
/**
|
||||
* Date de fin d'activité comme coach.
|
||||
*/
|
||||
@Column(name = "end_date")
|
||||
private LocalDate endDate;
|
||||
|
||||
/**
|
||||
* Note moyenne du coach (calculée à partir des évaluations).
|
||||
*/
|
||||
@Column(name = "average_rating", precision = 3, scale = 2)
|
||||
private BigDecimal averageRating;
|
||||
|
||||
/**
|
||||
* Nombre total d'évaluations reçues.
|
||||
*/
|
||||
@Column(name = "total_ratings", nullable = false)
|
||||
private int totalRatings = 0;
|
||||
|
||||
/**
|
||||
* Nombre total de sessions effectuées.
|
||||
*/
|
||||
@Column(name = "total_sessions", nullable = false)
|
||||
private int totalSessions = 0;
|
||||
|
||||
/**
|
||||
* Revenus totaux générés.
|
||||
*/
|
||||
@Column(name = "total_revenue", precision = 15, scale = 2)
|
||||
private BigDecimal totalRevenue = BigDecimal.ZERO;
|
||||
|
||||
/**
|
||||
* Notes internes sur le coach.
|
||||
*/
|
||||
@Column(name = "notes", columnDefinition = "TEXT")
|
||||
private String notes;
|
||||
|
||||
/**
|
||||
* Constructeur par défaut.
|
||||
*/
|
||||
public Coach() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructeur avec les champs obligatoires.
|
||||
*
|
||||
* @param user l'utilisateur associé
|
||||
* @param specialization la spécialisation du coach
|
||||
*/
|
||||
public Coach(User user, String specialization) {
|
||||
this();
|
||||
this.user = user;
|
||||
this.specialization = specialization;
|
||||
this.startDate = LocalDate.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Active le coach.
|
||||
*/
|
||||
public void activate() {
|
||||
this.status = CoachStatus.ACTIVE;
|
||||
this.availableForBooking = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Désactive le coach.
|
||||
*/
|
||||
public void deactivate() {
|
||||
this.status = CoachStatus.INACTIVE;
|
||||
this.availableForBooking = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Met le coach en congé.
|
||||
*/
|
||||
public void setOnLeave() {
|
||||
this.status = CoachStatus.ON_LEAVE;
|
||||
this.availableForBooking = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Termine l'activité du coach.
|
||||
*/
|
||||
public void terminate() {
|
||||
this.status = CoachStatus.TERMINATED;
|
||||
this.availableForBooking = false;
|
||||
this.endDate = LocalDate.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour la note moyenne après une nouvelle évaluation.
|
||||
*
|
||||
* @param newRating la nouvelle note reçue
|
||||
*/
|
||||
public void updateRating(BigDecimal newRating) {
|
||||
if (newRating != null && newRating.compareTo(BigDecimal.ZERO) > 0) {
|
||||
if (averageRating == null) {
|
||||
averageRating = newRating;
|
||||
totalRatings = 1;
|
||||
} else {
|
||||
BigDecimal totalScore = averageRating.multiply(new BigDecimal(totalRatings));
|
||||
totalScore = totalScore.add(newRating);
|
||||
totalRatings++;
|
||||
averageRating = totalScore.divide(new BigDecimal(totalRatings), 2, java.math.RoundingMode.HALF_UP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Incrémente le nombre de sessions effectuées.
|
||||
*/
|
||||
public void incrementSessionCount() {
|
||||
this.totalSessions++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute des revenus au total.
|
||||
*
|
||||
* @param amount le montant à ajouter
|
||||
*/
|
||||
public void addRevenue(BigDecimal amount) {
|
||||
if (amount != null && amount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
this.totalRevenue = this.totalRevenue.add(amount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si le coach est disponible pour les réservations.
|
||||
*
|
||||
* @return true si disponible, false sinon
|
||||
*/
|
||||
public boolean isAvailableForBooking() {
|
||||
return availableForBooking && status == CoachStatus.ACTIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si le coach offre un type de service spécifique.
|
||||
*
|
||||
* @param serviceType le type de service à vérifier
|
||||
* @return true si le coach offre ce service, false sinon
|
||||
*/
|
||||
public boolean offersService(ServiceType serviceType) {
|
||||
return serviceTypes != null && serviceTypes.contains(serviceType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode de recherche par ID utilisateur.
|
||||
*
|
||||
* @param userId l'ID de l'utilisateur
|
||||
* @return le coach trouvé ou null
|
||||
*/
|
||||
public static Coach findByUserId(Long userId) {
|
||||
return find("#Coach.findByUserId", userId).firstResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode de recherche par statut.
|
||||
*
|
||||
* @param status le statut à rechercher
|
||||
* @return la liste des coaches avec ce statut
|
||||
*/
|
||||
public static List<Coach> findByStatus(CoachStatus status) {
|
||||
return find("#Coach.findByStatus", status).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode de recherche par spécialisation.
|
||||
*
|
||||
* @param specialization la spécialisation à rechercher
|
||||
* @return la liste des coaches avec cette spécialisation
|
||||
*/
|
||||
public static List<Coach> findBySpecialization(String specialization) {
|
||||
return find("#Coach.findBySpecialization", specialization).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode de recherche des coaches disponibles.
|
||||
*
|
||||
* @return la liste des coaches disponibles pour réservation
|
||||
*/
|
||||
public static List<Coach> findAvailableCoaches() {
|
||||
return find("#Coach.findAvailableCoaches").list();
|
||||
}
|
||||
|
||||
// Getters et Setters
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public String getSpecialization() {
|
||||
return specialization;
|
||||
}
|
||||
|
||||
public void setSpecialization(String specialization) {
|
||||
this.specialization = specialization;
|
||||
}
|
||||
|
||||
public String getBio() {
|
||||
return bio;
|
||||
}
|
||||
|
||||
public void setBio(String bio) {
|
||||
this.bio = bio;
|
||||
}
|
||||
|
||||
public Integer getYearsOfExperience() {
|
||||
return yearsOfExperience;
|
||||
}
|
||||
|
||||
public void setYearsOfExperience(Integer yearsOfExperience) {
|
||||
this.yearsOfExperience = yearsOfExperience;
|
||||
}
|
||||
|
||||
public String getCertifications() {
|
||||
return certifications;
|
||||
}
|
||||
|
||||
public void setCertifications(String certifications) {
|
||||
this.certifications = certifications;
|
||||
}
|
||||
|
||||
public String getLanguages() {
|
||||
return languages;
|
||||
}
|
||||
|
||||
public void setLanguages(String languages) {
|
||||
this.languages = languages;
|
||||
}
|
||||
|
||||
public BigDecimal getHourlyRate() {
|
||||
return hourlyRate;
|
||||
}
|
||||
|
||||
public void setHourlyRate(BigDecimal hourlyRate) {
|
||||
this.hourlyRate = hourlyRate;
|
||||
}
|
||||
|
||||
public CoachStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(CoachStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public void setAvailableForBooking(boolean availableForBooking) {
|
||||
this.availableForBooking = availableForBooking;
|
||||
}
|
||||
|
||||
public Set<ServiceType> getServiceTypes() {
|
||||
return serviceTypes;
|
||||
}
|
||||
|
||||
public void setServiceTypes(Set<ServiceType> serviceTypes) {
|
||||
this.serviceTypes = serviceTypes;
|
||||
}
|
||||
|
||||
public LocalTime getWorkingHoursStart() {
|
||||
return workingHoursStart;
|
||||
}
|
||||
|
||||
public void setWorkingHoursStart(LocalTime workingHoursStart) {
|
||||
this.workingHoursStart = workingHoursStart;
|
||||
}
|
||||
|
||||
public LocalTime getWorkingHoursEnd() {
|
||||
return workingHoursEnd;
|
||||
}
|
||||
|
||||
public void setWorkingHoursEnd(LocalTime workingHoursEnd) {
|
||||
this.workingHoursEnd = workingHoursEnd;
|
||||
}
|
||||
|
||||
public String getWorkingDays() {
|
||||
return workingDays;
|
||||
}
|
||||
|
||||
public void setWorkingDays(String workingDays) {
|
||||
this.workingDays = workingDays;
|
||||
}
|
||||
|
||||
public String getTimezone() {
|
||||
return timezone;
|
||||
}
|
||||
|
||||
public void setTimezone(String timezone) {
|
||||
this.timezone = timezone;
|
||||
}
|
||||
|
||||
public LocalDate getStartDate() {
|
||||
return startDate;
|
||||
}
|
||||
|
||||
public void setStartDate(LocalDate startDate) {
|
||||
this.startDate = startDate;
|
||||
}
|
||||
|
||||
public LocalDate getEndDate() {
|
||||
return endDate;
|
||||
}
|
||||
|
||||
public void setEndDate(LocalDate endDate) {
|
||||
this.endDate = endDate;
|
||||
}
|
||||
|
||||
public BigDecimal getAverageRating() {
|
||||
return averageRating;
|
||||
}
|
||||
|
||||
public void setAverageRating(BigDecimal averageRating) {
|
||||
this.averageRating = averageRating;
|
||||
}
|
||||
|
||||
public int getTotalRatings() {
|
||||
return totalRatings;
|
||||
}
|
||||
|
||||
public void setTotalRatings(int totalRatings) {
|
||||
this.totalRatings = totalRatings;
|
||||
}
|
||||
|
||||
public int getTotalSessions() {
|
||||
return totalSessions;
|
||||
}
|
||||
|
||||
public void setTotalSessions(int totalSessions) {
|
||||
this.totalSessions = totalSessions;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalRevenue() {
|
||||
return totalRevenue;
|
||||
}
|
||||
|
||||
public void setTotalRevenue(BigDecimal totalRevenue) {
|
||||
this.totalRevenue = totalRevenue;
|
||||
}
|
||||
|
||||
public String getNotes() {
|
||||
return notes;
|
||||
}
|
||||
|
||||
public void setNotes(String notes) {
|
||||
this.notes = notes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Coach{" +
|
||||
"id=" + id +
|
||||
", specialization='" + specialization + '\'' +
|
||||
", status=" + status +
|
||||
", availableForBooking=" + availableForBooking +
|
||||
", averageRating=" + averageRating +
|
||||
", totalSessions=" + totalSessions +
|
||||
", totalRevenue=" + totalRevenue +
|
||||
'}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Énumération des statuts de coach.
|
||||
*/
|
||||
public enum CoachStatus {
|
||||
/**
|
||||
* Coach actif et disponible.
|
||||
*/
|
||||
ACTIVE,
|
||||
|
||||
/**
|
||||
* Coach inactif temporairement.
|
||||
*/
|
||||
INACTIVE,
|
||||
|
||||
/**
|
||||
* Coach en congé.
|
||||
*/
|
||||
ON_LEAVE,
|
||||
|
||||
/**
|
||||
* Coach dont le contrat est terminé.
|
||||
*/
|
||||
TERMINATED
|
||||
}
|
||||
}
|
||||
432
src/main/java/com/gbcm/server/impl/entity/User.java
Normal file
432
src/main/java/com/gbcm/server/impl/entity/User.java
Normal file
@@ -0,0 +1,432 @@
|
||||
package com.gbcm.server.impl.entity;
|
||||
|
||||
import com.gbcm.server.api.enums.UserRole;
|
||||
import io.quarkus.security.jpa.Password;
|
||||
import io.quarkus.security.jpa.Roles;
|
||||
import io.quarkus.security.jpa.UserDefinition;
|
||||
import io.quarkus.security.jpa.Username;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Entité représentant un utilisateur de la plateforme GBCM.
|
||||
* Utilisée pour l'authentification et l'autorisation avec Quarkus Security.
|
||||
*
|
||||
* @author GBCM Development Team
|
||||
* @version 1.0
|
||||
* @since 1.0
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "users", indexes = {
|
||||
@Index(name = "idx_users_email", columnList = "email", unique = true),
|
||||
@Index(name = "idx_users_role", columnList = "role"),
|
||||
@Index(name = "idx_users_active", columnList = "active"),
|
||||
@Index(name = "idx_users_deleted", columnList = "deleted")
|
||||
})
|
||||
@UserDefinition
|
||||
@NamedQueries({
|
||||
@NamedQuery(name = "User.findByEmail",
|
||||
query = "SELECT u FROM User u WHERE u.email = :email AND u.deleted = false"),
|
||||
@NamedQuery(name = "User.findActiveUsers",
|
||||
query = "SELECT u FROM User u WHERE u.active = true AND u.deleted = false"),
|
||||
@NamedQuery(name = "User.findByRole",
|
||||
query = "SELECT u FROM User u WHERE u.role = :role AND u.deleted = false"),
|
||||
@NamedQuery(name = "User.searchByNameOrEmail",
|
||||
query = "SELECT u FROM User u WHERE (LOWER(u.firstName) LIKE LOWER(:search) OR LOWER(u.lastName) LIKE LOWER(:search) OR LOWER(u.email) LIKE LOWER(:search)) AND u.deleted = false")
|
||||
})
|
||||
public class User extends BaseEntity {
|
||||
|
||||
/**
|
||||
* Identifiant unique de l'utilisateur.
|
||||
*/
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* Prénom de l'utilisateur.
|
||||
*/
|
||||
@Column(name = "first_name", nullable = false, length = 50)
|
||||
@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.
|
||||
*/
|
||||
@Column(name = "last_name", nullable = false, length = 50)
|
||||
@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.
|
||||
* Utilisée comme nom d'utilisateur pour l'authentification.
|
||||
*/
|
||||
@Column(name = "email", nullable = false, unique = true, length = 255)
|
||||
@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")
|
||||
@Username
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* Mot de passe haché de l'utilisateur.
|
||||
* Utilisé pour l'authentification avec Quarkus Security.
|
||||
*/
|
||||
@Column(name = "password_hash", nullable = false, length = 255)
|
||||
@NotBlank(message = "Le mot de passe est obligatoire")
|
||||
@Size(max = 255, message = "Le hash du mot de passe ne peut pas dépasser 255 caractères")
|
||||
@Password
|
||||
private String passwordHash;
|
||||
|
||||
/**
|
||||
* Numéro de téléphone de l'utilisateur.
|
||||
*/
|
||||
@Column(name = "phone", length = 20)
|
||||
@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 et l'accès aux fonctionnalités.
|
||||
*/
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "role", nullable = false, length = 20)
|
||||
@NotNull(message = "Le rôle est obligatoire")
|
||||
@Roles
|
||||
private UserRole role;
|
||||
|
||||
/**
|
||||
* Statut d'activation du compte utilisateur.
|
||||
* true = compte actif, false = compte désactivé.
|
||||
*/
|
||||
@Column(name = "active", nullable = false)
|
||||
private boolean active = true;
|
||||
|
||||
/**
|
||||
* Date de dernière connexion de l'utilisateur.
|
||||
*/
|
||||
@Column(name = "last_login_at")
|
||||
private LocalDateTime lastLoginAt;
|
||||
|
||||
/**
|
||||
* Adresse IP de la dernière connexion.
|
||||
*/
|
||||
@Column(name = "last_login_ip", length = 45)
|
||||
@Size(max = 45, message = "L'adresse IP ne peut pas dépasser 45 caractères")
|
||||
private String lastLoginIp;
|
||||
|
||||
/**
|
||||
* Nombre de tentatives de connexion échouées consécutives.
|
||||
*/
|
||||
@Column(name = "failed_login_attempts", nullable = false)
|
||||
private int failedLoginAttempts = 0;
|
||||
|
||||
/**
|
||||
* Date de verrouillage du compte (après trop de tentatives échouées).
|
||||
*/
|
||||
@Column(name = "locked_until")
|
||||
private LocalDateTime lockedUntil;
|
||||
|
||||
/**
|
||||
* Token de réinitialisation de mot de passe.
|
||||
*/
|
||||
@Column(name = "password_reset_token", length = 255)
|
||||
@Size(max = 255, message = "Le token de réinitialisation ne peut pas dépasser 255 caractères")
|
||||
private String passwordResetToken;
|
||||
|
||||
/**
|
||||
* Date d'expiration du token de réinitialisation.
|
||||
*/
|
||||
@Column(name = "password_reset_expires_at")
|
||||
private LocalDateTime passwordResetExpiresAt;
|
||||
|
||||
/**
|
||||
* Constructeur par défaut.
|
||||
*/
|
||||
public User() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructeur avec les champs obligatoires.
|
||||
*
|
||||
* @param firstName le prénom
|
||||
* @param lastName le nom de famille
|
||||
* @param email l'adresse email
|
||||
* @param passwordHash le mot de passe haché
|
||||
* @param role le rôle de l'utilisateur
|
||||
*/
|
||||
public User(String firstName, String lastName, String email, String passwordHash, UserRole role) {
|
||||
this();
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
this.email = email;
|
||||
this.passwordHash = passwordHash;
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne le nom complet de l'utilisateur.
|
||||
*
|
||||
* @return le nom complet (prénom + nom)
|
||||
*/
|
||||
public String getFullName() {
|
||||
return firstName + " " + lastName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si le compte est verrouillé.
|
||||
*
|
||||
* @return true si le compte est verrouillé, false sinon
|
||||
*/
|
||||
public boolean isLocked() {
|
||||
return lockedUntil != null && LocalDateTime.now().isBefore(lockedUntil);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verrouille le compte pour une durée spécifiée.
|
||||
*
|
||||
* @param lockDurationMinutes durée de verrouillage en minutes
|
||||
*/
|
||||
public void lockAccount(int lockDurationMinutes) {
|
||||
this.lockedUntil = LocalDateTime.now().plusMinutes(lockDurationMinutes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Déverrouille le compte et remet à zéro les tentatives échouées.
|
||||
*/
|
||||
public void unlockAccount() {
|
||||
this.lockedUntil = null;
|
||||
this.failedLoginAttempts = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Incrémente le nombre de tentatives de connexion échouées.
|
||||
*/
|
||||
public void incrementFailedLoginAttempts() {
|
||||
this.failedLoginAttempts++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remet à zéro les tentatives de connexion échouées.
|
||||
*/
|
||||
public void resetFailedLoginAttempts() {
|
||||
this.failedLoginAttempts = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour les informations de dernière connexion.
|
||||
*
|
||||
* @param ipAddress l'adresse IP de connexion
|
||||
*/
|
||||
public void updateLastLogin(String ipAddress) {
|
||||
this.lastLoginAt = LocalDateTime.now();
|
||||
this.lastLoginIp = ipAddress;
|
||||
resetFailedLoginAttempts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un token de réinitialisation de mot de passe.
|
||||
*
|
||||
* @param token le token généré
|
||||
* @param expirationHours durée de validité en heures
|
||||
*/
|
||||
public void setPasswordResetToken(String token, int expirationHours) {
|
||||
this.passwordResetToken = token;
|
||||
this.passwordResetExpiresAt = LocalDateTime.now().plusHours(expirationHours);
|
||||
}
|
||||
|
||||
/**
|
||||
* Efface le token de réinitialisation de mot de passe.
|
||||
*/
|
||||
public void clearPasswordResetToken() {
|
||||
this.passwordResetToken = null;
|
||||
this.passwordResetExpiresAt = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si le token de réinitialisation est valide.
|
||||
*
|
||||
* @param token le token à vérifier
|
||||
* @return true si le token est valide, false sinon
|
||||
*/
|
||||
public boolean isPasswordResetTokenValid(String token) {
|
||||
return passwordResetToken != null &&
|
||||
passwordResetToken.equals(token) &&
|
||||
passwordResetExpiresAt != null &&
|
||||
LocalDateTime.now().isBefore(passwordResetExpiresAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode de recherche par email.
|
||||
*
|
||||
* @param email l'adresse email à rechercher
|
||||
* @return l'utilisateur trouvé ou null
|
||||
*/
|
||||
public static User findByEmail(String email) {
|
||||
return find("#User.findByEmail", email).firstResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode de recherche des utilisateurs actifs.
|
||||
*
|
||||
* @return la liste des utilisateurs actifs
|
||||
*/
|
||||
public static List<User> findActiveUsers() {
|
||||
return find("#User.findActiveUsers").list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode de recherche par rôle.
|
||||
*
|
||||
* @param role le rôle à rechercher
|
||||
* @return la liste des utilisateurs avec ce rôle
|
||||
*/
|
||||
public static List<User> findByRole(UserRole role) {
|
||||
return find("#User.findByRole", role).list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode de recherche par nom ou email.
|
||||
*
|
||||
* @param search le terme de recherche
|
||||
* @return la liste des utilisateurs correspondants
|
||||
*/
|
||||
public static List<User> searchByNameOrEmail(String search) {
|
||||
String searchPattern = "%" + search + "%";
|
||||
return find("#User.searchByNameOrEmail", searchPattern).list();
|
||||
}
|
||||
|
||||
// Getters et Setters
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getPasswordHash() {
|
||||
return passwordHash;
|
||||
}
|
||||
|
||||
public void setPasswordHash(String passwordHash) {
|
||||
this.passwordHash = passwordHash;
|
||||
}
|
||||
|
||||
public String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public void setPhone(String phone) {
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
public UserRole getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(UserRole role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public void setActive(boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
public LocalDateTime getLastLoginAt() {
|
||||
return lastLoginAt;
|
||||
}
|
||||
|
||||
public void setLastLoginAt(LocalDateTime lastLoginAt) {
|
||||
this.lastLoginAt = lastLoginAt;
|
||||
}
|
||||
|
||||
public String getLastLoginIp() {
|
||||
return lastLoginIp;
|
||||
}
|
||||
|
||||
public void setLastLoginIp(String lastLoginIp) {
|
||||
this.lastLoginIp = lastLoginIp;
|
||||
}
|
||||
|
||||
public int getFailedLoginAttempts() {
|
||||
return failedLoginAttempts;
|
||||
}
|
||||
|
||||
public void setFailedLoginAttempts(int failedLoginAttempts) {
|
||||
this.failedLoginAttempts = failedLoginAttempts;
|
||||
}
|
||||
|
||||
public LocalDateTime getLockedUntil() {
|
||||
return lockedUntil;
|
||||
}
|
||||
|
||||
public void setLockedUntil(LocalDateTime lockedUntil) {
|
||||
this.lockedUntil = lockedUntil;
|
||||
}
|
||||
|
||||
public String getPasswordResetToken() {
|
||||
return passwordResetToken;
|
||||
}
|
||||
|
||||
public LocalDateTime getPasswordResetExpiresAt() {
|
||||
return passwordResetExpiresAt;
|
||||
}
|
||||
|
||||
public void setPasswordResetExpiresAt(LocalDateTime passwordResetExpiresAt) {
|
||||
this.passwordResetExpiresAt = passwordResetExpiresAt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "User{" +
|
||||
"id=" + id +
|
||||
", firstName='" + firstName + '\'' +
|
||||
", lastName='" + lastName + '\'' +
|
||||
", email='" + email + '\'' +
|
||||
", role=" + role +
|
||||
", active=" + active +
|
||||
", lastLoginAt=" + lastLoginAt +
|
||||
", failedLoginAttempts=" + failedLoginAttempts +
|
||||
", locked=" + isLocked() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -1,249 +0,0 @@
|
||||
package com.gbcm.server.services.impl;
|
||||
|
||||
import com.gbcm.server.api.dto.auth.LoginRequestDTO;
|
||||
import com.gbcm.server.api.dto.auth.LoginResponseDTO;
|
||||
import com.gbcm.server.api.dto.user.UserDTO;
|
||||
import com.gbcm.server.api.exceptions.AuthenticationException;
|
||||
import com.gbcm.server.api.exceptions.GBCMException;
|
||||
import com.gbcm.server.api.interfaces.AuthService;
|
||||
import com.gbcm.server.entities.User;
|
||||
import com.gbcm.server.services.security.JwtService;
|
||||
import com.gbcm.server.utils.EmailUtils;
|
||||
|
||||
import io.quarkus.elytron.security.common.BcryptUtil;
|
||||
import io.quarkus.logging.Log;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import jakarta.validation.Valid;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Implémentation du service d'authentification
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class AuthServiceImpl implements AuthService {
|
||||
|
||||
@Inject
|
||||
JwtService jwtService;
|
||||
|
||||
@Inject
|
||||
EmailUtils emailUtils;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public LoginResponseDTO login(@Valid LoginRequestDTO loginRequest)
|
||||
throws AuthenticationException, GBCMException {
|
||||
|
||||
Log.infof("Tentative de connexion pour: %s", loginRequest.getEmail());
|
||||
|
||||
try {
|
||||
// Recherche de l'utilisateur
|
||||
User user = User.findByEmailAndActive(loginRequest.getEmail(), true);
|
||||
if (user == null) {
|
||||
Log.warnf("Utilisateur non trouvé ou inactif: %s", loginRequest.getEmail());
|
||||
throw new AuthenticationException("Identifiants invalides");
|
||||
}
|
||||
|
||||
// Vérification du mot de passe
|
||||
if (!BcryptUtil.matches(loginRequest.getPassword(), user.passwordHash)) {
|
||||
Log.warnf("Mot de passe incorrect pour: %s", loginRequest.getEmail());
|
||||
throw new AuthenticationException("Identifiants invalides");
|
||||
}
|
||||
|
||||
// Mise à jour de la dernière connexion
|
||||
user.updateLastLogin();
|
||||
user.persist();
|
||||
|
||||
// Génération du token JWT
|
||||
String token = jwtService.generateToken(user);
|
||||
LocalDateTime expiresAt = LocalDateTime.now().plusHours(1);
|
||||
|
||||
// Création de la réponse
|
||||
UserDTO userDTO = mapToUserDTO(user);
|
||||
LoginResponseDTO response = LoginResponseDTO.success(token, expiresAt, userDTO);
|
||||
|
||||
if (loginRequest.isRememberMe()) {
|
||||
String refreshToken = jwtService.generateRefreshToken(user);
|
||||
response.setRefreshToken(refreshToken);
|
||||
}
|
||||
|
||||
Log.infof("Connexion réussie pour: %s", loginRequest.getEmail());
|
||||
return response;
|
||||
|
||||
} catch (AuthenticationException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
Log.errorf(e, "Erreur lors de la connexion pour: %s", loginRequest.getEmail());
|
||||
throw new GBCMException("Erreur système lors de la connexion");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void logout(String authToken) throws AuthenticationException {
|
||||
try {
|
||||
// Extraction du token (suppression du préfixe "Bearer ")
|
||||
String token = extractToken(authToken);
|
||||
|
||||
// Validation et extraction des informations utilisateur
|
||||
UserDTO userDTO = jwtService.validateToken(token);
|
||||
|
||||
// Ajout du token à la blacklist
|
||||
jwtService.blacklistToken(token);
|
||||
|
||||
Log.infof("Déconnexion réussie pour l'utilisateur: %s", userDTO.getEmail());
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.errorf(e, "Erreur lors de la déconnexion");
|
||||
throw new AuthenticationException("Erreur lors de la déconnexion");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginResponseDTO refreshToken(String refreshToken) throws AuthenticationException {
|
||||
try {
|
||||
// Validation du refresh token
|
||||
UserDTO userDTO = jwtService.validateRefreshToken(refreshToken);
|
||||
|
||||
// Recherche de l'utilisateur
|
||||
User user = User.findByEmail(userDTO.getEmail());
|
||||
if (user == null || !user.active) {
|
||||
throw new AuthenticationException("Utilisateur non trouvé ou inactif");
|
||||
}
|
||||
|
||||
// Génération d'un nouveau token
|
||||
String newToken = jwtService.generateToken(user);
|
||||
LocalDateTime expiresAt = LocalDateTime.now().plusHours(1);
|
||||
|
||||
return LoginResponseDTO.success(newToken, expiresAt, userDTO);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.errorf(e, "Erreur lors du rafraîchissement du token");
|
||||
throw new AuthenticationException("Token de rafraîchissement invalide");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDTO validateToken(String authToken) throws AuthenticationException {
|
||||
try {
|
||||
String token = extractToken(authToken);
|
||||
return jwtService.validateToken(token);
|
||||
} catch (Exception e) {
|
||||
Log.errorf(e, "Erreur lors de la validation du token");
|
||||
throw new AuthenticationException("Token invalide");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void forgotPassword(String email) throws GBCMException {
|
||||
try {
|
||||
User user = User.findByEmailAndActive(email, true);
|
||||
if (user == null) {
|
||||
// Pour des raisons de sécurité, on ne révèle pas si l'email existe
|
||||
Log.warnf("Demande de réinitialisation pour email inexistant: %s", email);
|
||||
return;
|
||||
}
|
||||
|
||||
// Génération du token de réinitialisation
|
||||
String resetToken = UUID.randomUUID().toString();
|
||||
user.passwordResetToken = resetToken;
|
||||
user.passwordResetExpiresAt = LocalDateTime.now().plusHours(24);
|
||||
user.persist();
|
||||
|
||||
// Envoi de l'email
|
||||
emailUtils.sendPasswordResetEmail(user.email, user.getFullName(), resetToken);
|
||||
|
||||
Log.infof("Email de réinitialisation envoyé à: %s", email);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.errorf(e, "Erreur lors de l'envoi de l'email de réinitialisation");
|
||||
throw new GBCMException("Erreur lors de l'envoi de l'email");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void resetPassword(String resetToken, String newPassword) throws GBCMException {
|
||||
try {
|
||||
User user = User.findByPasswordResetToken(resetToken);
|
||||
if (user == null || !user.isPasswordResetTokenValid()) {
|
||||
throw new GBCMException("Token de réinitialisation invalide ou expiré");
|
||||
}
|
||||
|
||||
// Mise à jour du mot de passe
|
||||
user.passwordHash = BcryptUtil.bcryptHash(newPassword);
|
||||
user.passwordResetToken = null;
|
||||
user.passwordResetExpiresAt = null;
|
||||
user.updatedAt = LocalDateTime.now();
|
||||
user.persist();
|
||||
|
||||
Log.infof("Mot de passe réinitialisé pour: %s", user.email);
|
||||
|
||||
} catch (GBCMException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
Log.errorf(e, "Erreur lors de la réinitialisation du mot de passe");
|
||||
throw new GBCMException("Erreur lors de la réinitialisation");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void changePassword(String authToken, String oldPassword, String newPassword)
|
||||
throws AuthenticationException, GBCMException {
|
||||
try {
|
||||
// Validation du token et récupération de l'utilisateur
|
||||
UserDTO userDTO = validateToken(authToken);
|
||||
User user = User.findByEmail(userDTO.getEmail());
|
||||
|
||||
if (user == null) {
|
||||
throw new AuthenticationException("Utilisateur non trouvé");
|
||||
}
|
||||
|
||||
// Vérification de l'ancien mot de passe
|
||||
if (!BcryptUtil.matches(oldPassword, user.passwordHash)) {
|
||||
throw new GBCMException("Ancien mot de passe incorrect");
|
||||
}
|
||||
|
||||
// Mise à jour du mot de passe
|
||||
user.passwordHash = BcryptUtil.bcryptHash(newPassword);
|
||||
user.updatedAt = LocalDateTime.now();
|
||||
user.persist();
|
||||
|
||||
Log.infof("Mot de passe changé pour: %s", user.email);
|
||||
|
||||
} catch (AuthenticationException | GBCMException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
Log.errorf(e, "Erreur lors du changement de mot de passe");
|
||||
throw new GBCMException("Erreur lors du changement de mot de passe");
|
||||
}
|
||||
}
|
||||
|
||||
// Méthodes utilitaires privées
|
||||
private String extractToken(String authToken) throws AuthenticationException {
|
||||
if (authToken == null || !authToken.startsWith("Bearer ")) {
|
||||
throw new AuthenticationException("Format de token invalide");
|
||||
}
|
||||
return authToken.substring(7);
|
||||
}
|
||||
|
||||
private UserDTO mapToUserDTO(User user) {
|
||||
UserDTO dto = new UserDTO();
|
||||
dto.setId(user.id);
|
||||
dto.setFirstName(user.firstName);
|
||||
dto.setLastName(user.lastName);
|
||||
dto.setEmail(user.email);
|
||||
dto.setPhone(user.phone);
|
||||
dto.setRole(user.role);
|
||||
dto.setActive(user.active);
|
||||
dto.setCreatedAt(user.createdAt);
|
||||
dto.setUpdatedAt(user.updatedAt);
|
||||
dto.setLastLoginAt(user.lastLoginAt);
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user