Compare commits

...

2 Commits

3 changed files with 158 additions and 146 deletions

View File

@@ -16,65 +16,92 @@ public class PrestashopProxyController {
this.prestashopClient = prestashopClient;
}
// Utilitaire pour extraire /products, /products/446, etc.
/**
* Extrait le path Presta à partir de la requête entrante.
* Exemple :
* - requestURI = /api/ps/products/446
* - retourne /products/446
*/
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
return relative.replaceFirst("^/api/ps", ""); // => /products/446
// On enlève le préfixe /api/ps => /products/446
return relative.replaceFirst("^/api/ps", "");
}
// ---------- GET ----------
@GetMapping("/**")
public ResponseEntity<String> proxyGet(HttpServletRequest request) {
String path = extractPath(request);
String query = request.getQueryString();
String query = request.getQueryString(); // ex: display=full&filter[id_product]=123
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);
}
@PutMapping("/**")
public ResponseEntity<String> proxyPut(HttpServletRequest request,
@RequestBody String body) {
String path = extractPath(request);
String query = request.getQueryString();
String responseBody = prestashopClient.put(path, query, body);
return ResponseEntity
.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(responseBody);
}
// ---------- POST ----------
@PostMapping("/**")
public ResponseEntity<String> proxyPost(HttpServletRequest request,
@RequestBody String body) {
public ResponseEntity<String> proxyPost(
HttpServletRequest request,
@RequestBody String bodyFromFront
) {
String path = extractPath(request);
String query = request.getQueryString();
String responseBody = prestashopClient.post(path, query, body);
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;
return ResponseEntity
.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(responseBody);
.contentType(mediaType)
.body(body);
}
// ---------- PUT ----------
@PutMapping("/**")
public ResponseEntity<String> proxyPut(
HttpServletRequest request,
@RequestBody String bodyFromFront
) {
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;
return ResponseEntity
.ok()
.contentType(mediaType)
.body(body);
}
// ---------- DELETE ----------
@DeleteMapping("/**")
public ResponseEntity<String> proxyDelete(HttpServletRequest request) {
public ResponseEntity<Void> proxyDelete(HttpServletRequest request) {
String path = extractPath(request);
String query = request.getQueryString();
String responseBody = prestashopClient.delete(path, query);
prestashopClient.delete(path, query);
return ResponseEntity
.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(responseBody);
return ResponseEntity.noContent().build();
}
}

View File

@@ -5,7 +5,6 @@ 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;
@@ -30,18 +29,45 @@ public class PrestashopClient {
this.basicAuth = basicAuth;
}
// =========================
// GET : on force JSON + full
// =========================
public String get(String path, String ignoredQuery) {
/**
* 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) {
UriComponentsBuilder builder = UriComponentsBuilder
.fromHttpUrl(baseUrl)
.path("/api")
.path(path)
.queryParam("output_format", "JSON")
.queryParam("display", "full");
.path(path);
String url = builder.build(true).toUriString();
// On propage TOUT ce que le front a mis (display, filter[...], etc.)
if (query != null && !query.isBlank()) {
builder.query(query);
}
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);
log.info("[PrestaShop] GET {}", url);
@@ -59,10 +85,8 @@ 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: "
throw new RuntimeException("PrestaShop returned non-2xx status on GET: "
+ response.getStatusCode() + " for URL " + url);
}
@@ -73,79 +97,20 @@ public class PrestashopClient {
}
}
// =========================
// PUT : on respecte la query du front
// =========================
public String put(String path, String query, String body) {
UriComponentsBuilder builder = UriComponentsBuilder
.fromHttpUrl(baseUrl)
.path("/api")
.path(path);
if (query != null && !query.isBlank()) {
// on laisse Angular décider (ex: output_format=JSON)
builder.query(query);
}
String url = builder.build(true).toUriString();
log.info("[PrestaShop] PUT {}", url);
log.debug("[PrestaShop] PUT body = {}", body);
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth);
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(List.of(MediaType.APPLICATION_JSON));
HttpEntity<String> entity = new HttpEntity<>(body, headers);
try {
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();
} catch (HttpStatusCodeException e) {
// Ici on log le body d'erreur de Presta !
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);
}
}
// =========================
// POST : création
// =========================
/**
* 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) {
UriComponentsBuilder builder = UriComponentsBuilder
.fromHttpUrl(baseUrl)
.path("/api")
.path(path);
String url = buildUrl(path, query, false);
if (query != null && !query.isBlank()) {
builder.query(query);
}
String url = builder.build(true).toUriString();
log.info("[PrestaShop] POST {}", url);
log.debug("[PrestaShop] POST body = {}", body);
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth);
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(List.of(MediaType.APPLICATION_JSON));
headers.setContentType(MediaType.APPLICATION_XML);
headers.setAccept(List.of(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML));
HttpEntity<String> entity = new HttpEntity<>(body, headers);
@@ -157,43 +122,66 @@ 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: "
+ response.getStatusCode() + " for URL " + url);
throw new RuntimeException("PrestaShop returned non-2xx status on POST: "
+ response.getStatusCode() + " for URL " + url
+ " - response: " + 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);
}
}
// =========================
// DELETE : suppression
// =========================
public String delete(String path, String query) {
UriComponentsBuilder builder = UriComponentsBuilder
.fromHttpUrl(baseUrl)
.path("/api")
.path(path);
/**
* 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);
if (query != null && !query.isBlank()) {
builder.query(query);
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<>(body, headers);
try {
ResponseEntity<String> response = restTemplate.exchange(
url,
HttpMethod.PUT,
entity,
String.class
);
if (!response.getStatusCode().is2xxSuccessful()) {
throw new RuntimeException("PrestaShop returned non-2xx status on PUT: "
+ response.getStatusCode() + " for URL " + url
+ " - response: " + response.getBody());
}
String url = builder.build(true).toUriString();
return response.getBody();
} 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);
log.info("[PrestaShop] DELETE {}", url);
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth);
headers.setAccept(List.of(MediaType.APPLICATION_JSON));
HttpEntity<Void> entity = new HttpEntity<>(headers);
@@ -205,18 +193,11 @@ 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: "
+ response.getStatusCode() + " for URL " + url);
throw new RuntimeException("PrestaShop returned non-2xx status on DELETE: "
+ response.getStatusCode() + " for URL " + url
+ " - response: " + 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);

View File

@@ -18,14 +18,16 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 0 1 auto;
flex: 1 1 auto;
}
.nav-actions {
display: flex;
gap: 0.5rem;
align-items: center;
flex: 0 0 auto;
flex: 0 1 auto;
justify-content: flex-end;
flex-wrap: nowrap;
}
.mat-menu-item mat-icon {
@@ -53,15 +55,17 @@
gap: 8px;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
button {
.nav-actions button {
padding: 6px 8px;
font-size: 0.95rem;
min-width: 0;
white-space: nowrap;
}
mat-icon {
.nav-actions mat-icon {
font-size: 20px;
}
line-height: 1;
}
}