package com.gbcm.server.impl.entity; import java.time.LocalDateTime; import java.util.List; import com.gbcm.server.api.enums.UserRole; import com.gbcm.server.api.enums.UserStatus; import io.quarkus.hibernate.orm.panache.PanacheQuery; import io.quarkus.panache.common.Parameters; 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.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Index; import jakarta.persistence.NamedQueries; import jakarta.persistence.NamedQuery; import jakarta.persistence.Table; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; /** * 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.status = 'ACTIVE' 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") private UserRole role; /** * Statut de l'utilisateur dans le système. * Détermine si l'utilisateur peut se connecter et utiliser le système. */ @Enumerated(EnumType.STRING) @Column(name = "status", nullable = false, length = 20) @NotNull(message = "Le statut est obligatoire") private UserStatus status = UserStatus.ACTIVE; /** * Rôle de l'utilisateur sous forme de String pour Quarkus Security JPA. * Cette propriété est utilisée par le système de sécurité pour l'authentification. */ @Roles public String getRoleString() { return role != null ? role.name() : null; } /** * 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", Parameters.with("email", email)).firstResult(); } /** * Méthode de vérification d'existence par email. * * @param email l'adresse email à vérifier * @return true si l'utilisateur existe, false sinon */ public static boolean existsByEmail(String email) { return find("#User.findByEmail", Parameters.with("email", email)).count() > 0; } /** * Méthode de recherche des utilisateurs actifs. * * @return la liste des utilisateurs actifs */ public static List 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 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 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 UserStatus getStatus() { return status; } public void setStatus(UserStatus status) { this.status = status; } 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() + '}'; } }