From f4696b5f5b02a3631b7bed049e0a2212cae84e6d Mon Sep 17 00:00:00 2001 From: Vincent Guillet Date: Wed, 26 Nov 2025 14:08:33 +0100 Subject: [PATCH] Refactor PrestashopClient and PrestashopProxyController for improved error handling and response management --- .../prestashop/PrestashopProxyController.java | 70 +++---- .../service/prestashop/PrestashopClient.java | 193 ++++++++++++------ 2 files changed, 149 insertions(+), 114 deletions(-) diff --git a/api/src/main/java/fr/gameovergne/api/controller/prestashop/PrestashopProxyController.java b/api/src/main/java/fr/gameovergne/api/controller/prestashop/PrestashopProxyController.java index 3216eb0..011cfff 100644 --- a/api/src/main/java/fr/gameovergne/api/controller/prestashop/PrestashopProxyController.java +++ b/api/src/main/java/fr/gameovergne/api/controller/prestashop/PrestashopProxyController.java @@ -16,92 +16,70 @@ public class PrestashopProxyController { this.prestashopClient = prestashopClient; } - /** - * Extrait le path Presta à partir de la requête entrante. - * Exemple : - * - requestURI = /api/ps/products/446 - * - retourne /products/446 - */ + // Utilitaire pour extraire /products, /products/446, etc. private String extractPath(HttpServletRequest request) { String fullPath = request.getRequestURI(); // ex: /api/ps/products/446 String contextPath = request.getContextPath(); // souvent "" String relative = fullPath.substring(contextPath.length()); // /api/ps/products/446 - - // On enlève le préfixe /api/ps => /products/446 - return relative.replaceFirst("^/api/ps", ""); + return relative.replaceFirst("^/api/ps", ""); // => /products/446 } - // ---------- GET ---------- - + /* ---------------- GET ---------------- */ @GetMapping("/**") public ResponseEntity proxyGet(HttpServletRequest request) { String path = extractPath(request); - String query = request.getQueryString(); // ex: display=full&filter[id_product]=123 + String query = request.getQueryString(); // on laisse tel quel String body = prestashopClient.get(path, query); - // On sait qu'on force output_format=JSON côté client.get(...) return ResponseEntity .ok() .contentType(MediaType.APPLICATION_JSON) .body(body); } - // ---------- POST ---------- - + /* ---------------- POST ---------------- */ @PostMapping("/**") - public ResponseEntity proxyPost( - HttpServletRequest request, - @RequestBody String bodyFromFront - ) { + public ResponseEntity proxyPost(HttpServletRequest request, + @RequestBody String xmlBody) { String path = extractPath(request); String query = request.getQueryString(); - String body = prestashopClient.post(path, query, bodyFromFront); - - // Si Angular demande output_format=JSON dans la query, on peut renvoyer JSON, - // sinon ce sera typiquement du XML. Dans tous les cas on renvoie le raw body. - MediaType mediaType = (query != null && query.contains("output_format=JSON")) - ? MediaType.APPLICATION_JSON - : MediaType.APPLICATION_XML; + String responseBody = prestashopClient.post(path, query, xmlBody); + // Presta peut renvoyer JSON ou XML, mais côté front tu traites en JSON return ResponseEntity .ok() - .contentType(mediaType) - .body(body); + .contentType(MediaType.APPLICATION_JSON) + .body(responseBody); } - // ---------- PUT ---------- - + /* ---------------- PUT ---------------- */ @PutMapping("/**") - public ResponseEntity proxyPut( - HttpServletRequest request, - @RequestBody String bodyFromFront - ) { + public ResponseEntity proxyPut(HttpServletRequest request, + @RequestBody String xmlBody) { String path = extractPath(request); String query = request.getQueryString(); - String body = prestashopClient.put(path, query, bodyFromFront); - - MediaType mediaType = (query != null && query.contains("output_format=JSON")) - ? MediaType.APPLICATION_JSON - : MediaType.APPLICATION_XML; + String responseBody = prestashopClient.put(path, query, xmlBody); return ResponseEntity .ok() - .contentType(mediaType) - .body(body); + .contentType(MediaType.APPLICATION_JSON) + .body(responseBody); } - // ---------- DELETE ---------- - + /* ---------------- DELETE ---------------- */ @DeleteMapping("/**") - public ResponseEntity proxyDelete(HttpServletRequest request) { + public ResponseEntity proxyDelete(HttpServletRequest request) { String path = extractPath(request); String query = request.getQueryString(); - prestashopClient.delete(path, query); + String responseBody = prestashopClient.delete(path, query); - return ResponseEntity.noContent().build(); + return ResponseEntity + .ok() + .contentType(MediaType.APPLICATION_JSON) + .body(responseBody); } } \ No newline at end of file diff --git a/api/src/main/java/fr/gameovergne/api/service/prestashop/PrestashopClient.java b/api/src/main/java/fr/gameovergne/api/service/prestashop/PrestashopClient.java index 933ecd1..97afe61 100644 --- a/api/src/main/java/fr/gameovergne/api/service/prestashop/PrestashopClient.java +++ b/api/src/main/java/fr/gameovergne/api/service/prestashop/PrestashopClient.java @@ -5,6 +5,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.*; import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; @@ -29,50 +30,49 @@ public class PrestashopClient { this.basicAuth = basicAuth; } - /** - * Construit l'URL finale vers PrestaShop en propageant la query d'origine. - * - * @param path ex: "/products", "/products/446" - * @param query queryString brute reçue côté API (peut être null) - * @param addDefaultFormatAndDisplay si true, ajoute output_format=JSON et display=full - */ - private String buildUrl(String path, String query, boolean addDefaultFormatAndDisplay) { + /* ------------------------------------------------------------------ + * Helpers communs + * ------------------------------------------------------------------ */ + + private HttpHeaders baseHeaders() { + HttpHeaders headers = new HttpHeaders(); + headers.set(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth); + return headers; + } + + private String normalizePath(String path) { + if (path == null || path.isBlank()) { + return "/"; + } + return path.startsWith("/") ? path : "/" + path; + } + + /* ------------------------------------------------------------------ + * GET : force JSON + display=full + * - on garde tous les autres paramètres (filter[..], limit, etc.) + * - on écrase/normalise seulement output_format et display + * => équivalent à ce que tu faisais en front avec output_format=JSON&display=full + * ------------------------------------------------------------------ */ + public String get(String path, String query) { + String normalizedPath = normalizePath(path); + UriComponentsBuilder builder = UriComponentsBuilder .fromHttpUrl(baseUrl) .path("/api") - .path(path); + .path(normalizedPath); - // On propage TOUT ce que le front a mis (display, filter[...], etc.) if (query != null && !query.isBlank()) { - builder.query(query); + builder.query(query); // on garde tous les filtres venus du front } - if (addDefaultFormatAndDisplay) { - // Si le front n'a pas mis output_format, on force JSON - if (query == null || !query.contains("output_format=")) { - builder.queryParam("output_format", "JSON"); - } - // Si le front n'a pas mis display, on force full - if (query == null || !query.contains("display=")) { - builder.queryParam("display", "full"); - } - } - - return builder.build(true).toUriString(); - } - - /** - * GET générique vers PrestaShop. - * - Propage la queryString Angular (display, filter[...], etc.) - * - Ajoute output_format=JSON & display=full si absents. - */ - public String get(String path, String query) { - String url = buildUrl(path, query, true); + // on force ces deux-là pour corriger les soucis des crochets + id seuls + builder.replaceQueryParam("output_format", "JSON"); + builder.replaceQueryParam("display", "full"); + String url = builder.build(true).toUriString(); log.info("[PrestaShop] GET {}", url); - HttpHeaders headers = new HttpHeaders(); - headers.set(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth); + HttpHeaders headers = baseHeaders(); headers.setAccept(List.of(MediaType.APPLICATION_JSON)); HttpEntity entity = new HttpEntity<>(headers); @@ -85,34 +85,50 @@ public class PrestashopClient { String.class ); + log.info("[PrestaShop] Réponse GET {} pour {}", response.getStatusCode(), url); + if (!response.getStatusCode().is2xxSuccessful()) { - throw new RuntimeException("PrestaShop returned non-2xx status on GET: " + throw new RuntimeException("PrestaShop returned non-2xx status: " + response.getStatusCode() + " for URL " + url); } return response.getBody(); + } catch (HttpStatusCodeException e) { + log.error("[PrestaShop] Erreur GET {} status={} body={}", + url, e.getStatusCode(), e.getResponseBodyAsString(), e); + throw new RuntimeException("Erreur GET PrestaShop", e); } catch (RestClientException e) { log.error("[PrestaShop] Erreur GET {}", url, e); throw new RuntimeException("Erreur GET PrestaShop", e); } } - /** - * POST générique vers PrestaShop. - * On propage la query telle quelle (si Angular met output_format=JSON, etc.). - * Le body est du XML (templates construits côté Angular). - */ - public String post(String path, String query, String body) { - String url = buildUrl(path, query, false); + /* ------------------------------------------------------------------ + * POST : XML vers Presta + * - on transmet la query EXACTE venant du front (price[use_tax], filter, etc.) + * - PAS de output_format / display forcés ici + * ------------------------------------------------------------------ */ + public String post(String path, String query, String xmlBody) { + String normalizedPath = normalizePath(path); + UriComponentsBuilder builder = UriComponentsBuilder + .fromHttpUrl(baseUrl) + .path("/api") + .path(normalizedPath); + + if (query != null && !query.isBlank()) { + builder.query(query); + } + + String url = builder.build(true).toUriString(); log.info("[PrestaShop] POST {}", url); + log.debug("[PrestaShop] POST body=\n{}", xmlBody); - HttpHeaders headers = new HttpHeaders(); - headers.set(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth); - headers.setContentType(MediaType.APPLICATION_XML); + HttpHeaders headers = baseHeaders(); + headers.setContentType(MediaType.APPLICATION_XML); // Presta attend du XML sur POST/PUT headers.setAccept(List.of(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)); - HttpEntity entity = new HttpEntity<>(body, headers); + HttpEntity entity = new HttpEntity<>(xmlBody, headers); try { ResponseEntity response = restTemplate.exchange( @@ -122,34 +138,50 @@ public class PrestashopClient { String.class ); + log.info("[PrestaShop] Réponse POST {} pour {}", response.getStatusCode(), url); + if (!response.getStatusCode().is2xxSuccessful()) { - throw new RuntimeException("PrestaShop returned non-2xx status on POST: " + throw new RuntimeException("PrestaShop returned non-2xx status: " + response.getStatusCode() + " for URL " + url - + " - response: " + response.getBody()); + + " body=" + response.getBody()); } return response.getBody(); + } catch (HttpStatusCodeException e) { + log.error("[PrestaShop] Erreur POST {} status={} body={}", + url, e.getStatusCode(), e.getResponseBodyAsString(), e); + throw new RuntimeException("Erreur POST PrestaShop", e); } catch (RestClientException e) { log.error("[PrestaShop] Erreur POST {}", url, e); throw new RuntimeException("Erreur POST PrestaShop", e); } } - /** - * PUT générique vers PrestaShop. - * Le body est du XML (templates construits côté Angular). - */ - public String put(String path, String query, String body) { - String url = buildUrl(path, query, false); + /* ------------------------------------------------------------------ + * PUT : XML vers Presta + * - pareil que POST : on garde la query telle quelle, pas de display=full ici + * ------------------------------------------------------------------ */ + public String put(String path, String query, String xmlBody) { + String normalizedPath = normalizePath(path); + UriComponentsBuilder builder = UriComponentsBuilder + .fromHttpUrl(baseUrl) + .path("/api") + .path(normalizedPath); + + if (query != null && !query.isBlank()) { + builder.query(query); + } + + String url = builder.build(true).toUriString(); log.info("[PrestaShop] PUT {}", url); + log.debug("[PrestaShop] PUT body=\n{}", xmlBody); - HttpHeaders headers = new HttpHeaders(); - headers.set(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth); + HttpHeaders headers = baseHeaders(); headers.setContentType(MediaType.APPLICATION_XML); headers.setAccept(List.of(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)); - HttpEntity entity = new HttpEntity<>(body, headers); + HttpEntity entity = new HttpEntity<>(xmlBody, headers); try { ResponseEntity response = restTemplate.exchange( @@ -159,29 +191,46 @@ public class PrestashopClient { String.class ); + log.info("[PrestaShop] Réponse PUT {} pour {}", response.getStatusCode(), url); + if (!response.getStatusCode().is2xxSuccessful()) { - throw new RuntimeException("PrestaShop returned non-2xx status on PUT: " + throw new RuntimeException("PrestaShop returned non-2xx status: " + response.getStatusCode() + " for URL " + url - + " - response: " + response.getBody()); + + " body=" + response.getBody()); } return response.getBody(); + } catch (HttpStatusCodeException e) { + log.error("[PrestaShop] Erreur PUT {} status={} body={}", + url, e.getStatusCode(), e.getResponseBodyAsString(), e); + throw new RuntimeException("Erreur PUT PrestaShop", e); } catch (RestClientException e) { log.error("[PrestaShop] Erreur PUT {}", url, e); throw new RuntimeException("Erreur PUT PrestaShop", e); } } - /** - * DELETE générique vers PrestaShop. - */ - public void delete(String path, String query) { - String url = buildUrl(path, query, false); + /* ------------------------------------------------------------------ + * DELETE + * - idem : on relaie la query telle quelle + * ------------------------------------------------------------------ */ + public String delete(String path, String query) { + String normalizedPath = normalizePath(path); + UriComponentsBuilder builder = UriComponentsBuilder + .fromHttpUrl(baseUrl) + .path("/api") + .path(normalizedPath); + + if (query != null && !query.isBlank()) { + builder.query(query); + } + + String url = builder.build(true).toUriString(); log.info("[PrestaShop] DELETE {}", url); - HttpHeaders headers = new HttpHeaders(); - headers.set(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth); + HttpHeaders headers = baseHeaders(); + headers.setAccept(List.of(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)); HttpEntity entity = new HttpEntity<>(headers); @@ -193,11 +242,19 @@ public class PrestashopClient { String.class ); + log.info("[PrestaShop] Réponse DELETE {} pour {}", response.getStatusCode(), url); + if (!response.getStatusCode().is2xxSuccessful()) { - throw new RuntimeException("PrestaShop returned non-2xx status on DELETE: " + throw new RuntimeException("PrestaShop returned non-2xx status: " + response.getStatusCode() + " for URL " + url - + " - response: " + response.getBody()); + + " body=" + response.getBody()); } + + return response.getBody(); + } catch (HttpStatusCodeException e) { + log.error("[PrestaShop] Erreur DELETE {} status={} body={}", + url, e.getStatusCode(), e.getResponseBodyAsString(), e); + throw new RuntimeException("Erreur DELETE PrestaShop", e); } catch (RestClientException e) { log.error("[PrestaShop] Erreur DELETE {}", url, e); throw new RuntimeException("Erreur DELETE PrestaShop", e);