Amélioration de l'IA pour la partie atelier PDF
Mise en place d'un outil permettant de faire des tableau d'objets pour des boutiques par exemple
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
package com.loremind.application.campaigncontext;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Campaign;
|
||||
import com.loremind.domain.campaigncontext.CatalogItem;
|
||||
import com.loremind.domain.campaigncontext.ItemCatalog;
|
||||
import com.loremind.domain.campaigncontext.ports.CampaignRepository;
|
||||
import com.loremind.domain.campaigncontext.ports.ItemCatalogGenerator;
|
||||
import com.loremind.domain.campaigncontext.ports.ItemCatalogRepository;
|
||||
import com.loremind.domain.gamesystemcontext.ports.GameSystemRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Service d'application des catalogues d'objets (campagne) : CRUD + génération IA.
|
||||
*/
|
||||
@Service
|
||||
public class ItemCatalogService {
|
||||
|
||||
private final ItemCatalogRepository repository;
|
||||
private final ItemCatalogGenerator generator;
|
||||
private final CampaignRepository campaignRepository;
|
||||
private final GameSystemRepository gameSystemRepository;
|
||||
|
||||
public ItemCatalogService(
|
||||
ItemCatalogRepository repository,
|
||||
ItemCatalogGenerator generator,
|
||||
CampaignRepository campaignRepository,
|
||||
GameSystemRepository gameSystemRepository) {
|
||||
this.repository = repository;
|
||||
this.generator = generator;
|
||||
this.campaignRepository = campaignRepository;
|
||||
this.gameSystemRepository = gameSystemRepository;
|
||||
}
|
||||
|
||||
public record CatalogData(
|
||||
String name,
|
||||
String description,
|
||||
String icon,
|
||||
List<CatalogItem> items,
|
||||
String campaignId,
|
||||
Integer order
|
||||
) {}
|
||||
|
||||
public ItemCatalog createCatalog(CatalogData data) {
|
||||
int order = data.order() != null ? data.order() : nextOrderFor(data.campaignId());
|
||||
ItemCatalog catalog = ItemCatalog.builder()
|
||||
.name(data.name())
|
||||
.description(data.description())
|
||||
.icon(data.icon())
|
||||
.items(copyItems(data.items()))
|
||||
.campaignId(data.campaignId())
|
||||
.order(order)
|
||||
.build();
|
||||
return repository.save(catalog);
|
||||
}
|
||||
|
||||
public Optional<ItemCatalog> getCatalogById(String id) {
|
||||
return repository.findById(id);
|
||||
}
|
||||
|
||||
public List<ItemCatalog> getCatalogsByCampaignId(String campaignId) {
|
||||
return repository.findByCampaignId(campaignId);
|
||||
}
|
||||
|
||||
public ItemCatalog updateCatalog(String id, CatalogData data) {
|
||||
ItemCatalog existing = repository.findById(id)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Catalogue d'objets introuvable: " + id));
|
||||
existing.setName(data.name());
|
||||
existing.setDescription(data.description());
|
||||
existing.setIcon(data.icon());
|
||||
existing.setItems(copyItems(data.items()));
|
||||
if (data.order() != null) {
|
||||
existing.setOrder(data.order());
|
||||
}
|
||||
return repository.save(existing);
|
||||
}
|
||||
|
||||
public void deleteCatalog(String id) {
|
||||
repository.deleteById(id);
|
||||
}
|
||||
|
||||
/** Génère une PROPOSITION de catalogue (non persistée) via l'IA, contextualisée campagne. */
|
||||
public ItemCatalog generateProposal(String campaignId, String description) {
|
||||
ItemCatalogGenerator.GeneratedCatalog g = generator.generate(description, buildContext(campaignId));
|
||||
return ItemCatalog.builder()
|
||||
.name(g.name())
|
||||
.description(g.description())
|
||||
.campaignId(campaignId)
|
||||
.items(g.items() != null ? new ArrayList<>(g.items()) : new ArrayList<>())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static List<CatalogItem> copyItems(List<CatalogItem> items) {
|
||||
return items != null ? new ArrayList<>(items) : new ArrayList<>();
|
||||
}
|
||||
|
||||
private String buildContext(String campaignId) {
|
||||
if (campaignId == null) return "";
|
||||
Campaign campaign = campaignRepository.findById(campaignId).orElse(null);
|
||||
if (campaign == null) return "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Campagne : ").append(campaign.getName());
|
||||
if (campaign.getDescription() != null && !campaign.getDescription().isBlank()) {
|
||||
sb.append(" — ").append(campaign.getDescription().trim());
|
||||
}
|
||||
if (campaign.getGameSystemId() != null && !campaign.getGameSystemId().isBlank()) {
|
||||
gameSystemRepository.findById(campaign.getGameSystemId())
|
||||
.ifPresent(gs -> sb.append("\nSystème de jeu : ").append(gs.getName()));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private int nextOrderFor(String campaignId) {
|
||||
return repository.findByCampaignId(campaignId).stream()
|
||||
.mapToInt(ItemCatalog::getOrder)
|
||||
.max()
|
||||
.orElse(-1) + 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.loremind.domain.campaigncontext;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Un objet d'un {@link ItemCatalog} (boutique, butin, trésor…).
|
||||
* Value object possédé par le catalogue : remplacé en bloc à chaque mise à jour.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class CatalogItem {
|
||||
|
||||
private String name;
|
||||
|
||||
/** Prix libre (ex. « 50 po », « 2 pa »). Nullable. */
|
||||
private String price;
|
||||
|
||||
/** Catégorie de regroupement (ex. « Armes », « Potions »). Nullable. */
|
||||
private String category;
|
||||
|
||||
/** Description / effet (markdown). Nullable. */
|
||||
private String description;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.loremind.domain.campaigncontext;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Catalogue d'objets prédéfinis d'une campagne : une boutique, un butin, un trésor…
|
||||
* Le MJ le remplit à la main ou via l'IA, et le consulte (notamment en session) quand
|
||||
* les joueurs visitent une échoppe. Scope campagne (cross-aggregate via ID).
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class ItemCatalog {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
|
||||
/** Description libre (à quoi sert ce catalogue / cette boutique). Nullable. */
|
||||
private String description;
|
||||
|
||||
/** Clé d'icône (lucide) pour la sidebar/fiche. Nullable. */
|
||||
private String icon;
|
||||
|
||||
/** Référence vers la Campaign parente (cross-aggregate via ID). */
|
||||
private String campaignId;
|
||||
|
||||
/** Ordre d'affichage dans la liste des catalogues de la campagne. */
|
||||
private int order;
|
||||
|
||||
/** Objets ordonnés du catalogue. Jamais null après construction. */
|
||||
private List<CatalogItem> items;
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
public List<CatalogItem> getItems() {
|
||||
if (items == null) items = new ArrayList<>();
|
||||
return items;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.loremind.domain.campaigncontext.ports;
|
||||
|
||||
/**
|
||||
* Échec de génération IA d'un catalogue d'objets (Brain injoignable, erreur du
|
||||
* modèle, réponse inexploitable…). Mappée en HTTP 502 par le contrôleur.
|
||||
*/
|
||||
public class ItemCatalogGenerationException extends RuntimeException {
|
||||
public ItemCatalogGenerationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ItemCatalogGenerationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.loremind.domain.campaigncontext.ports;
|
||||
|
||||
import com.loremind.domain.campaigncontext.CatalogItem;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Port de sortie : génération IA d'un catalogue d'objets. Implémenté par un client
|
||||
* du Brain (service IA Python).
|
||||
*/
|
||||
public interface ItemCatalogGenerator {
|
||||
|
||||
/** Catalogue proposé (non persisté) à partir d'une description. */
|
||||
record GeneratedCatalog(String name, String description, List<CatalogItem> items) {}
|
||||
|
||||
/**
|
||||
* Génère une proposition de catalogue (objets) sur le sujet donné, en s'appuyant
|
||||
* sur le contexte (campagne, système…) s'il est fourni.
|
||||
*/
|
||||
GeneratedCatalog generate(String description, String context);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.loremind.domain.campaigncontext.ports;
|
||||
|
||||
import com.loremind.domain.campaigncontext.ItemCatalog;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Port de sortie pour la persistance des {@link ItemCatalog}.
|
||||
*/
|
||||
public interface ItemCatalogRepository {
|
||||
|
||||
ItemCatalog save(ItemCatalog catalog);
|
||||
|
||||
Optional<ItemCatalog> findById(String id);
|
||||
|
||||
List<ItemCatalog> findByCampaignId(String campaignId);
|
||||
|
||||
void deleteById(String id);
|
||||
|
||||
boolean existsById(String id);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.loremind.infrastructure.ai;
|
||||
|
||||
import com.loremind.domain.campaigncontext.CatalogItem;
|
||||
import com.loremind.domain.campaigncontext.ports.ItemCatalogGenerationException;
|
||||
import com.loremind.domain.campaigncontext.ports.ItemCatalogGenerator;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.ResourceAccessException;
|
||||
import org.springframework.web.client.RestClientResponseException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Adapter de sortie : génère un catalogue d'objets via le Brain (POST one-shot).
|
||||
*/
|
||||
@Component
|
||||
public class BrainItemCatalogClient implements ItemCatalogGenerator {
|
||||
|
||||
private static final String GENERATE_PATH = "/generate/item-catalog";
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
private final String baseUrl;
|
||||
|
||||
public BrainItemCatalogClient(
|
||||
RestTemplate restTemplate,
|
||||
@Value("${brain.base-url}") String baseUrl) {
|
||||
this.restTemplate = restTemplate;
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public GeneratedCatalog generate(String description, String context) {
|
||||
Map<String, Object> req = new LinkedHashMap<>();
|
||||
req.put("description", description == null ? "" : description);
|
||||
req.put("context", context == null ? "" : context);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(req, headers);
|
||||
|
||||
Map<String, Object> resp;
|
||||
try {
|
||||
resp = restTemplate.postForObject(baseUrl + GENERATE_PATH, entity, Map.class);
|
||||
} catch (ResourceAccessException e) {
|
||||
throw new ItemCatalogGenerationException("Le Brain est injoignable (timeout ou arrêté).", e);
|
||||
} catch (RestClientResponseException e) {
|
||||
throw new ItemCatalogGenerationException(
|
||||
"Le Brain a répondu HTTP " + e.getStatusCode().value() + " : " + e.getResponseBodyAsString(), e);
|
||||
} catch (Exception e) {
|
||||
throw new ItemCatalogGenerationException("Erreur inattendue lors de l'appel au Brain.", e);
|
||||
}
|
||||
if (resp == null) {
|
||||
throw new ItemCatalogGenerationException("Le Brain a renvoyé une réponse vide.");
|
||||
}
|
||||
|
||||
List<CatalogItem> items = new ArrayList<>();
|
||||
Object rawItems = resp.get("items");
|
||||
if (rawItems instanceof List<?> list) {
|
||||
for (Object item : list) {
|
||||
if (!(item instanceof Map<?, ?> m)) continue;
|
||||
String name = asString(m.get("name"));
|
||||
if (name == null || name.isBlank()) continue;
|
||||
items.add(CatalogItem.builder()
|
||||
.name(name)
|
||||
.price(asString(m.get("price")))
|
||||
.category(asString(m.get("category")))
|
||||
.description(asString(m.get("description")))
|
||||
.build());
|
||||
}
|
||||
}
|
||||
if (items.isEmpty()) {
|
||||
throw new ItemCatalogGenerationException("Aucun objet généré — réessaie ou reformule.");
|
||||
}
|
||||
String name = asString(resp.get("name"));
|
||||
return new GeneratedCatalog(
|
||||
name != null && !name.isBlank() ? name : description,
|
||||
asString(resp.get("description")),
|
||||
items);
|
||||
}
|
||||
|
||||
private static String asString(Object o) {
|
||||
return o != null ? o.toString() : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.loremind.infrastructure.persistence.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* Entité JPA d'un objet de catalogue (enfant de {@link ItemCatalogJpaEntity}).
|
||||
* Ordonné par {@code position}. Référence parente exclue de toString/equals.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "catalog_items", indexes = {
|
||||
@Index(name = "idx_catalog_items_catalog_id", columnList = "catalog_id")
|
||||
})
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CatalogItemJpaEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(length = 64)
|
||||
private String price;
|
||||
|
||||
@Column(length = 128)
|
||||
private String category;
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String description;
|
||||
|
||||
@Column(nullable = false)
|
||||
private int position;
|
||||
|
||||
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "catalog_id", nullable = false)
|
||||
@ToString.Exclude
|
||||
@EqualsAndHashCode.Exclude
|
||||
private ItemCatalogJpaEntity catalog;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.loremind.infrastructure.persistence.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Entité JPA d'un catalogue d'objets (parent) avec ses objets ordonnés (enfants).
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "item_catalogs", indexes = {
|
||||
@Index(name = "idx_item_catalogs_campaign_id", columnList = "campaign_id")
|
||||
})
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ItemCatalogJpaEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String description;
|
||||
|
||||
@Column(length = 64)
|
||||
private String icon;
|
||||
|
||||
@Column(name = "campaign_id", nullable = false)
|
||||
private Long campaignId;
|
||||
|
||||
@Column(name = "\"order\"", nullable = false)
|
||||
private int order;
|
||||
|
||||
@OneToMany(
|
||||
mappedBy = "catalog",
|
||||
cascade = CascadeType.ALL,
|
||||
orphanRemoval = true,
|
||||
fetch = FetchType.LAZY
|
||||
)
|
||||
@OrderBy("position ASC")
|
||||
@Builder.Default
|
||||
private List<CatalogItemJpaEntity> items = new ArrayList<>();
|
||||
|
||||
@Column(name = "created_at", nullable = false, updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "updated_at", nullable = false)
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
createdAt = LocalDateTime.now();
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.loremind.infrastructure.persistence.jpa;
|
||||
|
||||
import com.loremind.infrastructure.persistence.entity.ItemCatalogJpaEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface ItemCatalogJpaRepository extends JpaRepository<ItemCatalogJpaEntity, Long> {
|
||||
|
||||
List<ItemCatalogJpaEntity> findByCampaignIdOrderByOrderAsc(Long campaignId);
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.loremind.infrastructure.persistence.postgres;
|
||||
|
||||
import com.loremind.domain.campaigncontext.CatalogItem;
|
||||
import com.loremind.domain.campaigncontext.ItemCatalog;
|
||||
import com.loremind.domain.campaigncontext.ports.ItemCatalogRepository;
|
||||
import com.loremind.infrastructure.persistence.entity.CatalogItemJpaEntity;
|
||||
import com.loremind.infrastructure.persistence.entity.ItemCatalogJpaEntity;
|
||||
import com.loremind.infrastructure.persistence.jpa.ItemCatalogJpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Repository
|
||||
public class PostgresItemCatalogRepository implements ItemCatalogRepository {
|
||||
|
||||
private final ItemCatalogJpaRepository jpaRepository;
|
||||
|
||||
public PostgresItemCatalogRepository(ItemCatalogJpaRepository jpaRepository) {
|
||||
this.jpaRepository = jpaRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public ItemCatalog save(ItemCatalog catalog) {
|
||||
ItemCatalogJpaEntity entity = (catalog.getId() != null)
|
||||
? jpaRepository.findById(Long.parseLong(catalog.getId())).orElseGet(ItemCatalogJpaEntity::new)
|
||||
: new ItemCatalogJpaEntity();
|
||||
|
||||
entity.setName(catalog.getName());
|
||||
entity.setDescription(catalog.getDescription());
|
||||
entity.setIcon(catalog.getIcon());
|
||||
entity.setCampaignId(Long.parseLong(catalog.getCampaignId()));
|
||||
entity.setOrder(catalog.getOrder());
|
||||
|
||||
// Remplacement en bloc des objets (orphanRemoval supprime les anciens).
|
||||
entity.getItems().clear();
|
||||
int position = 0;
|
||||
for (CatalogItem it : catalog.getItems()) {
|
||||
entity.getItems().add(CatalogItemJpaEntity.builder()
|
||||
.name(it.getName())
|
||||
.price(it.getPrice())
|
||||
.category(it.getCategory())
|
||||
.description(it.getDescription())
|
||||
.position(position++)
|
||||
.catalog(entity)
|
||||
.build());
|
||||
}
|
||||
|
||||
return toDomainEntity(jpaRepository.save(entity));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Optional<ItemCatalog> findById(String id) {
|
||||
return jpaRepository.findById(Long.parseLong(id)).map(this::toDomainEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<ItemCatalog> findByCampaignId(String campaignId) {
|
||||
return jpaRepository.findByCampaignIdOrderByOrderAsc(Long.parseLong(campaignId)).stream()
|
||||
.map(this::toDomainEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteById(String id) {
|
||||
jpaRepository.deleteById(Long.parseLong(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsById(String id) {
|
||||
return jpaRepository.existsById(Long.parseLong(id));
|
||||
}
|
||||
|
||||
private ItemCatalog toDomainEntity(ItemCatalogJpaEntity e) {
|
||||
List<CatalogItem> items = e.getItems().stream()
|
||||
.map(c -> CatalogItem.builder()
|
||||
.name(c.getName())
|
||||
.price(c.getPrice())
|
||||
.category(c.getCategory())
|
||||
.description(c.getDescription())
|
||||
.build())
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
return ItemCatalog.builder()
|
||||
.id(e.getId().toString())
|
||||
.name(e.getName())
|
||||
.description(e.getDescription())
|
||||
.icon(e.getIcon())
|
||||
.campaignId(e.getCampaignId().toString())
|
||||
.order(e.getOrder())
|
||||
.items(items)
|
||||
.createdAt(e.getCreatedAt())
|
||||
.updatedAt(e.getUpdatedAt())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.loremind.infrastructure.web.controller;
|
||||
|
||||
import com.loremind.application.campaigncontext.ItemCatalogService;
|
||||
import com.loremind.domain.campaigncontext.ItemCatalog;
|
||||
import com.loremind.domain.campaigncontext.ports.ItemCatalogGenerationException;
|
||||
import com.loremind.infrastructure.web.dto.campaigncontext.ItemCatalogDTO;
|
||||
import com.loremind.infrastructure.web.mapper.ItemCatalogMapper;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/item-catalogs")
|
||||
public class ItemCatalogController {
|
||||
|
||||
private final ItemCatalogService service;
|
||||
private final ItemCatalogMapper mapper;
|
||||
|
||||
public ItemCatalogController(ItemCatalogService service, ItemCatalogMapper mapper) {
|
||||
this.service = service;
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<ItemCatalogDTO> create(@RequestBody ItemCatalogDTO dto) {
|
||||
ItemCatalog created = service.createCatalog(toData(dto, null));
|
||||
return ResponseEntity.ok(mapper.toDTO(created));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<ItemCatalogDTO> getById(@PathVariable String id) {
|
||||
return service.getCatalogById(id)
|
||||
.map(c -> ResponseEntity.ok(mapper.toDTO(c)))
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@GetMapping("/campaign/{campaignId}")
|
||||
public ResponseEntity<List<ItemCatalogDTO>> getByCampaign(@PathVariable String campaignId) {
|
||||
List<ItemCatalogDTO> dtos = service.getCatalogsByCampaignId(campaignId).stream()
|
||||
.map(mapper::toDTO)
|
||||
.collect(Collectors.toList());
|
||||
return ResponseEntity.ok(dtos);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<ItemCatalogDTO> update(@PathVariable String id, @RequestBody ItemCatalogDTO dto) {
|
||||
ItemCatalog updated = service.updateCatalog(id, toData(dto, dto.getOrder()));
|
||||
return ResponseEntity.ok(mapper.toDTO(updated));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Void> delete(@PathVariable String id) {
|
||||
service.deleteCatalog(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
/** Génère une PROPOSITION de catalogue via l'IA (non persistée) — l'UI préremplit le formulaire. */
|
||||
@PostMapping("/generate")
|
||||
public ResponseEntity<ItemCatalogDTO> generate(@RequestBody GenerateRequest req) {
|
||||
try {
|
||||
ItemCatalog proposal = service.generateProposal(req.campaignId(), req.description());
|
||||
return ResponseEntity.ok(mapper.toDTO(proposal));
|
||||
} catch (ItemCatalogGenerationException e) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_GATEWAY, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private ItemCatalogService.CatalogData toData(ItemCatalogDTO dto, Integer order) {
|
||||
return new ItemCatalogService.CatalogData(
|
||||
dto.getName(),
|
||||
dto.getDescription(),
|
||||
dto.getIcon(),
|
||||
mapper.toDomainItems(dto.getItems()),
|
||||
dto.getCampaignId(),
|
||||
order
|
||||
);
|
||||
}
|
||||
|
||||
public record GenerateRequest(String campaignId, String description) {}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.loremind.infrastructure.web.dto.campaigncontext;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* DTO d'un objet de catalogue.
|
||||
*/
|
||||
@Data
|
||||
public class CatalogItemDTO {
|
||||
private String name;
|
||||
private String price;
|
||||
private String category;
|
||||
private String description;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.loremind.infrastructure.web.dto.campaigncontext;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* DTO d'un catalogue d'objets (avec ses objets).
|
||||
*/
|
||||
@Data
|
||||
public class ItemCatalogDTO {
|
||||
private String id;
|
||||
private String name;
|
||||
private String description;
|
||||
private String icon;
|
||||
private String campaignId;
|
||||
private int order;
|
||||
private List<CatalogItemDTO> items = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.loremind.infrastructure.web.mapper;
|
||||
|
||||
import com.loremind.domain.campaigncontext.CatalogItem;
|
||||
import com.loremind.domain.campaigncontext.ItemCatalog;
|
||||
import com.loremind.infrastructure.web.dto.campaigncontext.CatalogItemDTO;
|
||||
import com.loremind.infrastructure.web.dto.campaigncontext.ItemCatalogDTO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
public class ItemCatalogMapper {
|
||||
|
||||
public ItemCatalogDTO toDTO(ItemCatalog c) {
|
||||
if (c == null) return null;
|
||||
ItemCatalogDTO dto = new ItemCatalogDTO();
|
||||
dto.setId(c.getId());
|
||||
dto.setName(c.getName());
|
||||
dto.setDescription(c.getDescription());
|
||||
dto.setIcon(c.getIcon());
|
||||
dto.setCampaignId(c.getCampaignId());
|
||||
dto.setOrder(c.getOrder());
|
||||
dto.setItems(c.getItems().stream().map(this::toItemDTO).collect(Collectors.toList()));
|
||||
return dto;
|
||||
}
|
||||
|
||||
public List<CatalogItem> toDomainItems(List<CatalogItemDTO> dtos) {
|
||||
if (dtos == null) return List.of();
|
||||
return dtos.stream().map(this::toDomainItem).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private CatalogItemDTO toItemDTO(CatalogItem i) {
|
||||
CatalogItemDTO dto = new CatalogItemDTO();
|
||||
dto.setName(i.getName());
|
||||
dto.setPrice(i.getPrice());
|
||||
dto.setCategory(i.getCategory());
|
||||
dto.setDescription(i.getDescription());
|
||||
return dto;
|
||||
}
|
||||
|
||||
private CatalogItem toDomainItem(CatalogItemDTO dto) {
|
||||
return CatalogItem.builder()
|
||||
.name(dto.getName())
|
||||
.price(dto.getPrice())
|
||||
.category(dto.getCategory())
|
||||
.description(dto.getDescription())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user