Refactor PrestashopClient and PrestashopProxyController to use API key for authentication and simplify request handling
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
package fr.gameovergne.api.config;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
@ConfigurationProperties(prefix = "prestashop")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class PrestashopProperties {
|
||||||
|
private String baseUrl;
|
||||||
|
private String apiKey;
|
||||||
|
}
|
||||||
@@ -1,95 +1,48 @@
|
|||||||
package fr.gameovergne.api.controller;
|
package fr.gameovergne.api.controller;// package fr.gameovergne.api.controller;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import fr.gameovergne.api.service.PrestashopClient;
|
import fr.gameovergne.api.service.PrestashopClient;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import org.springframework.http.MediaType;
|
import java.util.Map;
|
||||||
import org.springframework.http.ResponseEntity;
|
import java.util.stream.Collectors;
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/ps")
|
@RequestMapping("/prestashop")
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class PrestashopProxyController {
|
public class PrestashopProxyController {
|
||||||
|
|
||||||
private final PrestashopClient prestashopClient;
|
private final PrestashopClient prestashopClient;
|
||||||
|
|
||||||
public PrestashopProxyController(PrestashopClient prestashopClient) {
|
@GetMapping("/categories")
|
||||||
this.prestashopClient = prestashopClient;
|
public String getCategories(@RequestParam Map<String, String> params) {
|
||||||
|
String query = buildQuery(params);
|
||||||
|
return prestashopClient.get("/categories" + query);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- utilitaire pour extraire le path Presta ----------
|
@GetMapping("/manufacturers")
|
||||||
|
public String getManufacturers(@RequestParam Map<String, String> params) {
|
||||||
private String extractPrestaPath(HttpServletRequest request) {
|
String query = buildQuery(params);
|
||||||
// Traefik strip déjà /gameovergne-api, donc Spring voit /api/ps/...
|
return prestashopClient.get("/manufacturers" + query);
|
||||||
String uri = request.getRequestURI(); // ex: /api/ps/categories
|
|
||||||
String prefix = "/api/ps";
|
|
||||||
String path = uri.startsWith(prefix) ? uri.substring(prefix.length()) : uri;
|
|
||||||
if (path.isEmpty()) {
|
|
||||||
path = "/";
|
|
||||||
}
|
|
||||||
return path;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- GET : /api/ps/** -> Presta GET ----------
|
@GetMapping("/suppliers")
|
||||||
|
public String getSuppliers(@RequestParam Map<String, String> params) {
|
||||||
@GetMapping("/**")
|
String query = buildQuery(params);
|
||||||
public ResponseEntity<String> proxyGet(HttpServletRequest request) {
|
return prestashopClient.get("/suppliers" + query);
|
||||||
String path = extractPrestaPath(request); // ex: "/categories"
|
|
||||||
String query = request.getQueryString(); // ex: "display=[id,name]&output_format=JSON"
|
|
||||||
|
|
||||||
String body = prestashopClient.get(path, query);
|
|
||||||
|
|
||||||
// Presta renvoie du JSON (output_format=JSON), donc on force application/json
|
|
||||||
return ResponseEntity
|
|
||||||
.ok()
|
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
|
||||||
.body(body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- POST : /api/ps/** -> Presta POST ----------
|
private String buildQuery(Map<String, String> params) {
|
||||||
|
if (params == null || params.isEmpty()) {
|
||||||
@PostMapping("/**")
|
return "";
|
||||||
public ResponseEntity<String> proxyPost(HttpServletRequest request,
|
|
||||||
@RequestBody(required = false) String xmlBody) {
|
|
||||||
String path = extractPrestaPath(request);
|
|
||||||
String query = request.getQueryString();
|
|
||||||
|
|
||||||
String responseBody = prestashopClient.post(path, query, xmlBody != null ? xmlBody : "");
|
|
||||||
|
|
||||||
// Les POST/PUT/DELETE Presta renvoient typiquement de l’XML
|
|
||||||
return ResponseEntity
|
|
||||||
.ok()
|
|
||||||
.contentType(MediaType.APPLICATION_XML)
|
|
||||||
.body(responseBody);
|
|
||||||
}
|
}
|
||||||
|
String queryString = params.entrySet().stream()
|
||||||
// ---------- PUT : /api/ps/** -> Presta PUT ----------
|
.map(e -> e.getKey() + "=" + e.getValue())
|
||||||
|
.collect(Collectors.joining("&"));
|
||||||
@PutMapping("/**")
|
return "?" + queryString;
|
||||||
public ResponseEntity<String> proxyPut(HttpServletRequest request,
|
|
||||||
@RequestBody(required = false) String xmlBody) {
|
|
||||||
String path = extractPrestaPath(request);
|
|
||||||
String query = request.getQueryString();
|
|
||||||
|
|
||||||
String responseBody = prestashopClient.put(path, query, xmlBody != null ? xmlBody : "");
|
|
||||||
|
|
||||||
return ResponseEntity
|
|
||||||
.ok()
|
|
||||||
.contentType(MediaType.APPLICATION_XML)
|
|
||||||
.body(responseBody);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- DELETE : /api/ps/** -> Presta DELETE ----------
|
|
||||||
|
|
||||||
@DeleteMapping("/**")
|
|
||||||
public ResponseEntity<String> proxyDelete(HttpServletRequest request) {
|
|
||||||
String path = extractPrestaPath(request);
|
|
||||||
String query = request.getQueryString();
|
|
||||||
|
|
||||||
String responseBody = prestashopClient.delete(path, query);
|
|
||||||
|
|
||||||
return ResponseEntity
|
|
||||||
.ok()
|
|
||||||
.contentType(MediaType.APPLICATION_XML)
|
|
||||||
.body(responseBody);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,186 +1,70 @@
|
|||||||
package fr.gameovergne.api.service;
|
package fr.gameovergne.api.service;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.http.*;
|
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class PrestashopClient {
|
public class PrestashopClient {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(PrestashopClient.class);
|
private final RestTemplate restTemplate;
|
||||||
|
|
||||||
private final RestTemplate restTemplate = new RestTemplate();
|
|
||||||
private final String baseUrl;
|
private final String baseUrl;
|
||||||
private final String basicAuth; // base64 SANS le "Basic "
|
|
||||||
|
|
||||||
public PrestashopClient(
|
public PrestashopClient(
|
||||||
@Value("${prestashop.base-url}") String baseUrl,
|
@Value("${prestashop.base-url}") String baseUrl,
|
||||||
@Value("${prestashop.basic-auth}") String basicAuth
|
@Value("${prestashop.api-key}") String rawApiKey,
|
||||||
|
RestTemplateBuilder builder
|
||||||
) {
|
) {
|
||||||
|
// Normalisation baseUrl (pas de / final)
|
||||||
|
if (baseUrl.endsWith("/")) {
|
||||||
|
baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
|
||||||
|
}
|
||||||
this.baseUrl = baseUrl;
|
this.baseUrl = baseUrl;
|
||||||
this.basicAuth = basicAuth;
|
|
||||||
|
String apiKey = rawApiKey == null ? null : rawApiKey.trim();
|
||||||
|
|
||||||
|
if (apiKey == null || apiKey.isBlank()) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"PrestaShop API key is null/blank (prestashop.api-key)."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== GET ==========
|
// Logs pour contrôle visuel
|
||||||
|
log.info("[PrestaShop] API key length = {}", apiKey.length());
|
||||||
|
log.info("[PrestaShop] API key prefix = {}****", apiKey.substring(0, 4));
|
||||||
|
|
||||||
|
// IMPORTANT : Basic Auth = username = clé, password = vide
|
||||||
|
this.restTemplate = builder
|
||||||
|
.basicAuthentication(apiKey, "")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param path ex: "/categories", "/products/12"
|
* Appel simple GET PrestaShop.
|
||||||
* @param rawQuery ex: "display=[id,name]&output_format=JSON&filter[active]=1", ou null
|
* Exemple d'appel depuis le controller :
|
||||||
|
* client.get("/categories?display=[id,name,active]&output_format=JSON");
|
||||||
*/
|
*/
|
||||||
public String get(String path, String rawQuery) {
|
public String get(String pathAndQuery) {
|
||||||
UriComponentsBuilder builder = UriComponentsBuilder
|
String url;
|
||||||
.fromHttpUrl(baseUrl)
|
|
||||||
.path("/api")
|
|
||||||
.path(path);
|
|
||||||
|
|
||||||
if (rawQuery != null && !rawQuery.isBlank()) {
|
// On accepte soit un chemin relatif, soit une URL complète (au cas où)
|
||||||
// On laisse le caller (notre proxy / Angular via Spring) décider des paramètres
|
if (pathAndQuery.startsWith("http://") || pathAndQuery.startsWith("https://")) {
|
||||||
builder.query(rawQuery);
|
url = pathAndQuery;
|
||||||
|
} else if (pathAndQuery.startsWith("/")) {
|
||||||
|
url = baseUrl + pathAndQuery;
|
||||||
} else {
|
} else {
|
||||||
// fallback par défaut si aucun paramètre reçu
|
url = baseUrl + "/" + pathAndQuery;
|
||||||
builder
|
|
||||||
.queryParam("output_format", "JSON")
|
|
||||||
.queryParam("display", "full");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String url = builder.build(true).toUriString();
|
|
||||||
log.info("[PrestaShop] GET {}", url);
|
log.info("[PrestaShop] GET {}", url);
|
||||||
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
return restTemplate
|
||||||
headers.set(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth);
|
.exchange(url, HttpMethod.GET, HttpEntity.EMPTY, String.class)
|
||||||
headers.setAccept(List.of(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML));
|
.getBody();
|
||||||
|
|
||||||
HttpEntity<Void> entity = new HttpEntity<>(headers);
|
|
||||||
|
|
||||||
ResponseEntity<String> response = restTemplate.exchange(
|
|
||||||
url,
|
|
||||||
HttpMethod.GET,
|
|
||||||
entity,
|
|
||||||
String.class
|
|
||||||
);
|
|
||||||
|
|
||||||
log.info("[PrestaShop] Réponse GET {} pour {}", response.getStatusCode(), url);
|
|
||||||
|
|
||||||
if (!response.getStatusCode().is2xxSuccessful()) {
|
|
||||||
throw new RuntimeException("PrestaShop returned non-2xx status: "
|
|
||||||
+ response.getStatusCode() + " for URL " + url);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.getBody();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== POST ==========
|
|
||||||
public String post(String path, String query, String xmlBody) {
|
|
||||||
UriComponentsBuilder builder = UriComponentsBuilder
|
|
||||||
.fromHttpUrl(baseUrl)
|
|
||||||
.path("/api")
|
|
||||||
.path(path);
|
|
||||||
|
|
||||||
if (query != null && !query.isBlank()) {
|
|
||||||
builder.query(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
String url = builder.build(true).toUriString();
|
|
||||||
log.info("[PrestaShop] POST {}", url);
|
|
||||||
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.set(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth);
|
|
||||||
headers.setContentType(MediaType.APPLICATION_XML); // XML obligatoire
|
|
||||||
headers.setAccept(List.of(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML));
|
|
||||||
|
|
||||||
HttpEntity<String> entity = new HttpEntity<>(xmlBody, headers);
|
|
||||||
|
|
||||||
ResponseEntity<String> response = restTemplate.exchange(
|
|
||||||
url,
|
|
||||||
HttpMethod.POST,
|
|
||||||
entity,
|
|
||||||
String.class
|
|
||||||
);
|
|
||||||
log.info("[PrestaShop] Réponse POST {} pour {}", response.getStatusCode(), url);
|
|
||||||
|
|
||||||
if (!response.getStatusCode().is2xxSuccessful()) {
|
|
||||||
throw new RuntimeException("PrestaShop returned non-2xx status: "
|
|
||||||
+ response.getStatusCode() + " for URL " + url);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.getBody();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== PUT ==========
|
|
||||||
public String put(String path, String query, String xmlBody) {
|
|
||||||
UriComponentsBuilder builder = UriComponentsBuilder
|
|
||||||
.fromHttpUrl(baseUrl)
|
|
||||||
.path("/api")
|
|
||||||
.path(path);
|
|
||||||
|
|
||||||
if (query != null && !query.isBlank()) {
|
|
||||||
builder.query(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
String url = builder.build(true).toUriString();
|
|
||||||
log.info("[PrestaShop] PUT {}", url);
|
|
||||||
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.set(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth);
|
|
||||||
headers.setContentType(MediaType.APPLICATION_XML);
|
|
||||||
headers.setAccept(List.of(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML));
|
|
||||||
|
|
||||||
HttpEntity<String> entity = new HttpEntity<>(xmlBody, headers);
|
|
||||||
|
|
||||||
ResponseEntity<String> response = restTemplate.exchange(
|
|
||||||
url,
|
|
||||||
HttpMethod.PUT,
|
|
||||||
entity,
|
|
||||||
String.class
|
|
||||||
);
|
|
||||||
log.info("[PrestaShop] Réponse PUT {} pour {}", response.getStatusCode(), url);
|
|
||||||
|
|
||||||
if (!response.getStatusCode().is2xxSuccessful()) {
|
|
||||||
throw new RuntimeException("PrestaShop returned non-2xx status: "
|
|
||||||
+ response.getStatusCode() + " for URL " + url);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.getBody();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== DELETE ==========
|
|
||||||
public String delete(String path, String query) {
|
|
||||||
UriComponentsBuilder builder = UriComponentsBuilder
|
|
||||||
.fromHttpUrl(baseUrl)
|
|
||||||
.path("/api")
|
|
||||||
.path(path);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
HttpEntity<Void> entity = new HttpEntity<>(headers);
|
|
||||||
|
|
||||||
ResponseEntity<String> response = restTemplate.exchange(
|
|
||||||
url,
|
|
||||||
HttpMethod.DELETE,
|
|
||||||
entity,
|
|
||||||
String.class
|
|
||||||
);
|
|
||||||
log.info("[PrestaShop] Réponse DELETE {} pour {}", response.getStatusCode(), url);
|
|
||||||
|
|
||||||
if (!response.getStatusCode().is2xxSuccessful()) {
|
|
||||||
throw new RuntimeException("PrestaShop returned non-2xx status: "
|
|
||||||
+ response.getStatusCode() + " for URL " + url);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.getBody();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,5 +13,4 @@ spring.jpa.show-sql=true
|
|||||||
jwt.secret=a23ac96ce968bf13099d99410b951dd498118851bdfc996a3f844bd68b1b2afd
|
jwt.secret=a23ac96ce968bf13099d99410b951dd498118851bdfc996a3f844bd68b1b2afd
|
||||||
|
|
||||||
prestashop.base-url=https://shop.gameovergne.fr
|
prestashop.base-url=https://shop.gameovergne.fr
|
||||||
prestashop.basic-auth=2AQPG13MJ8X117U6FJ5NGHPS93HE34AB
|
prestashop.api-key=${PRESTASHOP_API_KEY}
|
||||||
#prestashop.basic-auth=Basic MkFRUEcxM01KOFgxMTdVNkZKNU5HSFBTOTNIRTM0QUI=
|
|
||||||
Reference in New Issue
Block a user