feat: PHASE 1 - Adresses et Rôles/Permissions

PHASE 1.1 - Entité Adresse:
- Création entité Adresse avec types (SIEGE_SOCIAL, BUREAU, DOMICILE, AUTRE)
- Relations flexibles: Organisation, Membre, Evenement
- Enum TypeAdresse dans module API (DRY/WOU)
- Repository et Service AdresseService
- Relations bidirectionnelles mises à jour

PHASE 1.2 - Système Rôles et Permissions:
- Entité Role avec types (SYSTEME, ORGANISATION, PERSONNALISE)
- Entité Permission avec structure MODULE > RESSOURCE > ACTION
- Tables de liaison MembreRole et RolePermission
- Repositories pour toutes les entités
- Services RoleService et PermissionService
- Relations bidirectionnelles dans Membre

Respect strict DRY/WOU:
- Enums dans module API réutilisables
- Patterns de service cohérents
- Relations JPA standardisées
This commit is contained in:
dahoud
2025-11-30 01:31:12 +00:00
parent e26ae459e4
commit f930ae7341
25 changed files with 3583 additions and 91 deletions

View File

@@ -0,0 +1,154 @@
package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.adresse.TypeAdresse;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Entité Adresse pour la gestion des adresses des organisations, membres et événements
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Entity
@Table(
name = "adresses",
indexes = {
@Index(name = "idx_adresse_ville", columnList = "ville"),
@Index(name = "idx_adresse_pays", columnList = "pays"),
@Index(name = "idx_adresse_type", columnList = "type_adresse"),
@Index(name = "idx_adresse_organisation", columnList = "organisation_id"),
@Index(name = "idx_adresse_membre", columnList = "membre_id"),
@Index(name = "idx_adresse_evenement", columnList = "evenement_id")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class Adresse extends BaseEntity {
/** Type d'adresse */
@Enumerated(EnumType.STRING)
@Column(name = "type_adresse", nullable = false, length = 50)
private dev.lions.unionflow.server.api.enums.adresse.TypeAdresse typeAdresse;
/** Adresse complète */
@Column(name = "adresse", length = 500)
private String adresse;
/** Complément d'adresse */
@Column(name = "complement_adresse", length = 200)
private String complementAdresse;
/** Code postal */
@Column(name = "code_postal", length = 20)
private String codePostal;
/** Ville */
@Column(name = "ville", length = 100)
private String ville;
/** Région */
@Column(name = "region", length = 100)
private String region;
/** Pays */
@Column(name = "pays", length = 100)
private String pays;
/** Coordonnées géographiques - Latitude */
@DecimalMin(value = "-90.0", message = "La latitude doit être comprise entre -90 et 90")
@DecimalMax(value = "90.0", message = "La latitude doit être comprise entre -90 et 90")
@Digits(integer = 3, fraction = 6)
@Column(name = "latitude", precision = 9, scale = 6)
private BigDecimal latitude;
/** Coordonnées géographiques - Longitude */
@DecimalMin(value = "-180.0", message = "La longitude doit être comprise entre -180 et 180")
@DecimalMax(value = "180.0", message = "La longitude doit être comprise entre -180 et 180")
@Digits(integer = 3, fraction = 6)
@Column(name = "longitude", precision = 9, scale = 6)
private BigDecimal longitude;
/** Adresse principale (une seule par entité) */
@Builder.Default
@Column(name = "principale", nullable = false)
private Boolean principale = false;
/** Libellé personnalisé */
@Column(name = "libelle", length = 100)
private String libelle;
/** Notes et commentaires */
@Column(name = "notes", length = 500)
private String notes;
// Relations
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id")
private Organisation organisation;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre_id")
private Membre membre;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "evenement_id")
private Evenement evenement;
/** Méthode métier pour obtenir l'adresse complète formatée */
public String getAdresseComplete() {
StringBuilder sb = new StringBuilder();
if (adresse != null && !adresse.isEmpty()) {
sb.append(adresse);
}
if (complementAdresse != null && !complementAdresse.isEmpty()) {
if (sb.length() > 0) sb.append(", ");
sb.append(complementAdresse);
}
if (codePostal != null && !codePostal.isEmpty()) {
if (sb.length() > 0) sb.append(", ");
sb.append(codePostal);
}
if (ville != null && !ville.isEmpty()) {
if (sb.length() > 0) sb.append(" ");
sb.append(ville);
}
if (region != null && !region.isEmpty()) {
if (sb.length() > 0) sb.append(", ");
sb.append(region);
}
if (pays != null && !pays.isEmpty()) {
if (sb.length() > 0) sb.append(", ");
sb.append(pays);
}
return sb.toString();
}
/** Méthode métier pour vérifier si l'adresse a des coordonnées GPS */
public boolean hasCoordinates() {
return latitude != null && longitude != null;
}
/** Callback JPA avant la persistance */
@PrePersist
protected void onCreate() {
super.onCreate(); // Appelle le onCreate de BaseEntity
if (typeAdresse == null) {
typeAdresse = dev.lions.unionflow.server.api.enums.adresse.TypeAdresse.AUTRE;
}
if (principale == null) {
principale = false;
}
}
}

View File

@@ -118,6 +118,10 @@ public class Evenement extends BaseEntity {
@Builder.Default
private List<InscriptionEvenement> inscriptions = new ArrayList<>();
@OneToMany(mappedBy = "evenement", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<Adresse> adresses = new ArrayList<>();
/** Types d'événements */
public enum TypeEvenement {
ASSEMBLEE_GENERALE("Assemblée Générale"),

View File

@@ -5,6 +5,8 @@ import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
@@ -67,6 +69,14 @@ public class Membre extends BaseEntity {
@JoinColumn(name = "organisation_id")
private Organisation organisation;
@OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<Adresse> adresses = new ArrayList<>();
@OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<MembreRole> roles = new ArrayList<>();
/** Méthode métier pour obtenir le nom complet */
public String getNomComplet() {
return prenom + " " + nom;

View File

@@ -0,0 +1,88 @@
package dev.lions.unionflow.server.entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Table de liaison entre Membre et Role
* Permet à un membre d'avoir plusieurs rôles avec dates de début/fin
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Entity
@Table(
name = "membres_roles",
indexes = {
@Index(name = "idx_membre_role_membre", columnList = "membre_id"),
@Index(name = "idx_membre_role_role", columnList = "role_id"),
@Index(name = "idx_membre_role_actif", columnList = "actif")
},
uniqueConstraints = {
@UniqueConstraint(
name = "uk_membre_role",
columnNames = {"membre_id", "role_id"})
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class MembreRole extends BaseEntity {
/** Membre */
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre_id", nullable = false)
private Membre membre;
/** Rôle */
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "role_id", nullable = false)
private Role role;
/** Date de début d'attribution */
@Column(name = "date_debut")
private LocalDate dateDebut;
/** Date de fin d'attribution (null = permanent) */
@Column(name = "date_fin")
private LocalDate dateFin;
/** Commentaire sur l'attribution */
@Column(name = "commentaire", length = 500)
private String commentaire;
/** Méthode métier pour vérifier si l'attribution est active */
public boolean isActif() {
if (!Boolean.TRUE.equals(getActif())) {
return false;
}
LocalDate aujourdhui = LocalDate.now();
if (dateDebut != null && aujourdhui.isBefore(dateDebut)) {
return false;
}
if (dateFin != null && aujourdhui.isAfter(dateFin)) {
return false;
}
return true;
}
/** Callback JPA avant la persistance */
@PrePersist
protected void onCreate() {
super.onCreate();
if (dateDebut == null) {
dateDebut = LocalDate.now();
}
}
}

View File

@@ -191,6 +191,10 @@ public class Organisation extends BaseEntity {
@Builder.Default
private List<Membre> membres = new ArrayList<>();
@OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<Adresse> adresses = new ArrayList<>();
/** Méthode métier pour obtenir le nom complet avec sigle */
public String getNomComplet() {
if (nomCourt != null && !nomCourt.isEmpty()) {

View File

@@ -0,0 +1,90 @@
package dev.lions.unionflow.server.entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Entité Permission pour la gestion des permissions granulaires
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Entity
@Table(
name = "permissions",
indexes = {
@Index(name = "idx_permission_code", columnList = "code", unique = true),
@Index(name = "idx_permission_module", columnList = "module"),
@Index(name = "idx_permission_ressource", columnList = "ressource")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class Permission extends BaseEntity {
/** Code unique de la permission (format: MODULE > RESSOURCE > ACTION) */
@NotBlank
@Column(name = "code", unique = true, nullable = false, length = 100)
private String code;
/** Module (ex: ORGANISATION, MEMBRE, COTISATION) */
@NotBlank
@Column(name = "module", nullable = false, length = 50)
private String module;
/** Ressource (ex: MEMBRE, COTISATION, ADHESION) */
@NotBlank
@Column(name = "ressource", nullable = false, length = 50)
private String ressource;
/** Action (ex: CREATE, READ, UPDATE, DELETE, VALIDATE) */
@NotBlank
@Column(name = "action", nullable = false, length = 50)
private String action;
/** Libellé de la permission */
@Column(name = "libelle", length = 200)
private String libelle;
/** Description de la permission */
@Column(name = "description", length = 500)
private String description;
/** Rôles associés */
@OneToMany(mappedBy = "permission", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<RolePermission> roles = new ArrayList<>();
/** Méthode métier pour générer le code à partir des composants */
public static String genererCode(String module, String ressource, String action) {
return String.format("%s > %s > %s", module.toUpperCase(), ressource.toUpperCase(), action.toUpperCase());
}
/** Méthode métier pour vérifier si le code est valide */
public boolean isCodeValide() {
return code != null && code.contains(" > ") && code.split(" > ").length == 3;
}
/** Callback JPA avant la persistance */
@PrePersist
protected void onCreate() {
super.onCreate();
// Générer le code si non fourni
if (code == null || code.isEmpty()) {
if (module != null && ressource != null && action != null) {
code = genererCode(module, ressource, action);
}
}
}
}

View File

@@ -0,0 +1,105 @@
package dev.lions.unionflow.server.entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Entité Role pour la gestion des rôles dans le système
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Entity
@Table(
name = "roles",
indexes = {
@Index(name = "idx_role_code", columnList = "code", unique = true),
@Index(name = "idx_role_actif", columnList = "actif"),
@Index(name = "idx_role_niveau", columnList = "niveau_hierarchique")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class Role extends BaseEntity {
/** Code unique du rôle */
@NotBlank
@Column(name = "code", unique = true, nullable = false, length = 50)
private String code;
/** Libellé du rôle */
@NotBlank
@Column(name = "libelle", nullable = false, length = 100)
private String libelle;
/** Description du rôle */
@Column(name = "description", length = 500)
private String description;
/** Niveau hiérarchique (plus bas = plus prioritaire) */
@NotNull
@Builder.Default
@Column(name = "niveau_hierarchique", nullable = false)
private Integer niveauHierarchique = 100;
/** Type de rôle */
@Enumerated(EnumType.STRING)
@Column(name = "type_role", nullable = false, length = 50)
private TypeRole typeRole;
/** Organisation propriétaire (null pour rôles système) */
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organisation_id")
private Organisation organisation;
/** Permissions associées */
@OneToMany(mappedBy = "role", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<RolePermission> permissions = new ArrayList<>();
/** Énumération des types de rôle */
public enum TypeRole {
SYSTEME("Rôle Système"),
ORGANISATION("Rôle Organisation"),
PERSONNALISE("Rôle Personnalisé");
private final String libelle;
TypeRole(String libelle) {
this.libelle = libelle;
}
public String getLibelle() {
return libelle;
}
}
/** Méthode métier pour vérifier si c'est un rôle système */
public boolean isRoleSysteme() {
return TypeRole.SYSTEME.equals(typeRole);
}
/** Callback JPA avant la persistance */
@PrePersist
protected void onCreate() {
super.onCreate();
if (typeRole == null) {
typeRole = TypeRole.PERSONNALISE;
}
if (niveauHierarchique == null) {
niveauHierarchique = 100;
}
}
}

View File

@@ -0,0 +1,54 @@
package dev.lions.unionflow.server.entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* Table de liaison entre Role et Permission
* Permet à un rôle d'avoir plusieurs permissions
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@Entity
@Table(
name = "roles_permissions",
indexes = {
@Index(name = "idx_role_permission_role", columnList = "role_id"),
@Index(name = "idx_role_permission_permission", columnList = "permission_id")
},
uniqueConstraints = {
@UniqueConstraint(
name = "uk_role_permission",
columnNames = {"role_id", "permission_id"})
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class RolePermission extends BaseEntity {
/** Rôle */
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "role_id", nullable = false)
private Role role;
/** Permission */
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "permission_id", nullable = false)
private Permission permission;
/** Commentaire sur l'association */
@Column(name = "commentaire", length = 500)
private String commentaire;
}

View File

@@ -0,0 +1,101 @@
package dev.lions.unionflow.server.repository;
import dev.lions.unionflow.server.entity.Adresse;
import dev.lions.unionflow.server.entity.Adresse.TypeAdresse;
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
/**
* Repository pour l'entité Adresse
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@ApplicationScoped
public class AdresseRepository implements PanacheRepository<Adresse> {
/**
* Trouve toutes les adresses d'une organisation
*
* @param organisationId ID de l'organisation
* @return Liste des adresses
*/
public List<Adresse> findByOrganisationId(UUID organisationId) {
return find("organisation.id", organisationId).list();
}
/**
* Trouve l'adresse principale d'une organisation
*
* @param organisationId ID de l'organisation
* @return Adresse principale ou Optional.empty()
*/
public Optional<Adresse> findPrincipaleByOrganisationId(UUID organisationId) {
return find("organisation.id = ?1 AND principale = true", organisationId).firstResultOptional();
}
/**
* Trouve toutes les adresses d'un membre
*
* @param membreId ID du membre
* @return Liste des adresses
*/
public List<Adresse> findByMembreId(UUID membreId) {
return find("membre.id", membreId).list();
}
/**
* Trouve l'adresse principale d'un membre
*
* @param membreId ID du membre
* @return Adresse principale ou Optional.empty()
*/
public Optional<Adresse> findPrincipaleByMembreId(UUID membreId) {
return find("membre.id = ?1 AND principale = true", membreId).firstResultOptional();
}
/**
* Trouve l'adresse d'un événement
*
* @param evenementId ID de l'événement
* @return Adresse ou Optional.empty()
*/
public Optional<Adresse> findByEvenementId(UUID evenementId) {
return find("evenement.id", evenementId).firstResultOptional();
}
/**
* Trouve les adresses par type
*
* @param typeAdresse Type d'adresse
* @return Liste des adresses
*/
public List<Adresse> findByType(TypeAdresse typeAdresse) {
return find("typeAdresse", typeAdresse).list();
}
/**
* Trouve les adresses par ville
*
* @param ville Nom de la ville
* @return Liste des adresses
*/
public List<Adresse> findByVille(String ville) {
return find("LOWER(ville) = LOWER(?1)", ville).list();
}
/**
* Trouve les adresses par pays
*
* @param pays Nom du pays
* @return Liste des adresses
*/
public List<Adresse> findByPays(String pays) {
return find("LOWER(pays) = LOWER(?1)", pays).list();
}
}

View File

@@ -0,0 +1,66 @@
package dev.lions.unionflow.server.repository;
import dev.lions.unionflow.server.entity.MembreRole;
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
/**
* Repository pour l'entité MembreRole
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@ApplicationScoped
public class MembreRoleRepository implements PanacheRepository<MembreRole> {
/**
* Trouve tous les rôles d'un membre
*
* @param membreId ID du membre
* @return Liste des attributions de rôles
*/
public List<MembreRole> findByMembreId(UUID membreId) {
return find("membre.id = ?1 AND actif = true", membreId).list();
}
/**
* Trouve tous les rôles actifs d'un membre (dans la période valide)
*
* @param membreId ID du membre
* @return Liste des attributions de rôles actives
*/
public List<MembreRole> findActifsByMembreId(UUID membreId) {
LocalDate aujourdhui = LocalDate.now();
return find(
"membre.id = ?1 AND actif = true AND (dateDebut IS NULL OR dateDebut <= ?2) AND (dateFin IS NULL OR dateFin >= ?2)",
membreId,
aujourdhui)
.list();
}
/**
* Trouve tous les membres ayant un rôle spécifique
*
* @param roleId ID du rôle
* @return Liste des attributions de rôles
*/
public List<MembreRole> findByRoleId(UUID roleId) {
return find("role.id = ?1 AND actif = true", roleId).list();
}
/**
* Trouve une attribution spécifique membre-role
*
* @param membreId ID du membre
* @param roleId ID du rôle
* @return Attribution ou null
*/
public MembreRole findByMembreAndRole(UUID membreId, UUID roleId) {
return find("membre.id = ?1 AND role.id = ?2", membreId, roleId).firstResult();
}
}

View File

@@ -0,0 +1,73 @@
package dev.lions.unionflow.server.repository;
import dev.lions.unionflow.server.entity.Permission;
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.List;
import java.util.Optional;
/**
* Repository pour l'entité Permission
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@ApplicationScoped
public class PermissionRepository implements PanacheRepository<Permission> {
/**
* Trouve une permission par son code
*
* @param code Code de la permission
* @return Permission ou Optional.empty()
*/
public Optional<Permission> findByCode(String code) {
return find("code", code).firstResultOptional();
}
/**
* Trouve les permissions par module
*
* @param module Nom du module
* @return Liste des permissions
*/
public List<Permission> findByModule(String module) {
return find("LOWER(module) = LOWER(?1) AND actif = true", module).list();
}
/**
* Trouve les permissions par ressource
*
* @param ressource Nom de la ressource
* @return Liste des permissions
*/
public List<Permission> findByRessource(String ressource) {
return find("LOWER(ressource) = LOWER(?1) AND actif = true", ressource).list();
}
/**
* Trouve les permissions par module et ressource
*
* @param module Nom du module
* @param ressource Nom de la ressource
* @return Liste des permissions
*/
public List<Permission> findByModuleAndRessource(String module, String ressource) {
return find(
"LOWER(module) = LOWER(?1) AND LOWER(ressource) = LOWER(?2) AND actif = true",
module,
ressource)
.list();
}
/**
* Trouve toutes les permissions actives
*
* @return Liste des permissions actives
*/
public List<Permission> findAllActives() {
return find("actif = true").order("module ASC, ressource ASC, action ASC").list();
}
}

View File

@@ -0,0 +1,50 @@
package dev.lions.unionflow.server.repository;
import dev.lions.unionflow.server.entity.RolePermission;
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.List;
import java.util.UUID;
/**
* Repository pour l'entité RolePermission
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@ApplicationScoped
public class RolePermissionRepository implements PanacheRepository<RolePermission> {
/**
* Trouve toutes les permissions d'un rôle
*
* @param roleId ID du rôle
* @return Liste des associations rôle-permission
*/
public List<RolePermission> findByRoleId(UUID roleId) {
return find("role.id = ?1 AND actif = true", roleId).list();
}
/**
* Trouve tous les rôles ayant une permission spécifique
*
* @param permissionId ID de la permission
* @return Liste des associations rôle-permission
*/
public List<RolePermission> findByPermissionId(UUID permissionId) {
return find("permission.id = ?1 AND actif = true", permissionId).list();
}
/**
* Trouve une association spécifique rôle-permission
*
* @param roleId ID du rôle
* @param permissionId ID de la permission
* @return Association ou null
*/
public RolePermission findByRoleAndPermission(UUID roleId, UUID permissionId) {
return find("role.id = ?1 AND permission.id = ?2", roleId, permissionId).firstResult();
}
}

View File

@@ -0,0 +1,75 @@
package dev.lions.unionflow.server.repository;
import dev.lions.unionflow.server.entity.Role;
import dev.lions.unionflow.server.entity.Role.TypeRole;
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
/**
* Repository pour l'entité Role
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@ApplicationScoped
public class RoleRepository implements PanacheRepository<Role> {
/**
* Trouve un rôle par son code
*
* @param code Code du rôle
* @return Rôle ou Optional.empty()
*/
public Optional<Role> findByCode(String code) {
return find("code", code).firstResultOptional();
}
/**
* Trouve tous les rôles système
*
* @return Liste des rôles système
*/
public List<Role> findRolesSysteme() {
return find("typeRole = ?1 AND actif = true", TypeRole.SYSTEME)
.order("niveauHierarchique ASC")
.list();
}
/**
* Trouve tous les rôles d'une organisation
*
* @param organisationId ID de l'organisation
* @return Liste des rôles
*/
public List<Role> findByOrganisationId(UUID organisationId) {
return find("organisation.id = ?1 AND actif = true", organisationId)
.order("niveauHierarchique ASC")
.list();
}
/**
* Trouve tous les rôles actifs
*
* @return Liste des rôles actifs
*/
public List<Role> findAllActifs() {
return find("actif = true").order("niveauHierarchique ASC").list();
}
/**
* Trouve les rôles par type
*
* @param typeRole Type de rôle
* @return Liste des rôles
*/
public List<Role> findByType(TypeRole typeRole) {
return find("typeRole = ?1 AND actif = true", typeRole)
.order("niveauHierarchique ASC")
.list();
}
}

View File

@@ -0,0 +1,358 @@
package dev.lions.unionflow.server.service;
import dev.lions.unionflow.server.api.dto.adresse.AdresseDTO;
import dev.lions.unionflow.server.api.enums.adresse.TypeAdresse;
import dev.lions.unionflow.server.entity.Adresse;
import dev.lions.unionflow.server.entity.Evenement;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.AdresseRepository;
import dev.lions.unionflow.server.repository.EvenementRepository;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.NotFoundException;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import org.jboss.logging.Logger;
/**
* Service métier pour la gestion des adresses
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@ApplicationScoped
public class AdresseService {
private static final Logger LOG = Logger.getLogger(AdresseService.class);
@Inject AdresseRepository adresseRepository;
@Inject OrganisationRepository organisationRepository;
@Inject MembreRepository membreRepository;
@Inject EvenementRepository evenementRepository;
/**
* Crée une nouvelle adresse
*
* @param adresseDTO DTO de l'adresse à créer
* @return DTO de l'adresse créée
*/
@Transactional
public AdresseDTO creerAdresse(AdresseDTO adresseDTO) {
LOG.infof("Création d'une nouvelle adresse de type: %s", adresseDTO.getTypeAdresse());
Adresse adresse = convertToEntity(adresseDTO);
// Gestion de l'adresse principale
if (Boolean.TRUE.equals(adresseDTO.getPrincipale())) {
desactiverAutresPrincipales(adresseDTO);
}
adresseRepository.persist(adresse);
LOG.infof("Adresse créée avec succès: ID=%s", adresse.getId());
return convertToDTO(adresse);
}
/**
* Met à jour une adresse existante
*
* @param id ID de l'adresse
* @param adresseDTO DTO avec les nouvelles données
* @return DTO de l'adresse mise à jour
*/
@Transactional
public AdresseDTO mettreAJourAdresse(UUID id, AdresseDTO adresseDTO) {
LOG.infof("Mise à jour de l'adresse ID: %s", id);
Adresse adresse =
adresseRepository
.findByIdOptional(id)
.orElseThrow(() -> new NotFoundException("Adresse non trouvée avec l'ID: " + id));
// Mise à jour des champs
updateFromDTO(adresse, adresseDTO);
// Gestion de l'adresse principale
if (Boolean.TRUE.equals(adresseDTO.getPrincipale())) {
desactiverAutresPrincipales(adresseDTO);
}
adresseRepository.persist(adresse);
LOG.infof("Adresse mise à jour avec succès: ID=%s", id);
return convertToDTO(adresse);
}
/**
* Supprime une adresse
*
* @param id ID de l'adresse
*/
@Transactional
public void supprimerAdresse(UUID id) {
LOG.infof("Suppression de l'adresse ID: %s", id);
Adresse adresse =
adresseRepository
.findByIdOptional(id)
.orElseThrow(() -> new NotFoundException("Adresse non trouvée avec l'ID: " + id));
adresseRepository.delete(adresse);
LOG.infof("Adresse supprimée avec succès: ID=%s", id);
}
/**
* Trouve une adresse par son ID
*
* @param id ID de l'adresse
* @return DTO de l'adresse
*/
public AdresseDTO trouverParId(UUID id) {
return adresseRepository
.findByIdOptional(id)
.map(this::convertToDTO)
.orElseThrow(() -> new NotFoundException("Adresse non trouvée avec l'ID: " + id));
}
/**
* Trouve toutes les adresses d'une organisation
*
* @param organisationId ID de l'organisation
* @return Liste des adresses
*/
public List<AdresseDTO> trouverParOrganisation(UUID organisationId) {
return adresseRepository.findByOrganisationId(organisationId).stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
/**
* Trouve toutes les adresses d'un membre
*
* @param membreId ID du membre
* @return Liste des adresses
*/
public List<AdresseDTO> trouverParMembre(UUID membreId) {
return adresseRepository.findByMembreId(membreId).stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
/**
* Trouve l'adresse d'un événement
*
* @param evenementId ID de l'événement
* @return DTO de l'adresse ou null
*/
public AdresseDTO trouverParEvenement(UUID evenementId) {
return adresseRepository
.findByEvenementId(evenementId)
.map(this::convertToDTO)
.orElse(null);
}
/**
* Trouve l'adresse principale d'une organisation
*
* @param organisationId ID de l'organisation
* @return DTO de l'adresse principale ou null
*/
public AdresseDTO trouverPrincipaleParOrganisation(UUID organisationId) {
return adresseRepository
.findPrincipaleByOrganisationId(organisationId)
.map(this::convertToDTO)
.orElse(null);
}
/**
* Trouve l'adresse principale d'un membre
*
* @param membreId ID du membre
* @return DTO de l'adresse principale ou null
*/
public AdresseDTO trouverPrincipaleParMembre(UUID membreId) {
return adresseRepository
.findPrincipaleByMembreId(membreId)
.map(this::convertToDTO)
.orElse(null);
}
// ========================================
// MÉTHODES PRIVÉES
// ========================================
/** Désactive les autres adresses principales pour la même entité */
private void desactiverAutresPrincipales(AdresseDTO adresseDTO) {
List<Adresse> autresPrincipales;
if (adresseDTO.getOrganisationId() != null) {
autresPrincipales =
adresseRepository
.find("organisation.id = ?1 AND principale = true", adresseDTO.getOrganisationId())
.list();
} else if (adresseDTO.getMembreId() != null) {
autresPrincipales =
adresseRepository
.find("membre.id = ?1 AND principale = true", adresseDTO.getMembreId())
.list();
} else {
return; // Pas d'entité associée
}
autresPrincipales.forEach(adr -> adr.setPrincipale(false));
}
/** Convertit une entité en DTO */
private AdresseDTO convertToDTO(Adresse adresse) {
if (adresse == null) {
return null;
}
AdresseDTO dto = new AdresseDTO();
dto.setId(adresse.getId());
dto.setTypeAdresse(convertTypeAdresse(adresse.getTypeAdresse()));
dto.setAdresse(adresse.getAdresse());
dto.setComplementAdresse(adresse.getComplementAdresse());
dto.setCodePostal(adresse.getCodePostal());
dto.setVille(adresse.getVille());
dto.setRegion(adresse.getRegion());
dto.setPays(adresse.getPays());
dto.setLatitude(adresse.getLatitude());
dto.setLongitude(adresse.getLongitude());
dto.setPrincipale(adresse.getPrincipale());
dto.setLibelle(adresse.getLibelle());
dto.setNotes(adresse.getNotes());
if (adresse.getOrganisation() != null) {
dto.setOrganisationId(adresse.getOrganisation().getId());
}
if (adresse.getMembre() != null) {
dto.setMembreId(adresse.getMembre().getId());
}
if (adresse.getEvenement() != null) {
dto.setEvenementId(adresse.getEvenement().getId());
}
dto.setAdresseComplete(adresse.getAdresseComplete());
dto.setDateCreation(adresse.getDateCreation());
dto.setDateModification(adresse.getDateModification());
dto.setActif(adresse.getActif());
return dto;
}
/** Convertit un DTO en entité */
private Adresse convertToEntity(AdresseDTO dto) {
if (dto == null) {
return null;
}
Adresse adresse = new Adresse();
adresse.setTypeAdresse(convertTypeAdresse(dto.getTypeAdresse()));
adresse.setAdresse(dto.getAdresse());
adresse.setComplementAdresse(dto.getComplementAdresse());
adresse.setCodePostal(dto.getCodePostal());
adresse.setVille(dto.getVille());
adresse.setRegion(dto.getRegion());
adresse.setPays(dto.getPays());
adresse.setLatitude(dto.getLatitude());
adresse.setLongitude(dto.getLongitude());
adresse.setPrincipale(dto.getPrincipale() != null ? dto.getPrincipale() : false);
adresse.setLibelle(dto.getLibelle());
adresse.setNotes(dto.getNotes());
// Relations
if (dto.getOrganisationId() != null) {
Organisation org =
organisationRepository
.findByIdOptional(dto.getOrganisationId())
.orElseThrow(
() ->
new NotFoundException(
"Organisation non trouvée avec l'ID: " + dto.getOrganisationId()));
adresse.setOrganisation(org);
}
if (dto.getMembreId() != null) {
Membre membre =
membreRepository
.findByIdOptional(dto.getMembreId())
.orElseThrow(
() -> new NotFoundException("Membre non trouvé avec l'ID: " + dto.getMembreId()));
adresse.setMembre(membre);
}
if (dto.getEvenementId() != null) {
Evenement evenement =
evenementRepository
.findByIdOptional(dto.getEvenementId())
.orElseThrow(
() ->
new NotFoundException(
"Événement non trouvé avec l'ID: " + dto.getEvenementId()));
adresse.setEvenement(evenement);
}
return adresse;
}
/** Met à jour une entité à partir d'un DTO */
private void updateFromDTO(Adresse adresse, AdresseDTO dto) {
if (dto.getTypeAdresse() != null) {
adresse.setTypeAdresse(convertTypeAdresse(dto.getTypeAdresse()));
}
if (dto.getAdresse() != null) {
adresse.setAdresse(dto.getAdresse());
}
if (dto.getComplementAdresse() != null) {
adresse.setComplementAdresse(dto.getComplementAdresse());
}
if (dto.getCodePostal() != null) {
adresse.setCodePostal(dto.getCodePostal());
}
if (dto.getVille() != null) {
adresse.setVille(dto.getVille());
}
if (dto.getRegion() != null) {
adresse.setRegion(dto.getRegion());
}
if (dto.getPays() != null) {
adresse.setPays(dto.getPays());
}
if (dto.getLatitude() != null) {
adresse.setLatitude(dto.getLatitude());
}
if (dto.getLongitude() != null) {
adresse.setLongitude(dto.getLongitude());
}
if (dto.getPrincipale() != null) {
adresse.setPrincipale(dto.getPrincipale());
}
if (dto.getLibelle() != null) {
adresse.setLibelle(dto.getLibelle());
}
if (dto.getNotes() != null) {
adresse.setNotes(dto.getNotes());
}
}
/** Convertit TypeAdresse (entité) vers TypeAdresse (DTO) - même enum, pas de conversion nécessaire */
private TypeAdresse convertTypeAdresse(TypeAdresse type) {
return type; // Même enum, pas de conversion nécessaire
}
/** Convertit TypeAdresse (DTO) vers TypeAdresse (entité) - même enum, pas de conversion nécessaire */
private TypeAdresse convertTypeAdresse(TypeAdresse type) {
return type != null ? type : TypeAdresse.AUTRE; // Même enum, valeur par défaut si null
}
}

View File

@@ -0,0 +1,165 @@
package dev.lions.unionflow.server.service;
import dev.lions.unionflow.server.entity.Permission;
import dev.lions.unionflow.server.repository.PermissionRepository;
import dev.lions.unionflow.server.service.KeycloakService;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.NotFoundException;
import java.util.List;
import java.util.UUID;
import org.jboss.logging.Logger;
/**
* Service métier pour la gestion des permissions
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@ApplicationScoped
public class PermissionService {
private static final Logger LOG = Logger.getLogger(PermissionService.class);
@Inject PermissionRepository permissionRepository;
@Inject KeycloakService keycloakService;
/**
* Crée une nouvelle permission
*
* @param permission Permission à créer
* @return Permission créée
*/
@Transactional
public Permission creerPermission(Permission permission) {
LOG.infof("Création d'une nouvelle permission: %s", permission.getCode());
// Vérifier l'unicité du code
if (permissionRepository.findByCode(permission.getCode()).isPresent()) {
throw new IllegalArgumentException(
"Une permission avec ce code existe déjà: " + permission.getCode());
}
// Générer le code si non fourni
if (permission.getCode() == null || permission.getCode().isEmpty()) {
permission.setCode(
Permission.genererCode(
permission.getModule(), permission.getRessource(), permission.getAction()));
}
// Métadonnées
permission.setCreePar(keycloakService.getCurrentUserEmail());
permissionRepository.persist(permission);
LOG.infof(
"Permission créée avec succès: ID=%s, Code=%s",
permission.getId(), permission.getCode());
return permission;
}
/**
* Met à jour une permission existante
*
* @param id ID de la permission
* @param permissionModifiee Permission avec les modifications
* @return Permission mise à jour
*/
@Transactional
public Permission mettreAJourPermission(UUID id, Permission permissionModifiee) {
LOG.infof("Mise à jour de la permission ID: %s", id);
Permission permission =
permissionRepository
.findByIdOptional(id)
.orElseThrow(() -> new NotFoundException("Permission non trouvée avec l'ID: " + id));
// Mise à jour
permission.setCode(permissionModifiee.getCode());
permission.setModule(permissionModifiee.getModule());
permission.setRessource(permissionModifiee.getRessource());
permission.setAction(permissionModifiee.getAction());
permission.setLibelle(permissionModifiee.getLibelle());
permission.setDescription(permissionModifiee.getDescription());
permission.setModifiePar(keycloakService.getCurrentUserEmail());
permissionRepository.persist(permission);
LOG.infof("Permission mise à jour avec succès: ID=%s", id);
return permission;
}
/**
* Trouve une permission par son ID
*
* @param id ID de la permission
* @return Permission ou null
*/
public Permission trouverParId(UUID id) {
return permissionRepository.findByIdOptional(id).orElse(null);
}
/**
* Trouve une permission par son code
*
* @param code Code de la permission
* @return Permission ou null
*/
public Permission trouverParCode(String code) {
return permissionRepository.findByCode(code).orElse(null);
}
/**
* Liste les permissions par module
*
* @param module Nom du module
* @return Liste des permissions
*/
public List<Permission> listerParModule(String module) {
return permissionRepository.findByModule(module);
}
/**
* Liste les permissions par ressource
*
* @param ressource Nom de la ressource
* @return Liste des permissions
*/
public List<Permission> listerParRessource(String ressource) {
return permissionRepository.findByRessource(ressource);
}
/**
* Liste toutes les permissions actives
*
* @return Liste des permissions actives
*/
public List<Permission> listerToutesActives() {
return permissionRepository.findAllActives();
}
/**
* Supprime (désactive) une permission
*
* @param id ID de la permission
*/
@Transactional
public void supprimerPermission(UUID id) {
LOG.infof("Suppression de la permission ID: %s", id);
Permission permission =
permissionRepository
.findByIdOptional(id)
.orElseThrow(() -> new NotFoundException("Permission non trouvée avec l'ID: " + id));
permission.setActif(false);
permission.setModifiePar(keycloakService.getCurrentUserEmail());
permissionRepository.persist(permission);
LOG.infof("Permission supprimée avec succès: ID=%s", id);
}
}

View File

@@ -0,0 +1,171 @@
package dev.lions.unionflow.server.service;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.entity.Role;
import dev.lions.unionflow.server.entity.Role.TypeRole;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import dev.lions.unionflow.server.repository.RoleRepository;
import dev.lions.unionflow.server.service.KeycloakService;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.NotFoundException;
import java.util.List;
import java.util.UUID;
import org.jboss.logging.Logger;
/**
* Service métier pour la gestion des rôles
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@ApplicationScoped
public class RoleService {
private static final Logger LOG = Logger.getLogger(RoleService.class);
@Inject RoleRepository roleRepository;
@Inject OrganisationRepository organisationRepository;
@Inject KeycloakService keycloakService;
/**
* Crée un nouveau rôle
*
* @param role Rôle à créer
* @return Rôle créé
*/
@Transactional
public Role creerRole(Role role) {
LOG.infof("Création d'un nouveau rôle: %s", role.getCode());
// Vérifier l'unicité du code
if (roleRepository.findByCode(role.getCode()).isPresent()) {
throw new IllegalArgumentException("Un rôle avec ce code existe déjà: " + role.getCode());
}
// Métadonnées
role.setCreePar(keycloakService.getCurrentUserEmail());
roleRepository.persist(role);
LOG.infof("Rôle créé avec succès: ID=%s, Code=%s", role.getId(), role.getCode());
return role;
}
/**
* Met à jour un rôle existant
*
* @param id ID du rôle
* @param roleModifie Rôle avec les modifications
* @return Rôle mis à jour
*/
@Transactional
public Role mettreAJourRole(UUID id, Role roleModifie) {
LOG.infof("Mise à jour du rôle ID: %s", id);
Role role =
roleRepository
.findByIdOptional(id)
.orElseThrow(() -> new NotFoundException("Rôle non trouvé avec l'ID: " + id));
// Vérifier l'unicité du code si modifié
if (!role.getCode().equals(roleModifie.getCode())) {
if (roleRepository.findByCode(roleModifie.getCode()).isPresent()) {
throw new IllegalArgumentException("Un rôle avec ce code existe déjà: " + roleModifie.getCode());
}
}
// Mise à jour
role.setCode(roleModifie.getCode());
role.setLibelle(roleModifie.getLibelle());
role.setDescription(roleModifie.getDescription());
role.setNiveauHierarchique(roleModifie.getNiveauHierarchique());
role.setTypeRole(roleModifie.getTypeRole());
role.setOrganisation(roleModifie.getOrganisation());
role.setModifiePar(keycloakService.getCurrentUserEmail());
roleRepository.persist(role);
LOG.infof("Rôle mis à jour avec succès: ID=%s", id);
return role;
}
/**
* Trouve un rôle par son ID
*
* @param id ID du rôle
* @return Rôle ou null
*/
public Role trouverParId(UUID id) {
return roleRepository.findByIdOptional(id).orElse(null);
}
/**
* Trouve un rôle par son code
*
* @param code Code du rôle
* @return Rôle ou null
*/
public Role trouverParCode(String code) {
return roleRepository.findByCode(code).orElse(null);
}
/**
* Liste tous les rôles système
*
* @return Liste des rôles système
*/
public List<Role> listerRolesSysteme() {
return roleRepository.findRolesSysteme();
}
/**
* Liste tous les rôles d'une organisation
*
* @param organisationId ID de l'organisation
* @return Liste des rôles
*/
public List<Role> listerParOrganisation(UUID organisationId) {
return roleRepository.findByOrganisationId(organisationId);
}
/**
* Liste tous les rôles actifs
*
* @return Liste des rôles actifs
*/
public List<Role> listerTousActifs() {
return roleRepository.findAllActifs();
}
/**
* Supprime (désactive) un rôle
*
* @param id ID du rôle
*/
@Transactional
public void supprimerRole(UUID id) {
LOG.infof("Suppression du rôle ID: %s", id);
Role role =
roleRepository
.findByIdOptional(id)
.orElseThrow(() -> new NotFoundException("Rôle non trouvé avec l'ID: " + id));
// Vérifier si c'est un rôle système
if (role.isRoleSysteme()) {
throw new IllegalStateException("Impossible de supprimer un rôle système");
}
role.setActif(false);
role.setModifiePar(keycloakService.getCurrentUserEmail());
roleRepository.persist(role);
LOG.infof("Rôle supprimé avec succès: ID=%s", id);
}
}