Server-Api OK

This commit is contained in:
DahoudG
2025-09-10 22:02:16 +00:00
parent b2a23bdf89
commit bf79fa4e04
36 changed files with 8934 additions and 0 deletions

View File

@@ -65,6 +65,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-h2</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-flyway</artifactId>
@@ -98,6 +102,14 @@
<artifactId>quarkus-hibernate-validator</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<!-- Tests -->
<dependency>
<groupId>io.quarkus</groupId>

View File

@@ -0,0 +1,96 @@
package dev.lions.unionflow.server.entity;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.*;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* Entité Membre avec Lombok
*/
@Entity
@Table(name = "membres", indexes = {
@Index(name = "idx_membre_email", columnList = "email", unique = true),
@Index(name = "idx_membre_numero", columnList = "numeroMembre", unique = true),
@Index(name = "idx_membre_actif", columnList = "actif")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Membre extends PanacheEntity {
@NotBlank
@Column(name = "numero_membre", unique = true, nullable = false, length = 20)
private String numeroMembre;
@NotBlank
@Column(name = "prenom", nullable = false, length = 100)
private String prenom;
@NotBlank
@Column(name = "nom", nullable = false, length = 100)
private String nom;
@Email
@NotBlank
@Column(name = "email", unique = true, nullable = false, length = 255)
private String email;
@Column(name = "telephone", length = 20)
private String telephone;
@NotNull
@Column(name = "date_naissance", nullable = false)
private LocalDate dateNaissance;
@NotNull
@Column(name = "date_adhesion", nullable = false)
private LocalDate dateAdhesion;
@Builder.Default
@Column(name = "actif", nullable = false)
private Boolean actif = true;
@Builder.Default
@Column(name = "date_creation", nullable = false)
private LocalDateTime dateCreation = LocalDateTime.now();
@Column(name = "date_modification")
private LocalDateTime dateModification;
/**
* Méthode métier pour obtenir le nom complet
*/
public String getNomComplet() {
return prenom + " " + nom;
}
/**
* Méthode métier pour vérifier si le membre est majeur
*/
public boolean isMajeur() {
return dateNaissance.isBefore(LocalDate.now().minusYears(18));
}
/**
* Méthode métier pour calculer l'âge
*/
public int getAge() {
return LocalDate.now().getYear() - dateNaissance.getYear();
}
@PreUpdate
public void preUpdate() {
this.dateModification = LocalDateTime.now();
}
}

View File

@@ -0,0 +1,51 @@
package dev.lions.unionflow.server.repository;
import dev.lions.unionflow.server.entity.Membre;
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.List;
import java.util.Optional;
/**
* Repository pour l'entité Membre
*/
@ApplicationScoped
public class MembreRepository implements PanacheRepository<Membre> {
/**
* Trouve un membre par son email
*/
public Optional<Membre> findByEmail(String email) {
return find("email", email).firstResultOptional();
}
/**
* Trouve un membre par son numéro
*/
public Optional<Membre> findByNumeroMembre(String numeroMembre) {
return find("numeroMembre", numeroMembre).firstResultOptional();
}
/**
* Trouve tous les membres actifs
*/
public List<Membre> findAllActifs() {
return find("actif", true).list();
}
/**
* Compte le nombre de membres actifs
*/
public long countActifs() {
return count("actif", true);
}
/**
* Trouve les membres par nom ou prénom (recherche partielle)
*/
public List<Membre> findByNomOrPrenom(String recherche) {
return find("lower(nom) like ?1 or lower(prenom) like ?1",
"%" + recherche.toLowerCase() + "%").list();
}
}

View File

@@ -0,0 +1,132 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.service.MembreService;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.Logger;
import java.util.List;
import java.util.Map;
/**
* Resource REST pour la gestion des membres
*/
@Path("/api/membres")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@ApplicationScoped
@Tag(name = "Membres", description = "API de gestion des membres")
public class MembreResource {
private static final Logger LOG = Logger.getLogger(MembreResource.class);
@Inject
MembreService membreService;
@GET
@Operation(summary = "Lister tous les membres actifs")
@APIResponse(responseCode = "200", description = "Liste des membres actifs")
public Response listerMembres() {
LOG.info("Récupération de la liste des membres actifs");
List<Membre> membres = membreService.listerMembresActifs();
return Response.ok(membres).build();
}
@GET
@Path("/{id}")
@Operation(summary = "Récupérer un membre par son ID")
@APIResponse(responseCode = "200", description = "Membre trouvé")
@APIResponse(responseCode = "404", description = "Membre non trouvé")
public Response obtenirMembre(@Parameter(description = "ID du membre") @PathParam("id") Long id) {
LOG.infof("Récupération du membre ID: %d", id);
return membreService.trouverParId(id)
.map(membre -> Response.ok(membre).build())
.orElse(Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("message", "Membre non trouvé")).build());
}
@POST
@Operation(summary = "Créer un nouveau membre")
@APIResponse(responseCode = "201", description = "Membre créé avec succès")
@APIResponse(responseCode = "400", description = "Données invalides")
public Response creerMembre(@Valid Membre membre) {
LOG.infof("Création d'un nouveau membre: %s", membre.getEmail());
try {
Membre nouveauMembre = membreService.creerMembre(membre);
return Response.status(Response.Status.CREATED).entity(nouveauMembre).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("message", e.getMessage())).build();
}
}
@PUT
@Path("/{id}")
@Operation(summary = "Mettre à jour un membre existant")
@APIResponse(responseCode = "200", description = "Membre mis à jour avec succès")
@APIResponse(responseCode = "404", description = "Membre non trouvé")
@APIResponse(responseCode = "400", description = "Données invalides")
public Response mettreAJourMembre(@Parameter(description = "ID du membre") @PathParam("id") Long id,
@Valid Membre membre) {
LOG.infof("Mise à jour du membre ID: %d", id);
try {
Membre membreMisAJour = membreService.mettreAJourMembre(id, membre);
return Response.ok(membreMisAJour).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("message", e.getMessage())).build();
}
}
@DELETE
@Path("/{id}")
@Operation(summary = "Désactiver un membre")
@APIResponse(responseCode = "204", description = "Membre désactivé avec succès")
@APIResponse(responseCode = "404", description = "Membre non trouvé")
public Response desactiverMembre(@Parameter(description = "ID du membre") @PathParam("id") Long id) {
LOG.infof("Désactivation du membre ID: %d", id);
try {
membreService.desactiverMembre(id);
return Response.noContent().build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("message", e.getMessage())).build();
}
}
@GET
@Path("/recherche")
@Operation(summary = "Rechercher des membres par nom ou prénom")
@APIResponse(responseCode = "200", description = "Résultats de la recherche")
public Response rechercherMembres(@Parameter(description = "Terme de recherche") @QueryParam("q") String recherche) {
LOG.infof("Recherche de membres avec le terme: %s", recherche);
if (recherche == null || recherche.trim().isEmpty()) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("message", "Le terme de recherche est requis")).build();
}
List<Membre> membres = membreService.rechercherMembres(recherche.trim());
return Response.ok(membres).build();
}
@GET
@Path("/stats")
@Operation(summary = "Obtenir les statistiques des membres")
@APIResponse(responseCode = "200", description = "Statistiques des membres")
public Response obtenirStatistiques() {
LOG.info("Récupération des statistiques des membres");
long nombreMembresActifs = membreService.compterMembresActifs();
return Response.ok(Map.of(
"nombreMembresActifs", nombreMembresActifs,
"timestamp", java.time.LocalDateTime.now()
)).build();
}
}

View File

@@ -0,0 +1,143 @@
package dev.lions.unionflow.server.service;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.repository.MembreRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import org.jboss.logging.Logger;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
/**
* Service métier pour les membres
*/
@ApplicationScoped
public class MembreService {
private static final Logger LOG = Logger.getLogger(MembreService.class);
@Inject
MembreRepository membreRepository;
/**
* Crée un nouveau membre
*/
@Transactional
public Membre creerMembre(Membre membre) {
LOG.infof("Création d'un nouveau membre: %s", membre.getEmail());
// Générer un numéro de membre unique
if (membre.getNumeroMembre() == null || membre.getNumeroMembre().isEmpty()) {
membre.setNumeroMembre(genererNumeroMembre());
}
// Vérifier l'unicité de l'email
if (membreRepository.findByEmail(membre.getEmail()).isPresent()) {
throw new IllegalArgumentException("Un membre avec cet email existe déjà");
}
// Vérifier l'unicité du numéro de membre
if (membreRepository.findByNumeroMembre(membre.getNumeroMembre()).isPresent()) {
throw new IllegalArgumentException("Un membre avec ce numéro existe déjà");
}
membreRepository.persist(membre);
LOG.infof("Membre créé avec succès: %s (ID: %d)", membre.getNomComplet(), membre.id);
return membre;
}
/**
* Met à jour un membre existant
*/
@Transactional
public Membre mettreAJourMembre(Long id, Membre membreModifie) {
LOG.infof("Mise à jour du membre ID: %d", id);
Membre membre = membreRepository.findById(id);
if (membre == null) {
throw new IllegalArgumentException("Membre non trouvé avec l'ID: " + id);
}
// Vérifier l'unicité de l'email si modifié
if (!membre.getEmail().equals(membreModifie.getEmail())) {
if (membreRepository.findByEmail(membreModifie.getEmail()).isPresent()) {
throw new IllegalArgumentException("Un membre avec cet email existe déjà");
}
}
// Mettre à jour les champs
membre.setPrenom(membreModifie.getPrenom());
membre.setNom(membreModifie.getNom());
membre.setEmail(membreModifie.getEmail());
membre.setTelephone(membreModifie.getTelephone());
membre.setDateNaissance(membreModifie.getDateNaissance());
membre.setActif(membreModifie.getActif());
LOG.infof("Membre mis à jour avec succès: %s", membre.getNomComplet());
return membre;
}
/**
* Trouve un membre par son ID
*/
public Optional<Membre> trouverParId(Long id) {
return Optional.ofNullable(membreRepository.findById(id));
}
/**
* Trouve un membre par son email
*/
public Optional<Membre> trouverParEmail(String email) {
return membreRepository.findByEmail(email);
}
/**
* Liste tous les membres actifs
*/
public List<Membre> listerMembresActifs() {
return membreRepository.findAllActifs();
}
/**
* Recherche des membres par nom ou prénom
*/
public List<Membre> rechercherMembres(String recherche) {
return membreRepository.findByNomOrPrenom(recherche);
}
/**
* Désactive un membre
*/
@Transactional
public void desactiverMembre(Long id) {
LOG.infof("Désactivation du membre ID: %d", id);
Membre membre = membreRepository.findById(id);
if (membre == null) {
throw new IllegalArgumentException("Membre non trouvé avec l'ID: " + id);
}
membre.setActif(false);
LOG.infof("Membre désactivé: %s", membre.getNomComplet());
}
/**
* Génère un numéro de membre unique
*/
private String genererNumeroMembre() {
String prefix = "UF" + LocalDate.now().getYear();
String suffix = UUID.randomUUID().toString().substring(0, 8).toUpperCase();
return prefix + "-" + suffix;
}
/**
* Compte le nombre total de membres actifs
*/
public long compterMembresActifs() {
return membreRepository.countActifs();
}
}

View File

@@ -0,0 +1,24 @@
# Configuration de base pour UnionFlow Server
quarkus.application.name=unionflow-server
quarkus.http.port=8080
# Configuration de développement
%dev.quarkus.log.level=INFO
%dev.quarkus.log.console.enable=true
# Configuration de base de données (PostgreSQL)
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=unionflow
quarkus.datasource.password=unionflow
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/unionflow_dev
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=false
# Configuration pour le développement sans base de données externe
%dev.quarkus.datasource.db-kind=h2
%dev.quarkus.datasource.jdbc.url=jdbc:h2:mem:unionflow;DB_CLOSE_DELAY=-1
%dev.quarkus.hibernate-orm.database.generation=drop-and-create
# Configuration Swagger UI
quarkus.swagger-ui.always-include=true
quarkus.swagger-ui.path=/swagger-ui

View File

@@ -0,0 +1,223 @@
package dev.lions.unionflow.server.entity;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.time.LocalDate;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.*;
/**
* Tests unitaires pour valider les fonctionnalités Lombok de l'entité Membre
*/
@DisplayName("Tests Lombok - Entité Membre")
class MembreLombokTest {
private String numeroMembre;
private String prenom;
private String nom;
private String email;
private String telephone;
private LocalDate dateNaissance;
private LocalDate dateAdhesion;
@BeforeEach
void setUp() {
numeroMembre = "UF2025-ABC123";
prenom = "Jean";
nom = "Dupont";
email = "jean.dupont@example.com";
telephone = "+33123456789";
dateNaissance = LocalDate.of(1990, 5, 15);
dateAdhesion = LocalDate.of(2025, 1, 1);
}
@Test
@DisplayName("Test des annotations Lombok - Builder pattern")
void testLombokBuilder() {
// Given & When
Membre membre = Membre.builder()
.numeroMembre(numeroMembre)
.prenom(prenom)
.nom(nom)
.email(email)
.telephone(telephone)
.dateNaissance(dateNaissance)
.dateAdhesion(dateAdhesion)
.actif(true)
.build();
// Then
assertNotNull(membre);
assertEquals(numeroMembre, membre.getNumeroMembre());
assertEquals(prenom, membre.getPrenom());
assertEquals(nom, membre.getNom());
assertEquals(email, membre.getEmail());
assertEquals(telephone, membre.getTelephone());
assertEquals(dateNaissance, membre.getDateNaissance());
assertEquals(dateAdhesion, membre.getDateAdhesion());
assertTrue(membre.getActif());
assertNotNull(membre.getDateCreation());
}
@Test
@DisplayName("Test des annotations Lombok - Constructeur sans arguments")
void testLombokNoArgsConstructor() {
// Given & When
Membre membre = new Membre();
// Then
assertNotNull(membre);
assertNull(membre.getNumeroMembre());
assertNull(membre.getPrenom());
assertNull(membre.getNom());
assertNull(membre.getEmail());
assertNull(membre.getTelephone());
assertNull(membre.getDateNaissance());
assertNull(membre.getDateAdhesion());
assertTrue(membre.getActif()); // Valeur par défaut
assertNotNull(membre.getDateCreation()); // Valeur par défaut
}
@Test
@DisplayName("Test des annotations Lombok - Constructeur avec tous les arguments")
void testLombokAllArgsConstructor() {
// Given
LocalDateTime dateCreation = LocalDateTime.now();
LocalDateTime dateModification = LocalDateTime.now();
// When
Membre membre = new Membre(numeroMembre, prenom, nom, email, telephone,
dateNaissance, dateAdhesion, true, dateCreation, dateModification);
// Then
assertNotNull(membre);
assertEquals(numeroMembre, membre.getNumeroMembre());
assertEquals(prenom, membre.getPrenom());
assertEquals(nom, membre.getNom());
assertEquals(email, membre.getEmail());
assertEquals(telephone, membre.getTelephone());
assertEquals(dateNaissance, membre.getDateNaissance());
assertEquals(dateAdhesion, membre.getDateAdhesion());
assertTrue(membre.getActif());
assertEquals(dateCreation, membre.getDateCreation());
assertEquals(dateModification, membre.getDateModification());
}
@Test
@DisplayName("Test des annotations Lombok - Getters et Setters")
void testLombokGettersSetters() {
// Given
Membre membre = new Membre();
// When & Then - Test des setters
membre.setNumeroMembre(numeroMembre);
membre.setPrenom(prenom);
membre.setNom(nom);
membre.setEmail(email);
membre.setTelephone(telephone);
membre.setDateNaissance(dateNaissance);
membre.setDateAdhesion(dateAdhesion);
membre.setActif(false);
// Test des getters
assertEquals(numeroMembre, membre.getNumeroMembre());
assertEquals(prenom, membre.getPrenom());
assertEquals(nom, membre.getNom());
assertEquals(email, membre.getEmail());
assertEquals(telephone, membre.getTelephone());
assertEquals(dateNaissance, membre.getDateNaissance());
assertEquals(dateAdhesion, membre.getDateAdhesion());
assertFalse(membre.getActif());
}
@Test
@DisplayName("Test des annotations Lombok - equals et hashCode")
void testLombokEqualsHashCode() {
// Given
Membre membre1 = Membre.builder()
.numeroMembre(numeroMembre)
.prenom(prenom)
.nom(nom)
.email(email)
.build();
Membre membre2 = Membre.builder()
.numeroMembre(numeroMembre)
.prenom(prenom)
.nom(nom)
.email(email)
.build();
Membre membre3 = Membre.builder()
.numeroMembre("DIFFERENT")
.prenom(prenom)
.nom(nom)
.email(email)
.build();
// Then
assertEquals(membre1, membre2);
assertEquals(membre1.hashCode(), membre2.hashCode());
assertNotEquals(membre1, membre3);
assertNotEquals(membre1.hashCode(), membre3.hashCode());
}
@Test
@DisplayName("Test des annotations Lombok - toString")
void testLombokToString() {
// Given
Membre membre = Membre.builder()
.numeroMembre(numeroMembre)
.prenom(prenom)
.nom(nom)
.email(email)
.build();
// When
String toStringResult = membre.toString();
// Then
assertNotNull(toStringResult);
assertTrue(toStringResult.contains("Membre"));
assertTrue(toStringResult.contains(numeroMembre));
assertTrue(toStringResult.contains(prenom));
assertTrue(toStringResult.contains(nom));
assertTrue(toStringResult.contains(email));
}
@Test
@DisplayName("Test des méthodes métier personnalisées")
void testMethodesMetier() {
// Given
Membre membre = Membre.builder()
.prenom(prenom)
.nom(nom)
.dateNaissance(LocalDate.of(1990, 5, 15)) // 34 ans
.build();
// When & Then
assertEquals("Jean Dupont", membre.getNomComplet());
assertTrue(membre.isMajeur());
assertEquals(34, membre.getAge());
}
@Test
@DisplayName("Test des valeurs par défaut avec @Builder.Default")
void testBuilderDefaults() {
// Given & When
Membre membre = Membre.builder()
.numeroMembre(numeroMembre)
.prenom(prenom)
.nom(nom)
.email(email)
.build();
// Then
assertTrue(membre.getActif()); // Valeur par défaut
assertNotNull(membre.getDateCreation()); // Valeur par défaut
assertTrue(membre.getDateCreation().isBefore(LocalDateTime.now().plusSeconds(1)));
}
}