Files
btpxpress-backend/src/main/java/dev/lions/btpxpress/adapter/http/ComparaisonFournisseurResource.java
DahoudG 7df5f346f1 Refactor: Backend Frontend-Centric Auth - Suppression OIDC, validation JWT
Architecture modifiée pour Frontend-Centric Authentication:

1. **Suppression des dépendances OIDC**
   - quarkus-oidc → quarkus-smallrye-jwt
   - quarkus-keycloak-authorization → quarkus-smallrye-jwt-build
   - Le backend ne gère plus l'authentification OAuth

2. **Configuration JWT simple**
   - Validation des tokens JWT envoyés par le frontend
   - mp.jwt.verify.publickey.location (JWKS de Keycloak)
   - mp.jwt.verify.issuer (Keycloak realm)
   - Authentification via Authorization: Bearer header

3. **Suppression configurations OIDC**
   - application.properties: Suppression %dev.quarkus.oidc.*
   - application.properties: Suppression %prod.quarkus.oidc.*
   - application-prod.properties: Remplacement par mp.jwt.*
   - Logging: io.quarkus.oidc → io.quarkus.smallrye.jwt

4. **Sécurité simplifiée**
   - quarkus.security.auth.proactive=false
   - @Authenticated sur les endpoints
   - CORS configuré pour le frontend
   - Endpoints publics: /q/*, /openapi, /swagger-ui/*

Flux d'authentification:
1️⃣ Frontend → Keycloak (OAuth login)
2️⃣ Frontend ← Keycloak (access_token)
3️⃣ Frontend → Backend (Authorization: Bearer token)
4️⃣ Backend valide le token JWT (signature + issuer)
5️⃣ Backend → Frontend (données API)

Avantages:
 Pas de secret backend à gérer
 Pas de client btpxpress-backend dans Keycloak
 Séparation claire frontend/backend
 Backend devient une API REST stateless
 Tokens gérés par le frontend (localStorage/sessionStorage)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-31 17:05:11 +00:00

520 lines
19 KiB
Java

package dev.lions.btpxpress.adapter.http;
import dev.lions.btpxpress.application.service.ComparaisonFournisseurService;
import dev.lions.btpxpress.domain.core.entity.*;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* API REST pour la comparaison des fournisseurs EXPOSITION: Endpoints pour l'aide à la décision et
* l'optimisation des achats BTP
*/
@Path("/api/v1/comparaisons-fournisseurs")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class ComparaisonFournisseurResource {
private static final Logger logger =
LoggerFactory.getLogger(ComparaisonFournisseurResource.class);
@Inject ComparaisonFournisseurService comparaisonService;
// === ENDPOINTS DE CONSULTATION ===
@GET
@Path("/")
public Response findAll(
@QueryParam("page") @DefaultValue("0") int page,
@QueryParam("size") @DefaultValue("50") int size) {
try {
logger.debug("GET /api/comparaisons-fournisseurs/ - page: {}, size: {}", page, size);
List<ComparaisonFournisseur> comparaisons;
if (page > 0 || size < 1000) {
comparaisons = comparaisonService.findAll(page, size);
} else {
comparaisons = comparaisonService.findAll();
}
return Response.ok(comparaisons).build();
} catch (Exception e) {
logger.error("Erreur lors de la récupération des comparaisons", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Erreur lors de la récupération des comparaisons: " + e.getMessage())
.build();
}
}
@GET
@Path("/{id}")
public Response findById(@PathParam("id") UUID id) {
try {
logger.debug("GET /api/comparaisons-fournisseurs/{}", id);
ComparaisonFournisseur comparaison = comparaisonService.findByIdRequired(id);
return Response.ok(comparaison).build();
} catch (NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
} catch (Exception e) {
logger.error("Erreur lors de la récupération de la comparaison: " + id, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Erreur lors de la récupération de la comparaison: " + e.getMessage())
.build();
}
}
@GET
@Path("/materiel/{materielId}")
public Response findByMateriel(@PathParam("materielId") UUID materielId) {
try {
logger.debug("GET /api/comparaisons-fournisseurs/materiel/{}", materielId);
List<ComparaisonFournisseur> comparaisons = comparaisonService.findByMateriel(materielId);
return Response.ok(comparaisons).build();
} catch (Exception e) {
logger.error(
"Erreur lors de la récupération des comparaisons pour matériel: " + materielId, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Erreur lors de la récupération des comparaisons: " + e.getMessage())
.build();
}
}
@GET
@Path("/fournisseur/{fournisseurId}")
public Response findByFournisseur(@PathParam("fournisseurId") UUID fournisseurId) {
try {
logger.debug("GET /api/comparaisons-fournisseurs/fournisseur/{}", fournisseurId);
List<ComparaisonFournisseur> comparaisons =
comparaisonService.findByFournisseur(fournisseurId);
return Response.ok(comparaisons).build();
} catch (Exception e) {
logger.error(
"Erreur lors de la récupération des comparaisons pour fournisseur: " + fournisseurId, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Erreur lors de la récupération des comparaisons: " + e.getMessage())
.build();
}
}
@GET
@Path("/session/{sessionId}")
public Response findBySession(@PathParam("sessionId") String sessionId) {
try {
logger.debug("GET /api/comparaisons-fournisseurs/session/{}", sessionId);
List<ComparaisonFournisseur> comparaisons = comparaisonService.findBySession(sessionId);
return Response.ok(comparaisons).build();
} catch (Exception e) {
logger.error("Erreur lors de la récupération des comparaisons pour session: " + sessionId, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Erreur lors de la récupération des comparaisons: " + e.getMessage())
.build();
}
}
@GET
@Path("/search")
public Response search(@QueryParam("terme") String terme) {
try {
logger.debug("GET /api/comparaisons-fournisseurs/search?terme={}", terme);
List<ComparaisonFournisseur> resultats = comparaisonService.search(terme);
return Response.ok(resultats).build();
} catch (Exception e) {
logger.error("Erreur lors de la recherche avec terme: " + terme, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Erreur lors de la recherche: " + e.getMessage())
.build();
}
}
// === ENDPOINTS MÉTIER SPÉCIALISÉS ===
@GET
@Path("/meilleures-offres/{materielId}")
public Response findMeilleuresOffres(
@PathParam("materielId") UUID materielId,
@QueryParam("limite") @DefaultValue("5") int limite) {
try {
logger.debug("GET /api/comparaisons-fournisseurs/meilleures-offres/{}", materielId);
List<ComparaisonFournisseur> meilleures =
comparaisonService.findMeilleuresOffres(materielId, limite);
return Response.ok(meilleures).build();
} catch (Exception e) {
logger.error("Erreur lors de la récupération des meilleures offres: " + materielId, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Erreur lors de la récupération des meilleures offres: " + e.getMessage())
.build();
}
}
@GET
@Path("/recommandees")
public Response findOffresRecommandees() {
try {
logger.debug("GET /api/comparaisons-fournisseurs/recommandees");
List<ComparaisonFournisseur> recommandees = comparaisonService.findOffresRecommandees();
return Response.ok(recommandees).build();
} catch (Exception e) {
logger.error("Erreur lors de la récupération des offres recommandées", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Erreur lors de la récupération des offres recommandées: " + e.getMessage())
.build();
}
}
@GET
@Path("/gamme-prix")
public Response findByGammePrix(
@QueryParam("prixMin") @NotNull BigDecimal prixMin,
@QueryParam("prixMax") @NotNull BigDecimal prixMax) {
try {
logger.debug(
"GET /api/comparaisons-fournisseurs/gamme-prix?prixMin={}&prixMax={}", prixMin, prixMax);
List<ComparaisonFournisseur> comparaisons =
comparaisonService.findByGammePrix(prixMin, prixMax);
return Response.ok(comparaisons).build();
} catch (BadRequestException e) {
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
} catch (Exception e) {
logger.error("Erreur lors de la recherche par gamme de prix", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Erreur lors de la recherche par gamme de prix: " + e.getMessage())
.build();
}
}
@GET
@Path("/disponibles-delai")
public Response findDisponiblesDansDelai(
@QueryParam("maxJours") @DefaultValue("30") int maxJours) {
try {
logger.debug("GET /api/comparaisons-fournisseurs/disponibles-delai?maxJours={}", maxJours);
List<ComparaisonFournisseur> disponibles =
comparaisonService.findDisponiblesDansDelai(maxJours);
return Response.ok(disponibles).build();
} catch (Exception e) {
logger.error("Erreur lors de la recherche par délai", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Erreur lors de la recherche par délai: " + e.getMessage())
.build();
}
}
// === ENDPOINTS DE CRÉATION ET GESTION ===
@POST
@Path("/lancer-comparaison")
public Response lancerComparaison(@Valid LancerComparaisonRequest request) {
try {
logger.info("POST /api/comparaisons-fournisseurs/lancer-comparaison");
String sessionId =
comparaisonService.lancerComparaison(
request.materielId,
request.quantiteDemandee,
request.uniteDemandee,
request.dateDebutSouhaitee,
request.dateFinSouhaitee,
request.lieuLivraison,
request.evaluateur);
return Response.status(Response.Status.CREATED)
.entity(Map.of("sessionId", sessionId))
.build();
} catch (NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
} catch (Exception e) {
logger.error("Erreur lors du lancement de la comparaison", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Erreur lors du lancement de la comparaison: " + e.getMessage())
.build();
}
}
@PUT
@Path("/{id}")
public Response updateComparaison(
@PathParam("id") UUID id, @Valid UpdateComparaisonRequest request) {
try {
logger.info("PUT /api/comparaisons-fournisseurs/{}", id);
ComparaisonFournisseurService.ComparaisonUpdateRequest updateRequest =
mapToServiceRequest(request);
ComparaisonFournisseur comparaison = comparaisonService.updateComparaison(id, updateRequest);
return Response.ok(comparaison).build();
} catch (NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
} catch (Exception e) {
logger.error("Erreur lors de la mise à jour de la comparaison: " + id, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Erreur lors de la mise à jour de la comparaison: " + e.getMessage())
.build();
}
}
@PUT
@Path("/{id}/calculer-scores")
public Response calculerScores(@PathParam("id") UUID id, @Valid CalculerScoresRequest request) {
try {
logger.info("PUT /api/comparaisons-fournisseurs/{}/calculer-scores", id);
ComparaisonFournisseur comparaison = comparaisonService.findByIdRequired(id);
comparaisonService.calculerScores(comparaison, request.poidsCriteres);
return Response.ok(comparaison).build();
} catch (NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
} catch (Exception e) {
logger.error("Erreur lors du calcul des scores: " + id, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Erreur lors du calcul des scores: " + e.getMessage())
.build();
}
}
@PUT
@Path("/session/{sessionId}/classer")
public Response classerComparaisons(@PathParam("sessionId") String sessionId) {
try {
logger.info("PUT /api/comparaisons-fournisseurs/session/{}/classer", sessionId);
comparaisonService.classerComparaisons(sessionId);
List<ComparaisonFournisseur> comparaisons = comparaisonService.findBySession(sessionId);
return Response.ok(
Map.of("message", "Classement effectué avec succès", "comparaisons", comparaisons))
.build();
} catch (Exception e) {
logger.error("Erreur lors du classement des comparaisons: " + sessionId, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Erreur lors du classement des comparaisons: " + e.getMessage())
.build();
}
}
// === ENDPOINTS D'ANALYSE ET RAPPORTS ===
@GET
@Path("/statistiques")
public Response getStatistiques() {
try {
logger.debug("GET /api/comparaisons-fournisseurs/statistiques");
Map<String, Object> statistiques = comparaisonService.getStatistiques();
return Response.ok(statistiques).build();
} catch (Exception e) {
logger.error("Erreur lors de la génération des statistiques", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Erreur lors de la génération des statistiques: " + e.getMessage())
.build();
}
}
@GET
@Path("/evolution-prix/{materielId}")
public Response analyserEvolutionPrix(
@PathParam("materielId") UUID materielId,
@QueryParam("dateDebut") @NotNull LocalDate dateDebut,
@QueryParam("dateFin") @NotNull LocalDate dateFin) {
try {
logger.debug("GET /api/comparaisons-fournisseurs/evolution-prix/{}", materielId);
List<Object> evolution =
comparaisonService.analyserEvolutionPrix(materielId, dateDebut, dateFin);
return Response.ok(evolution).build();
} catch (Exception e) {
logger.error("Erreur lors de l'analyse d'évolution des prix: " + materielId, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Erreur lors de l'analyse d'évolution des prix: " + e.getMessage())
.build();
}
}
@GET
@Path("/delais-fournisseurs")
public Response analyserDelaisFournisseurs() {
try {
logger.debug("GET /api/comparaisons-fournisseurs/delais-fournisseurs");
List<Object> delais = comparaisonService.analyserDelaisFournisseurs();
return Response.ok(delais).build();
} catch (Exception e) {
logger.error("Erreur lors de l'analyse des délais fournisseurs", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Erreur lors de l'analyse des délais fournisseurs: " + e.getMessage())
.build();
}
}
@GET
@Path("/rapport/{sessionId}")
public Response genererRapportComparaison(@PathParam("sessionId") String sessionId) {
try {
logger.debug("GET /api/comparaisons-fournisseurs/rapport/{}", sessionId);
Map<String, Object> rapport = comparaisonService.genererRapportComparaison(sessionId);
return Response.ok(rapport).build();
} catch (NotFoundException e) {
return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
} catch (Exception e) {
logger.error("Erreur lors de la génération du rapport: " + sessionId, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Erreur lors de la génération du rapport: " + e.getMessage())
.build();
}
}
// === ENDPOINTS UTILITAIRES ===
@GET
@Path("/criteres-comparaison")
public Response getCriteresComparaison() {
try {
CritereComparaison[] criteres = CritereComparaison.values();
List<Map<String, Object>> criteresInfo =
Arrays.stream(criteres)
.map(
critere -> {
Map<String, Object> map = new HashMap<>();
map.put("code", critere.name());
map.put("libelle", critere.getLibelle());
map.put("description", critere.getDescription());
map.put("poidsDefaut", critere.getPoidsDefaut());
map.put("uniteMesure", critere.getUniteMesure());
map.put("icone", critere.getIcone());
map.put("couleur", critere.getCouleur());
return map;
})
.collect(Collectors.toList());
return Response.ok(criteresInfo).build();
} catch (Exception e) {
logger.error("Erreur lors de la récupération des critères", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Erreur lors de la récupération des critères: " + e.getMessage())
.build();
}
}
// === MÉTHODES UTILITAIRES ===
private ComparaisonFournisseurService.ComparaisonUpdateRequest mapToServiceRequest(
UpdateComparaisonRequest request) {
ComparaisonFournisseurService.ComparaisonUpdateRequest serviceRequest =
new ComparaisonFournisseurService.ComparaisonUpdateRequest();
serviceRequest.disponible = request.disponible;
serviceRequest.quantiteDisponible = request.quantiteDisponible;
serviceRequest.dateDisponibilite = request.dateDisponibilite;
serviceRequest.delaiLivraisonJours = request.delaiLivraisonJours;
serviceRequest.prixUnitaireHT = request.prixUnitaireHT;
serviceRequest.fraisLivraison = request.fraisLivraison;
serviceRequest.fraisInstallation = request.fraisInstallation;
serviceRequest.fraisMaintenance = request.fraisMaintenance;
serviceRequest.cautionDemandee = request.cautionDemandee;
serviceRequest.remiseAppliquee = request.remiseAppliquee;
serviceRequest.dureeValiditeOffre = request.dureeValiditeOffre;
serviceRequest.delaiPaiement = request.delaiPaiement;
serviceRequest.garantieMois = request.garantieMois;
serviceRequest.maintenanceIncluse = request.maintenanceIncluse;
serviceRequest.formationIncluse = request.formationIncluse;
serviceRequest.noteQualite = request.noteQualite;
serviceRequest.noteFiabilite = request.noteFiabilite;
serviceRequest.distanceKm = request.distanceKm;
serviceRequest.conditionsParticulieres = request.conditionsParticulieres;
serviceRequest.avantages = request.avantages;
serviceRequest.inconvenients = request.inconvenients;
serviceRequest.commentairesEvaluateur = request.commentairesEvaluateur;
serviceRequest.recommandations = request.recommandations;
serviceRequest.poidsCriteres = request.poidsCriteres;
return serviceRequest;
}
// === CLASSES DE REQUÊTE ===
public static class LancerComparaisonRequest {
@NotNull public UUID materielId;
@NotNull public BigDecimal quantiteDemandee;
public String uniteDemandee;
public LocalDate dateDebutSouhaitee;
public LocalDate dateFinSouhaitee;
public String lieuLivraison;
public String evaluateur;
}
public static class UpdateComparaisonRequest {
public Boolean disponible;
public BigDecimal quantiteDisponible;
public LocalDate dateDisponibilite;
public Integer delaiLivraisonJours;
public BigDecimal prixUnitaireHT;
public BigDecimal fraisLivraison;
public BigDecimal fraisInstallation;
public BigDecimal fraisMaintenance;
public BigDecimal cautionDemandee;
public BigDecimal remiseAppliquee;
public Integer dureeValiditeOffre;
public Integer delaiPaiement;
public Integer garantieMois;
public Boolean maintenanceIncluse;
public Boolean formationIncluse;
public BigDecimal noteQualite;
public BigDecimal noteFiabilite;
public BigDecimal distanceKm;
public String conditionsParticulieres;
public String avantages;
public String inconvenients;
public String commentairesEvaluateur;
public String recommandations;
public Map<CritereComparaison, Integer> poidsCriteres;
}
public static class CalculerScoresRequest {
public Map<CritereComparaison, Integer> poidsCriteres;
}
}