Refactoring
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.client.dto.AdhesionDTO;
|
||||
import dev.lions.unionflow.server.api.dto.finance.request.*;
|
||||
import dev.lions.unionflow.server.api.dto.finance.response.*;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
@@ -17,6 +19,7 @@ import java.util.UUID;
|
||||
* @version 1.0
|
||||
*/
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(dev.lions.unionflow.client.security.AuthHeaderFactory.class)
|
||||
@Path("/api/adhesions")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@@ -26,7 +29,7 @@ public interface AdhesionService {
|
||||
* Récupère toutes les adhésions avec pagination
|
||||
*/
|
||||
@GET
|
||||
List<AdhesionDTO> listerToutes(
|
||||
List<AdhesionResponse> listerToutes(
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size
|
||||
);
|
||||
@@ -36,27 +39,27 @@ public interface AdhesionService {
|
||||
*/
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
AdhesionDTO obtenirParId(@PathParam("id") UUID id);
|
||||
AdhesionResponse obtenirParId(@PathParam("id") UUID id);
|
||||
|
||||
/**
|
||||
* Récupère une adhésion par son numéro de référence
|
||||
*/
|
||||
@GET
|
||||
@Path("/reference/{numeroReference}")
|
||||
AdhesionDTO obtenirParReference(@PathParam("numeroReference") String numeroReference);
|
||||
AdhesionResponse obtenirParReference(@PathParam("numeroReference") String numeroReference);
|
||||
|
||||
/**
|
||||
* Crée une nouvelle adhésion
|
||||
*/
|
||||
@POST
|
||||
AdhesionDTO creer(AdhesionDTO adhesion);
|
||||
|
||||
AdhesionResponse creer(CreateAdhesionRequest adhesion);
|
||||
|
||||
/**
|
||||
* Met à jour une adhésion existante
|
||||
*/
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
AdhesionDTO modifier(@PathParam("id") UUID id, AdhesionDTO adhesion);
|
||||
AdhesionResponse modifier(@PathParam("id") UUID id, UpdateAdhesionRequest adhesion);
|
||||
|
||||
/**
|
||||
* Supprime une adhésion
|
||||
@@ -70,7 +73,7 @@ public interface AdhesionService {
|
||||
*/
|
||||
@POST
|
||||
@Path("/{id}/approuver")
|
||||
AdhesionDTO approuver(
|
||||
AdhesionResponse approuver(
|
||||
@PathParam("id") UUID id,
|
||||
@QueryParam("approuvePar") String approuvePar
|
||||
);
|
||||
@@ -80,7 +83,7 @@ public interface AdhesionService {
|
||||
*/
|
||||
@POST
|
||||
@Path("/{id}/rejeter")
|
||||
AdhesionDTO rejeter(
|
||||
AdhesionResponse rejeter(
|
||||
@PathParam("id") UUID id,
|
||||
@QueryParam("motifRejet") String motifRejet
|
||||
);
|
||||
@@ -90,7 +93,7 @@ public interface AdhesionService {
|
||||
*/
|
||||
@POST
|
||||
@Path("/{id}/paiement")
|
||||
AdhesionDTO enregistrerPaiement(
|
||||
AdhesionResponse enregistrerPaiement(
|
||||
@PathParam("id") UUID id,
|
||||
@QueryParam("montantPaye") BigDecimal montantPaye,
|
||||
@QueryParam("methodePaiement") String methodePaiement,
|
||||
@@ -102,7 +105,7 @@ public interface AdhesionService {
|
||||
*/
|
||||
@GET
|
||||
@Path("/membre/{membreId}")
|
||||
List<AdhesionDTO> obtenirParMembre(
|
||||
List<AdhesionResponse> obtenirParMembre(
|
||||
@PathParam("membreId") UUID membreId,
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size
|
||||
@@ -113,7 +116,7 @@ public interface AdhesionService {
|
||||
*/
|
||||
@GET
|
||||
@Path("/organisation/{organisationId}")
|
||||
List<AdhesionDTO> obtenirParOrganisation(
|
||||
List<AdhesionResponse> obtenirParOrganisation(
|
||||
@PathParam("organisationId") UUID organisationId,
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size
|
||||
@@ -124,7 +127,7 @@ public interface AdhesionService {
|
||||
*/
|
||||
@GET
|
||||
@Path("/statut/{statut}")
|
||||
List<AdhesionDTO> obtenirParStatut(
|
||||
List<AdhesionResponse> obtenirParStatut(
|
||||
@PathParam("statut") String statut,
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size
|
||||
@@ -135,7 +138,7 @@ public interface AdhesionService {
|
||||
*/
|
||||
@GET
|
||||
@Path("/en-attente")
|
||||
List<AdhesionDTO> obtenirEnAttente(
|
||||
List<AdhesionResponse> obtenirEnAttente(
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size
|
||||
);
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.role.response.RoleResponse;
|
||||
import dev.lions.unionflow.server.api.dto.user.request.CreateUserRequest;
|
||||
import dev.lions.unionflow.server.api.dto.user.request.UpdateUserRequest;
|
||||
import dev.lions.unionflow.server.api.dto.user.response.UserResponse;
|
||||
import dev.lions.unionflow.server.api.dto.base.PageResponse;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Client REST pour l'API admin utilisateurs Keycloak (/api/admin/users).
|
||||
* Réservé aux utilisateurs avec rôle SUPER_ADMIN.
|
||||
*/
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(dev.lions.unionflow.client.security.AuthHeaderFactory.class)
|
||||
@Path("/api/admin/users")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public interface AdminUserService {
|
||||
|
||||
@GET
|
||||
PageResponse<UserResponse> lister(
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size,
|
||||
@QueryParam("search") String search
|
||||
);
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
UserResponse obtenirParId(@PathParam("id") String id);
|
||||
|
||||
@GET
|
||||
@Path("/roles")
|
||||
List<RoleResponse> getRealmRoles();
|
||||
|
||||
@GET
|
||||
@Path("/{id}/roles")
|
||||
List<RoleResponse> getUserRoles(@PathParam("id") String id);
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/roles")
|
||||
void setUserRoles(@PathParam("id") String id, List<String> roleNames);
|
||||
|
||||
@POST
|
||||
UserResponse creer(CreateUserRequest request);
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
UserResponse mettreAJour(@PathParam("id") String id, UpdateUserRequest request);
|
||||
}
|
||||
@@ -1,13 +1,17 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.client.dto.AnalyticsDataDTO;
|
||||
import dev.lions.unionflow.server.api.dto.analytics.AnalyticsDataResponse;
|
||||
import dev.lions.unionflow.server.api.dto.analytics.DashboardWidgetResponse;
|
||||
import dev.lions.unionflow.server.api.dto.analytics.KPITrendResponse;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(dev.lions.unionflow.client.security.AuthHeaderFactory.class)
|
||||
@Path("/api/v1/analytics")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@@ -15,7 +19,7 @@ public interface AnalyticsService {
|
||||
|
||||
@GET
|
||||
@Path("/metriques/{typeMetrique}")
|
||||
AnalyticsDataDTO calculerMetrique(
|
||||
AnalyticsDataResponse calculerMetrique(
|
||||
@PathParam("typeMetrique") String typeMetrique,
|
||||
@QueryParam("periode") String periode,
|
||||
@QueryParam("organisationId") String organisationId
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.client.dto.AssociationDTO;
|
||||
import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationResponse;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
@@ -8,78 +9,101 @@ import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(dev.lions.unionflow.client.security.AuthHeaderFactory.class)
|
||||
@Path("/api/organisations")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public interface AssociationService {
|
||||
|
||||
|
||||
@GET
|
||||
List<AssociationDTO> listerToutes(
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("1000") int size
|
||||
);
|
||||
|
||||
PagedResponseDTO<OrganisationResponse> listerToutes(
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("1000") int size);
|
||||
|
||||
class PagedResponseDTO<T> {
|
||||
public List<T> data;
|
||||
public Long total;
|
||||
public Integer page;
|
||||
public Integer size;
|
||||
public Integer totalPages;
|
||||
|
||||
public List<T> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(List<T> data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public Long getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(Long total) {
|
||||
this.total = total;
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
AssociationDTO obtenirParId(@PathParam("id") UUID id);
|
||||
|
||||
OrganisationResponse obtenirParId(@PathParam("id") UUID id);
|
||||
|
||||
@GET
|
||||
@Path("/recherche")
|
||||
List<AssociationDTO> rechercher(
|
||||
@QueryParam("nom") String nom,
|
||||
@QueryParam("type") String type,
|
||||
@QueryParam("statut") String statut,
|
||||
@QueryParam("region") String region,
|
||||
@QueryParam("ville") String ville,
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size
|
||||
);
|
||||
|
||||
PagedResponseDTO<OrganisationResponse> rechercher(
|
||||
@QueryParam("nom") String nom,
|
||||
@QueryParam("type") String type,
|
||||
@QueryParam("statut") String statut,
|
||||
@QueryParam("region") String region,
|
||||
@QueryParam("ville") String ville,
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size);
|
||||
|
||||
@GET
|
||||
@Path("/type/{type}")
|
||||
List<AssociationDTO> listerParType(@PathParam("type") String type);
|
||||
|
||||
List<OrganisationResponse> listerParType(@PathParam("type") String type);
|
||||
|
||||
@GET
|
||||
@Path("/region/{region}")
|
||||
List<AssociationDTO> listerParRegion(@PathParam("region") String region);
|
||||
|
||||
List<OrganisationResponse> listerParRegion(@PathParam("region") String region);
|
||||
|
||||
@POST
|
||||
AssociationDTO creer(AssociationDTO association);
|
||||
|
||||
OrganisationResponse creer(OrganisationResponse association);
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
AssociationDTO modifier(@PathParam("id") UUID id, AssociationDTO association);
|
||||
|
||||
OrganisationResponse modifier(@PathParam("id") UUID id, OrganisationResponse association);
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
void supprimer(@PathParam("id") UUID id);
|
||||
|
||||
|
||||
// Côté serveur: POST /{id}/activer
|
||||
@POST
|
||||
@Path("/{id}/activer")
|
||||
AssociationDTO activer(@PathParam("id") UUID id);
|
||||
|
||||
OrganisationResponse activer(@PathParam("id") UUID id);
|
||||
|
||||
// Suspension: POST /{id}/suspendre (alias historique "désactiver")
|
||||
@POST
|
||||
@Path("/{id}/suspendre")
|
||||
AssociationDTO suspendre(@PathParam("id") UUID id);
|
||||
|
||||
OrganisationResponse suspendre(@PathParam("id") UUID id);
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/dissoudre")
|
||||
AssociationDTO dissoudre(@PathParam("id") UUID id);
|
||||
|
||||
OrganisationResponse dissoudre(@PathParam("id") UUID id);
|
||||
|
||||
@GET
|
||||
@Path("/statistiques")
|
||||
StatistiquesAssociationDTO obtenirStatistiques();
|
||||
|
||||
|
||||
@GET
|
||||
@Path("/{id}/membres/count")
|
||||
Long compterMembres(@PathParam("id") UUID id);
|
||||
|
||||
|
||||
@GET
|
||||
@Path("/{id}/performance")
|
||||
PerformanceAssociationDTO obtenirPerformance(@PathParam("id") UUID id);
|
||||
|
||||
|
||||
// Classes DTO internes
|
||||
class StatistiquesAssociationDTO {
|
||||
public Long totalAssociations;
|
||||
@@ -91,39 +115,85 @@ public interface AssociationService {
|
||||
public Double tauxActivite;
|
||||
public java.util.Map<String, Long> repartitionParType;
|
||||
public java.util.Map<String, Long> repartitionParRegion;
|
||||
|
||||
|
||||
// Constructeurs
|
||||
public StatistiquesAssociationDTO() {}
|
||||
|
||||
public StatistiquesAssociationDTO() {
|
||||
}
|
||||
|
||||
// Getters et setters
|
||||
public Long getTotalAssociations() { return totalAssociations; }
|
||||
public void setTotalAssociations(Long totalAssociations) { this.totalAssociations = totalAssociations; }
|
||||
|
||||
public Long getAssociationsActives() { return associationsActives; }
|
||||
public void setAssociationsActives(Long associationsActives) { this.associationsActives = associationsActives; }
|
||||
|
||||
public Long getAssociationsInactives() { return associationsInactives; }
|
||||
public void setAssociationsInactives(Long associationsInactives) { this.associationsInactives = associationsInactives; }
|
||||
|
||||
public Long getAssociationsSuspendues() { return associationsSuspendues; }
|
||||
public void setAssociationsSuspendues(Long associationsSuspendues) { this.associationsSuspendues = associationsSuspendues; }
|
||||
|
||||
public Long getAssociationsDissoutes() { return associationsDissoutes; }
|
||||
public void setAssociationsDissoutes(Long associationsDissoutes) { this.associationsDissoutes = associationsDissoutes; }
|
||||
|
||||
public Long getNouvellesAssociations30Jours() { return nouvellesAssociations30Jours; }
|
||||
public void setNouvellesAssociations30Jours(Long nouvellesAssociations30Jours) { this.nouvellesAssociations30Jours = nouvellesAssociations30Jours; }
|
||||
|
||||
public Double getTauxActivite() { return tauxActivite; }
|
||||
public void setTauxActivite(Double tauxActivite) { this.tauxActivite = tauxActivite; }
|
||||
|
||||
public java.util.Map<String, Long> getRepartitionParType() { return repartitionParType; }
|
||||
public void setRepartitionParType(java.util.Map<String, Long> repartitionParType) { this.repartitionParType = repartitionParType; }
|
||||
|
||||
public java.util.Map<String, Long> getRepartitionParRegion() { return repartitionParRegion; }
|
||||
public void setRepartitionParRegion(java.util.Map<String, Long> repartitionParRegion) { this.repartitionParRegion = repartitionParRegion; }
|
||||
public Long getTotalAssociations() {
|
||||
return totalAssociations;
|
||||
}
|
||||
|
||||
public void setTotalAssociations(Long totalAssociations) {
|
||||
this.totalAssociations = totalAssociations;
|
||||
}
|
||||
|
||||
public Long getAssociationsActives() {
|
||||
return associationsActives;
|
||||
}
|
||||
|
||||
public void setAssociationsActives(Long associationsActives) {
|
||||
this.associationsActives = associationsActives;
|
||||
}
|
||||
|
||||
public Long getAssociationsInactives() {
|
||||
return associationsInactives;
|
||||
}
|
||||
|
||||
public void setAssociationsInactives(Long associationsInactives) {
|
||||
this.associationsInactives = associationsInactives;
|
||||
}
|
||||
|
||||
public Long getAssociationsSuspendues() {
|
||||
return associationsSuspendues;
|
||||
}
|
||||
|
||||
public void setAssociationsSuspendues(Long associationsSuspendues) {
|
||||
this.associationsSuspendues = associationsSuspendues;
|
||||
}
|
||||
|
||||
public Long getAssociationsDissoutes() {
|
||||
return associationsDissoutes;
|
||||
}
|
||||
|
||||
public void setAssociationsDissoutes(Long associationsDissoutes) {
|
||||
this.associationsDissoutes = associationsDissoutes;
|
||||
}
|
||||
|
||||
public Long getNouvellesAssociations30Jours() {
|
||||
return nouvellesAssociations30Jours;
|
||||
}
|
||||
|
||||
public void setNouvellesAssociations30Jours(Long nouvellesAssociations30Jours) {
|
||||
this.nouvellesAssociations30Jours = nouvellesAssociations30Jours;
|
||||
}
|
||||
|
||||
public Double getTauxActivite() {
|
||||
return tauxActivite;
|
||||
}
|
||||
|
||||
public void setTauxActivite(Double tauxActivite) {
|
||||
this.tauxActivite = tauxActivite;
|
||||
}
|
||||
|
||||
public java.util.Map<String, Long> getRepartitionParType() {
|
||||
return repartitionParType;
|
||||
}
|
||||
|
||||
public void setRepartitionParType(java.util.Map<String, Long> repartitionParType) {
|
||||
this.repartitionParType = repartitionParType;
|
||||
}
|
||||
|
||||
public java.util.Map<String, Long> getRepartitionParRegion() {
|
||||
return repartitionParRegion;
|
||||
}
|
||||
|
||||
public void setRepartitionParRegion(java.util.Map<String, Long> repartitionParRegion) {
|
||||
this.repartitionParRegion = repartitionParRegion;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PerformanceAssociationDTO {
|
||||
public UUID associationId;
|
||||
public String nom;
|
||||
@@ -133,33 +203,74 @@ public interface AssociationService {
|
||||
public Integer scoreFinances;
|
||||
public String tendance;
|
||||
public java.time.LocalDateTime derniereMiseAJour;
|
||||
|
||||
|
||||
// Constructeurs
|
||||
public PerformanceAssociationDTO() {}
|
||||
|
||||
public PerformanceAssociationDTO() {
|
||||
}
|
||||
|
||||
// Getters et setters
|
||||
public UUID getAssociationId() { return associationId; }
|
||||
public void setAssociationId(UUID associationId) { this.associationId = associationId; }
|
||||
|
||||
public String getNom() { return nom; }
|
||||
public void setNom(String nom) { this.nom = nom; }
|
||||
|
||||
public Integer getScoreGlobal() { return scoreGlobal; }
|
||||
public void setScoreGlobal(Integer scoreGlobal) { this.scoreGlobal = scoreGlobal; }
|
||||
|
||||
public Integer getScoreMembres() { return scoreMembres; }
|
||||
public void setScoreMembres(Integer scoreMembres) { this.scoreMembres = scoreMembres; }
|
||||
|
||||
public Integer getScoreActivites() { return scoreActivites; }
|
||||
public void setScoreActivites(Integer scoreActivites) { this.scoreActivites = scoreActivites; }
|
||||
|
||||
public Integer getScoreFinances() { return scoreFinances; }
|
||||
public void setScoreFinances(Integer scoreFinances) { this.scoreFinances = scoreFinances; }
|
||||
|
||||
public String getTendance() { return tendance; }
|
||||
public void setTendance(String tendance) { this.tendance = tendance; }
|
||||
|
||||
public java.time.LocalDateTime getDerniereMiseAJour() { return derniereMiseAJour; }
|
||||
public void setDerniereMiseAJour(java.time.LocalDateTime derniereMiseAJour) { this.derniereMiseAJour = derniereMiseAJour; }
|
||||
public UUID getAssociationId() {
|
||||
return associationId;
|
||||
}
|
||||
|
||||
public void setAssociationId(UUID associationId) {
|
||||
this.associationId = associationId;
|
||||
}
|
||||
|
||||
public String getNom() {
|
||||
return nom;
|
||||
}
|
||||
|
||||
public void setNom(String nom) {
|
||||
this.nom = nom;
|
||||
}
|
||||
|
||||
public Integer getScoreGlobal() {
|
||||
return scoreGlobal;
|
||||
}
|
||||
|
||||
public void setScoreGlobal(Integer scoreGlobal) {
|
||||
this.scoreGlobal = scoreGlobal;
|
||||
}
|
||||
|
||||
public Integer getScoreMembres() {
|
||||
return scoreMembres;
|
||||
}
|
||||
|
||||
public void setScoreMembres(Integer scoreMembres) {
|
||||
this.scoreMembres = scoreMembres;
|
||||
}
|
||||
|
||||
public Integer getScoreActivites() {
|
||||
return scoreActivites;
|
||||
}
|
||||
|
||||
public void setScoreActivites(Integer scoreActivites) {
|
||||
this.scoreActivites = scoreActivites;
|
||||
}
|
||||
|
||||
public Integer getScoreFinances() {
|
||||
return scoreFinances;
|
||||
}
|
||||
|
||||
public void setScoreFinances(Integer scoreFinances) {
|
||||
this.scoreFinances = scoreFinances;
|
||||
}
|
||||
|
||||
public String getTendance() {
|
||||
return tendance;
|
||||
}
|
||||
|
||||
public void setTendance(String tendance) {
|
||||
this.tendance = tendance;
|
||||
}
|
||||
|
||||
public java.time.LocalDateTime getDerniereMiseAJour() {
|
||||
return derniereMiseAJour;
|
||||
}
|
||||
|
||||
public void setDerniereMiseAJour(java.time.LocalDateTime derniereMiseAJour) {
|
||||
this.derniereMiseAJour = derniereMiseAJour;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.client.dto.AuditLogDTO;
|
||||
import dev.lions.unionflow.server.api.dto.admin.response.AuditLogResponse;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.Map;
|
||||
@@ -13,8 +13,8 @@ import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
*/
|
||||
@RegisterRestClient(baseUri = "http://localhost:8085")
|
||||
@RegisterClientHeaders
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(dev.lions.unionflow.client.security.AuthHeaderFactory.class)
|
||||
@Path("/api/audit")
|
||||
public interface AuditService {
|
||||
|
||||
@@ -43,7 +43,7 @@ public interface AuditService {
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
AuditLogDTO enregistrerLog(AuditLogDTO dto);
|
||||
AuditLogResponse enregistrerLog(AuditLogResponse dto);
|
||||
|
||||
@GET
|
||||
@Path("/statistiques")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.client.dto.auth.LoginRequest;
|
||||
import dev.lions.unionflow.client.dto.auth.LoginResponse;
|
||||
import dev.lions.unionflow.server.api.dto.auth.request.LoginRequest;
|
||||
import dev.lions.unionflow.server.api.dto.auth.response.LoginResponse;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.ws.rs.client.Client;
|
||||
import jakarta.ws.rs.client.ClientBuilder;
|
||||
@@ -9,6 +9,7 @@ import jakarta.ws.rs.client.Entity;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -38,18 +39,18 @@ public class AuthenticationService {
|
||||
|
||||
if (response.getStatus() == 200) {
|
||||
LoginResponse loginResponse = response.readEntity(LoginResponse.class);
|
||||
LOGGER.info("Authentification réussie pour l'utilisateur: " + loginRequest.getUsername());
|
||||
LOGGER.info("Authentification réussie pour l'utilisateur: " + loginRequest.username());
|
||||
return loginResponse;
|
||||
} else {
|
||||
LOGGER.warning("Échec de l'authentification. Code de statut: " + response.getStatus());
|
||||
throw new AuthenticationException("Nom d'utilisateur ou mot de passe incorrect");
|
||||
}
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors de l'authentification: " + e.getMessage());
|
||||
|
||||
|
||||
// Mode simulation pour le développement
|
||||
if ("demo".equals(loginRequest.getUsername()) || isValidDemoCredentials(loginRequest)) {
|
||||
if ("demo".equals(loginRequest.username()) || isValidDemoCredentials(loginRequest)) {
|
||||
return createDemoLoginResponse(loginRequest);
|
||||
}
|
||||
|
||||
@@ -93,21 +94,21 @@ public class AuthenticationService {
|
||||
}
|
||||
|
||||
private boolean isValidDemoCredentials(LoginRequest request) {
|
||||
return ("admin".equals(request.getUsername()) && "admin".equals(request.getPassword())) ||
|
||||
("superadmin".equals(request.getUsername()) && "admin".equals(request.getPassword())) ||
|
||||
("membre".equals(request.getUsername()) && "membre".equals(request.getPassword()));
|
||||
return ("admin".equals(request.username()) && "admin".equals(request.password())) ||
|
||||
("superadmin".equals(request.username()) && "admin".equals(request.password())) ||
|
||||
("membre".equals(request.username()) && "membre".equals(request.password()));
|
||||
}
|
||||
|
||||
private LoginResponse createDemoLoginResponse(LoginRequest request) {
|
||||
LoginResponse.UserInfo userInfo = new LoginResponse.UserInfo();
|
||||
|
||||
|
||||
// UUIDs fixes pour la démonstration (pour cohérence entre les sessions)
|
||||
UUID superAdminId = UUID.fromString("00000000-0000-0000-0000-000000000001");
|
||||
UUID adminId = UUID.fromString("00000000-0000-0000-0000-000000000002");
|
||||
UUID membreId = UUID.fromString("00000000-0000-0000-0000-000000000003");
|
||||
UUID entiteId = UUID.fromString("00000000-0000-0000-0000-000000000010");
|
||||
|
||||
switch (request.getUsername()) {
|
||||
|
||||
switch (request.username()) {
|
||||
case "superadmin":
|
||||
userInfo.setId(superAdminId);
|
||||
userInfo.setNom("Diallo");
|
||||
@@ -156,13 +157,18 @@ public class AuthenticationService {
|
||||
userInfo.setEntite(entiteMembre);
|
||||
break;
|
||||
}
|
||||
|
||||
return new LoginResponse(
|
||||
"demo_access_token_" + System.currentTimeMillis(),
|
||||
"demo_refresh_token_" + System.currentTimeMillis(),
|
||||
3600L, // 1 heure
|
||||
userInfo
|
||||
);
|
||||
|
||||
long expiresIn = 3600L; // 1 heure en secondes
|
||||
LocalDateTime expirationDate = LocalDateTime.now().plusSeconds(expiresIn);
|
||||
|
||||
return LoginResponse.builder()
|
||||
.accessToken("demo_access_token_" + System.currentTimeMillis())
|
||||
.refreshToken("demo_refresh_token_" + System.currentTimeMillis())
|
||||
.tokenType("Bearer")
|
||||
.expiresIn(expiresIn)
|
||||
.expirationDate(expirationDate)
|
||||
.user(userInfo)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static class AuthenticationException extends RuntimeException {
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Service de cache pour optimiser les performances de l'application.
|
||||
*
|
||||
* <p>Ce service fournit un cache en mémoire pour les données fréquemment accédées,
|
||||
* avec support de l'expiration automatique et de l'invalidation manuelle.
|
||||
*
|
||||
* <p><strong>Production-ready:</strong> Cache thread-safe, gestion de l'expiration,
|
||||
* invalidation sélective, et métriques de performance.
|
||||
*
|
||||
* <p><strong>Usage:</strong>
|
||||
* <pre>{@code
|
||||
* @Inject
|
||||
* CacheService cacheService;
|
||||
*
|
||||
* // Obtenir une valeur du cache ou la charger si absente
|
||||
* List<TypeOrganisationDTO> types = cacheService.getOrLoad(
|
||||
* "types-organisation",
|
||||
* () -> typeOrganisationService.list(true),
|
||||
* 300 // Expire après 5 minutes
|
||||
* );
|
||||
*
|
||||
* // Invalider le cache
|
||||
* cacheService.invalidate("types-organisation");
|
||||
* }</pre>
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-12-24
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class CacheService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(CacheService.class);
|
||||
|
||||
@Inject
|
||||
MetricsService metricsService;
|
||||
|
||||
/**
|
||||
* Cache thread-safe pour stocker les données.
|
||||
*/
|
||||
private final Map<String, CacheEntry<?>> cache = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Nombre maximum d'entrées dans le cache (pour éviter les fuites mémoire).
|
||||
*/
|
||||
private static final int MAX_CACHE_SIZE = 1000;
|
||||
|
||||
/**
|
||||
* Obtient une valeur du cache ou la charge si absente.
|
||||
*
|
||||
* @param key Clé du cache
|
||||
* @param loader Fonction pour charger la valeur si absente
|
||||
* @param ttlSeconds Durée de vie en secondes (0 = pas d'expiration)
|
||||
* @return La valeur du cache ou chargée
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getOrLoad(String key, Supplier<T> loader, int ttlSeconds) {
|
||||
if (key == null || key.trim().isEmpty()) {
|
||||
LOG.warn("Tentative d'accès au cache avec une clé null ou vide");
|
||||
return loader.get();
|
||||
}
|
||||
|
||||
// Vérifier si la valeur est en cache et valide
|
||||
CacheEntry<?> entry = cache.get(key);
|
||||
if (entry != null && entry.isValid(ttlSeconds)) {
|
||||
LOG.debugf("Cache hit pour la clé: %s", key);
|
||||
if (metricsService != null) {
|
||||
metricsService.recordCacheHit();
|
||||
}
|
||||
return (T) entry.getValue();
|
||||
}
|
||||
|
||||
// Charger la valeur
|
||||
LOG.debugf("Cache miss pour la clé: %s - chargement depuis le backend", key);
|
||||
if (metricsService != null) {
|
||||
metricsService.recordCacheMiss();
|
||||
}
|
||||
try {
|
||||
T value = loader.get();
|
||||
|
||||
// Vérifier la taille du cache avant d'ajouter
|
||||
if (cache.size() >= MAX_CACHE_SIZE) {
|
||||
LOG.warnf("Cache plein (%d entrées) - nettoyage des entrées expirées", cache.size());
|
||||
cleanupExpiredEntries();
|
||||
|
||||
// Si toujours plein, supprimer les entrées les plus anciennes
|
||||
if (cache.size() >= MAX_CACHE_SIZE) {
|
||||
evictOldestEntries(MAX_CACHE_SIZE / 10); // Supprimer 10% des entrées
|
||||
}
|
||||
}
|
||||
|
||||
// Mettre en cache
|
||||
cache.put(key, new CacheEntry<>(value, LocalDateTime.now()));
|
||||
|
||||
return value;
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors du chargement de la valeur pour la clé: %s", key);
|
||||
throw new RuntimeException("Erreur lors du chargement de la valeur du cache", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient une valeur du cache sans la charger si absente.
|
||||
*
|
||||
* @param key Clé du cache
|
||||
* @return La valeur ou null si absente ou expirée
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(String key) {
|
||||
if (key == null || key.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CacheEntry<?> entry = cache.get(key);
|
||||
if (entry != null && entry.isValid(0)) { // 0 = pas d'expiration
|
||||
return (T) entry.getValue();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Met une valeur dans le cache.
|
||||
*
|
||||
* @param key Clé du cache
|
||||
* @param value Valeur à mettre en cache
|
||||
* @param ttlSeconds Durée de vie en secondes (0 = pas d'expiration)
|
||||
*/
|
||||
public <T> void put(String key, T value, int ttlSeconds) {
|
||||
if (key == null || key.trim().isEmpty()) {
|
||||
LOG.warn("Tentative de mise en cache avec une clé null ou vide");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cache.size() >= MAX_CACHE_SIZE) {
|
||||
cleanupExpiredEntries();
|
||||
}
|
||||
|
||||
cache.put(key, new CacheEntry<>(value, LocalDateTime.now()));
|
||||
LOG.debugf("Valeur mise en cache pour la clé: %s (TTL: %d secondes)", key, ttlSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalide une entrée du cache.
|
||||
*
|
||||
* @param key Clé à invalider
|
||||
*/
|
||||
public void invalidate(String key) {
|
||||
if (key != null && cache.remove(key) != null) {
|
||||
LOG.debugf("Cache invalidé pour la clé: %s", key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalide toutes les entrées du cache correspondant à un préfixe.
|
||||
*
|
||||
* @param prefix Préfixe des clés à invalider
|
||||
*/
|
||||
public void invalidateByPrefix(String prefix) {
|
||||
if (prefix == null || prefix.trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
for (String key : cache.keySet()) {
|
||||
if (key.startsWith(prefix)) {
|
||||
cache.remove(key);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
LOG.debugf("Cache invalidé pour %d entrées avec le préfixe: %s", count, prefix);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vide complètement le cache.
|
||||
*/
|
||||
public void clear() {
|
||||
int size = cache.size();
|
||||
cache.clear();
|
||||
LOG.infof("Cache vidé (%d entrées supprimées)", size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Nettoie les entrées expirées du cache.
|
||||
*/
|
||||
public void cleanupExpiredEntries() {
|
||||
int initialSize = cache.size();
|
||||
cache.entrySet().removeIf(entry -> !entry.getValue().isValid(0));
|
||||
int removed = initialSize - cache.size();
|
||||
|
||||
if (removed > 0) {
|
||||
LOG.debugf("Nettoyage du cache: %d entrées expirées supprimées", removed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime les entrées les plus anciennes du cache.
|
||||
*/
|
||||
private void evictOldestEntries(int count) {
|
||||
cache.entrySet().stream()
|
||||
.sorted((e1, e2) -> e1.getValue().getTimestamp().compareTo(e2.getValue().getTimestamp()))
|
||||
.limit(count)
|
||||
.forEach(entry -> cache.remove(entry.getKey()));
|
||||
|
||||
LOG.debugf("Éviction de %d entrées les plus anciennes du cache", count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient la taille actuelle du cache.
|
||||
*/
|
||||
public int size() {
|
||||
return cache.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Entrée du cache avec timestamp.
|
||||
*/
|
||||
private static class CacheEntry<T> {
|
||||
private final T value;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public CacheEntry(T value, LocalDateTime timestamp) {
|
||||
this.value = value;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public T getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public LocalDateTime getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'entrée est valide (non expirée).
|
||||
*
|
||||
* @param ttlSeconds Durée de vie en secondes (0 = pas d'expiration)
|
||||
*/
|
||||
public boolean isValid(int ttlSeconds) {
|
||||
if (ttlSeconds <= 0) {
|
||||
return true; // Pas d'expiration
|
||||
}
|
||||
|
||||
LocalDateTime expiration = timestamp.plusSeconds(ttlSeconds);
|
||||
return LocalDateTime.now().isBefore(expiration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.comptabilite.request.*;
|
||||
import dev.lions.unionflow.server.api.dto.comptabilite.response.*;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Service REST Client pour la gestion comptable
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
*/
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(dev.lions.unionflow.client.security.AuthHeaderFactory.class)
|
||||
@Path("/api/comptabilite")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public interface ComptabiliteService {
|
||||
|
||||
// ========================================
|
||||
// COMPTES COMPTABLES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Liste tous les comptes comptables actifs
|
||||
*/
|
||||
@GET
|
||||
@Path("/comptes")
|
||||
List<CompteComptableResponse> listerComptes();
|
||||
|
||||
/**
|
||||
* Crée un nouveau compte comptable
|
||||
*/
|
||||
@POST
|
||||
@Path("/comptes")
|
||||
CompteComptableResponse creerCompte(CreateCompteComptableRequest request);
|
||||
|
||||
/**
|
||||
* Trouve un compte comptable par son ID
|
||||
*/
|
||||
@GET
|
||||
@Path("/comptes/{id}")
|
||||
CompteComptableResponse obtenirCompte(@PathParam("id") UUID id);
|
||||
|
||||
// ========================================
|
||||
// JOURNAUX COMPTABLES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Liste tous les journaux comptables actifs
|
||||
*/
|
||||
@GET
|
||||
@Path("/journaux")
|
||||
List<JournalComptableResponse> listerJournaux();
|
||||
|
||||
/**
|
||||
* Crée un nouveau journal comptable
|
||||
*/
|
||||
@POST
|
||||
@Path("/journaux")
|
||||
JournalComptableResponse creerJournal(CreateJournalComptableRequest request);
|
||||
|
||||
/**
|
||||
* Trouve un journal comptable par son ID
|
||||
*/
|
||||
@GET
|
||||
@Path("/journaux/{id}")
|
||||
JournalComptableResponse obtenirJournal(@PathParam("id") UUID id);
|
||||
|
||||
// ========================================
|
||||
// ÉCRITURES COMPTABLES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Crée une nouvelle écriture comptable
|
||||
*/
|
||||
@POST
|
||||
@Path("/ecritures")
|
||||
EcritureComptableResponse creerEcriture(CreateEcritureComptableRequest request);
|
||||
|
||||
/**
|
||||
* Trouve une écriture comptable par son ID
|
||||
*/
|
||||
@GET
|
||||
@Path("/ecritures/{id}")
|
||||
EcritureComptableResponse obtenirEcriture(@PathParam("id") UUID id);
|
||||
|
||||
/**
|
||||
* Liste les écritures d'un journal
|
||||
*/
|
||||
@GET
|
||||
@Path("/ecritures/journal/{journalId}")
|
||||
List<EcritureComptableResponse> listerEcrituresParJournal(@PathParam("journalId") UUID journalId);
|
||||
|
||||
/**
|
||||
* Liste les écritures d'une organisation
|
||||
*/
|
||||
@GET
|
||||
@Path("/ecritures/organisation/{organisationId}")
|
||||
List<EcritureComptableResponse> listerEcrituresParOrganisation(@PathParam("organisationId") UUID organisationId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.config.request.*;
|
||||
import dev.lions.unionflow.server.api.dto.config.response.*;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Service REST client pour la gestion de la configuration système
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
*/
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(dev.lions.unionflow.client.security.AuthHeaderFactory.class)
|
||||
@Path("/api/configuration")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public interface ConfigurationService {
|
||||
|
||||
@GET
|
||||
List<ConfigurationResponse> listerConfigurations();
|
||||
|
||||
@GET
|
||||
@Path("/{cle}")
|
||||
ConfigurationResponse obtenirConfiguration(@PathParam("cle") String cle);
|
||||
|
||||
@PUT
|
||||
@Path("/{cle}")
|
||||
ConfigurationResponse mettreAJourConfiguration(@PathParam("cle") String cle, UpdateConfigurationRequest request);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.client.dto.CotisationDTO;
|
||||
import dev.lions.unionflow.server.api.dto.cotisation.request.CreateCotisationRequest;
|
||||
import dev.lions.unionflow.server.api.dto.cotisation.response.CotisationResponse;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
@@ -16,6 +18,7 @@ import java.util.UUID;
|
||||
* @version 1.0
|
||||
*/
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(dev.lions.unionflow.client.security.AuthHeaderFactory.class)
|
||||
@Path("/api/cotisations")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@@ -25,7 +28,7 @@ public interface CotisationService {
|
||||
* Récupère toutes les cotisations avec pagination
|
||||
*/
|
||||
@GET
|
||||
List<CotisationDTO> listerToutes(
|
||||
List<CotisationResponse> listerToutes(
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size
|
||||
);
|
||||
@@ -35,21 +38,21 @@ public interface CotisationService {
|
||||
*/
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
CotisationDTO obtenirParId(@PathParam("id") UUID id);
|
||||
CotisationResponse obtenirParId(@PathParam("id") UUID id);
|
||||
|
||||
/**
|
||||
* Récupère une cotisation par son numéro de référence
|
||||
*/
|
||||
@GET
|
||||
@Path("/reference/{numeroReference}")
|
||||
CotisationDTO obtenirParReference(@PathParam("numeroReference") String numeroReference);
|
||||
CotisationResponse obtenirParReference(@PathParam("numeroReference") String numeroReference);
|
||||
|
||||
/**
|
||||
* Récupère les cotisations d'un membre
|
||||
*/
|
||||
@GET
|
||||
@Path("/membre/{membreId}")
|
||||
List<CotisationDTO> obtenirParMembre(
|
||||
List<CotisationResponse> obtenirParMembre(
|
||||
@PathParam("membreId") UUID membreId,
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size
|
||||
@@ -60,7 +63,7 @@ public interface CotisationService {
|
||||
*/
|
||||
@GET
|
||||
@Path("/statut/{statut}")
|
||||
List<CotisationDTO> obtenirParStatut(
|
||||
List<CotisationResponse> obtenirParStatut(
|
||||
@PathParam("statut") String statut,
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size
|
||||
@@ -71,7 +74,7 @@ public interface CotisationService {
|
||||
*/
|
||||
@GET
|
||||
@Path("/en-retard")
|
||||
List<CotisationDTO> obtenirEnRetard(
|
||||
List<CotisationResponse> obtenirEnRetard(
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size
|
||||
);
|
||||
@@ -81,7 +84,7 @@ public interface CotisationService {
|
||||
*/
|
||||
@GET
|
||||
@Path("/recherche")
|
||||
List<CotisationDTO> rechercher(
|
||||
List<CotisationResponse> rechercher(
|
||||
@QueryParam("membreId") UUID membreId,
|
||||
@QueryParam("statut") String statut,
|
||||
@QueryParam("typeCotisation") String typeCotisation,
|
||||
@@ -102,14 +105,14 @@ public interface CotisationService {
|
||||
* Crée une nouvelle cotisation
|
||||
*/
|
||||
@POST
|
||||
CotisationDTO creer(CotisationDTO cotisation);
|
||||
CotisationResponse creer(CreateCotisationRequest request);
|
||||
|
||||
/**
|
||||
* Met à jour une cotisation existante
|
||||
*/
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
CotisationDTO modifier(@PathParam("id") UUID id, CotisationDTO cotisation);
|
||||
CotisationResponse modifier(@PathParam("id") UUID id, CotisationResponse cotisation);
|
||||
|
||||
/**
|
||||
* Supprime une cotisation
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.dashboard.DashboardDataResponse;
|
||||
import dev.lions.unionflow.server.api.dto.dashboard.DashboardStatsResponse;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Service REST Client pour les APIs du dashboard
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
*/
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(dev.lions.unionflow.client.security.AuthHeaderFactory.class)
|
||||
@Path("/api/v1/dashboard")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public interface DashboardService {
|
||||
|
||||
/**
|
||||
* Récupère toutes les données du dashboard
|
||||
*/
|
||||
@GET
|
||||
@Path("/data")
|
||||
DashboardDataResponse getDashboardData(
|
||||
@QueryParam("organizationId") String organizationId,
|
||||
@QueryParam("userId") String userId
|
||||
);
|
||||
|
||||
/**
|
||||
* Récupère uniquement les statistiques du dashboard
|
||||
*/
|
||||
@GET
|
||||
@Path("/stats")
|
||||
DashboardStatsResponse getDashboardStats(
|
||||
@QueryParam("organizationId") String organizationId,
|
||||
@QueryParam("userId") String userId
|
||||
);
|
||||
|
||||
/**
|
||||
* Récupère les activités récentes
|
||||
*/
|
||||
@GET
|
||||
@Path("/activities")
|
||||
Map<String, Object> getRecentActivities(
|
||||
@QueryParam("organizationId") String organizationId,
|
||||
@QueryParam("userId") String userId,
|
||||
@QueryParam("limit") @DefaultValue("10") int limit
|
||||
);
|
||||
|
||||
/**
|
||||
* Récupère les événements à venir
|
||||
*/
|
||||
@GET
|
||||
@Path("/events/upcoming")
|
||||
Map<String, Object> getUpcomingEvents(
|
||||
@QueryParam("organizationId") String organizationId,
|
||||
@QueryParam("userId") String userId,
|
||||
@QueryParam("limit") @DefaultValue("5") int limit
|
||||
);
|
||||
|
||||
/**
|
||||
* Rafraîchit les données du dashboard
|
||||
*/
|
||||
@POST
|
||||
@Path("/refresh")
|
||||
Map<String, Object> refreshDashboard(
|
||||
@QueryParam("organizationId") String organizationId,
|
||||
@QueryParam("userId") String userId
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,31 +1,34 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.client.dto.DemandeAideDTO;
|
||||
import dev.lions.unionflow.server.api.dto.solidarite.request.CreateDemandeAideRequest;
|
||||
import dev.lions.unionflow.server.api.dto.solidarite.response.DemandeAideResponse;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(dev.lions.unionflow.client.security.AuthHeaderFactory.class)
|
||||
@Path("/api/demandes-aide")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public interface DemandeAideService {
|
||||
|
||||
@GET
|
||||
List<DemandeAideDTO> listerToutes(
|
||||
List<DemandeAideResponse> listerToutes(
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size
|
||||
);
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
DemandeAideDTO obtenirParId(@PathParam("id") UUID id);
|
||||
DemandeAideResponse obtenirParId(@PathParam("id") UUID id);
|
||||
|
||||
@GET
|
||||
@Path("/search")
|
||||
List<DemandeAideDTO> rechercher(
|
||||
List<DemandeAideResponse> rechercher(
|
||||
@QueryParam("statut") String statut,
|
||||
@QueryParam("type") String type,
|
||||
@QueryParam("urgence") String urgence,
|
||||
@@ -34,11 +37,11 @@ public interface DemandeAideService {
|
||||
);
|
||||
|
||||
@POST
|
||||
DemandeAideDTO creer(DemandeAideDTO demande);
|
||||
DemandeAideResponse creer(CreateDemandeAideRequest request);
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
DemandeAideDTO modifier(@PathParam("id") UUID id, DemandeAideDTO demande);
|
||||
DemandeAideResponse modifier(@PathParam("id") UUID id, DemandeAideResponse demande);
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@@ -46,10 +49,10 @@ public interface DemandeAideService {
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/approuver")
|
||||
DemandeAideDTO approuver(@PathParam("id") UUID id);
|
||||
DemandeAideResponse approuver(@PathParam("id") UUID id);
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/rejeter")
|
||||
DemandeAideDTO rejeter(@PathParam("id") UUID id);
|
||||
DemandeAideResponse rejeter(@PathParam("id") UUID id);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.document.request.*;
|
||||
import dev.lions.unionflow.server.api.dto.document.response.*;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Service REST Client pour la gestion documentaire
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
*/
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(dev.lions.unionflow.client.security.AuthHeaderFactory.class)
|
||||
@Path("/api/documents")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public interface DocumentService {
|
||||
|
||||
/**
|
||||
* Crée un nouveau document
|
||||
*/
|
||||
@POST
|
||||
DocumentResponse creerDocument(CreateDocumentRequest request);
|
||||
|
||||
/**
|
||||
* Trouve un document par son ID
|
||||
*/
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
DocumentResponse obtenirDocument(@PathParam("id") UUID id);
|
||||
|
||||
/**
|
||||
* Enregistre un téléchargement de document
|
||||
*/
|
||||
@POST
|
||||
@Path("/{id}/telechargement")
|
||||
void enregistrerTelechargement(@PathParam("id") UUID id);
|
||||
|
||||
/**
|
||||
* Crée une pièce jointe
|
||||
*/
|
||||
@POST
|
||||
@Path("/pieces-jointes")
|
||||
PieceJointeResponse creerPieceJointe(CreatePieceJointeRequest request);
|
||||
|
||||
/**
|
||||
* Liste toutes les pièces jointes d'un document
|
||||
*/
|
||||
@GET
|
||||
@Path("/{documentId}/pieces-jointes")
|
||||
List<PieceJointeResponse> listerPiecesJointes(@PathParam("documentId") UUID documentId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,292 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.faces.application.FacesMessage;
|
||||
import jakarta.faces.context.FacesContext;
|
||||
import jakarta.inject.Inject;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Service centralisé de gestion des erreurs et messages utilisateur.
|
||||
*
|
||||
* <p>Ce service fournit une interface unifiée pour :
|
||||
* <ul>
|
||||
* <li>Gérer les erreurs backend de manière cohérente</li>
|
||||
* <li>Afficher des messages utilisateur appropriés</li>
|
||||
* <li>Logger les erreurs de manière structurée</li>
|
||||
* <li>Gérer les cas d'erreur spécifiques (connexion, autorisation, validation, etc.)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p><strong>Production-ready:</strong> Gestion complète des erreurs avec logging structuré,
|
||||
* messages utilisateur appropriés, et gestion des cas limites.
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-12-24
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class ErrorHandlerService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(ErrorHandlerService.class);
|
||||
|
||||
@Inject
|
||||
FacesContext facesContext;
|
||||
|
||||
/**
|
||||
* Gère une exception backend et affiche un message approprié à l'utilisateur.
|
||||
*
|
||||
* @param exception L'exception à gérer
|
||||
* @param contextMessage Message contextuel pour le logging (ex: "lors de la création d'une organisation")
|
||||
* @param userFriendlyMessage Message affiché à l'utilisateur (peut être null pour utiliser le message par défaut)
|
||||
*/
|
||||
public void handleException(Exception exception, String contextMessage, String userFriendlyMessage) {
|
||||
if (exception == null) {
|
||||
LOG.warn("handleException appelé avec exception null pour: " + contextMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
// Logging structuré de l'erreur
|
||||
logError(exception, contextMessage);
|
||||
|
||||
// Déterminer le type d'erreur et le message approprié
|
||||
FacesMessage.Severity severity = FacesMessage.SEVERITY_ERROR;
|
||||
String message = userFriendlyMessage != null ? userFriendlyMessage : getDefaultErrorMessage(exception);
|
||||
String detail = getErrorDetail(exception);
|
||||
|
||||
// Gestion spécifique selon le type d'exception
|
||||
if (exception instanceof RestClientExceptionMapper.UnauthorizedException) {
|
||||
severity = FacesMessage.SEVERITY_ERROR;
|
||||
message = "Erreur d'autorisation";
|
||||
detail = "Vous n'êtes pas autorisé à effectuer cette action. Veuillez vérifier vos permissions.";
|
||||
} else if (exception instanceof RestClientExceptionMapper.ForbiddenException) {
|
||||
severity = FacesMessage.SEVERITY_ERROR;
|
||||
message = "Accès interdit";
|
||||
detail = "Vous n'avez pas les permissions nécessaires pour accéder à cette ressource.";
|
||||
} else if (exception instanceof RestClientExceptionMapper.BadRequestException) {
|
||||
severity = FacesMessage.SEVERITY_ERROR;
|
||||
message = "Données invalides";
|
||||
detail = extractValidationErrors(exception.getMessage());
|
||||
} else if (exception instanceof RestClientExceptionMapper.ConflictException) {
|
||||
severity = FacesMessage.SEVERITY_WARN;
|
||||
message = "Conflit";
|
||||
detail = "Cette ressource existe déjà ou est en conflit avec une autre.";
|
||||
} else if (exception instanceof RestClientExceptionMapper.NotFoundException) {
|
||||
severity = FacesMessage.SEVERITY_WARN;
|
||||
message = "Ressource introuvable";
|
||||
detail = "La ressource demandée n'existe pas ou a été supprimée.";
|
||||
} else if (exception instanceof RestClientExceptionMapper.UnprocessableEntityException) {
|
||||
severity = FacesMessage.SEVERITY_ERROR;
|
||||
message = "Données non valides";
|
||||
detail = extractValidationErrors(exception.getMessage());
|
||||
} else if (exception instanceof jakarta.ws.rs.ProcessingException) {
|
||||
severity = FacesMessage.SEVERITY_ERROR;
|
||||
message = "Erreur de connexion";
|
||||
detail = "Impossible de se connecter au serveur. Vérifiez votre connexion réseau et que le serveur est démarré.";
|
||||
} else if (exception instanceof java.net.ConnectException ||
|
||||
exception.getCause() instanceof java.net.ConnectException) {
|
||||
severity = FacesMessage.SEVERITY_ERROR;
|
||||
message = "Serveur inaccessible";
|
||||
detail = "Le serveur backend n'est pas accessible. Vérifiez qu'il est démarré et accessible.";
|
||||
} else if (exception instanceof java.util.concurrent.TimeoutException ||
|
||||
exception.getCause() instanceof java.util.concurrent.TimeoutException) {
|
||||
severity = FacesMessage.SEVERITY_WARN;
|
||||
message = "Délai d'attente dépassé";
|
||||
detail = "La requête a pris trop de temps. Veuillez réessayer.";
|
||||
}
|
||||
|
||||
// Afficher le message à l'utilisateur
|
||||
addFacesMessage(severity, message, detail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gère une erreur de validation et affiche les messages appropriés.
|
||||
*
|
||||
* @param validationErrors Liste des erreurs de validation
|
||||
* @param contextMessage Message contextuel pour le logging
|
||||
*/
|
||||
public void handleValidationErrors(java.util.List<String> validationErrors, String contextMessage) {
|
||||
if (validationErrors == null || validationErrors.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.warnf("Erreurs de validation %s: %d erreur(s)", contextMessage, validationErrors.size());
|
||||
|
||||
// Afficher chaque erreur de validation
|
||||
for (String error : validationErrors) {
|
||||
addFacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur de validation", error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche un message de succès à l'utilisateur.
|
||||
*
|
||||
* @param summary Résumé du message
|
||||
* @param detail Détail du message
|
||||
*/
|
||||
public void showSuccess(String summary, String detail) {
|
||||
addFacesMessage(FacesMessage.SEVERITY_INFO, summary, detail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche un message d'information à l'utilisateur.
|
||||
*
|
||||
* @param summary Résumé du message
|
||||
* @param detail Détail du message
|
||||
*/
|
||||
public void showInfo(String summary, String detail) {
|
||||
addFacesMessage(FacesMessage.SEVERITY_INFO, summary, detail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche un message d'avertissement à l'utilisateur.
|
||||
*
|
||||
* @param summary Résumé du message
|
||||
* @param detail Détail du message
|
||||
*/
|
||||
public void showWarning(String summary, String detail) {
|
||||
addFacesMessage(FacesMessage.SEVERITY_WARN, summary, detail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log l'erreur de manière structurée.
|
||||
*/
|
||||
private void logError(Exception exception, String contextMessage) {
|
||||
String errorId = java.util.UUID.randomUUID().toString().substring(0, 8);
|
||||
|
||||
LOG.errorf("=== ERREUR [%s] ===", errorId);
|
||||
LOG.errorf("Contexte: %s", contextMessage);
|
||||
LOG.errorf("Type: %s", exception.getClass().getName());
|
||||
LOG.errorf("Message: %s", exception.getMessage());
|
||||
|
||||
if (exception.getCause() != null) {
|
||||
LOG.errorf("Cause: %s - %s",
|
||||
exception.getCause().getClass().getName(),
|
||||
exception.getCause().getMessage());
|
||||
}
|
||||
|
||||
// Stack trace complet en mode DEBUG seulement
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.errorf("Stack trace:", exception);
|
||||
} else {
|
||||
// En production, logger seulement les 5 premières lignes du stack trace
|
||||
StackTraceElement[] stackTrace = exception.getStackTrace();
|
||||
int maxLines = Math.min(5, stackTrace.length);
|
||||
for (int i = 0; i < maxLines; i++) {
|
||||
LOG.errorf(" at %s", stackTrace[i]);
|
||||
}
|
||||
}
|
||||
|
||||
LOG.errorf("=== FIN ERREUR [%s] ===", errorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient le message d'erreur par défaut selon le type d'exception.
|
||||
*/
|
||||
private String getDefaultErrorMessage(Exception exception) {
|
||||
if (exception.getMessage() != null && !exception.getMessage().trim().isEmpty()) {
|
||||
return exception.getMessage();
|
||||
}
|
||||
return "Une erreur inattendue s'est produite. Veuillez réessayer.";
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrait les détails d'erreur de validation depuis le message d'exception.
|
||||
*/
|
||||
private String extractValidationErrors(String errorMessage) {
|
||||
if (errorMessage == null || errorMessage.trim().isEmpty()) {
|
||||
return "Les données fournies ne sont pas valides.";
|
||||
}
|
||||
|
||||
// Si le message contient du JSON (erreur de validation Bean Validation)
|
||||
if (errorMessage.contains("\"objectName\"") || errorMessage.contains("\"attributeName\"")) {
|
||||
try {
|
||||
// Essayer d'extraire les informations de validation
|
||||
if (errorMessage.contains("\"attributeName\"")) {
|
||||
// Extraire le nom de l'attribut
|
||||
int attrIndex = errorMessage.indexOf("\"attributeName\"");
|
||||
if (attrIndex > 0) {
|
||||
int start = errorMessage.indexOf("\"", attrIndex + 16) + 1;
|
||||
int end = errorMessage.indexOf("\"", start);
|
||||
if (start > 0 && end > start) {
|
||||
String attributeName = errorMessage.substring(start, end);
|
||||
return "Le champ '" + attributeName + "' contient une valeur invalide.";
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.debugf("Impossible d'extraire les détails de validation: %s", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient le détail de l'erreur pour l'affichage à l'utilisateur.
|
||||
*/
|
||||
private String getErrorDetail(Exception exception) {
|
||||
String message = exception.getMessage();
|
||||
|
||||
// Pour les erreurs de validation, extraire les détails
|
||||
if (exception instanceof RestClientExceptionMapper.BadRequestException ||
|
||||
exception instanceof RestClientExceptionMapper.UnprocessableEntityException) {
|
||||
return extractValidationErrors(message);
|
||||
}
|
||||
|
||||
// Pour les autres erreurs, utiliser le message de l'exception
|
||||
if (message != null && !message.trim().isEmpty()) {
|
||||
// Limiter la longueur du message pour l'utilisateur
|
||||
if (message.length() > 200) {
|
||||
return message.substring(0, 197) + "...";
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
return "Une erreur technique s'est produite. Veuillez contacter le support si le problème persiste.";
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute un message Faces avec gestion du Flash Scope pour les redirections.
|
||||
*/
|
||||
private void addFacesMessage(FacesMessage.Severity severity, String summary, String detail) {
|
||||
if (facesContext == null) {
|
||||
facesContext = FacesContext.getCurrentInstance();
|
||||
}
|
||||
|
||||
if (facesContext != null) {
|
||||
FacesMessage message = new FacesMessage(severity, summary, detail);
|
||||
facesContext.addMessage(null, message);
|
||||
|
||||
// Activer le Flash Scope pour que le message survive à une redirection
|
||||
facesContext.getExternalContext().getFlash().setKeepMessages(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si une exception est une erreur de connexion au backend.
|
||||
*/
|
||||
public boolean isConnectionError(Exception exception) {
|
||||
return exception instanceof jakarta.ws.rs.ProcessingException ||
|
||||
exception instanceof java.net.ConnectException ||
|
||||
(exception.getCause() != null && exception.getCause() instanceof java.net.ConnectException) ||
|
||||
exception instanceof java.util.concurrent.TimeoutException ||
|
||||
(exception.getCause() != null && exception.getCause() instanceof java.util.concurrent.TimeoutException);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si une exception est une erreur d'autorisation.
|
||||
*/
|
||||
public boolean isAuthorizationError(Exception exception) {
|
||||
return exception instanceof RestClientExceptionMapper.UnauthorizedException ||
|
||||
exception instanceof RestClientExceptionMapper.ForbiddenException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si une exception est une erreur de validation.
|
||||
*/
|
||||
public boolean isValidationError(Exception exception) {
|
||||
return exception instanceof RestClientExceptionMapper.BadRequestException ||
|
||||
exception instanceof RestClientExceptionMapper.UnprocessableEntityException;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.client.dto.EvenementDTO;
|
||||
import dev.lions.unionflow.server.api.dto.evenement.response.EvenementResponse;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
@@ -16,6 +17,7 @@ import java.util.UUID;
|
||||
* @version 2.0
|
||||
*/
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(dev.lions.unionflow.client.security.AuthHeaderFactory.class)
|
||||
@Path("/api/evenements")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@@ -37,20 +39,20 @@ public interface EvenementService {
|
||||
*/
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
EvenementDTO obtenirParId(@PathParam("id") UUID id);
|
||||
EvenementResponse obtenirParId(@PathParam("id") UUID id);
|
||||
|
||||
/**
|
||||
* Crée un nouvel événement
|
||||
*/
|
||||
@POST
|
||||
EvenementDTO creer(EvenementDTO evenement);
|
||||
EvenementResponse creer(EvenementResponse evenement);
|
||||
|
||||
/**
|
||||
* Met à jour un événement existant
|
||||
*/
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
EvenementDTO modifier(@PathParam("id") UUID id, EvenementDTO evenement);
|
||||
EvenementResponse modifier(@PathParam("id") UUID id, EvenementResponse evenement);
|
||||
|
||||
/**
|
||||
* Supprime un événement
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.List;
|
||||
@@ -10,6 +11,7 @@ import java.util.UUID;
|
||||
* Service REST client pour l'export des données
|
||||
*/
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(dev.lions.unionflow.client.security.AuthHeaderFactory.class)
|
||||
@Path("/api/export")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public interface ExportClientService {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.favoris.request.*;
|
||||
import dev.lions.unionflow.server.api.dto.favoris.response.*;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Service REST client pour la gestion des favoris
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
*/
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(dev.lions.unionflow.client.security.AuthHeaderFactory.class)
|
||||
@Path("/api/favoris")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public interface FavorisService {
|
||||
|
||||
@GET
|
||||
@Path("/utilisateur/{utilisateurId}")
|
||||
List<FavoriResponse> listerFavoris(@PathParam("utilisateurId") UUID utilisateurId);
|
||||
|
||||
@POST
|
||||
FavoriResponse creerFavori(CreateFavoriRequest request);
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
void supprimerFavori(@PathParam("id") UUID id);
|
||||
|
||||
@GET
|
||||
@Path("/utilisateur/{utilisateurId}/statistiques")
|
||||
Map<String, Object> obtenirStatistiques(@PathParam("utilisateurId") UUID utilisateurId);
|
||||
}
|
||||
|
||||
@@ -1,39 +1,41 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.client.dto.FormulaireDTO;
|
||||
import dev.lions.unionflow.server.api.dto.formuleabonnement.response.FormuleAbonnementResponse;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(dev.lions.unionflow.client.security.AuthHeaderFactory.class)
|
||||
@Path("/api/formulaires")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public interface FormulaireService {
|
||||
|
||||
@GET
|
||||
List<FormulaireDTO> listerTous();
|
||||
List<FormuleAbonnementResponse> listerTous();
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
FormulaireDTO obtenirParId(@PathParam("id") UUID id);
|
||||
FormuleAbonnementResponse obtenirParId(@PathParam("id") UUID id);
|
||||
|
||||
@GET
|
||||
@Path("/actifs")
|
||||
List<FormulaireDTO> listerActifs();
|
||||
List<FormuleAbonnementResponse> listerActifs();
|
||||
|
||||
@GET
|
||||
@Path("/populaires")
|
||||
List<FormulaireDTO> listerPopulaires();
|
||||
List<FormuleAbonnementResponse> listerPopulaires();
|
||||
|
||||
@POST
|
||||
FormulaireDTO creer(FormulaireDTO formulaire);
|
||||
FormuleAbonnementResponse creer(FormuleAbonnementResponse formulaire);
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
FormulaireDTO modifier(@PathParam("id") UUID id, FormulaireDTO formulaire);
|
||||
FormuleAbonnementResponse modifier(@PathParam("id") UUID id, FormuleAbonnementResponse formulaire);
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@@ -41,10 +43,10 @@ public interface FormulaireService {
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/activer")
|
||||
FormulaireDTO activer(@PathParam("id") UUID id);
|
||||
FormuleAbonnementResponse activer(@PathParam("id") UUID id);
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/desactiver")
|
||||
FormulaireDTO desactiver(@PathParam("id") UUID id);
|
||||
FormuleAbonnementResponse desactiver(@PathParam("id") UUID id);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package dev.lions.unionflow.client.service;
|
||||
|
||||
import jakarta.ws.rs.FormParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
|
||||
import org.jboss.resteasy.reactive.PartType;
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.client.dto.MembreDTO;
|
||||
import dev.lions.unionflow.server.api.dto.membre.response.MembreResponse;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
@@ -8,112 +9,120 @@ import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(dev.lions.unionflow.client.security.AuthHeaderFactory.class)
|
||||
@Path("/api/membres")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public interface MembreService {
|
||||
|
||||
|
||||
@GET
|
||||
List<MembreDTO> listerTous();
|
||||
|
||||
List<MembreResponse> listerTous();
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
MembreDTO obtenirParId(@PathParam("id") UUID id);
|
||||
|
||||
MembreResponse obtenirParId(@PathParam("id") UUID id);
|
||||
|
||||
@GET
|
||||
@Path("/numero/{numeroMembre}")
|
||||
MembreDTO obtenirParNumero(@PathParam("numeroMembre") String numeroMembre);
|
||||
|
||||
MembreResponse obtenirParNumero(@PathParam("numeroMembre") String numeroMembre);
|
||||
|
||||
@GET
|
||||
@Path("/search")
|
||||
List<MembreDTO> rechercher(
|
||||
@QueryParam("nom") String nom,
|
||||
@QueryParam("prenom") String prenom,
|
||||
@QueryParam("email") String email,
|
||||
@QueryParam("telephone") String telephone,
|
||||
@QueryParam("statut") String statut,
|
||||
@QueryParam("associationId") UUID associationId,
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size
|
||||
);
|
||||
|
||||
List<MembreResponse> rechercher(
|
||||
@QueryParam("nom") String nom,
|
||||
@QueryParam("prenom") String prenom,
|
||||
@QueryParam("email") String email,
|
||||
@QueryParam("telephone") String telephone,
|
||||
@QueryParam("statut") String statut,
|
||||
@QueryParam("associationId") UUID associationId,
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size);
|
||||
|
||||
@POST
|
||||
@Path("/search/advanced")
|
||||
dev.lions.unionflow.server.api.dto.membre.MembreSearchResultDTO rechercherAvance(
|
||||
dev.lions.unionflow.server.api.dto.membre.MembreSearchCriteria criteria,
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size,
|
||||
@QueryParam("sort") @DefaultValue("nom") String sortField,
|
||||
@QueryParam("direction") @DefaultValue("asc") String sortDirection);
|
||||
|
||||
@GET
|
||||
@Path("/association/{associationId}")
|
||||
List<MembreDTO> listerParAssociation(@PathParam("associationId") UUID associationId);
|
||||
|
||||
List<MembreResponse> listerParAssociation(@PathParam("associationId") UUID associationId);
|
||||
|
||||
@GET
|
||||
@Path("/actifs")
|
||||
List<MembreDTO> listerActifs();
|
||||
|
||||
List<MembreResponse> listerActifs();
|
||||
|
||||
@GET
|
||||
@Path("/inactifs")
|
||||
List<MembreDTO> listerInactifs();
|
||||
|
||||
List<MembreResponse> listerInactifs();
|
||||
|
||||
@POST
|
||||
MembreDTO creer(MembreDTO membre);
|
||||
|
||||
MembreResponse creer(MembreResponse membre);
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
MembreDTO modifier(@PathParam("id") UUID id, MembreDTO membre);
|
||||
|
||||
MembreResponse modifier(@PathParam("id") UUID id, MembreResponse membre);
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
void supprimer(@PathParam("id") UUID id);
|
||||
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/activer")
|
||||
MembreDTO activer(@PathParam("id") UUID id);
|
||||
|
||||
MembreResponse activer(@PathParam("id") UUID id);
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/desactiver")
|
||||
MembreDTO desactiver(@PathParam("id") UUID id);
|
||||
|
||||
MembreResponse desactiver(@PathParam("id") UUID id);
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/suspendre")
|
||||
MembreDTO suspendre(@PathParam("id") UUID id);
|
||||
|
||||
MembreResponse suspendre(@PathParam("id") UUID id);
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/radier")
|
||||
MembreDTO radier(@PathParam("id") UUID id);
|
||||
|
||||
MembreResponse radier(@PathParam("id") UUID id);
|
||||
|
||||
@GET
|
||||
@Path("/statistiques")
|
||||
@Path("/stats")
|
||||
StatistiquesMembreDTO obtenirStatistiques();
|
||||
|
||||
|
||||
@GET
|
||||
@Path("/export")
|
||||
@Produces({"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "text/csv", "application/pdf", "application/json"})
|
||||
@Produces({ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "text/csv", "application/pdf",
|
||||
"application/json" })
|
||||
byte[] exporterExcel(
|
||||
@QueryParam("format") @DefaultValue("EXCEL") String format,
|
||||
@QueryParam("associationId") UUID associationId,
|
||||
@QueryParam("statut") String statut,
|
||||
@QueryParam("type") String type,
|
||||
@QueryParam("dateAdhesionDebut") String dateAdhesionDebut,
|
||||
@QueryParam("dateAdhesionFin") String dateAdhesionFin,
|
||||
@QueryParam("colonnes") List<String> colonnesExport,
|
||||
@QueryParam("inclureHeaders") @DefaultValue("true") boolean inclureHeaders,
|
||||
@QueryParam("formaterDates") @DefaultValue("true") boolean formaterDates,
|
||||
@QueryParam("inclureStatistiques") @DefaultValue("false") boolean inclureStatistiques,
|
||||
@QueryParam("motDePasse") String motDePasse
|
||||
);
|
||||
|
||||
@QueryParam("format") @DefaultValue("EXCEL") String format,
|
||||
@QueryParam("associationId") UUID associationId,
|
||||
@QueryParam("statut") String statut,
|
||||
@QueryParam("type") String type,
|
||||
@QueryParam("dateAdhesionDebut") String dateAdhesionDebut,
|
||||
@QueryParam("dateAdhesionFin") String dateAdhesionFin,
|
||||
@QueryParam("colonnes") List<String> colonnesExport,
|
||||
@QueryParam("inclureHeaders") @DefaultValue("true") boolean inclureHeaders,
|
||||
@QueryParam("formaterDates") @DefaultValue("true") boolean formaterDates,
|
||||
@QueryParam("inclureStatistiques") @DefaultValue("false") boolean inclureStatistiques,
|
||||
@QueryParam("motDePasse") String motDePasse);
|
||||
|
||||
@GET
|
||||
@Path("/export/count")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
Long compterMembresPourExport(
|
||||
@QueryParam("associationId") UUID associationId,
|
||||
@QueryParam("statut") String statut,
|
||||
@QueryParam("type") String type,
|
||||
@QueryParam("dateAdhesionDebut") String dateAdhesionDebut,
|
||||
@QueryParam("dateAdhesionFin") String dateAdhesionFin
|
||||
);
|
||||
|
||||
@QueryParam("associationId") UUID associationId,
|
||||
@QueryParam("statut") String statut,
|
||||
@QueryParam("type") String type,
|
||||
@QueryParam("dateAdhesionDebut") String dateAdhesionDebut,
|
||||
@QueryParam("dateAdhesionFin") String dateAdhesionFin);
|
||||
|
||||
@POST
|
||||
@Path("/import")
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
ResultatImportDTO importerDonnees(MembreImportMultipartForm form);
|
||||
|
||||
|
||||
@GET
|
||||
@Path("/import/modele")
|
||||
@Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
@@ -132,9 +141,9 @@ public interface MembreService {
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
byte[] exporterSelection(
|
||||
List<UUID> membreIds,
|
||||
@QueryParam("format") @DefaultValue("EXCEL") String format);
|
||||
|
||||
List<UUID> membreIds,
|
||||
@QueryParam("format") @DefaultValue("EXCEL") String format);
|
||||
|
||||
// Classes DTO internes pour les réponses spécialisées
|
||||
class StatistiquesMembreDTO {
|
||||
public Long totalMembres;
|
||||
@@ -145,60 +154,127 @@ public interface MembreService {
|
||||
public Long nouveauxMembres30Jours;
|
||||
public Double tauxActivite;
|
||||
public Double tauxCroissance;
|
||||
|
||||
|
||||
// Constructeurs
|
||||
public StatistiquesMembreDTO() {}
|
||||
|
||||
public StatistiquesMembreDTO() {
|
||||
}
|
||||
|
||||
// Getters et setters
|
||||
public Long getTotalMembres() { return totalMembres; }
|
||||
public void setTotalMembres(Long totalMembres) { this.totalMembres = totalMembres; }
|
||||
|
||||
public Long getMembresActifs() { return membresActifs; }
|
||||
public void setMembresActifs(Long membresActifs) { this.membresActifs = membresActifs; }
|
||||
|
||||
public Long getMembresInactifs() { return membresInactifs; }
|
||||
public void setMembresInactifs(Long membresInactifs) { this.membresInactifs = membresInactifs; }
|
||||
|
||||
public Long getMembresSuspendus() { return membresSuspendus; }
|
||||
public void setMembresSuspendus(Long membresSuspendus) { this.membresSuspendus = membresSuspendus; }
|
||||
|
||||
public Long getMembresRadies() { return membresRadies; }
|
||||
public void setMembresRadies(Long membresRadies) { this.membresRadies = membresRadies; }
|
||||
|
||||
public Long getNouveauxMembres30Jours() { return nouveauxMembres30Jours; }
|
||||
public void setNouveauxMembres30Jours(Long nouveauxMembres30Jours) { this.nouveauxMembres30Jours = nouveauxMembres30Jours; }
|
||||
|
||||
public Double getTauxActivite() { return tauxActivite; }
|
||||
public void setTauxActivite(Double tauxActivite) { this.tauxActivite = tauxActivite; }
|
||||
|
||||
public Double getTauxCroissance() { return tauxCroissance; }
|
||||
public void setTauxCroissance(Double tauxCroissance) { this.tauxCroissance = tauxCroissance; }
|
||||
public Long getTotalMembres() {
|
||||
return totalMembres;
|
||||
}
|
||||
|
||||
public void setTotalMembres(Long totalMembres) {
|
||||
this.totalMembres = totalMembres;
|
||||
}
|
||||
|
||||
public Long getMembresActifs() {
|
||||
return membresActifs;
|
||||
}
|
||||
|
||||
public void setMembresActifs(Long membresActifs) {
|
||||
this.membresActifs = membresActifs;
|
||||
}
|
||||
|
||||
public Long getMembresInactifs() {
|
||||
return membresInactifs;
|
||||
}
|
||||
|
||||
public void setMembresInactifs(Long membresInactifs) {
|
||||
this.membresInactifs = membresInactifs;
|
||||
}
|
||||
|
||||
public Long getMembresSuspendus() {
|
||||
return membresSuspendus;
|
||||
}
|
||||
|
||||
public void setMembresSuspendus(Long membresSuspendus) {
|
||||
this.membresSuspendus = membresSuspendus;
|
||||
}
|
||||
|
||||
public Long getMembresRadies() {
|
||||
return membresRadies;
|
||||
}
|
||||
|
||||
public void setMembresRadies(Long membresRadies) {
|
||||
this.membresRadies = membresRadies;
|
||||
}
|
||||
|
||||
public Long getNouveauxMembres30Jours() {
|
||||
return nouveauxMembres30Jours;
|
||||
}
|
||||
|
||||
public void setNouveauxMembres30Jours(Long nouveauxMembres30Jours) {
|
||||
this.nouveauxMembres30Jours = nouveauxMembres30Jours;
|
||||
}
|
||||
|
||||
public Double getTauxActivite() {
|
||||
return tauxActivite;
|
||||
}
|
||||
|
||||
public void setTauxActivite(Double tauxActivite) {
|
||||
this.tauxActivite = tauxActivite;
|
||||
}
|
||||
|
||||
public Double getTauxCroissance() {
|
||||
return tauxCroissance;
|
||||
}
|
||||
|
||||
public void setTauxCroissance(Double tauxCroissance) {
|
||||
this.tauxCroissance = tauxCroissance;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ResultatImportDTO {
|
||||
public Integer totalLignes;
|
||||
public Integer lignesTraitees;
|
||||
public Integer lignesErreur;
|
||||
public List<String> erreurs;
|
||||
public List<MembreDTO> membresImportes;
|
||||
|
||||
public List<MembreResponse> membresImportes;
|
||||
|
||||
// Constructeurs
|
||||
public ResultatImportDTO() {}
|
||||
|
||||
public ResultatImportDTO() {
|
||||
}
|
||||
|
||||
// Getters et setters
|
||||
public Integer getTotalLignes() { return totalLignes; }
|
||||
public void setTotalLignes(Integer totalLignes) { this.totalLignes = totalLignes; }
|
||||
|
||||
public Integer getLignesTraitees() { return lignesTraitees; }
|
||||
public void setLignesTraitees(Integer lignesTraitees) { this.lignesTraitees = lignesTraitees; }
|
||||
|
||||
public Integer getLignesErreur() { return lignesErreur; }
|
||||
public void setLignesErreur(Integer lignesErreur) { this.lignesErreur = lignesErreur; }
|
||||
|
||||
public List<String> getErreurs() { return erreurs; }
|
||||
public void setErreurs(List<String> erreurs) { this.erreurs = erreurs; }
|
||||
|
||||
public List<MembreDTO> getMembresImportes() { return membresImportes; }
|
||||
public void setMembresImportes(List<MembreDTO> membresImportes) { this.membresImportes = membresImportes; }
|
||||
public Integer getTotalLignes() {
|
||||
return totalLignes;
|
||||
}
|
||||
|
||||
public void setTotalLignes(Integer totalLignes) {
|
||||
this.totalLignes = totalLignes;
|
||||
}
|
||||
|
||||
public Integer getLignesTraitees() {
|
||||
return lignesTraitees;
|
||||
}
|
||||
|
||||
public void setLignesTraitees(Integer lignesTraitees) {
|
||||
this.lignesTraitees = lignesTraitees;
|
||||
}
|
||||
|
||||
public Integer getLignesErreur() {
|
||||
return lignesErreur;
|
||||
}
|
||||
|
||||
public void setLignesErreur(Integer lignesErreur) {
|
||||
this.lignesErreur = lignesErreur;
|
||||
}
|
||||
|
||||
public List<String> getErreurs() {
|
||||
return erreurs;
|
||||
}
|
||||
|
||||
public void setErreurs(List<String> erreurs) {
|
||||
this.erreurs = erreurs;
|
||||
}
|
||||
|
||||
public List<MembreResponse> getMembresImportes() {
|
||||
return membresImportes;
|
||||
}
|
||||
|
||||
public void setMembresImportes(List<MembreResponse> membresImportes) {
|
||||
this.membresImportes = membresImportes;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* Service de monitoring et métriques pour la production
|
||||
*
|
||||
* Collecte et expose des métriques applicatives :
|
||||
* - Compteurs d'appels backend
|
||||
* - Temps de réponse (min, max, moyenne)
|
||||
* - Taux d'erreurs
|
||||
* - Utilisation du cache
|
||||
* - Statistiques de retry
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2026-01-04
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class MetricsService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(MetricsService.class);
|
||||
|
||||
// Compteurs d'appels backend
|
||||
private final Map<String, AtomicLong> backendCallCounters = new ConcurrentHashMap<>();
|
||||
private final Map<String, AtomicLong> backendSuccessCounters = new ConcurrentHashMap<>();
|
||||
private final Map<String, AtomicLong> backendErrorCounters = new ConcurrentHashMap<>();
|
||||
|
||||
// Temps de réponse
|
||||
private final Map<String, ResponseTimeStats> responseTimeStats = new ConcurrentHashMap<>();
|
||||
|
||||
// Cache metrics
|
||||
private final AtomicLong cacheHits = new AtomicLong(0);
|
||||
private final AtomicLong cacheMisses = new AtomicLong(0);
|
||||
|
||||
// Retry metrics
|
||||
private final AtomicLong retryAttempts = new AtomicLong(0);
|
||||
private final AtomicLong retrySuccesses = new AtomicLong(0);
|
||||
private final AtomicLong retryFailures = new AtomicLong(0);
|
||||
|
||||
// Validation metrics
|
||||
private final AtomicLong validationSuccesses = new AtomicLong(0);
|
||||
private final AtomicLong validationFailures = new AtomicLong(0);
|
||||
|
||||
// Timestamp de démarrage
|
||||
private final Instant startTime = Instant.now();
|
||||
|
||||
/**
|
||||
* Enregistre un appel backend
|
||||
*/
|
||||
public void recordBackendCall(String serviceName, boolean success, long durationMs) {
|
||||
backendCallCounters.computeIfAbsent(serviceName, k -> new AtomicLong(0)).incrementAndGet();
|
||||
|
||||
if (success) {
|
||||
backendSuccessCounters.computeIfAbsent(serviceName, k -> new AtomicLong(0)).incrementAndGet();
|
||||
} else {
|
||||
backendErrorCounters.computeIfAbsent(serviceName, k -> new AtomicLong(0)).incrementAndGet();
|
||||
}
|
||||
|
||||
// Enregistrer le temps de réponse
|
||||
ResponseTimeStats stats = responseTimeStats.computeIfAbsent(serviceName, k -> new ResponseTimeStats());
|
||||
stats.record(durationMs);
|
||||
|
||||
LOG.debugf("Métrique backend: %s - success=%b, durée=%dms", serviceName, success, durationMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre un hit de cache
|
||||
*/
|
||||
public void recordCacheHit() {
|
||||
cacheHits.incrementAndGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre un miss de cache
|
||||
*/
|
||||
public void recordCacheMiss() {
|
||||
cacheMisses.incrementAndGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre une tentative de retry
|
||||
*/
|
||||
public void recordRetryAttempt() {
|
||||
retryAttempts.incrementAndGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre un retry réussi
|
||||
*/
|
||||
public void recordRetrySuccess() {
|
||||
retrySuccesses.incrementAndGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre un retry échoué
|
||||
*/
|
||||
public void recordRetryFailure() {
|
||||
retryFailures.incrementAndGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre une validation
|
||||
*/
|
||||
public void recordValidation(boolean success) {
|
||||
if (success) {
|
||||
validationSuccesses.incrementAndGet();
|
||||
} else {
|
||||
validationFailures.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient les métriques globales
|
||||
*/
|
||||
public Map<String, Object> getGlobalMetrics() {
|
||||
Map<String, Object> metrics = new HashMap<>();
|
||||
|
||||
// Uptime
|
||||
Duration uptime = Duration.between(startTime, Instant.now());
|
||||
metrics.put("uptime_seconds", uptime.getSeconds());
|
||||
metrics.put("uptime_hours", uptime.toHours());
|
||||
|
||||
// Backend calls
|
||||
long totalCalls = backendCallCounters.values().stream()
|
||||
.mapToLong(AtomicLong::get)
|
||||
.sum();
|
||||
long totalSuccesses = backendSuccessCounters.values().stream()
|
||||
.mapToLong(AtomicLong::get)
|
||||
.sum();
|
||||
long totalErrors = backendErrorCounters.values().stream()
|
||||
.mapToLong(AtomicLong::get)
|
||||
.sum();
|
||||
|
||||
metrics.put("backend_calls_total", totalCalls);
|
||||
metrics.put("backend_calls_success", totalSuccesses);
|
||||
metrics.put("backend_calls_errors", totalErrors);
|
||||
metrics.put("backend_success_rate", totalCalls > 0 ? (double) totalSuccesses / totalCalls * 100 : 0);
|
||||
|
||||
// Cache
|
||||
long totalCacheAccess = cacheHits.get() + cacheMisses.get();
|
||||
metrics.put("cache_hits", cacheHits.get());
|
||||
metrics.put("cache_misses", cacheMisses.get());
|
||||
metrics.put("cache_hit_rate", totalCacheAccess > 0 ? (double) cacheHits.get() / totalCacheAccess * 100 : 0);
|
||||
|
||||
// Retry
|
||||
metrics.put("retry_attempts", retryAttempts.get());
|
||||
metrics.put("retry_successes", retrySuccesses.get());
|
||||
metrics.put("retry_failures", retryFailures.get());
|
||||
metrics.put("retry_success_rate", retryAttempts.get() > 0 ? (double) retrySuccesses.get() / retryAttempts.get() * 100 : 0);
|
||||
|
||||
// Validation
|
||||
long totalValidations = validationSuccesses.get() + validationFailures.get();
|
||||
metrics.put("validation_successes", validationSuccesses.get());
|
||||
metrics.put("validation_failures", validationFailures.get());
|
||||
metrics.put("validation_success_rate", totalValidations > 0 ? (double) validationSuccesses.get() / totalValidations * 100 : 0);
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient les métriques par service
|
||||
*/
|
||||
public Map<String, Map<String, Object>> getServiceMetrics() {
|
||||
Map<String, Map<String, Object>> serviceMetrics = new HashMap<>();
|
||||
|
||||
for (String serviceName : backendCallCounters.keySet()) {
|
||||
Map<String, Object> metrics = new HashMap<>();
|
||||
|
||||
long calls = backendCallCounters.getOrDefault(serviceName, new AtomicLong(0)).get();
|
||||
long successes = backendSuccessCounters.getOrDefault(serviceName, new AtomicLong(0)).get();
|
||||
long errors = backendErrorCounters.getOrDefault(serviceName, new AtomicLong(0)).get();
|
||||
|
||||
metrics.put("calls", calls);
|
||||
metrics.put("successes", successes);
|
||||
metrics.put("errors", errors);
|
||||
metrics.put("success_rate", calls > 0 ? (double) successes / calls * 100 : 0);
|
||||
|
||||
ResponseTimeStats stats = responseTimeStats.get(serviceName);
|
||||
if (stats != null) {
|
||||
metrics.put("response_time_min_ms", stats.getMin());
|
||||
metrics.put("response_time_max_ms", stats.getMax());
|
||||
metrics.put("response_time_avg_ms", stats.getAverage());
|
||||
metrics.put("response_time_p95_ms", stats.getP95());
|
||||
}
|
||||
|
||||
serviceMetrics.put(serviceName, metrics);
|
||||
}
|
||||
|
||||
return serviceMetrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient un résumé des métriques sous forme de String (pour logging)
|
||||
*/
|
||||
public String getMetricsSummary() {
|
||||
Map<String, Object> metrics = getGlobalMetrics();
|
||||
return String.format(
|
||||
"Métriques UnionFlow - Uptime: %dh, Backend: %d appels (%d%% succès), " +
|
||||
"Cache: %d hits (%d%% hit rate), Retry: %d tentatives (%d%% succès)",
|
||||
metrics.get("uptime_hours"),
|
||||
metrics.get("backend_calls_total"),
|
||||
Math.round((Double) metrics.get("backend_success_rate")),
|
||||
metrics.get("cache_hits"),
|
||||
Math.round((Double) metrics.get("cache_hit_rate")),
|
||||
metrics.get("retry_attempts"),
|
||||
Math.round((Double) metrics.get("retry_success_rate"))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Réinitialise toutes les métriques
|
||||
*/
|
||||
public void resetMetrics() {
|
||||
LOG.info("Réinitialisation de toutes les métriques");
|
||||
|
||||
backendCallCounters.clear();
|
||||
backendSuccessCounters.clear();
|
||||
backendErrorCounters.clear();
|
||||
responseTimeStats.clear();
|
||||
|
||||
cacheHits.set(0);
|
||||
cacheMisses.set(0);
|
||||
retryAttempts.set(0);
|
||||
retrySuccesses.set(0);
|
||||
retryFailures.set(0);
|
||||
validationSuccesses.set(0);
|
||||
validationFailures.set(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log les métriques actuelles
|
||||
*/
|
||||
public void logMetrics() {
|
||||
LOG.info(getMetricsSummary());
|
||||
|
||||
// Détail par service
|
||||
Map<String, Map<String, Object>> serviceMetrics = getServiceMetrics();
|
||||
for (Map.Entry<String, Map<String, Object>> entry : serviceMetrics.entrySet()) {
|
||||
Map<String, Object> metrics = entry.getValue();
|
||||
LOG.infof("Service %s: %d appels, %d ms avg, %d%% succès",
|
||||
entry.getKey(),
|
||||
metrics.get("calls"),
|
||||
metrics.getOrDefault("response_time_avg_ms", 0L),
|
||||
Math.round((Double) metrics.getOrDefault("success_rate", 0.0)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Classe interne pour statistiques de temps de réponse
|
||||
*/
|
||||
private static class ResponseTimeStats {
|
||||
private long min = Long.MAX_VALUE;
|
||||
private long max = Long.MIN_VALUE;
|
||||
private long sum = 0;
|
||||
private long count = 0;
|
||||
private final List<Long> samples = new ArrayList<>();
|
||||
private static final int MAX_SAMPLES = 100; // Garder les 100 derniers pour P95
|
||||
|
||||
public synchronized void record(long durationMs) {
|
||||
min = Math.min(min, durationMs);
|
||||
max = Math.max(max, durationMs);
|
||||
sum += durationMs;
|
||||
count++;
|
||||
|
||||
samples.add(durationMs);
|
||||
if (samples.size() > MAX_SAMPLES) {
|
||||
samples.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
public long getMin() {
|
||||
return min == Long.MAX_VALUE ? 0 : min;
|
||||
}
|
||||
|
||||
public long getMax() {
|
||||
return max == Long.MIN_VALUE ? 0 : max;
|
||||
}
|
||||
|
||||
public long getAverage() {
|
||||
return count > 0 ? sum / count : 0;
|
||||
}
|
||||
|
||||
public long getP95() {
|
||||
if (samples.isEmpty()) return 0;
|
||||
|
||||
List<Long> sorted = new ArrayList<>(samples);
|
||||
sorted.sort(Long::compareTo);
|
||||
int index = (int) Math.ceil(sorted.size() * 0.95) - 1;
|
||||
return sorted.get(Math.max(0, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.List;
|
||||
@@ -10,6 +11,7 @@ import java.util.Map;
|
||||
* Service REST client pour les notifications
|
||||
*/
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(dev.lions.unionflow.client.security.AuthHeaderFactory.class)
|
||||
@Path("/api/notifications")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.notification.request.*;
|
||||
import dev.lions.unionflow.server.api.dto.notification.response.*;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.List;
|
||||
@@ -8,17 +11,74 @@ import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Service REST Client pour la gestion des notifications (WOU/DRY)
|
||||
* Service REST Client pour la gestion des notifications
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
*/
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(dev.lions.unionflow.client.security.AuthHeaderFactory.class)
|
||||
@Path("/api/notifications")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public interface NotificationService {
|
||||
|
||||
// ========================================
|
||||
// TEMPLATES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Crée un nouveau template de notification
|
||||
*/
|
||||
@POST
|
||||
@Path("/templates")
|
||||
TemplateNotificationResponse creerTemplate(CreateTemplateNotificationRequest request);
|
||||
|
||||
// ========================================
|
||||
// NOTIFICATIONS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Crée une nouvelle notification
|
||||
*/
|
||||
@POST
|
||||
NotificationResponse creerNotification(CreateNotificationRequest request);
|
||||
|
||||
/**
|
||||
* Marque une notification comme lue
|
||||
*/
|
||||
@POST
|
||||
@Path("/{id}/marquer-lue")
|
||||
NotificationResponse marquerCommeLue(@PathParam("id") UUID id);
|
||||
|
||||
/**
|
||||
* Trouve une notification par son ID
|
||||
*/
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
NotificationResponse obtenirNotification(@PathParam("id") UUID id);
|
||||
|
||||
/**
|
||||
* Liste toutes les notifications d'un membre
|
||||
*/
|
||||
@GET
|
||||
@Path("/membre/{membreId}")
|
||||
List<NotificationResponse> listerNotificationsParMembre(@PathParam("membreId") UUID membreId);
|
||||
|
||||
/**
|
||||
* Liste les notifications non lues d'un membre
|
||||
*/
|
||||
@GET
|
||||
@Path("/membre/{membreId}/non-lues")
|
||||
List<NotificationResponse> listerNotificationsNonLuesParMembre(@PathParam("membreId") UUID membreId);
|
||||
|
||||
/**
|
||||
* Liste les notifications en attente d'envoi
|
||||
*/
|
||||
@GET
|
||||
@Path("/en-attente-envoi")
|
||||
List<NotificationResponse> listerNotificationsEnAttenteEnvoi();
|
||||
|
||||
/**
|
||||
* Envoie des notifications groupées à plusieurs membres (WOU/DRY)
|
||||
*
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(dev.lions.unionflow.client.security.AuthHeaderFactory.class)
|
||||
@Path("/api/preferences")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
|
||||
@@ -1,41 +1,58 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import jakarta.ws.rs.core.MultivaluedMap;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class RestClientExceptionMapper implements ResponseExceptionMapper<RuntimeException> {
|
||||
|
||||
@Override
|
||||
public RuntimeException toThrowable(Response response) {
|
||||
int status = response.getStatus();
|
||||
String reasonPhrase = response.getStatusInfo().getReasonPhrase();
|
||||
|
||||
// Lire le corps de la réponse pour plus de détails
|
||||
String body = "";
|
||||
java.util.logging.Logger logger = java.util.logging.Logger.getLogger(RestClientExceptionMapper.class.getName());
|
||||
|
||||
// Logger l'URL et le statut pour debugging
|
||||
try {
|
||||
if (response.hasEntity()) {
|
||||
body = response.readEntity(String.class);
|
||||
if (response.getLocation() != null) {
|
||||
logger.severe("Erreur backend - URL: " + response.getLocation() + " - Status: " + status);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
body = "Impossible de lire le détail de l'erreur";
|
||||
// Ignorer si on ne peut pas obtenir l'URL
|
||||
}
|
||||
|
||||
|
||||
// Lire le corps de la réponse pour plus de détails
|
||||
// SÉCURITÉ: Ne pas exposer les détails des erreurs serveur (5xx) au client
|
||||
String body = "";
|
||||
boolean shouldIncludeBody = status >= 400 && status < 500; // Seulement pour erreurs client (4xx)
|
||||
|
||||
if (shouldIncludeBody) {
|
||||
try {
|
||||
if (response.hasEntity()) {
|
||||
body = response.readEntity(String.class);
|
||||
logger.severe("Corps de la réponse (4xx): " + body);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
body = "Impossible de lire le détail de l'erreur";
|
||||
logger.warning("Impossible de lire le corps de la réponse: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Logger toutes les erreurs pour debugging
|
||||
logger.severe("Erreur backend - HTTP " + status + " (" + reasonPhrase + ")");
|
||||
|
||||
return switch (status) {
|
||||
case 400 -> new BadRequestException("Requête invalide: " + body);
|
||||
case 401 -> new UnauthorizedException("Non autorisé: " + reasonPhrase);
|
||||
case 403 -> new ForbiddenException("Accès interdit: " + reasonPhrase);
|
||||
case 404 -> new NotFoundException("Ressource non trouvée: " + reasonPhrase);
|
||||
case 401 -> new UnauthorizedException("Non autorisé");
|
||||
case 403 -> new ForbiddenException("Accès interdit");
|
||||
case 404 -> new NotFoundException("Ressource non trouvée");
|
||||
case 409 -> new ConflictException("Conflit: " + body);
|
||||
case 422 -> new UnprocessableEntityException("Données non valides: " + body);
|
||||
case 500 -> new InternalServerErrorException("Erreur serveur interne: " + body);
|
||||
case 502 -> new BadGatewayException("Erreur de passerelle: " + reasonPhrase);
|
||||
case 503 -> new ServiceUnavailableException("Service indisponible: " + reasonPhrase);
|
||||
case 504 -> new GatewayTimeoutException("Timeout de passerelle: " + reasonPhrase);
|
||||
default -> new UnknownHttpStatusException("Erreur HTTP " + status + ": " + reasonPhrase + (body.isEmpty() ? "" : " - " + body));
|
||||
// SÉCURITÉ: Erreurs 5xx - Messages génériques sans détails backend
|
||||
case 500 -> new InternalServerErrorException("Erreur serveur interne. Veuillez réessayer ultérieurement.");
|
||||
case 502 -> new BadGatewayException("Service temporairement indisponible");
|
||||
case 503 -> new ServiceUnavailableException("Service indisponible. Veuillez réessayer ultérieurement.");
|
||||
case 504 -> new GatewayTimeoutException("Délai d'attente dépassé. Veuillez réessayer.");
|
||||
default -> new UnknownHttpStatusException("Une erreur est survenue. Veuillez réessayer.");
|
||||
};
|
||||
}
|
||||
|
||||
@@ -83,4 +100,4 @@ public class RestClientExceptionMapper implements ResponseExceptionMapper<Runtim
|
||||
public static class UnknownHttpStatusException extends RuntimeException {
|
||||
public UnknownHttpStatusException(String message) { super(message); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Service de retry automatique pour les appels backend en cas d'échec temporaire.
|
||||
*
|
||||
* <p>Ce service implémente une stratégie de retry intelligente pour gérer:
|
||||
* - Erreurs temporaires (503 Service Unavailable, 504 Gateway Timeout)
|
||||
* - Timeouts réseau
|
||||
* - Erreurs de connexion
|
||||
*
|
||||
* <p><strong>Production-ready:</strong> Gestion complète des retries avec backoff exponentiel,
|
||||
* limite de tentatives, et logging approprié.
|
||||
*
|
||||
* <p><strong>Usage:</strong>
|
||||
* <pre>{@code
|
||||
* @Inject
|
||||
* RetryService retryService;
|
||||
*
|
||||
* OrganisationResponse org = retryService.executeWithRetry(
|
||||
* () -> associationService.creer(nouvelleOrganisation),
|
||||
* "création d'une organisation"
|
||||
* );
|
||||
* }</pre>
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-12-24
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class RetryService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(RetryService.class);
|
||||
|
||||
@Inject
|
||||
MetricsService metricsService;
|
||||
|
||||
/**
|
||||
* Nombre maximum de tentatives (incluant la première).
|
||||
*/
|
||||
private static final int MAX_ATTEMPTS = 3;
|
||||
|
||||
/**
|
||||
* Délai initial entre les tentatives en millisecondes.
|
||||
*/
|
||||
private static final long INITIAL_DELAY_MS = 1000;
|
||||
|
||||
/**
|
||||
* Multiplicateur pour le backoff exponentiel.
|
||||
*/
|
||||
private static final double BACKOFF_MULTIPLIER = 2.0;
|
||||
|
||||
/**
|
||||
* Exécute une opération avec retry automatique en cas d'échec temporaire.
|
||||
*
|
||||
* @param operation L'opération à exécuter (Supplier)
|
||||
* @param contextMessage Message contextuel pour le logging (ex: "création d'une organisation")
|
||||
* @return Le résultat de l'opération
|
||||
* @throws Exception Si toutes les tentatives échouent ou si l'erreur n'est pas retryable
|
||||
*/
|
||||
public <T> T executeWithRetrySupplier(Supplier<T> operation, String contextMessage) throws Exception {
|
||||
return executeWithRetry(() -> operation.get(), contextMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exécute une opération avec retry automatique en cas d'échec temporaire.
|
||||
*
|
||||
* @param operation L'opération à exécuter (Callable)
|
||||
* @param contextMessage Message contextuel pour le logging
|
||||
* @return Le résultat de l'opération
|
||||
* @throws Exception Si toutes les tentatives échouent ou si l'erreur n'est pas retryable
|
||||
*/
|
||||
public <T> T executeWithRetry(Callable<T> operation, String contextMessage) throws Exception {
|
||||
Exception lastException = null;
|
||||
|
||||
for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
||||
try {
|
||||
LOG.debugf("Tentative %d/%d pour: %s", attempt, MAX_ATTEMPTS, contextMessage);
|
||||
|
||||
T result = operation.call();
|
||||
|
||||
if (attempt > 1) {
|
||||
LOG.infof("Succès après %d tentative(s) pour: %s", attempt, contextMessage);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
lastException = e;
|
||||
|
||||
// Vérifier si l'erreur est retryable
|
||||
if (!isRetryable(e)) {
|
||||
LOG.debugf("Erreur non retryable pour: %s - %s", contextMessage, e.getClass().getSimpleName());
|
||||
throw e; // Ne pas retryer
|
||||
}
|
||||
|
||||
// Si c'est la dernière tentative, re-lancer l'exception
|
||||
if (attempt == MAX_ATTEMPTS) {
|
||||
LOG.errorf(e, "Échec après %d tentative(s) pour: %s", MAX_ATTEMPTS, contextMessage);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Calculer le délai avant la prochaine tentative (backoff exponentiel)
|
||||
long delay = calculateDelay(attempt);
|
||||
LOG.warnf("Tentative %d/%d échouée pour: %s - Retry dans %d ms - Erreur: %s",
|
||||
attempt, MAX_ATTEMPTS, contextMessage, delay, e.getMessage());
|
||||
|
||||
// Attendre avant la prochaine tentative
|
||||
try {
|
||||
Thread.sleep(delay);
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("Retry interrompu", ie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ne devrait jamais arriver ici, mais au cas où
|
||||
throw lastException != null ? lastException : new RuntimeException("Échec inattendu");
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si une exception est retryable (erreur temporaire).
|
||||
*/
|
||||
private boolean isRetryable(Exception e) {
|
||||
// Erreurs de connexion (retryable)
|
||||
if (e instanceof java.net.ConnectException ||
|
||||
e instanceof java.net.SocketTimeoutException ||
|
||||
e instanceof java.util.concurrent.TimeoutException ||
|
||||
e.getCause() instanceof java.net.ConnectException ||
|
||||
e.getCause() instanceof java.net.SocketTimeoutException ||
|
||||
e.getCause() instanceof java.util.concurrent.TimeoutException) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Erreurs REST Client spécifiques
|
||||
if (e instanceof RestClientExceptionMapper.ServiceUnavailableException ||
|
||||
e instanceof RestClientExceptionMapper.GatewayTimeoutException ||
|
||||
e instanceof RestClientExceptionMapper.BadGatewayException) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Erreurs ProcessingException (connexion, timeout, etc.)
|
||||
if (e instanceof jakarta.ws.rs.ProcessingException) {
|
||||
String message = e.getMessage();
|
||||
if (message != null && (
|
||||
message.contains("Connection") ||
|
||||
message.contains("timeout") ||
|
||||
message.contains("refused") ||
|
||||
message.contains("unreachable"))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Erreurs 5xx (sauf 500 qui peut être une erreur permanente)
|
||||
if (e instanceof RestClientExceptionMapper.InternalServerErrorException) {
|
||||
// 500 peut être retryable si c'est une erreur temporaire du serveur
|
||||
return true;
|
||||
}
|
||||
|
||||
// Par défaut, ne pas retryer (erreurs 4xx, validation, etc.)
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule le délai avant la prochaine tentative (backoff exponentiel).
|
||||
*/
|
||||
private long calculateDelay(int attempt) {
|
||||
// Backoff exponentiel: INITIAL_DELAY * (BACKOFF_MULTIPLIER ^ (attempt - 1))
|
||||
double delay = INITIAL_DELAY_MS * Math.pow(BACKOFF_MULTIPLIER, attempt - 1);
|
||||
|
||||
// Limiter à 10 secondes maximum
|
||||
return Math.min((long) delay, 10000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exécute une opération avec retry, mais sans propagation d'exception (retourne null en cas d'échec).
|
||||
*
|
||||
* <p>Utile pour les opérations non critiques où on peut continuer même en cas d'échec.
|
||||
*/
|
||||
public <T> T executeWithRetryOrNull(Callable<T> operation, String contextMessage) {
|
||||
try {
|
||||
return executeWithRetry(operation, contextMessage);
|
||||
} catch (Exception e) {
|
||||
LOG.warnf(e, "Échec définitif pour: %s - Retour de null", contextMessage);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.client.dto.SouscriptionDTO;
|
||||
import dev.lions.unionflow.server.api.dto.abonnement.response.AbonnementResponse;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(dev.lions.unionflow.client.security.AuthHeaderFactory.class)
|
||||
@Path("/api/souscriptions")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public interface SouscriptionService {
|
||||
|
||||
@GET
|
||||
List<SouscriptionDTO> listerToutes(
|
||||
List<AbonnementResponse> listerToutes(
|
||||
@QueryParam("organisationId") UUID organisationId,
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size
|
||||
@@ -22,18 +24,18 @@ public interface SouscriptionService {
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
SouscriptionDTO obtenirParId(@PathParam("id") UUID id);
|
||||
AbonnementResponse obtenirParId(@PathParam("id") UUID id);
|
||||
|
||||
@GET
|
||||
@Path("/organisation/{organisationId}/active")
|
||||
SouscriptionDTO obtenirActive(@PathParam("organisationId") UUID organisationId);
|
||||
AbonnementResponse obtenirActive(@PathParam("organisationId") UUID organisationId);
|
||||
|
||||
@POST
|
||||
SouscriptionDTO creer(SouscriptionDTO souscription);
|
||||
AbonnementResponse creer(AbonnementResponse souscription);
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
SouscriptionDTO modifier(@PathParam("id") UUID id, SouscriptionDTO souscription);
|
||||
AbonnementResponse modifier(@PathParam("id") UUID id, AbonnementResponse souscription);
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@@ -41,6 +43,6 @@ public interface SouscriptionService {
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/renouveler")
|
||||
SouscriptionDTO renouveler(@PathParam("id") UUID id);
|
||||
AbonnementResponse renouveler(@PathParam("id") UUID id);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.suggestion.request.*;
|
||||
import dev.lions.unionflow.server.api.dto.suggestion.response.*;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Service REST client pour la gestion des suggestions
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
*/
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(dev.lions.unionflow.client.security.AuthHeaderFactory.class)
|
||||
@Path("/api/suggestions")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public interface SuggestionService {
|
||||
|
||||
@GET
|
||||
List<SuggestionResponse> listerSuggestions();
|
||||
|
||||
@POST
|
||||
SuggestionResponse creerSuggestion(CreateSuggestionRequest request);
|
||||
|
||||
@POST
|
||||
@Path("/{id}/voter")
|
||||
void voterPourSuggestion(@PathParam("id") UUID id, @QueryParam("utilisateurId") UUID utilisateurId);
|
||||
|
||||
@GET
|
||||
@Path("/statistiques")
|
||||
Map<String, Object> obtenirStatistiques();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.ticket.request.*;
|
||||
import dev.lions.unionflow.server.api.dto.ticket.response.*;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Service REST client pour la gestion des tickets support
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
*/
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(dev.lions.unionflow.client.security.AuthHeaderFactory.class)
|
||||
@Path("/api/tickets")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public interface TicketService {
|
||||
|
||||
@GET
|
||||
@Path("/utilisateur/{utilisateurId}")
|
||||
List<TicketResponse> listerTickets(@PathParam("utilisateurId") UUID utilisateurId);
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
TicketResponse obtenirTicket(@PathParam("id") UUID id);
|
||||
|
||||
@POST
|
||||
TicketResponse creerTicket(CreateTicketRequest request);
|
||||
|
||||
@GET
|
||||
@Path("/utilisateur/{utilisateurId}/statistiques")
|
||||
Map<String, Object> obtenirStatistiques(@PathParam("utilisateurId") UUID utilisateurId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationResponse;
|
||||
import dev.lions.unionflow.server.api.dto.reference.response.TypeReferenceResponse;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.faces.model.SelectItem;
|
||||
import jakarta.inject.Inject;
|
||||
import org.eclipse.microprofile.rest.client.inject.RestClient;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Service applicatif (singleton) résolvant les libellés des types d'organisation
|
||||
* à partir du catalogue dynamique stocké en base.
|
||||
*
|
||||
* <p>Remplace tous les {@code switch} hardcodés répandus dans les beans JSF.
|
||||
* Le cache est invalidé explicitement après chaque mutation du catalogue
|
||||
* ({@link #recharger()}).
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class TypeCatalogueService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(TypeCatalogueService.class);
|
||||
|
||||
@Inject
|
||||
@RestClient
|
||||
TypeOrganisationClientService typeOrganisationClientService;
|
||||
|
||||
/** code → libelle, chargé paresseusement. */
|
||||
private volatile Map<String, String> libelleCache;
|
||||
/** liste complète (actifs + inactifs) pour les formulaires admin. */
|
||||
private volatile List<TypeReferenceResponse> catalogueCache;
|
||||
|
||||
// ── Chargement ──────────────────────────────────────────────────────────
|
||||
|
||||
private synchronized void charger() {
|
||||
if (libelleCache != null) return; // double-checked locking
|
||||
try {
|
||||
List<TypeReferenceResponse> types = typeOrganisationClientService.list(false);
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
for (TypeReferenceResponse t : types) {
|
||||
if (t.getCode() != null && t.getLibelle() != null) {
|
||||
map.put(t.getCode(), t.getLibelle());
|
||||
}
|
||||
}
|
||||
catalogueCache = List.copyOf(types);
|
||||
libelleCache = Collections.unmodifiableMap(map);
|
||||
LOG.infof("Catalogue types chargé : %d entrées", map.size());
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Impossible de charger le catalogue des types d'organisation");
|
||||
catalogueCache = List.of();
|
||||
libelleCache = Map.of();
|
||||
}
|
||||
}
|
||||
|
||||
/** Invalide le cache — à appeler après toute mutation du catalogue. */
|
||||
public void recharger() {
|
||||
libelleCache = null;
|
||||
catalogueCache = null;
|
||||
LOG.debug("Cache catalogue types invalidé");
|
||||
}
|
||||
|
||||
// ── Résolution ──────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Retourne le libellé correspondant au code, ou le code brut si inconnu.
|
||||
* Ne retourne jamais {@code null}.
|
||||
*/
|
||||
public String resolveLibelle(String code) {
|
||||
if (code == null || code.isBlank()) return "";
|
||||
if (libelleCache == null) charger();
|
||||
return libelleCache.getOrDefault(code, code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renseigne {@code typeOrganisationLibelle} sur chaque DTO de la liste d'après le catalogue.
|
||||
* Opération in-place, O(n).
|
||||
*/
|
||||
public void enrichir(List<OrganisationResponse> dtos) {
|
||||
if (dtos == null || dtos.isEmpty()) return;
|
||||
if (libelleCache == null) charger();
|
||||
for (OrganisationResponse dto : dtos) {
|
||||
dto.setTypeOrganisationLibelle(resolveLibelle(dto.getTypeOrganisation()));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Listes pour les composants UI ────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Retourne les types actifs sous forme de {@link SelectItem} avec un premier
|
||||
* élément vide portant le {@code placeholder} fourni.
|
||||
*/
|
||||
public List<SelectItem> getSelectItems(String placeholder) {
|
||||
if (libelleCache == null) charger();
|
||||
List<SelectItem> items = new ArrayList<>();
|
||||
items.add(new SelectItem("", placeholder));
|
||||
for (TypeReferenceResponse t : catalogueCache) {
|
||||
if (!Boolean.FALSE.equals(t.getActif())) {
|
||||
items.add(new SelectItem(t.getCode(), t.getLibelle()));
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Retourne tous les types (actifs + inactifs) — usage admin. */
|
||||
public List<TypeReferenceResponse> getCatalogueComplet() {
|
||||
if (catalogueCache == null) charger();
|
||||
return catalogueCache;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,33 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.client.dto.TypeOrganisationClientDTO;
|
||||
import dev.lions.unionflow.server.api.dto.reference.request.*;
|
||||
import dev.lions.unionflow.server.api.dto.reference.response.*;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
|
||||
/**
|
||||
* REST client pour le catalogue des types d'organisation.
|
||||
* REST client pour le catalogue des types d'organisation (références).
|
||||
*/
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@Path("/api/types-organisations")
|
||||
@RegisterClientHeaders(dev.lions.unionflow.client.security.AuthHeaderFactory.class)
|
||||
@Path("/api/references/types-organisation")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public interface TypeOrganisationClientService {
|
||||
|
||||
@GET
|
||||
List<TypeOrganisationClientDTO> list(@QueryParam("onlyActifs") @DefaultValue("true") boolean onlyActifs);
|
||||
List<TypeReferenceResponse> list(@QueryParam("onlyActifs") @DefaultValue("true") boolean onlyActifs);
|
||||
|
||||
@POST
|
||||
TypeOrganisationClientDTO create(TypeOrganisationClientDTO dto);
|
||||
TypeReferenceResponse create(CreateTypeReferenceRequest request);
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
TypeOrganisationClientDTO update(@PathParam("id") UUID id, TypeOrganisationClientDTO dto);
|
||||
TypeReferenceResponse update(@PathParam("id") UUID id, UpdateTypeReferenceRequest request);
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
|
||||
@@ -4,76 +4,251 @@ import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.Validator;
|
||||
import jakarta.validation.groups.Default;
|
||||
import org.jboss.logging.Logger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Service de validation centralisé avec feedback en temps réel
|
||||
*
|
||||
* Fournit des méthodes de validation pour les beans avec :
|
||||
* - Validation complète d'objets
|
||||
* - Validation de propriétés individuelles (temps réel)
|
||||
* - Validation de valeurs
|
||||
* - Support des groupes de validation
|
||||
* - Intégration avec ErrorHandlerService
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2026-01-04
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class ValidationService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(ValidationService.class);
|
||||
|
||||
@Inject
|
||||
Validator validator;
|
||||
|
||||
@Inject
|
||||
ErrorHandlerService errorHandler;
|
||||
|
||||
@Inject
|
||||
MetricsService metricsService;
|
||||
|
||||
/**
|
||||
* Valide un objet et retourne la liste des erreurs
|
||||
*
|
||||
* @param object L'objet à valider
|
||||
* @return Résultat de validation avec détails des erreurs
|
||||
*/
|
||||
public <T> ValidationResult validate(T object) {
|
||||
if (object == null) {
|
||||
LOG.warn("Tentative de validation d'un objet null");
|
||||
ValidationResult result = new ValidationResult();
|
||||
result.setValid(false);
|
||||
result.addErrorMessage("L'objet à valider est null");
|
||||
return result;
|
||||
}
|
||||
|
||||
Set<ConstraintViolation<T>> violations = validator.validate(object);
|
||||
LOG.debugf("Validation de %s : %d violation(s)", object.getClass().getSimpleName(), violations.size());
|
||||
|
||||
ValidationResult result = new ValidationResult();
|
||||
result.setValid(violations.isEmpty());
|
||||
|
||||
List<String> messages = new ArrayList<>();
|
||||
for (ConstraintViolation<T> violation : violations) {
|
||||
messages.add(violation.getPropertyPath() + ": " + violation.getMessage());
|
||||
String message = violation.getPropertyPath() + ": " + violation.getMessage();
|
||||
result.addErrorMessage(message);
|
||||
LOG.debugf("Violation: %s", message);
|
||||
}
|
||||
|
||||
// Enregistrer les métriques de validation
|
||||
if (metricsService != null) {
|
||||
metricsService.recordValidation(result.isValid());
|
||||
}
|
||||
result.setErrorMessages(messages);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide une propriété spécifique d'un objet
|
||||
* Valide un objet avec groupes de validation spécifiques
|
||||
*
|
||||
* @param object L'objet à valider
|
||||
* @param groups Groupes de validation à appliquer
|
||||
* @return Résultat de validation
|
||||
*/
|
||||
public <T> ValidationResult validateProperty(T object, String propertyName) {
|
||||
Set<ConstraintViolation<T>> violations = validator.validateProperty(object, propertyName);
|
||||
public <T> ValidationResult validate(T object, Class<?>... groups) {
|
||||
if (object == null) {
|
||||
ValidationResult result = new ValidationResult();
|
||||
result.setValid(false);
|
||||
result.addErrorMessage("L'objet à valider est null");
|
||||
return result;
|
||||
}
|
||||
|
||||
Set<ConstraintViolation<T>> violations = validator.validate(object, groups);
|
||||
LOG.debugf("Validation de %s avec groupes : %d violation(s)",
|
||||
object.getClass().getSimpleName(), violations.size());
|
||||
|
||||
ValidationResult result = new ValidationResult();
|
||||
result.setValid(violations.isEmpty());
|
||||
|
||||
List<String> messages = new ArrayList<>();
|
||||
for (ConstraintViolation<T> violation : violations) {
|
||||
messages.add(violation.getMessage());
|
||||
result.addErrorMessage(violation.getPropertyPath() + ": " + violation.getMessage());
|
||||
}
|
||||
result.setErrorMessages(messages);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide un objet et affiche les erreurs automatiquement via ErrorHandlerService
|
||||
*
|
||||
* @param object L'objet à valider
|
||||
* @param showMessagesToUser Si true, affiche les erreurs à l'utilisateur
|
||||
* @return true si valide, false sinon
|
||||
*/
|
||||
public <T> boolean validateAndShow(T object, boolean showMessagesToUser) {
|
||||
ValidationResult result = validate(object);
|
||||
|
||||
if (!result.isValid() && showMessagesToUser) {
|
||||
for (String message : result.getErrorMessages()) {
|
||||
errorHandler.showWarning("Validation", message);
|
||||
}
|
||||
}
|
||||
|
||||
return result.isValid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide une propriété spécifique d'un objet (utile pour validation en temps réel)
|
||||
*
|
||||
* @param object L'objet contenant la propriété
|
||||
* @param propertyName Nom de la propriété à valider
|
||||
* @return Résultat de validation
|
||||
*/
|
||||
public <T> ValidationResult validateProperty(T object, String propertyName) {
|
||||
if (object == null || propertyName == null || propertyName.isEmpty()) {
|
||||
ValidationResult result = new ValidationResult();
|
||||
result.setValid(false);
|
||||
result.addErrorMessage("Paramètres de validation invalides");
|
||||
return result;
|
||||
}
|
||||
|
||||
Set<ConstraintViolation<T>> violations = validator.validateProperty(object, propertyName);
|
||||
LOG.debugf("Validation propriété %s.%s : %d violation(s)",
|
||||
object.getClass().getSimpleName(), propertyName, violations.size());
|
||||
|
||||
ValidationResult result = new ValidationResult();
|
||||
result.setValid(violations.isEmpty());
|
||||
result.setPropertyName(propertyName);
|
||||
|
||||
for (ConstraintViolation<T> violation : violations) {
|
||||
result.addErrorMessage(violation.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide une propriété et affiche l'erreur si invalide
|
||||
* Utilisé pour validation AJAX en temps réel
|
||||
*
|
||||
* @param object L'objet
|
||||
* @param propertyName Propriété à valider
|
||||
* @param componentId ID du composant JSF pour cibler le message
|
||||
* @return true si valide
|
||||
*/
|
||||
public <T> boolean validatePropertyAndShow(T object, String propertyName, String componentId) {
|
||||
ValidationResult result = validateProperty(object, propertyName);
|
||||
|
||||
if (!result.isValid()) {
|
||||
String message = result.getFirstErrorMessage();
|
||||
if (message != null) {
|
||||
errorHandler.showWarning(propertyName, message);
|
||||
}
|
||||
}
|
||||
|
||||
return result.isValid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide une valeur contre les contraintes d'une propriété
|
||||
* Utile pour valider une valeur avant de l'assigner
|
||||
*
|
||||
* @param beanType Classe du bean
|
||||
* @param propertyName Nom de la propriété
|
||||
* @param value Valeur à valider
|
||||
* @return Résultat de validation
|
||||
*/
|
||||
public <T> ValidationResult validateValue(Class<T> beanType, String propertyName, Object value) {
|
||||
Set<ConstraintViolation<T>> violations = validator.validateValue(beanType, propertyName, value);
|
||||
LOG.debugf("Validation valeur %s.%s = %s : %d violation(s)",
|
||||
beanType.getSimpleName(), propertyName, value, violations.size());
|
||||
|
||||
ValidationResult result = new ValidationResult();
|
||||
result.setValid(violations.isEmpty());
|
||||
result.setPropertyName(propertyName);
|
||||
|
||||
List<String> messages = new ArrayList<>();
|
||||
for (ConstraintViolation<T> violation : violations) {
|
||||
messages.add(violation.getMessage());
|
||||
result.addErrorMessage(violation.getMessage());
|
||||
}
|
||||
result.setErrorMessages(messages);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide plusieurs objets en une seule opération
|
||||
*
|
||||
* @param objects Liste d'objets à valider
|
||||
* @return Résultat de validation global
|
||||
*/
|
||||
public ValidationResult validateMultiple(Object... objects) {
|
||||
ValidationResult globalResult = new ValidationResult();
|
||||
globalResult.setValid(true);
|
||||
|
||||
for (Object object : objects) {
|
||||
ValidationResult result = validate(object);
|
||||
if (!result.isValid()) {
|
||||
globalResult.setValid(false);
|
||||
globalResult.getErrorMessages().addAll(result.getErrorMessages());
|
||||
}
|
||||
}
|
||||
|
||||
return globalResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si un objet est valide (méthode simplifiée)
|
||||
*
|
||||
* @param object L'objet à valider
|
||||
* @return true si valide, false sinon
|
||||
*/
|
||||
public <T> boolean isValid(T object) {
|
||||
return object != null && validator.validate(object).isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si une propriété est valide (méthode simplifiée)
|
||||
*
|
||||
* @param object L'objet
|
||||
* @param propertyName Propriété à vérifier
|
||||
* @return true si valide
|
||||
*/
|
||||
public <T> boolean isPropertyValid(T object, String propertyName) {
|
||||
return object != null &&
|
||||
propertyName != null &&
|
||||
validator.validateProperty(object, propertyName).isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Classe pour encapsuler le résultat de validation
|
||||
*/
|
||||
public static class ValidationResult {
|
||||
private boolean valid;
|
||||
private boolean valid = true;
|
||||
private List<String> errorMessages = new ArrayList<>();
|
||||
private String propertyName;
|
||||
|
||||
public boolean isValid() {
|
||||
return valid;
|
||||
@@ -91,6 +266,18 @@ public class ValidationService {
|
||||
this.errorMessages = errorMessages;
|
||||
}
|
||||
|
||||
public void addErrorMessage(String message) {
|
||||
this.errorMessages.add(message);
|
||||
}
|
||||
|
||||
public String getPropertyName() {
|
||||
return propertyName;
|
||||
}
|
||||
|
||||
public void setPropertyName(String propertyName) {
|
||||
this.propertyName = propertyName;
|
||||
}
|
||||
|
||||
public String getFirstErrorMessage() {
|
||||
return errorMessages.isEmpty() ? null : errorMessages.get(0);
|
||||
}
|
||||
@@ -98,5 +285,22 @@ public class ValidationService {
|
||||
public String getAllErrorMessages() {
|
||||
return String.join(", ", errorMessages);
|
||||
}
|
||||
|
||||
public int getErrorCount() {
|
||||
return errorMessages.size();
|
||||
}
|
||||
|
||||
public boolean hasErrors() {
|
||||
return !valid || !errorMessages.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ValidationResult{" +
|
||||
"valid=" + valid +
|
||||
", errorCount=" + errorMessages.size() +
|
||||
", propertyName='" + propertyName + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.client.dto.WaveCheckoutSessionDTO;
|
||||
import dev.lions.unionflow.client.dto.WaveBalanceDTO;
|
||||
import dev.lions.unionflow.server.api.dto.paiement.WaveCheckoutSessionDTO;
|
||||
import dev.lions.unionflow.server.api.dto.paiement.WaveBalanceDTO;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
@@ -14,6 +14,7 @@ import java.math.BigDecimal;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
|
||||
/**
|
||||
* Service REST client pour l'intégration Wave Money
|
||||
@@ -22,7 +23,8 @@ import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@RegisterRestClient(baseUri = "http://localhost:8085")
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(dev.lions.unionflow.client.security.AuthHeaderFactory.class)
|
||||
@Path("/api/wave")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
|
||||
Reference in New Issue
Block a user