diff --git a/api/src/main/java/fr/gameovergne/api/service/PrestashopClient.java b/api/src/main/java/fr/gameovergne/api/service/PrestashopClient.java index efbb87c..9d179dc 100644 --- a/api/src/main/java/fr/gameovergne/api/service/PrestashopClient.java +++ b/api/src/main/java/fr/gameovergne/api/service/PrestashopClient.java @@ -1,88 +1,191 @@ package fr.gameovergne.api.service; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; +import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestClient; import java.net.URI; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Map; @Service +@Slf4j public class PrestashopClient { - private static final Logger log = LoggerFactory.getLogger(PrestashopClient.class); - private final RestClient client; - private final String baseUrl; + private final String baseUrl; // ex: https://shop.gameovergne.fr/api private final String apiKey; public PrestashopClient( @Value("${prestashop.api.base-url}") String baseUrl, @Value("${prestashop.api.key}") String apiKey ) { - this.baseUrl = baseUrl; // ex: https://shop.gameovergne.fr/api + // on normalise pour éviter les doubles / + if (baseUrl.endsWith("/")) { + this.baseUrl = baseUrl.substring(0, baseUrl.length() - 1); + } else { + this.baseUrl = baseUrl; + } this.apiKey = apiKey; - log.info("[PrestaShop] Base URL = {}", baseUrl); - log.info("[PrestaShop] API key length = {}", apiKey.length()); + log.info("[PrestaShop] Base URL = {}", this.baseUrl); + log.info("[PrestaShop] API key length = {}", apiKey != null ? apiKey.length() : 0); this.client = RestClient.builder() .defaultHeaders(headers -> { - // PLUS de Basic Auth => on force l’auth par ws_key headers.set(HttpHeaders.USER_AGENT, "curl/8.10.1"); + // Accept JSON par défaut ; pour XML on surchargera dans les méthodes XML headers.setAccept(List.of(MediaType.APPLICATION_JSON, MediaType.ALL)); }) .build(); } + private String normalizePath(String path) { + if (path == null || path.isBlank()) return ""; + // /products -> products + return path.startsWith("/") ? path.substring(1) : path; + } + /** - * Appel "proxy" brut : on reçoit la query string telle quelle et on injecte ws_key proprement. + * Construit une URL complète Presta avec ws_key et params encodés UNE SEULE FOIS. + * Utilisée par toutes les méthodes "admin" (getJson/getXml/postXml/putXml/delete). + */ + private String buildUrl(String path, MultiValueMap params) { + StringBuilder fullUrl = new StringBuilder(); + + fullUrl.append(baseUrl) + .append("/") + .append(normalizePath(path)); + + // auth par ws_key (remplace Basic Auth) + String encodedKey = URLEncoder.encode(apiKey, StandardCharsets.UTF_8); + fullUrl.append("?ws_key=").append(encodedKey); + + if (params != null && !params.isEmpty()) { + for (Map.Entry> entry : params.entrySet()) { + String name = entry.getKey(); + for (String value : entry.getValue()) { + fullUrl.append("&") + .append(URLEncoder.encode(name, StandardCharsets.UTF_8)) + .append("=") + .append(URLEncoder.encode(value, StandardCharsets.UTF_8)); + } + } + } + + return fullUrl.toString(); + } + + /** + * Utilisé par le contrôleur proxy : on reçoit la query string brute + * (déjà encodée, ex: display=%5Bid,name,active%5D&output_format=JSON) + * -> on NE LA RÉ-ENCODE PAS. */ public String getWithRawQuery(String resource, String rawQuery) { - try { - // resource = "categories", "products", ... - StringBuilder fullUrl = new StringBuilder(); - fullUrl.append(baseUrl); - if (!baseUrl.endsWith("/")) { - fullUrl.append("/"); - } - fullUrl.append(resource); + StringBuilder fullUrl = new StringBuilder(); - // On construit la query à la main, sans que Spring y touche - String encodedKey = URLEncoder.encode(apiKey, StandardCharsets.UTF_8); - fullUrl.append("?ws_key=").append(encodedKey); + fullUrl.append(baseUrl) + .append("/") + .append(normalizePath(resource)); - if (rawQuery != null && !rawQuery.isBlank()) { - // ATTENTION : rawQuery vient de request.getQueryString() donc déjà encodée. - // On l’ajoute telle quelle pour ne pas casser les [%5B ... %5D] - fullUrl.append("&").append(rawQuery); - } + String encodedKey = URLEncoder.encode(apiKey, StandardCharsets.UTF_8); + fullUrl.append("?ws_key=").append(encodedKey); - String urlString = fullUrl.toString(); - log.info("[PrestaShop] RAW GET via ws_key = {}", urlString); - - return client.get() - .uri(URI.create(urlString)) // IMPORTANT : on donne l’URI complète => pas de ré-encodage - .retrieve() - .body(String.class); - - } catch (Exception e) { - log.error("[PrestaShop] getWithRawQuery error for resource={} rawQuery={}", resource, rawQuery, e); - throw e; + if (rawQuery != null && !rawQuery.isBlank()) { + fullUrl.append("&").append(rawQuery); } + + String url = fullUrl.toString(); + log.info("[PrestaShop] RAW GET via ws_key = {}", url); + + return client.get() + .uri(URI.create(url)) + .accept(MediaType.APPLICATION_JSON, MediaType.ALL) + .retrieve() + .body(String.class); } - // (Optionnel) pour tes autres méthodes listSimple/listProducts, - // tu peux aussi passer par ws_key plutôt que BasicAuth, même principe : - public String getJson(String resource, String fieldsQuery) { - // ex fieldsQuery = "display=[id,name,active]&output_format=JSON" - String rawQuery = fieldsQuery; // tu peux garder le nom si tu veux - return getWithRawQuery(resource, rawQuery); + // ----------------------- + // GET JSON (utilisé PARTOUT dans PrestashopAdminService) + // ----------------------- + + public String getJson(String path, MultiValueMap params) { + String url = buildUrl(path, params); + log.info("[PrestaShop] GET JSON {}", url); + + return client.get() + .uri(URI.create(url)) + .accept(MediaType.APPLICATION_JSON, MediaType.ALL) + .retrieve() + .body(String.class); + } + + // ----------------------- + // GET XML + // ----------------------- + + public String getXml(String path, MultiValueMap params) { + String url = buildUrl(path, params); + log.info("[PrestaShop] GET XML {}", url); + + return client.get() + .uri(URI.create(url)) + .accept(MediaType.APPLICATION_XML, MediaType.TEXT_XML, MediaType.ALL) + .retrieve() + .body(String.class); + } + + // ----------------------- + // POST XML + // ----------------------- + + public String postXml(String path, MultiValueMap params, String xmlBody) { + String url = buildUrl(path, params); + log.info("[PrestaShop] POST XML {}", url); + + return client.post() + .uri(URI.create(url)) + .contentType(MediaType.APPLICATION_XML) + .accept(MediaType.APPLICATION_XML, MediaType.TEXT_XML, MediaType.ALL) + .body(xmlBody) + .retrieve() + .body(String.class); + } + + // ----------------------- + // PUT XML + // ----------------------- + + public String putXml(String path, MultiValueMap params, String xmlBody) { + String url = buildUrl(path, params); + log.info("[PrestaShop] PUT XML {}", url); + + return client.put() + .uri(URI.create(url)) + .contentType(MediaType.APPLICATION_XML) + .accept(MediaType.APPLICATION_XML, MediaType.TEXT_XML, MediaType.ALL) + .body(xmlBody) + .retrieve() + .body(String.class); + } + + // ----------------------- + // DELETE + // ----------------------- + + public void delete(String path, MultiValueMap params) { + String url = buildUrl(path, params); + log.info("[PrestaShop] DELETE {}", url); + + client.delete() + .uri(URI.create(url)) + .retrieve() + .body(Void.class); } } \ No newline at end of file