Add PrestashopAdminController and PrestashopAdminService for managing admin resources

This commit is contained in:
Vincent Guillet
2025-11-29 10:36:09 +01:00
parent 44764a5f14
commit e30fb83043
8 changed files with 1283 additions and 54 deletions

View File

@@ -0,0 +1,78 @@
package fr.gameovergne.api.controller;
import fr.gameovergne.api.dto.ps.*;
import fr.gameovergne.api.model.ps.SimpleResource;
import fr.gameovergne.api.service.PrestashopAdminService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/ps-admin")
@RequiredArgsConstructor
public class PrestashopAdminController {
private final PrestashopAdminService service;
// --- Simple resources ---
@GetMapping("/{resource}")
public List<PsItemDto> listSimple(@PathVariable SimpleResource resource) {
return service.listSimple(resource);
}
@PostMapping("/{resource}")
public long createSimple(@PathVariable SimpleResource resource,
@RequestParam String name) {
return service.createSimple(resource, name);
}
@PutMapping("/{resource}/{id}")
public void updateSimple(@PathVariable SimpleResource resource,
@PathVariable long id,
@RequestParam String name) {
service.updateSimple(resource, id, name);
}
@DeleteMapping("/{resource}/{id}")
public void deleteSimple(@PathVariable SimpleResource resource,
@PathVariable long id) {
service.deleteSimple(resource, id);
}
// --- Produits liste + flags + condition values ---
@GetMapping("/products")
public List<ProductListItemDto> listProducts(@RequestParam(required = false) String q) {
return service.listProducts(q);
}
@GetMapping("/products/{id}/flags")
public ProductFlagsDto getProductFlags(@PathVariable long id) {
return service.getProductFlags(id);
}
@GetMapping("/meta/condition-values")
public List<String> getConditionValues() {
return service.getConditionValues();
}
// --- Create / update / delete produit ---
@PostMapping("/products")
public long createProduct(@RequestBody PsProductDto dto) {
return service.createProduct(dto);
}
@PutMapping("/products/{id}")
public void updateProduct(@PathVariable long id,
@RequestBody PsProductDto dto) {
service.updateProduct(id, dto);
}
@DeleteMapping("/products/{id}")
public void deleteProduct(@PathVariable long id) {
service.deleteProduct(id);
}
}

View File

@@ -0,0 +1,12 @@
package fr.gameovergne.api.dto.ps;
import lombok.Builder;
import lombok.Value;
@Value
@Builder
public class ProductFlagsDto {
boolean complete;
boolean hasManual;
String conditionLabel;
}

View File

@@ -0,0 +1,16 @@
package fr.gameovergne.api.dto.ps;
import lombok.Builder;
import lombok.Value;
@Value
@Builder
public class ProductListItemDto {
Long id;
String name;
Long manufacturerId;
Long supplierId;
Long categoryId;
Double priceHt;
Integer quantity;
}

View File

@@ -0,0 +1,12 @@
package fr.gameovergne.api.dto.ps;
import lombok.Builder;
import lombok.Value;
@Value
@Builder
public class PsItemDto {
Long id;
String name;
Boolean active;
}

View File

@@ -0,0 +1,26 @@
package fr.gameovergne.api.dto.ps;
import lombok.Builder;
import lombok.Value;
@Value
@Builder
public class PsProductDto {
Long id; // optionnel pour update
String name;
Long manufacturerId;
Long supplierId;
Long categoryId;
Double priceTtc;
Double vatRate;
Integer quantity;
Boolean complete; // Complet: Oui/Non
Boolean hasManual; // Notice: Avec/Sans
String conditionLabel; // État: libellé
String description; // description libre
}

View File

@@ -0,0 +1,19 @@
package fr.gameovergne.api.model.ps;
public enum SimpleResource {
categories("categories", "category", true, true),
manufacturers("manufacturers", "manufacturer", false, false),
suppliers("suppliers", "supplier", false, false);
public final String path;
public final String root;
public final boolean needsDefaultLang;
public final boolean nameIsMultilang;
SimpleResource(String path, String root, boolean needsDefaultLang, boolean nameIsMultilang) {
this.path = path;
this.root = root;
this.needsDefaultLang = needsDefaultLang;
this.nameIsMultilang = nameIsMultilang;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,79 +1,97 @@
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.http.*;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClientException; import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestClient;
import org.springframework.web.server.ResponseStatusException; import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI; import java.nio.charset.StandardCharsets;
import java.util.Base64;
@Service @Service
@Slf4j
public class PrestashopClient { public class PrestashopClient {
private static final Logger log = LoggerFactory.getLogger(PrestashopClient.class); private final RestClient client;
private final String baseUrl; private final String baseUrl;
private final String apiKey;
private final RestTemplate restTemplate = new RestTemplate();
public PrestashopClient( public PrestashopClient(
@Value("${prestashop.api.base-url}") String baseUrl, @Value("${prestashop.base-url}") String baseUrl,
@Value("${prestashop.api.key}") String apiKey @Value("${prestashop.api-key}") String apiKey
) { ) {
this.baseUrl = baseUrl; // ex: https://shop.gameovergne.fr/api this.baseUrl = baseUrl;
this.apiKey = apiKey;
String basicAuth = Base64.getEncoder()
.encodeToString((apiKey + ":").getBytes(StandardCharsets.UTF_8));
this.client = RestClient.builder()
.defaultHeader(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth)
.build();
log.info("[PrestaShop] Base URL = {}", baseUrl); log.info("[PrestaShop] Base URL = {}", baseUrl);
log.info("[PrestaShop] API key length = {}", apiKey != null ? apiKey.length() : 0); log.info("[PrestaShop] API key length = {}", apiKey.length());
} }
public ResponseEntity<String> getWithRawQuery(String relativePath, String rawQuery) { private String buildUri(String path, MultiValueMap<String, String> params) {
// Normalisation du path UriComponentsBuilder builder = UriComponentsBuilder
String path = (relativePath == null) ? "" : relativePath; .fromHttpUrl(baseUrl + path);
if (!path.startsWith("/")) { if (params != null && !params.isEmpty()) {
path = "/" + path; builder.queryParams(params);
}
return builder.build(true).toUriString();
} }
// Construction manuelle de lURL public String getJson(String path, MultiValueMap<String, String> params) {
StringBuilder urlBuilder = new StringBuilder(); String uri = buildUri(path, params);
urlBuilder.append(baseUrl); log.info("[PrestaShop] GET JSON {}", uri);
urlBuilder.append(path); return client.get()
.uri(uri)
if (rawQuery != null && !rawQuery.isBlank()) { .accept(MediaType.APPLICATION_JSON)
urlBuilder.append('?').append(rawQuery); .retrieve()
.body(String.class);
} }
String urlString = urlBuilder.toString(); public String getXml(String path, MultiValueMap<String, String> params) {
log.info("[PrestaShop] GET {}", urlString); String uri = buildUri(path, params);
log.info("[PrestaShop] GET XML {}", uri);
return client.get()
.uri(uri)
.accept(MediaType.APPLICATION_XML)
.retrieve()
.body(String.class);
}
URI uri = URI.create(urlString); public String postXml(String path, MultiValueMap<String, String> params, String xmlBody) {
String uri = buildUri(path, params);
log.info("[PrestaShop] POST XML {}", uri);
return client.post()
.uri(uri)
.contentType(MediaType.APPLICATION_XML)
.body(xmlBody)
.retrieve()
.body(String.class);
}
HttpHeaders headers = new HttpHeaders(); public String putXml(String path, MultiValueMap<String, String> params, String xmlBody) {
headers.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE); String uri = buildUri(path, params);
// Presta: Basic Auth avec apiKey comme user et mot de passe vide log.info("[PrestaShop] PUT XML {}", uri);
headers.setBasicAuth(apiKey, ""); return client.put()
.uri(uri)
.contentType(MediaType.APPLICATION_XML)
.body(xmlBody)
.retrieve()
.body(String.class);
}
HttpEntity<Void> entity = new HttpEntity<>(headers); public void delete(String path, MultiValueMap<String, String> params) {
String uri = buildUri(path, params);
try { log.info("[PrestaShop] DELETE {}", uri);
ResponseEntity<String> response = client.delete()
restTemplate.exchange(uri, HttpMethod.GET, entity, String.class); .uri(uri)
.retrieve()
log.info("[PrestaShop] Response {} {}", response.getStatusCode().value(), .toBodilessEntity();
response.getBody());
return response;
} catch (RestClientException ex) {
log.error("[PrestaShop] Error calling {} : {}", urlString, ex.toString(), ex);
// On renvoie quelque chose de propre au client Angular
throw new ResponseStatusException(
HttpStatus.BAD_GATEWAY,
"Error while calling PrestaShop API",
ex
);
}
} }
} }