Initial commit - LoreMind project
This commit is contained in:
83
core/pom.xml
Normal file
83
core/pom.xml
Normal file
@@ -0,0 +1,83 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<groupId>com.loremind</groupId>
|
||||
<artifactId>loremind-core</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<name>LoreMind Core</name>
|
||||
<description>Backend Core - Architecture Hexagonale</description>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring Boot Web -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Data JPA -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- PostgreSQL Driver -->
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- H2 Database pour les tests -->
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Lombok (réduit le code boilerplate) -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
16
core/src/main/java/com/loremind/LoreMindApplication.java
Normal file
16
core/src/main/java/com/loremind/LoreMindApplication.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package com.loremind;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* Classe principale de l'application LoreMind.
|
||||
* Point d'entrée Spring Boot qui démarre l'application.
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class LoreMindApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(LoreMindApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.loremind.application.campaigncontext;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Arc;
|
||||
import com.loremind.domain.campaigncontext.ports.ArcRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Service d'application pour le contexte Arc.
|
||||
* Orchestre la logique métier en utilisant le Port ArcRepository.
|
||||
* Fait partie de la couche Application de l'Architecture Hexagonale.
|
||||
*/
|
||||
@Service
|
||||
public class ArcService {
|
||||
|
||||
private final ArcRepository arcRepository;
|
||||
|
||||
public ArcService(ArcRepository arcRepository) {
|
||||
this.arcRepository = arcRepository;
|
||||
}
|
||||
|
||||
public Arc createArc(String name, String description, String campaignId, int order) {
|
||||
Arc arc = Arc.builder()
|
||||
.name(name)
|
||||
.description(description)
|
||||
.campaignId(campaignId)
|
||||
.order(order)
|
||||
.build();
|
||||
return arcRepository.save(arc);
|
||||
}
|
||||
|
||||
public Optional<Arc> getArcById(String id) {
|
||||
return arcRepository.findById(id);
|
||||
}
|
||||
|
||||
public List<Arc> getAllArcs() {
|
||||
return arcRepository.findAll();
|
||||
}
|
||||
|
||||
public List<Arc> getArcsByCampaignId(String campaignId) {
|
||||
return arcRepository.findByCampaignId(campaignId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour un Arc avec tous ses champs narratifs.
|
||||
* Accepte un objet Arc pour éviter l'explosion de paramètres (Parameter Object pattern).
|
||||
*/
|
||||
public Arc updateArc(String id, Arc updated) {
|
||||
Optional<Arc> existingArc = arcRepository.findById(id);
|
||||
if (existingArc.isEmpty()) {
|
||||
throw new IllegalArgumentException("Arc non trouvé avec l'ID: " + id);
|
||||
}
|
||||
|
||||
Arc arc = existingArc.get();
|
||||
arc.setName(updated.getName());
|
||||
arc.setDescription(updated.getDescription());
|
||||
arc.setOrder(updated.getOrder());
|
||||
arc.setThemes(updated.getThemes());
|
||||
arc.setStakes(updated.getStakes());
|
||||
arc.setGmNotes(updated.getGmNotes());
|
||||
arc.setRewards(updated.getRewards());
|
||||
arc.setResolution(updated.getResolution());
|
||||
arc.setRelatedPageIds(updated.getRelatedPageIds());
|
||||
return arcRepository.save(arc);
|
||||
}
|
||||
|
||||
public void deleteArc(String id) {
|
||||
arcRepository.deleteById(id);
|
||||
}
|
||||
|
||||
public boolean arcExists(String id) {
|
||||
return arcRepository.existsById(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.loremind.application.campaigncontext;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Campaign;
|
||||
import com.loremind.domain.campaigncontext.ports.CampaignRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Service d'application pour le contexte Campaign.
|
||||
* Orchestre la logique métier en utilisant le Port CampaignRepository.
|
||||
* Fait partie de la couche Application de l'Architecture Hexagonale.
|
||||
*/
|
||||
@Service
|
||||
public class CampaignService {
|
||||
|
||||
private final CampaignRepository campaignRepository;
|
||||
|
||||
public CampaignService(CampaignRepository campaignRepository) {
|
||||
this.campaignRepository = campaignRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameter Object pour la création / mise à jour d'une Campaign.
|
||||
* Évite une signature à rallonge et rend les évolutions futures (theme,
|
||||
* coverImageUrl, etc.) sans casser les appelants.
|
||||
*
|
||||
* <p>{@code loreId} est nullable : une campagne peut exister sans univers associé.</p>
|
||||
*/
|
||||
public record CampaignData(String name, String description, String loreId) {}
|
||||
|
||||
public Campaign createCampaign(CampaignData data) {
|
||||
Campaign campaign = Campaign.builder()
|
||||
.name(data.name())
|
||||
.description(data.description())
|
||||
.loreId(normalizeLoreId(data.loreId()))
|
||||
.arcsCount(0)
|
||||
.build();
|
||||
return campaignRepository.save(campaign);
|
||||
}
|
||||
|
||||
public Optional<Campaign> getCampaignById(String id) {
|
||||
return campaignRepository.findById(id);
|
||||
}
|
||||
|
||||
public List<Campaign> getAllCampaigns() {
|
||||
return campaignRepository.findAll();
|
||||
}
|
||||
|
||||
public Campaign updateCampaign(String id, CampaignData data) {
|
||||
Optional<Campaign> existingCampaign = campaignRepository.findById(id);
|
||||
if (existingCampaign.isEmpty()) {
|
||||
throw new IllegalArgumentException("Campaign non trouvé avec l'ID: " + id);
|
||||
}
|
||||
|
||||
Campaign campaign = existingCampaign.get();
|
||||
campaign.setName(data.name());
|
||||
campaign.setDescription(data.description());
|
||||
campaign.setLoreId(normalizeLoreId(data.loreId()));
|
||||
return campaignRepository.save(campaign);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalise un loreId entrant : une chaîne vide/blanche est traitée comme "pas de lien".
|
||||
* Utile car les payloads JSON peuvent envoyer "" au lieu de null.
|
||||
*/
|
||||
private String normalizeLoreId(String loreId) {
|
||||
return (loreId == null || loreId.isBlank()) ? null : loreId;
|
||||
}
|
||||
|
||||
public void deleteCampaign(String id) {
|
||||
campaignRepository.deleteById(id);
|
||||
}
|
||||
|
||||
public boolean campaignExists(String id) {
|
||||
return campaignRepository.existsById(id);
|
||||
}
|
||||
|
||||
public List<Campaign> searchCampaigns(String query) {
|
||||
if (query == null || query.isBlank()) return List.of();
|
||||
return campaignRepository.searchByName(query.trim());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.loremind.application.campaigncontext;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Chapter;
|
||||
import com.loremind.domain.campaigncontext.ports.ChapterRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Service d'application pour le contexte Chapter.
|
||||
* Orchestre la logique métier en utilisant le Port ChapterRepository.
|
||||
* Fait partie de la couche Application de l'Architecture Hexagonale.
|
||||
*/
|
||||
@Service
|
||||
public class ChapterService {
|
||||
|
||||
private final ChapterRepository chapterRepository;
|
||||
|
||||
public ChapterService(ChapterRepository chapterRepository) {
|
||||
this.chapterRepository = chapterRepository;
|
||||
}
|
||||
|
||||
public Chapter createChapter(String name, String description, String arcId, int order) {
|
||||
Chapter chapter = Chapter.builder()
|
||||
.name(name)
|
||||
.description(description)
|
||||
.arcId(arcId)
|
||||
.order(order)
|
||||
.build();
|
||||
return chapterRepository.save(chapter);
|
||||
}
|
||||
|
||||
public Optional<Chapter> getChapterById(String id) {
|
||||
return chapterRepository.findById(id);
|
||||
}
|
||||
|
||||
public List<Chapter> getAllChapters() {
|
||||
return chapterRepository.findAll();
|
||||
}
|
||||
|
||||
public List<Chapter> getChaptersByArcId(String arcId) {
|
||||
return chapterRepository.findByArcId(arcId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour un Chapter avec tous ses champs narratifs (Parameter Object pattern).
|
||||
*/
|
||||
public Chapter updateChapter(String id, Chapter updated) {
|
||||
Optional<Chapter> existingChapter = chapterRepository.findById(id);
|
||||
if (existingChapter.isEmpty()) {
|
||||
throw new IllegalArgumentException("Chapter non trouvé avec l'ID: " + id);
|
||||
}
|
||||
|
||||
Chapter chapter = existingChapter.get();
|
||||
chapter.setName(updated.getName());
|
||||
chapter.setDescription(updated.getDescription());
|
||||
chapter.setOrder(updated.getOrder());
|
||||
chapter.setGmNotes(updated.getGmNotes());
|
||||
chapter.setPlayerObjectives(updated.getPlayerObjectives());
|
||||
chapter.setNarrativeStakes(updated.getNarrativeStakes());
|
||||
chapter.setRelatedPageIds(updated.getRelatedPageIds());
|
||||
return chapterRepository.save(chapter);
|
||||
}
|
||||
|
||||
public void deleteChapter(String id) {
|
||||
chapterRepository.deleteById(id);
|
||||
}
|
||||
|
||||
public boolean chapterExists(String id) {
|
||||
return chapterRepository.existsById(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.loremind.application.campaigncontext;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Scene;
|
||||
import com.loremind.domain.campaigncontext.ports.SceneRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Service d'application pour le contexte Scene.
|
||||
* Orchestre la logique métier en utilisant le Port SceneRepository.
|
||||
* Fait partie de la couche Application de l'Architecture Hexagonale.
|
||||
*/
|
||||
@Service
|
||||
public class SceneService {
|
||||
|
||||
private final SceneRepository sceneRepository;
|
||||
|
||||
public SceneService(SceneRepository sceneRepository) {
|
||||
this.sceneRepository = sceneRepository;
|
||||
}
|
||||
|
||||
public Scene createScene(String name, String description, String chapterId, int order) {
|
||||
Scene scene = Scene.builder()
|
||||
.name(name)
|
||||
.description(description)
|
||||
.chapterId(chapterId)
|
||||
.order(order)
|
||||
.build();
|
||||
return sceneRepository.save(scene);
|
||||
}
|
||||
|
||||
public Optional<Scene> getSceneById(String id) {
|
||||
return sceneRepository.findById(id);
|
||||
}
|
||||
|
||||
public List<Scene> getAllScenes() {
|
||||
return sceneRepository.findAll();
|
||||
}
|
||||
|
||||
public List<Scene> getScenesByChapterId(String chapterId) {
|
||||
return sceneRepository.findByChapterId(chapterId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour une Scene avec tous ses champs narratifs (Parameter Object pattern).
|
||||
*/
|
||||
public Scene updateScene(String id, Scene updated) {
|
||||
Optional<Scene> existingScene = sceneRepository.findById(id);
|
||||
if (existingScene.isEmpty()) {
|
||||
throw new IllegalArgumentException("Scene non trouvée avec l'ID: " + id);
|
||||
}
|
||||
|
||||
Scene scene = existingScene.get();
|
||||
scene.setName(updated.getName());
|
||||
scene.setDescription(updated.getDescription());
|
||||
scene.setOrder(updated.getOrder());
|
||||
scene.setLocation(updated.getLocation());
|
||||
scene.setTiming(updated.getTiming());
|
||||
scene.setAtmosphere(updated.getAtmosphere());
|
||||
scene.setPlayerNarration(updated.getPlayerNarration());
|
||||
scene.setGmSecretNotes(updated.getGmSecretNotes());
|
||||
scene.setChoicesConsequences(updated.getChoicesConsequences());
|
||||
scene.setCombatDifficulty(updated.getCombatDifficulty());
|
||||
scene.setEnemies(updated.getEnemies());
|
||||
scene.setRelatedPageIds(updated.getRelatedPageIds());
|
||||
return sceneRepository.save(scene);
|
||||
}
|
||||
|
||||
public void deleteScene(String id) {
|
||||
sceneRepository.deleteById(id);
|
||||
}
|
||||
|
||||
public boolean sceneExists(String id) {
|
||||
return sceneRepository.existsById(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.loremind.application.lorecontext;
|
||||
|
||||
import com.loremind.domain.lorecontext.LoreNode;
|
||||
import com.loremind.domain.lorecontext.ports.LoreNodeRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Service d'application pour le contexte LoreNode.
|
||||
* Orchestre la logique métier en utilisant le Port LoreNodeRepository.
|
||||
* Fait partie de la couche Application de l'Architecture Hexagonale.
|
||||
*/
|
||||
@Service
|
||||
public class LoreNodeService {
|
||||
|
||||
private final LoreNodeRepository loreNodeRepository;
|
||||
|
||||
public LoreNodeService(LoreNodeRepository loreNodeRepository) {
|
||||
this.loreNodeRepository = loreNodeRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée un LoreNode (dossier) à partir d'un "objet changes" porteur des valeurs
|
||||
* souhaitées (pattern Parameter Object) : évite les signatures qui gonflent
|
||||
* à chaque ajout de champ.
|
||||
*/
|
||||
public LoreNode createLoreNode(LoreNode changes) {
|
||||
LoreNode loreNode = LoreNode.builder()
|
||||
.name(changes.getName())
|
||||
.icon(changes.getIcon())
|
||||
.parentId(changes.getParentId())
|
||||
.loreId(changes.getLoreId())
|
||||
.build();
|
||||
return loreNodeRepository.save(loreNode);
|
||||
}
|
||||
|
||||
public Optional<LoreNode> getLoreNodeById(String id) {
|
||||
return loreNodeRepository.findById(id);
|
||||
}
|
||||
|
||||
public List<LoreNode> getAllLoreNodes() {
|
||||
return loreNodeRepository.findAll();
|
||||
}
|
||||
|
||||
public List<LoreNode> getLoreNodesByLoreId(String loreId) {
|
||||
return loreNodeRepository.findByLoreId(loreId);
|
||||
}
|
||||
|
||||
public List<LoreNode> getLoreNodesByParentId(String parentId) {
|
||||
return loreNodeRepository.findByParentId(parentId);
|
||||
}
|
||||
|
||||
public List<LoreNode> searchLoreNodes(String query) {
|
||||
if (query == null || query.isBlank()) return List.of();
|
||||
return loreNodeRepository.searchByName(query.trim());
|
||||
}
|
||||
|
||||
public LoreNode updateLoreNode(String id, LoreNode changes) {
|
||||
LoreNode existing = loreNodeRepository.findById(id)
|
||||
.orElseThrow(() -> new IllegalArgumentException("LoreNode non trouvé avec l'ID: " + id));
|
||||
|
||||
existing.setName(changes.getName());
|
||||
existing.setIcon(changes.getIcon());
|
||||
existing.setParentId(changes.getParentId());
|
||||
// loreId volontairement immuable (un dossier ne migre pas d'un Lore à l'autre).
|
||||
return loreNodeRepository.save(existing);
|
||||
}
|
||||
|
||||
public void deleteLoreNode(String id) {
|
||||
loreNodeRepository.deleteById(id);
|
||||
}
|
||||
|
||||
public boolean loreNodeExists(String id) {
|
||||
return loreNodeRepository.existsById(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.loremind.application.lorecontext;
|
||||
|
||||
import com.loremind.domain.lorecontext.Lore;
|
||||
import com.loremind.domain.lorecontext.ports.LoreNodeRepository;
|
||||
import com.loremind.domain.lorecontext.ports.LoreRepository;
|
||||
import com.loremind.domain.lorecontext.ports.PageRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Service d'application pour le contexte Lore.
|
||||
* Orchestre la logique métier en utilisant le Port LoreRepository.
|
||||
* Fait partie de la couche Application de l'Architecture Hexagonale.
|
||||
*
|
||||
* Note: les compteurs nodeCount/pageCount sont calculés à la volée via
|
||||
* countByLoreId sur les ports LoreNode et Page, plutôt que stockés en BDD
|
||||
* (la colonne existe encore mais n'est plus fiable). Source of truth = les
|
||||
* tables nodes/pages elles-mêmes, jamais de désync possible.
|
||||
*/
|
||||
@Service
|
||||
public class LoreService {
|
||||
|
||||
private final LoreRepository loreRepository;
|
||||
private final LoreNodeRepository loreNodeRepository;
|
||||
private final PageRepository pageRepository;
|
||||
|
||||
public LoreService(LoreRepository loreRepository,
|
||||
LoreNodeRepository loreNodeRepository,
|
||||
PageRepository pageRepository) {
|
||||
this.loreRepository = loreRepository;
|
||||
this.loreNodeRepository = loreNodeRepository;
|
||||
this.pageRepository = pageRepository;
|
||||
}
|
||||
|
||||
public Lore createLore(String name, String description) {
|
||||
Lore lore = Lore.builder()
|
||||
.name(name)
|
||||
.description(description)
|
||||
.nodeCount(0)
|
||||
.pageCount(0)
|
||||
.build();
|
||||
return loreRepository.save(lore);
|
||||
}
|
||||
|
||||
public Optional<Lore> getLoreById(String id) {
|
||||
return loreRepository.findById(id).map(this::withCounts);
|
||||
}
|
||||
|
||||
public List<Lore> getAllLores() {
|
||||
return loreRepository.findAll().stream()
|
||||
.map(this::withCounts)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Enrichit un Lore avec les compteurs calculés à la volée.
|
||||
* N+1 acceptable à l'échelle actuelle (quelques dizaines de Lores max).
|
||||
*/
|
||||
private Lore withCounts(Lore lore) {
|
||||
lore.setNodeCount((int) loreNodeRepository.countByLoreId(lore.getId()));
|
||||
lore.setPageCount((int) pageRepository.countByLoreId(lore.getId()));
|
||||
return lore;
|
||||
}
|
||||
|
||||
/** Recherche par nom (ILIKE). Résultats sans compteurs — pas utile pour la command palette. */
|
||||
public List<Lore> searchLores(String query) {
|
||||
if (query == null || query.isBlank()) return List.of();
|
||||
return loreRepository.searchByName(query.trim());
|
||||
}
|
||||
|
||||
public Lore updateLore(String id, String name, String description) {
|
||||
Optional<Lore> existingLore = loreRepository.findById(id);
|
||||
if (existingLore.isEmpty()) {
|
||||
throw new IllegalArgumentException("Lore non trouvé avec l'ID: " + id);
|
||||
}
|
||||
|
||||
Lore lore = existingLore.get();
|
||||
lore.setName(name);
|
||||
lore.setDescription(description);
|
||||
return loreRepository.save(lore);
|
||||
}
|
||||
|
||||
public void deleteLore(String id) {
|
||||
loreRepository.deleteById(id);
|
||||
}
|
||||
|
||||
public boolean loreExists(String id) {
|
||||
return loreRepository.existsById(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.loremind.application.lorecontext;
|
||||
|
||||
import com.loremind.domain.lorecontext.Page;
|
||||
import com.loremind.domain.lorecontext.ports.PageRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Service d'application pour le contexte Page.
|
||||
* Orchestre la logique métier via le Port PageRepository.
|
||||
* Couche Application de l'Architecture Hexagonale.
|
||||
*/
|
||||
@Service
|
||||
public class PageService {
|
||||
|
||||
private final PageRepository pageRepository;
|
||||
|
||||
public PageService(PageRepository pageRepository) {
|
||||
this.pageRepository = pageRepository;
|
||||
}
|
||||
|
||||
/** Création MVP : seuls les champs structurels sont requis. Le contenu est
|
||||
* enrichi plus tard depuis l'écran d'édition. */
|
||||
public Page createPage(String loreId, String nodeId, String templateId, String title) {
|
||||
Page page = Page.builder()
|
||||
.loreId(loreId)
|
||||
.nodeId(nodeId)
|
||||
.templateId(templateId)
|
||||
.title(title)
|
||||
.values(new HashMap<>())
|
||||
.tags(new ArrayList<>())
|
||||
.relatedPageIds(new ArrayList<>())
|
||||
.build();
|
||||
return pageRepository.save(page);
|
||||
}
|
||||
|
||||
public Optional<Page> getPageById(String id) {
|
||||
return pageRepository.findById(id);
|
||||
}
|
||||
|
||||
public List<Page> getAllPages() {
|
||||
return pageRepository.findAll();
|
||||
}
|
||||
|
||||
public List<Page> getPagesByLoreId(String loreId) {
|
||||
return pageRepository.findByLoreId(loreId);
|
||||
}
|
||||
|
||||
public List<Page> getPagesByNodeId(String nodeId) {
|
||||
return pageRepository.findByNodeId(nodeId);
|
||||
}
|
||||
|
||||
public List<Page> searchPages(String query) {
|
||||
if (query == null || query.isBlank()) return List.of();
|
||||
return pageRepository.searchByTitle(query.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour une page existante.
|
||||
* Parameter Object pattern : le controller construit une Page "changes"
|
||||
* avec les nouvelles valeurs, le service recharge et applique les champs modifiables.
|
||||
* Les champs structurels immuables sont : id, loreId, templateId.
|
||||
* Le nodeId est mutable (déplacement d'une page d'un noeud à l'autre).
|
||||
*/
|
||||
public Page updatePage(String id, Page changes) {
|
||||
Page existing = pageRepository.findById(id)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Page non trouvée avec l'ID: " + id));
|
||||
|
||||
existing.setTitle(changes.getTitle());
|
||||
existing.setNodeId(changes.getNodeId());
|
||||
existing.setValues(changes.getValues() != null
|
||||
? new HashMap<>(changes.getValues())
|
||||
: new HashMap<>());
|
||||
existing.setNotes(changes.getNotes());
|
||||
existing.setTags(changes.getTags() != null
|
||||
? new ArrayList<>(changes.getTags())
|
||||
: new ArrayList<>());
|
||||
existing.setRelatedPageIds(changes.getRelatedPageIds() != null
|
||||
? new ArrayList<>(changes.getRelatedPageIds())
|
||||
: new ArrayList<>());
|
||||
return pageRepository.save(existing);
|
||||
}
|
||||
|
||||
public void deletePage(String id) {
|
||||
pageRepository.deleteById(id);
|
||||
}
|
||||
|
||||
public boolean pageExists(String id) {
|
||||
return pageRepository.existsById(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.loremind.application.lorecontext;
|
||||
|
||||
import com.loremind.domain.lorecontext.Template;
|
||||
import com.loremind.domain.lorecontext.ports.TemplateRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Service d'application pour le contexte Template.
|
||||
* Orchestre la logique métier via le Port TemplateRepository.
|
||||
* Couche Application de l'Architecture Hexagonale.
|
||||
*/
|
||||
@Service
|
||||
public class TemplateService {
|
||||
|
||||
private final TemplateRepository templateRepository;
|
||||
|
||||
public TemplateService(TemplateRepository templateRepository) {
|
||||
this.templateRepository = templateRepository;
|
||||
}
|
||||
|
||||
public Template createTemplate(String loreId,
|
||||
String name,
|
||||
String description,
|
||||
String defaultNodeId,
|
||||
List<String> fields) {
|
||||
Template template = Template.builder()
|
||||
.loreId(loreId)
|
||||
.name(name)
|
||||
.description(description)
|
||||
.defaultNodeId(defaultNodeId)
|
||||
.fields(fields != null ? new ArrayList<>(fields) : new ArrayList<>())
|
||||
.build();
|
||||
return templateRepository.save(template);
|
||||
}
|
||||
|
||||
public Optional<Template> getTemplateById(String id) {
|
||||
return templateRepository.findById(id);
|
||||
}
|
||||
|
||||
public List<Template> getAllTemplates() {
|
||||
return templateRepository.findAll();
|
||||
}
|
||||
|
||||
public List<Template> getTemplatesByLoreId(String loreId) {
|
||||
return templateRepository.findByLoreId(loreId);
|
||||
}
|
||||
|
||||
public List<Template> searchTemplates(String query) {
|
||||
if (query == null || query.isBlank()) return List.of();
|
||||
return templateRepository.searchByName(query.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour un Template existant.
|
||||
* Pattern Parameter Object via l'entité Template elle-même :
|
||||
* le controller construit un Template avec les champs à modifier, le service
|
||||
* recharge l'existant et applique les changements.
|
||||
*/
|
||||
public Template updateTemplate(String id, Template changes) {
|
||||
Template existing = templateRepository.findById(id)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Template non trouvé avec l'ID: " + id));
|
||||
|
||||
existing.setName(changes.getName());
|
||||
existing.setDescription(changes.getDescription());
|
||||
existing.setDefaultNodeId(changes.getDefaultNodeId());
|
||||
existing.setFields(changes.getFields() != null
|
||||
? new ArrayList<>(changes.getFields())
|
||||
: new ArrayList<>());
|
||||
// loreId volontairement immuable : un template ne migre pas d'un Lore à l'autre.
|
||||
return templateRepository.save(existing);
|
||||
}
|
||||
|
||||
public void deleteTemplate(String id) {
|
||||
templateRepository.deleteById(id);
|
||||
}
|
||||
|
||||
public boolean templateExists(String id) {
|
||||
return templateRepository.existsById(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.loremind.domain.campaigncontext;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Entité de domaine représentant un Arc narratif.
|
||||
* Division majeure d'une Campaign (ex: "L'arc sombre").
|
||||
* Entité pure du domaine, sans dépendance technique.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class Arc {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private String description; // = Synopsis dans l'UI
|
||||
private String campaignId; // Référence vers la Campaign parente
|
||||
private int order; // Ordre de l'arc dans la campagne
|
||||
|
||||
// Champs narratifs enrichis (voir docs/maquettes/campagne/détail/)
|
||||
private String themes; // Thèmes principaux explorés dans cet arc
|
||||
private String stakes; // Enjeux globaux pour les personnages
|
||||
private String gmNotes; // Notes privées du MJ (non exportées vers FoundryVTT)
|
||||
private String rewards; // Récompenses et progression
|
||||
private String resolution; // Dénouement prévu
|
||||
|
||||
/**
|
||||
* IDs des pages du Lore associées à cet arc (weak cross-context references).
|
||||
* Permet au MJ de lier des PNJ, lieux ou lore items qui jouent un rôle dans cet arc.
|
||||
* Ne contient que des IDs ; pas d'import du Lore Context (respect des Bounded Contexts).
|
||||
* Initialisé en {@link ArrayList} vide dans le builder pour éviter les NPE.
|
||||
*/
|
||||
@Builder.Default
|
||||
private List<String> relatedPageIds = new ArrayList<>();
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
// ─────────────── Méthodes métier ───────────────
|
||||
|
||||
/** Ajoute un lien vers une page du Lore (idempotent : pas de doublon). */
|
||||
public void linkPage(String pageId) {
|
||||
if (relatedPageIds == null) relatedPageIds = new ArrayList<>();
|
||||
if (!relatedPageIds.contains(pageId)) {
|
||||
relatedPageIds.add(pageId);
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
/** Retire un lien vers une page du Lore (sans erreur si absent). */
|
||||
public void unlinkPage(String pageId) {
|
||||
if (relatedPageIds != null && relatedPageIds.remove(pageId)) {
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.loremind.domain.campaigncontext;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Entité de domaine représentant une Campaign.
|
||||
* Conteneur global pour organiser la narration d'une campagne.
|
||||
* Entité pure du domaine, sans dépendance technique.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class Campaign {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private String description;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
private int arcsCount;
|
||||
|
||||
/**
|
||||
* Référence faible (weak reference) vers un Lore.
|
||||
* Nullable : une campagne peut exister sans univers associé (one-shot, test, pitch libre).
|
||||
* Ce n'est qu'un ID : le Campaign Context ne dépend PAS du Lore Context
|
||||
* (respect des Bounded Contexts en DDD).
|
||||
*/
|
||||
private String loreId;
|
||||
|
||||
/** Associe cette campagne à un Lore existant. */
|
||||
public void linkToLore(String loreId) {
|
||||
this.loreId = loreId;
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
/** Retire l'association à un Lore (la campagne redevient "universe-agnostic"). */
|
||||
public void unlinkFromLore() {
|
||||
this.loreId = null;
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public boolean isLinkedToLore() {
|
||||
return this.loreId != null && !this.loreId.isBlank();
|
||||
}
|
||||
|
||||
// Méthode métier pour gérer le nombre d'arcs
|
||||
public void incrementArcsCount() {
|
||||
this.arcsCount++;
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public void decrementArcsCount() {
|
||||
if (this.arcsCount > 0) {
|
||||
this.arcsCount--;
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.loremind.domain.campaigncontext;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Entité de domaine représentant un Chapter.
|
||||
* Subdivision d'un Arc (ex: "Chapitre 1: Le début").
|
||||
* Entité pure du domaine, sans dépendance technique.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class Chapter {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private String description; // = Synopsis du chapitre dans l'UI
|
||||
private String arcId; // Référence vers l'Arc parent
|
||||
private int order; // Ordre du chapitre dans l'arc
|
||||
|
||||
// Champs narratifs enrichis (voir docs/maquettes/campagne/détail/)
|
||||
private String gmNotes; // Notes privées du MJ (non exportées vers FoundryVTT)
|
||||
private String playerObjectives; // Objectifs des joueurs dans ce chapitre
|
||||
private String narrativeStakes; // Enjeux narratifs dramatiques
|
||||
|
||||
/**
|
||||
* IDs des pages du Lore associées à ce chapitre (weak cross-context references).
|
||||
* Permet au MJ de lier des PNJ / lieux / éléments du Lore qui apparaissent dans ce chapitre.
|
||||
*/
|
||||
@Builder.Default
|
||||
private List<String> relatedPageIds = new ArrayList<>();
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
// ─────────────── Méthodes métier ───────────────
|
||||
|
||||
public void linkPage(String pageId) {
|
||||
if (relatedPageIds == null) relatedPageIds = new ArrayList<>();
|
||||
if (!relatedPageIds.contains(pageId)) {
|
||||
relatedPageIds.add(pageId);
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
public void unlinkPage(String pageId) {
|
||||
if (relatedPageIds != null && relatedPageIds.remove(pageId)) {
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.loremind.domain.campaigncontext;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Entité de domaine représentant une Scene.
|
||||
* Unité de jeu la plus fine, subdivision d'un Chapter (ex: "Scène 1: L'auberge").
|
||||
* Entité pure du domaine, sans dépendance technique.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class Scene {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private String description; // = Description courte dans l'UI
|
||||
private String chapterId; // Référence vers le Chapter parent
|
||||
private int order; // Ordre de la scène dans le chapitre
|
||||
|
||||
// === Contexte et ambiance ===
|
||||
private String location; // Lieu de la scène (ex: Taverne du Dragon d'Or)
|
||||
private String timing; // Moment (ex: Soir, à la tombée de la nuit)
|
||||
private String atmosphere; // Ambiance générale (sons, odeurs, émotions...)
|
||||
|
||||
// === Narration pour les joueurs ===
|
||||
private String playerNarration; // Texte lu directement aux joueurs
|
||||
|
||||
// === Notes et secrets du MJ (privé) ===
|
||||
private String gmSecretNotes; // Informations cachées, non visibles par les joueurs
|
||||
|
||||
// === Choix et conséquences ===
|
||||
private String choicesConsequences; // Options offertes aux joueurs et leurs conséquences
|
||||
|
||||
// === Combat ou rencontre ===
|
||||
private String combatDifficulty; // Difficulté estimée
|
||||
private String enemies; // Liste des ennemis et créatures
|
||||
|
||||
/**
|
||||
* IDs des pages du Lore associées à cette scène (weak cross-context references).
|
||||
* Très utile pour la préparation : épingler un lieu, un PNJ, une créature à une scène.
|
||||
*/
|
||||
@Builder.Default
|
||||
private List<String> relatedPageIds = new ArrayList<>();
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
// ─────────────── Méthodes métier ───────────────
|
||||
|
||||
public void linkPage(String pageId) {
|
||||
if (relatedPageIds == null) relatedPageIds = new ArrayList<>();
|
||||
if (!relatedPageIds.contains(pageId)) {
|
||||
relatedPageIds.add(pageId);
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
public void unlinkPage(String pageId) {
|
||||
if (relatedPageIds != null && relatedPageIds.remove(pageId)) {
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.loremind.domain.campaigncontext.ports;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Arc;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Port de sortie pour la persistance des Arcs.
|
||||
* Interface définie dans le domaine, implémentée par l'infrastructure.
|
||||
*/
|
||||
public interface ArcRepository {
|
||||
|
||||
Arc save(Arc arc);
|
||||
|
||||
Optional<Arc> findById(String id);
|
||||
|
||||
List<Arc> findByCampaignId(String campaignId);
|
||||
|
||||
List<Arc> findAll();
|
||||
|
||||
void deleteById(String id);
|
||||
|
||||
boolean existsById(String id);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.loremind.domain.campaigncontext.ports;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Campaign;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Port de sortie pour la persistance des Campaigns.
|
||||
* Interface définie dans le domaine, implémentée par l'infrastructure.
|
||||
*/
|
||||
public interface CampaignRepository {
|
||||
|
||||
Campaign save(Campaign campaign);
|
||||
|
||||
Optional<Campaign> findById(String id);
|
||||
|
||||
List<Campaign> findAll();
|
||||
|
||||
void deleteById(String id);
|
||||
|
||||
boolean existsById(String id);
|
||||
|
||||
List<Campaign> searchByName(String query);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.loremind.domain.campaigncontext.ports;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Chapter;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Port de sortie pour la persistance des Chapters.
|
||||
* Interface définie dans le domaine, implémentée par l'infrastructure.
|
||||
*/
|
||||
public interface ChapterRepository {
|
||||
|
||||
Chapter save(Chapter chapter);
|
||||
|
||||
Optional<Chapter> findById(String id);
|
||||
|
||||
List<Chapter> findByArcId(String arcId);
|
||||
|
||||
List<Chapter> findAll();
|
||||
|
||||
void deleteById(String id);
|
||||
|
||||
boolean existsById(String id);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.loremind.domain.campaigncontext.ports;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Scene;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Port de sortie pour la persistance des Scenes.
|
||||
* Interface définie dans le domaine, implémentée par l'infrastructure.
|
||||
*/
|
||||
public interface SceneRepository {
|
||||
|
||||
Scene save(Scene scene);
|
||||
|
||||
Optional<Scene> findById(String id);
|
||||
|
||||
List<Scene> findByChapterId(String chapterId);
|
||||
|
||||
List<Scene> findAll();
|
||||
|
||||
void deleteById(String id);
|
||||
|
||||
boolean existsById(String id);
|
||||
}
|
||||
48
core/src/main/java/com/loremind/domain/lorecontext/Lore.java
Normal file
48
core/src/main/java/com/loremind/domain/lorecontext/Lore.java
Normal file
@@ -0,0 +1,48 @@
|
||||
package com.loremind.domain.lorecontext;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Entité de domaine représentant un Univers de jeu (Lore).
|
||||
* Conteneur global pour organiser la connaissance d'un monde.
|
||||
* C'est une entité pure du domaine, sans dépendance technique (pas de JPA).
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class Lore {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private String description;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
private int nodeCount;
|
||||
private int pageCount;
|
||||
|
||||
// Méthodes métier pour gérer les métriques
|
||||
public void incrementNodeCount() {
|
||||
this.nodeCount++;
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public void decrementNodeCount() {
|
||||
if (this.nodeCount > 0) {
|
||||
this.nodeCount--;
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
public void incrementPageCount() {
|
||||
this.pageCount++;
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public void decrementPageCount() {
|
||||
if (this.pageCount > 0) {
|
||||
this.pageCount--;
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.loremind.domain.lorecontext;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Entité de domaine représentant un dossier dans l'arborescence d'un Lore.
|
||||
* Un dossier organise des Pages et peut contenir des sous-dossiers.
|
||||
* Structure hiérarchique via parentId (auto-référence).
|
||||
* Entité pure du domaine, sans dépendance technique.
|
||||
*
|
||||
* Note : le nom interne reste "LoreNode" pour des raisons historiques et
|
||||
* techniques (compatibilité BDD, couplage avec le code existant). L'UI expose
|
||||
* le concept sous le terme "dossier".
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class LoreNode {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
/** Clé de l'icône lucide choisie par l'utilisateur (ex: "users", "map-pin"). */
|
||||
private String icon;
|
||||
private String parentId; // Auto-référence pour l'arborescence
|
||||
private String loreId; // Référence vers le Lore parent
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
// Méthode métier pour vérifier si c'est un nœud racine
|
||||
public boolean isRoot() {
|
||||
return parentId == null || parentId.isEmpty();
|
||||
}
|
||||
|
||||
// Méthode métier pour vérifier si c'est un nœud feuille (sans enfants)
|
||||
// Note: La vérification réelle nécessite d'accéder aux enfants,
|
||||
// donc cette méthode est indicative et doit être complétée par le repository
|
||||
public boolean isLeaf() {
|
||||
// Cette logique sera implémentée au niveau du service/repository
|
||||
return false;
|
||||
}
|
||||
}
|
||||
82
core/src/main/java/com/loremind/domain/lorecontext/Page.java
Normal file
82
core/src/main/java/com/loremind/domain/lorecontext/Page.java
Normal file
@@ -0,0 +1,82 @@
|
||||
package com.loremind.domain.lorecontext;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Entité de domaine représentant une page de contenu rattachée à un LoreNode.
|
||||
*
|
||||
* Une page :
|
||||
* - appartient à un Lore (loreId — dénormalisé pour queries rapides)
|
||||
* - est rangée sous un LoreNode (nodeId)
|
||||
* - est générée depuis un Template (templateId) dont elle suit les `fields`
|
||||
* - stocke les valeurs de ses champs dynamiques dans `values` (fieldName → value)
|
||||
* - porte des métadonnées éditoriales : notes privées MJ, tags, liens inter-pages.
|
||||
*
|
||||
* Entité pure du domaine : aucune dépendance technique.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class Page {
|
||||
|
||||
private String id;
|
||||
private String loreId;
|
||||
private String nodeId;
|
||||
private String templateId;
|
||||
private String title;
|
||||
|
||||
/** Valeurs des champs dynamiques définis par le Template. */
|
||||
private Map<String, String> values;
|
||||
|
||||
/** Notes privées du MJ (non exportées vers FoundryVTT). */
|
||||
private String notes;
|
||||
|
||||
/** Étiquettes libres pour regroupement/recherche. */
|
||||
private List<String> tags;
|
||||
|
||||
/** IDs d'autres Pages liées (mêmes Lore ou cross-lore). */
|
||||
private List<String> relatedPageIds;
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
// --- Méthodes métier --------------------------------------------------
|
||||
|
||||
/** Met à jour la valeur d'un champ dynamique (création de la map si absente). */
|
||||
public void setFieldValue(String fieldName, String value) {
|
||||
if (values == null) {
|
||||
values = new HashMap<>();
|
||||
}
|
||||
values.put(fieldName, value);
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public String getFieldValue(String fieldName) {
|
||||
return values == null ? null : values.get(fieldName);
|
||||
}
|
||||
|
||||
public void addTag(String tag) {
|
||||
if (tag == null || tag.isBlank()) return;
|
||||
if (tags == null) tags = new ArrayList<>();
|
||||
if (!tags.contains(tag)) {
|
||||
tags.add(tag);
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
public void removeTag(String tag) {
|
||||
if (tags != null && tags.remove(tag)) {
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasTemplate() {
|
||||
return templateId != null && !templateId.isBlank();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.loremind.domain.lorecontext;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Entité de domaine représentant un Template pour la génération de Pages.
|
||||
*
|
||||
* Un Template :
|
||||
* - appartient à un Lore (loreId)
|
||||
* - définit le noeud par défaut où seront rangées les Pages créées (defaultNodeId)
|
||||
* - porte une liste ordonnée de noms de champs dynamiques (fields)
|
||||
* qui seront instanciés sur chaque Page produite depuis ce gabarit.
|
||||
*
|
||||
* Entité pure du domaine : aucune dépendance technique (Spring, JPA, etc.).
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class Template {
|
||||
|
||||
private String id;
|
||||
private String loreId; // Rattachement au Lore propriétaire
|
||||
private String name;
|
||||
private String description;
|
||||
private String defaultNodeId; // Noeud cible des Pages générées
|
||||
private List<String> fields; // Noms des champs dynamiques (ordonnés)
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
// --- Méthodes métier ---------------------------------------------------
|
||||
|
||||
/** Nombre de champs dynamiques définis (affiché dans le sidebar "X champs"). */
|
||||
public int fieldCount() {
|
||||
return fields == null ? 0 : fields.size();
|
||||
}
|
||||
|
||||
/** Ajoute un champ à la fin de la liste (ignore les doublons et les blancs). */
|
||||
public void addField(String fieldName) {
|
||||
if (fieldName == null || fieldName.isBlank()) {
|
||||
return;
|
||||
}
|
||||
if (fields == null) {
|
||||
fields = new ArrayList<>();
|
||||
}
|
||||
if (!fields.contains(fieldName)) {
|
||||
fields.add(fieldName);
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
/** Retire un champ s'il existe. */
|
||||
public void removeField(String fieldName) {
|
||||
if (fields != null && fields.remove(fieldName)) {
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.loremind.domain.lorecontext.ports;
|
||||
|
||||
import com.loremind.domain.lorecontext.LoreNode;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Port de sortie pour la persistance des LoreNodes.
|
||||
* Interface définie dans le domaine, implémentée par l'infrastructure.
|
||||
*/
|
||||
public interface LoreNodeRepository {
|
||||
|
||||
LoreNode save(LoreNode loreNode);
|
||||
|
||||
Optional<LoreNode> findById(String id);
|
||||
|
||||
List<LoreNode> findByLoreId(String loreId);
|
||||
|
||||
List<LoreNode> findByParentId(String parentId);
|
||||
|
||||
List<LoreNode> findAll();
|
||||
|
||||
void deleteById(String id);
|
||||
|
||||
boolean existsById(String id);
|
||||
|
||||
long countByLoreId(String loreId);
|
||||
|
||||
List<LoreNode> searchByName(String query);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.loremind.domain.lorecontext.ports;
|
||||
|
||||
import com.loremind.domain.lorecontext.Lore;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Port de sortie pour la persistance des Lores.
|
||||
* Interface définie dans le domaine, implémentée par l'infrastructure.
|
||||
* C'est un Port de sortie (Output Port) selon l'Architecture Hexagonale.
|
||||
*/
|
||||
public interface LoreRepository {
|
||||
|
||||
Lore save(Lore lore);
|
||||
|
||||
Optional<Lore> findById(String id);
|
||||
|
||||
List<Lore> findAll();
|
||||
|
||||
void deleteById(String id);
|
||||
|
||||
boolean existsById(String id);
|
||||
|
||||
List<Lore> searchByName(String query);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.loremind.domain.lorecontext.ports;
|
||||
|
||||
import com.loremind.domain.lorecontext.Page;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Port de sortie pour la persistance des Pages.
|
||||
* Interface définie dans le domaine, implémentée par l'infrastructure.
|
||||
*/
|
||||
public interface PageRepository {
|
||||
|
||||
Page save(Page page);
|
||||
|
||||
Optional<Page> findById(String id);
|
||||
|
||||
List<Page> findByLoreId(String loreId);
|
||||
|
||||
List<Page> findByNodeId(String nodeId);
|
||||
|
||||
List<Page> findByTemplateId(String templateId);
|
||||
|
||||
List<Page> findAll();
|
||||
|
||||
void deleteById(String id);
|
||||
|
||||
boolean existsById(String id);
|
||||
|
||||
long countByLoreId(String loreId);
|
||||
|
||||
List<Page> searchByTitle(String query);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.loremind.domain.lorecontext.ports;
|
||||
|
||||
import com.loremind.domain.lorecontext.Template;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Port de sortie pour la persistance des Templates.
|
||||
* Interface définie dans le domaine, implémentée par l'infrastructure.
|
||||
*/
|
||||
public interface TemplateRepository {
|
||||
|
||||
Template save(Template template);
|
||||
|
||||
Optional<Template> findById(String id);
|
||||
|
||||
List<Template> findAll();
|
||||
|
||||
/** Tous les templates rattachés à un Lore donné (pour le panneau sidebar). */
|
||||
List<Template> findByLoreId(String loreId);
|
||||
|
||||
void deleteById(String id);
|
||||
|
||||
boolean existsById(String id);
|
||||
|
||||
List<Template> searchByName(String query);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.loremind.infrastructure.persistence.converter;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
import jakarta.persistence.Converter;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Converter JPA pour convertir Map<String, Object> en String (JSON).
|
||||
* Compatible avec PostgreSQL (JSONB peut stocker du JSON dans TEXT).
|
||||
* Utilisé pour le champ structure de Template, mais peut servir pour tout champ JSON.
|
||||
* C'est un converter générique réutilisable.
|
||||
*/
|
||||
@Converter(autoApply = false)
|
||||
public class MapJsonConverter implements AttributeConverter<Map<String, Object>, String> {
|
||||
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public String convertToDatabaseColumn(Map<String, Object> attribute) {
|
||||
if (attribute == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return objectMapper.writeValueAsString(attribute);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalArgumentException("Erreur lors de la conversion Map vers JSON String", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, Object> convertToEntityAttribute(String dbData) {
|
||||
if (dbData == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(dbData, Map.class);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalArgumentException("Erreur lors de la conversion JSON String vers Map", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.loremind.infrastructure.persistence.converter;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
import jakarta.persistence.Converter;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Convertit une List<String> du domaine en chaîne JSON stockée en base, et
|
||||
* inversement. Utilisé pour les listes simples (ex: Template.fields).
|
||||
*
|
||||
* Ceci est un adaptateur technique d'infrastructure : il permet au domaine de
|
||||
* rester pur (juste une List<String>) pendant que JPA parle JSON à PostgreSQL.
|
||||
*/
|
||||
@Converter
|
||||
public class StringListJsonConverter implements AttributeConverter<List<String>, String> {
|
||||
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public String convertToDatabaseColumn(List<String> attribute) {
|
||||
if (attribute == null || attribute.isEmpty()) {
|
||||
return "[]";
|
||||
}
|
||||
try {
|
||||
return MAPPER.writeValueAsString(attribute);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Erreur sérialisation List<String> → JSON", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> convertToEntityAttribute(String dbData) {
|
||||
if (dbData == null || dbData.isBlank()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
try {
|
||||
return MAPPER.readValue(dbData, new TypeReference<List<String>>() {});
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Erreur désérialisation JSON → List<String>", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.loremind.infrastructure.persistence.converter;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
import jakarta.persistence.Converter;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Convertit une Map<String,String> du domaine en chaîne JSON et inversement.
|
||||
* Utilisé pour les maps clé-valeur simples (ex: Page.values — stocke les valeurs
|
||||
* des champs dynamiques définis par le Template).
|
||||
*/
|
||||
@Converter
|
||||
public class StringMapJsonConverter implements AttributeConverter<Map<String, String>, String> {
|
||||
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public String convertToDatabaseColumn(Map<String, String> attribute) {
|
||||
if (attribute == null || attribute.isEmpty()) {
|
||||
return "{}";
|
||||
}
|
||||
try {
|
||||
return MAPPER.writeValueAsString(attribute);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Erreur sérialisation Map<String,String> → JSON", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> convertToEntityAttribute(String dbData) {
|
||||
if (dbData == null || dbData.isBlank()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
try {
|
||||
return MAPPER.readValue(dbData, new TypeReference<Map<String, String>>() {});
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Erreur désérialisation JSON → Map<String,String>", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.loremind.infrastructure.persistence.entity;
|
||||
|
||||
import com.loremind.infrastructure.persistence.converter.StringListJsonConverter;
|
||||
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 pour la persistance des Arcs en base de données PostgreSQL.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "arcs")
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ArcJpaEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String description;
|
||||
|
||||
@Column(name = "campaign_id", nullable = false)
|
||||
private Long campaignId;
|
||||
|
||||
@Column(name = "\"order\"", nullable = false)
|
||||
private int order;
|
||||
|
||||
// Champs narratifs enrichis — ajoutés automatiquement par Hibernate DDL (ddl-auto=update)
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String themes;
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String stakes;
|
||||
|
||||
@Column(name = "gm_notes", columnDefinition = "TEXT")
|
||||
private String gmNotes;
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String rewards;
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String resolution;
|
||||
|
||||
/**
|
||||
* IDs des pages du Lore liées à cet arc, stockés en JSON dans une colonne TEXT.
|
||||
* Pas de FK cross-context : respect des Bounded Contexts.
|
||||
*/
|
||||
@Column(name = "related_page_ids", columnDefinition = "TEXT")
|
||||
@Convert(converter = StringListJsonConverter.class)
|
||||
@Builder.Default
|
||||
private List<String> relatedPageIds = 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,58 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Entité JPA pour la persistance des Campaigns en base de données PostgreSQL.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "campaigns")
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CampaignJpaEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String description;
|
||||
|
||||
@Column(name = "created_at", nullable = false, updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "updated_at", nullable = false)
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@Column(name = "arcs_count", nullable = false)
|
||||
private int arcsCount;
|
||||
|
||||
/**
|
||||
* ID du Lore associé (nullable).
|
||||
* Pas de @ManyToOne / pas de FK : c'est une weak reference inter-contexte.
|
||||
* Le Campaign Context et le Lore Context doivent pouvoir évoluer indépendamment.
|
||||
*/
|
||||
@Column(name = "lore_id")
|
||||
private String loreId;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
createdAt = LocalDateTime.now();
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.loremind.infrastructure.persistence.entity;
|
||||
|
||||
import com.loremind.infrastructure.persistence.converter.StringListJsonConverter;
|
||||
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 pour la persistance des Chapters en base de données PostgreSQL.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "chapters")
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ChapterJpaEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String description;
|
||||
|
||||
@Column(name = "arc_id", nullable = false)
|
||||
private Long arcId;
|
||||
|
||||
@Column(name = "\"order\"", nullable = false)
|
||||
private int order;
|
||||
|
||||
// Champs narratifs enrichis — ajoutés automatiquement par Hibernate DDL (ddl-auto=update)
|
||||
@Column(name = "gm_notes", columnDefinition = "TEXT")
|
||||
private String gmNotes;
|
||||
|
||||
@Column(name = "player_objectives", columnDefinition = "TEXT")
|
||||
private String playerObjectives;
|
||||
|
||||
@Column(name = "narrative_stakes", columnDefinition = "TEXT")
|
||||
private String narrativeStakes;
|
||||
|
||||
@Column(name = "related_page_ids", columnDefinition = "TEXT")
|
||||
@Convert(converter = StringListJsonConverter.class)
|
||||
@Builder.Default
|
||||
private List<String> relatedPageIds = 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,54 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Entité JPA pour la persistance des Lores en base de données PostgreSQL.
|
||||
* Cette classe contient des annotations JPA, donc elle est dans infrastructure, pas dans le domaine.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "lores")
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class LoreJpaEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String description;
|
||||
|
||||
@Column(name = "created_at", nullable = false, updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "updated_at", nullable = false)
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@Column(name = "node_count", nullable = false)
|
||||
private int nodeCount;
|
||||
|
||||
@Column(name = "page_count", nullable = false)
|
||||
private int pageCount;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
createdAt = LocalDateTime.now();
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Entité JPA pour la persistance des LoreNodes en base de données PostgreSQL.
|
||||
* Structure hiérarchique via parentId (auto-référence).
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "lore_nodes")
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class LoreNodeJpaEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
/** Clé de l'icône lucide-angular (ex: "users", "map-pin"). Nullable : un dossier peut ne pas avoir d'icône. */
|
||||
@Column(length = 64)
|
||||
private String icon;
|
||||
|
||||
@Column(name = "parent_id")
|
||||
private Long parentId; // Auto-référence pour l'arborescence
|
||||
|
||||
@Column(name = "lore_id", nullable = false)
|
||||
private Long loreId;
|
||||
|
||||
@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,76 @@
|
||||
package com.loremind.infrastructure.persistence.entity;
|
||||
|
||||
import com.loremind.infrastructure.persistence.converter.StringListJsonConverter;
|
||||
import com.loremind.infrastructure.persistence.converter.StringMapJsonConverter;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Entité JPA pour la persistance des Pages en PostgreSQL.
|
||||
* - lore_id dénormalisé (accès rapide aux pages d'un lore sans passer par nodes).
|
||||
* - values / tags / related_page_ids stockés en JSON (TEXT via converters).
|
||||
* - Note : colonne `values_json` car `values` est un mot-clé SQL réservé.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "pages")
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PageJpaEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "lore_id", nullable = false)
|
||||
private Long loreId;
|
||||
|
||||
@Column(name = "node_id", nullable = false)
|
||||
private Long nodeId;
|
||||
|
||||
@Column(name = "template_id")
|
||||
private Long templateId;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String title;
|
||||
|
||||
@Column(name = "values_json", columnDefinition = "TEXT")
|
||||
@Convert(converter = StringMapJsonConverter.class)
|
||||
private Map<String, String> values;
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String notes;
|
||||
|
||||
@Column(name = "tags", columnDefinition = "TEXT")
|
||||
@Convert(converter = StringListJsonConverter.class)
|
||||
private List<String> tags;
|
||||
|
||||
@Column(name = "related_page_ids", columnDefinition = "TEXT")
|
||||
@Convert(converter = StringListJsonConverter.class)
|
||||
private List<String> relatedPageIds;
|
||||
|
||||
@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,92 @@
|
||||
package com.loremind.infrastructure.persistence.entity;
|
||||
|
||||
import com.loremind.infrastructure.persistence.converter.StringListJsonConverter;
|
||||
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 pour la persistance des Scenes en base de données PostgreSQL.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "scenes")
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SceneJpaEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String description;
|
||||
|
||||
@Column(name = "chapter_id", nullable = false)
|
||||
private Long chapterId;
|
||||
|
||||
@Column(name = "\"order\"", nullable = false)
|
||||
private int order;
|
||||
|
||||
// Champs narratifs enrichis — ajoutés automatiquement par Hibernate (ddl-auto=update)
|
||||
|
||||
// Contexte et ambiance
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String location;
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String timing;
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String atmosphere;
|
||||
|
||||
// Narration
|
||||
@Column(name = "player_narration", columnDefinition = "TEXT")
|
||||
private String playerNarration;
|
||||
|
||||
// Secrets MJ
|
||||
@Column(name = "gm_secret_notes", columnDefinition = "TEXT")
|
||||
private String gmSecretNotes;
|
||||
|
||||
// Choix et conséquences
|
||||
@Column(name = "choices_consequences", columnDefinition = "TEXT")
|
||||
private String choicesConsequences;
|
||||
|
||||
// Combat
|
||||
@Column(name = "combat_difficulty", columnDefinition = "TEXT")
|
||||
private String combatDifficulty;
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String enemies;
|
||||
|
||||
@Column(name = "related_page_ids", columnDefinition = "TEXT")
|
||||
@Convert(converter = StringListJsonConverter.class)
|
||||
@Builder.Default
|
||||
private List<String> relatedPageIds = 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,63 @@
|
||||
package com.loremind.infrastructure.persistence.entity;
|
||||
|
||||
import com.loremind.infrastructure.persistence.converter.StringListJsonConverter;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Entité JPA pour la persistance des Templates en PostgreSQL.
|
||||
* - loreId et defaultNodeId : colonnes typées (FK logiques, pas de @ManyToOne
|
||||
* pour respecter l'isolation des Bounded Contexts).
|
||||
* - fields : stocké en JSON (TEXT) via StringListJsonConverter.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "templates")
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TemplateJpaEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "lore_id", nullable = false)
|
||||
private Long loreId;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String description;
|
||||
|
||||
@Column(name = "default_node_id")
|
||||
private Long defaultNodeId;
|
||||
|
||||
@Column(name = "fields", columnDefinition = "TEXT")
|
||||
@Convert(converter = StringListJsonConverter.class)
|
||||
private List<String> fields;
|
||||
|
||||
@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,16 @@
|
||||
package com.loremind.infrastructure.persistence.jpa;
|
||||
|
||||
import com.loremind.infrastructure.persistence.entity.ArcJpaEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Repository Spring Data JPA pour ArcJpaEntity.
|
||||
*/
|
||||
@Repository
|
||||
public interface ArcJpaRepository extends JpaRepository<ArcJpaEntity, Long> {
|
||||
|
||||
List<ArcJpaEntity> findByCampaignId(Long campaignId);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.loremind.infrastructure.persistence.jpa;
|
||||
|
||||
import com.loremind.infrastructure.persistence.entity.CampaignJpaEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Repository Spring Data JPA pour CampaignJpaEntity.
|
||||
*/
|
||||
@Repository
|
||||
public interface CampaignJpaRepository extends JpaRepository<CampaignJpaEntity, Long> {
|
||||
|
||||
@Query("SELECT c FROM CampaignJpaEntity c WHERE LOWER(c.name) LIKE LOWER(CONCAT('%', :query, '%'))")
|
||||
List<CampaignJpaEntity> findByNameContainingIgnoreCase(@Param("query") String query);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.loremind.infrastructure.persistence.jpa;
|
||||
|
||||
import com.loremind.infrastructure.persistence.entity.ChapterJpaEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Repository Spring Data JPA pour ChapterJpaEntity.
|
||||
*/
|
||||
@Repository
|
||||
public interface ChapterJpaRepository extends JpaRepository<ChapterJpaEntity, Long> {
|
||||
|
||||
List<ChapterJpaEntity> findByArcId(Long arcId);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.loremind.infrastructure.persistence.jpa;
|
||||
|
||||
import com.loremind.infrastructure.persistence.entity.LoreJpaEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Repository Spring Data JPA pour LoreJpaEntity.
|
||||
* Utilisé par l'adaptateur PostgresLoreRepository.
|
||||
*/
|
||||
@Repository
|
||||
public interface LoreJpaRepository extends JpaRepository<LoreJpaEntity, Long> {
|
||||
|
||||
List<LoreJpaEntity> findByNameContainingIgnoreCase(String query);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.loremind.infrastructure.persistence.jpa;
|
||||
|
||||
import com.loremind.infrastructure.persistence.entity.LoreNodeJpaEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Repository Spring Data JPA pour LoreNodeJpaEntity.
|
||||
*/
|
||||
@Repository
|
||||
public interface LoreNodeJpaRepository extends JpaRepository<LoreNodeJpaEntity, Long> {
|
||||
|
||||
List<LoreNodeJpaEntity> findByLoreId(Long loreId);
|
||||
|
||||
List<LoreNodeJpaEntity> findByParentId(Long parentId);
|
||||
|
||||
long countByLoreId(Long loreId);
|
||||
|
||||
List<LoreNodeJpaEntity> findByNameContainingIgnoreCase(String query);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.loremind.infrastructure.persistence.jpa;
|
||||
|
||||
import com.loremind.infrastructure.persistence.entity.PageJpaEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Repository Spring Data JPA pour PageJpaEntity.
|
||||
*/
|
||||
@Repository
|
||||
public interface PageJpaRepository extends JpaRepository<PageJpaEntity, Long> {
|
||||
|
||||
List<PageJpaEntity> findByLoreId(Long loreId);
|
||||
|
||||
List<PageJpaEntity> findByNodeId(Long nodeId);
|
||||
|
||||
List<PageJpaEntity> findByTemplateId(Long templateId);
|
||||
|
||||
long countByLoreId(Long loreId);
|
||||
|
||||
List<PageJpaEntity> findByTitleContainingIgnoreCase(String query);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.loremind.infrastructure.persistence.jpa;
|
||||
|
||||
import com.loremind.infrastructure.persistence.entity.SceneJpaEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Repository Spring Data JPA pour SceneJpaEntity.
|
||||
*/
|
||||
@Repository
|
||||
public interface SceneJpaRepository extends JpaRepository<SceneJpaEntity, Long> {
|
||||
|
||||
List<SceneJpaEntity> findByChapterId(Long chapterId);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.loremind.infrastructure.persistence.jpa;
|
||||
|
||||
import com.loremind.infrastructure.persistence.entity.TemplateJpaEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Repository Spring Data JPA pour TemplateJpaEntity.
|
||||
*/
|
||||
@Repository
|
||||
public interface TemplateJpaRepository extends JpaRepository<TemplateJpaEntity, Long> {
|
||||
|
||||
List<TemplateJpaEntity> findByLoreId(Long loreId);
|
||||
|
||||
List<TemplateJpaEntity> findByNameContainingIgnoreCase(String query);
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.loremind.infrastructure.persistence.postgres;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Arc;
|
||||
import com.loremind.domain.campaigncontext.ports.ArcRepository;
|
||||
import com.loremind.infrastructure.persistence.entity.ArcJpaEntity;
|
||||
import com.loremind.infrastructure.persistence.jpa.ArcJpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Adaptateur d'infrastructure qui implémente le Port ArcRepository.
|
||||
*/
|
||||
@Repository
|
||||
public class PostgresArcRepository implements ArcRepository {
|
||||
|
||||
private final ArcJpaRepository jpaRepository;
|
||||
|
||||
public PostgresArcRepository(ArcJpaRepository jpaRepository) {
|
||||
this.jpaRepository = jpaRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Arc save(Arc arc) {
|
||||
ArcJpaEntity jpaEntity = toJpaEntity(arc);
|
||||
ArcJpaEntity saved = jpaRepository.save(jpaEntity);
|
||||
return toDomainEntity(saved);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Arc> findById(String id) {
|
||||
Long longId = Long.parseLong(id);
|
||||
return jpaRepository.findById(longId)
|
||||
.map(this::toDomainEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Arc> findByCampaignId(String campaignId) {
|
||||
Long longCampaignId = Long.parseLong(campaignId);
|
||||
return jpaRepository.findByCampaignId(longCampaignId).stream()
|
||||
.map(this::toDomainEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Arc> findAll() {
|
||||
return jpaRepository.findAll().stream()
|
||||
.map(this::toDomainEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteById(String id) {
|
||||
Long longId = Long.parseLong(id);
|
||||
jpaRepository.deleteById(longId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsById(String id) {
|
||||
Long longId = Long.parseLong(id);
|
||||
return jpaRepository.existsById(longId);
|
||||
}
|
||||
|
||||
private Arc toDomainEntity(ArcJpaEntity jpaEntity) {
|
||||
return Arc.builder()
|
||||
.id(jpaEntity.getId().toString())
|
||||
.name(jpaEntity.getName())
|
||||
.description(jpaEntity.getDescription())
|
||||
.campaignId(jpaEntity.getCampaignId().toString())
|
||||
.order(jpaEntity.getOrder())
|
||||
.themes(jpaEntity.getThemes())
|
||||
.stakes(jpaEntity.getStakes())
|
||||
.gmNotes(jpaEntity.getGmNotes())
|
||||
.rewards(jpaEntity.getRewards())
|
||||
.resolution(jpaEntity.getResolution())
|
||||
// Defensive copy : jamais de null en domaine pour simplifier les appelants.
|
||||
.relatedPageIds(jpaEntity.getRelatedPageIds() != null
|
||||
? new ArrayList<>(jpaEntity.getRelatedPageIds())
|
||||
: new ArrayList<>())
|
||||
.createdAt(jpaEntity.getCreatedAt())
|
||||
.updatedAt(jpaEntity.getUpdatedAt())
|
||||
.build();
|
||||
}
|
||||
|
||||
private ArcJpaEntity toJpaEntity(Arc arc) {
|
||||
Long id = arc.getId() != null ? Long.parseLong(arc.getId()) : null;
|
||||
return ArcJpaEntity.builder()
|
||||
.id(id)
|
||||
.name(arc.getName())
|
||||
.description(arc.getDescription())
|
||||
.campaignId(Long.parseLong(arc.getCampaignId()))
|
||||
.order(arc.getOrder())
|
||||
.themes(arc.getThemes())
|
||||
.stakes(arc.getStakes())
|
||||
.gmNotes(arc.getGmNotes())
|
||||
.rewards(arc.getRewards())
|
||||
.resolution(arc.getResolution())
|
||||
.relatedPageIds(arc.getRelatedPageIds() != null
|
||||
? new ArrayList<>(arc.getRelatedPageIds())
|
||||
: new ArrayList<>())
|
||||
.createdAt(arc.getCreatedAt())
|
||||
.updatedAt(arc.getUpdatedAt())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.loremind.infrastructure.persistence.postgres;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Campaign;
|
||||
import com.loremind.domain.campaigncontext.ports.CampaignRepository;
|
||||
import com.loremind.infrastructure.persistence.entity.CampaignJpaEntity;
|
||||
import com.loremind.infrastructure.persistence.jpa.CampaignJpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Adaptateur d'infrastructure qui implémente le Port CampaignRepository.
|
||||
*/
|
||||
@Repository
|
||||
public class PostgresCampaignRepository implements CampaignRepository {
|
||||
|
||||
private final CampaignJpaRepository jpaRepository;
|
||||
|
||||
public PostgresCampaignRepository(CampaignJpaRepository jpaRepository) {
|
||||
this.jpaRepository = jpaRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Campaign save(Campaign campaign) {
|
||||
CampaignJpaEntity jpaEntity = toJpaEntity(campaign);
|
||||
CampaignJpaEntity saved = jpaRepository.save(jpaEntity);
|
||||
return toDomainEntity(saved);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Campaign> findById(String id) {
|
||||
Long longId = Long.parseLong(id);
|
||||
return jpaRepository.findById(longId)
|
||||
.map(this::toDomainEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Campaign> findAll() {
|
||||
return jpaRepository.findAll().stream()
|
||||
.map(this::toDomainEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteById(String id) {
|
||||
Long longId = Long.parseLong(id);
|
||||
jpaRepository.deleteById(longId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsById(String id) {
|
||||
Long longId = Long.parseLong(id);
|
||||
return jpaRepository.existsById(longId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Campaign> searchByName(String query) {
|
||||
return jpaRepository.findByNameContainingIgnoreCase(query).stream()
|
||||
.map(this::toDomainEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Campaign toDomainEntity(CampaignJpaEntity jpaEntity) {
|
||||
return Campaign.builder()
|
||||
.id(jpaEntity.getId().toString())
|
||||
.name(jpaEntity.getName())
|
||||
.description(jpaEntity.getDescription())
|
||||
.createdAt(jpaEntity.getCreatedAt())
|
||||
.updatedAt(jpaEntity.getUpdatedAt())
|
||||
.arcsCount(jpaEntity.getArcsCount())
|
||||
.loreId(jpaEntity.getLoreId())
|
||||
.build();
|
||||
}
|
||||
|
||||
private CampaignJpaEntity toJpaEntity(Campaign campaign) {
|
||||
Long id = campaign.getId() != null ? Long.parseLong(campaign.getId()) : null;
|
||||
return CampaignJpaEntity.builder()
|
||||
.id(id)
|
||||
.name(campaign.getName())
|
||||
.description(campaign.getDescription())
|
||||
.createdAt(campaign.getCreatedAt())
|
||||
.updatedAt(campaign.getUpdatedAt())
|
||||
.arcsCount(campaign.getArcsCount())
|
||||
.loreId(campaign.getLoreId())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.loremind.infrastructure.persistence.postgres;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Chapter;
|
||||
import com.loremind.domain.campaigncontext.ports.ChapterRepository;
|
||||
import com.loremind.infrastructure.persistence.entity.ChapterJpaEntity;
|
||||
import com.loremind.infrastructure.persistence.jpa.ChapterJpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Adaptateur d'infrastructure qui implémente le Port ChapterRepository.
|
||||
*/
|
||||
@Repository
|
||||
public class PostgresChapterRepository implements ChapterRepository {
|
||||
|
||||
private final ChapterJpaRepository jpaRepository;
|
||||
|
||||
public PostgresChapterRepository(ChapterJpaRepository jpaRepository) {
|
||||
this.jpaRepository = jpaRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Chapter save(Chapter chapter) {
|
||||
ChapterJpaEntity jpaEntity = toJpaEntity(chapter);
|
||||
ChapterJpaEntity saved = jpaRepository.save(jpaEntity);
|
||||
return toDomainEntity(saved);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Chapter> findById(String id) {
|
||||
Long longId = Long.parseLong(id);
|
||||
return jpaRepository.findById(longId)
|
||||
.map(this::toDomainEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Chapter> findByArcId(String arcId) {
|
||||
Long longArcId = Long.parseLong(arcId);
|
||||
return jpaRepository.findByArcId(longArcId).stream()
|
||||
.map(this::toDomainEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Chapter> findAll() {
|
||||
return jpaRepository.findAll().stream()
|
||||
.map(this::toDomainEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteById(String id) {
|
||||
Long longId = Long.parseLong(id);
|
||||
jpaRepository.deleteById(longId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsById(String id) {
|
||||
Long longId = Long.parseLong(id);
|
||||
return jpaRepository.existsById(longId);
|
||||
}
|
||||
|
||||
private Chapter toDomainEntity(ChapterJpaEntity jpaEntity) {
|
||||
return Chapter.builder()
|
||||
.id(jpaEntity.getId().toString())
|
||||
.name(jpaEntity.getName())
|
||||
.description(jpaEntity.getDescription())
|
||||
.arcId(jpaEntity.getArcId().toString())
|
||||
.order(jpaEntity.getOrder())
|
||||
.gmNotes(jpaEntity.getGmNotes())
|
||||
.playerObjectives(jpaEntity.getPlayerObjectives())
|
||||
.narrativeStakes(jpaEntity.getNarrativeStakes())
|
||||
.relatedPageIds(jpaEntity.getRelatedPageIds() != null
|
||||
? new ArrayList<>(jpaEntity.getRelatedPageIds())
|
||||
: new ArrayList<>())
|
||||
.createdAt(jpaEntity.getCreatedAt())
|
||||
.updatedAt(jpaEntity.getUpdatedAt())
|
||||
.build();
|
||||
}
|
||||
|
||||
private ChapterJpaEntity toJpaEntity(Chapter chapter) {
|
||||
Long id = chapter.getId() != null ? Long.parseLong(chapter.getId()) : null;
|
||||
return ChapterJpaEntity.builder()
|
||||
.id(id)
|
||||
.name(chapter.getName())
|
||||
.description(chapter.getDescription())
|
||||
.arcId(Long.parseLong(chapter.getArcId()))
|
||||
.order(chapter.getOrder())
|
||||
.gmNotes(chapter.getGmNotes())
|
||||
.playerObjectives(chapter.getPlayerObjectives())
|
||||
.narrativeStakes(chapter.getNarrativeStakes())
|
||||
.relatedPageIds(chapter.getRelatedPageIds() != null
|
||||
? new ArrayList<>(chapter.getRelatedPageIds())
|
||||
: new ArrayList<>())
|
||||
.createdAt(chapter.getCreatedAt())
|
||||
.updatedAt(chapter.getUpdatedAt())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.loremind.infrastructure.persistence.postgres;
|
||||
|
||||
import com.loremind.domain.lorecontext.LoreNode;
|
||||
import com.loremind.domain.lorecontext.ports.LoreNodeRepository;
|
||||
import com.loremind.infrastructure.persistence.entity.LoreNodeJpaEntity;
|
||||
import com.loremind.infrastructure.persistence.jpa.LoreNodeJpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Adaptateur d'infrastructure qui implémente le Port LoreNodeRepository.
|
||||
*/
|
||||
@Repository
|
||||
public class PostgresLoreNodeRepository implements LoreNodeRepository {
|
||||
|
||||
private final LoreNodeJpaRepository jpaRepository;
|
||||
|
||||
public PostgresLoreNodeRepository(LoreNodeJpaRepository jpaRepository) {
|
||||
this.jpaRepository = jpaRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoreNode save(LoreNode loreNode) {
|
||||
LoreNodeJpaEntity jpaEntity = toJpaEntity(loreNode);
|
||||
LoreNodeJpaEntity saved = jpaRepository.save(jpaEntity);
|
||||
return toDomainEntity(saved);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<LoreNode> findById(String id) {
|
||||
Long longId = Long.parseLong(id);
|
||||
return jpaRepository.findById(longId)
|
||||
.map(this::toDomainEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LoreNode> findByLoreId(String loreId) {
|
||||
Long longLoreId = Long.parseLong(loreId);
|
||||
return jpaRepository.findByLoreId(longLoreId).stream()
|
||||
.map(this::toDomainEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LoreNode> findByParentId(String parentId) {
|
||||
if (parentId == null || parentId.isEmpty()) {
|
||||
return jpaRepository.findByParentId(null).stream()
|
||||
.map(this::toDomainEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
Long longParentId = Long.parseLong(parentId);
|
||||
return jpaRepository.findByParentId(longParentId).stream()
|
||||
.map(this::toDomainEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LoreNode> findAll() {
|
||||
return jpaRepository.findAll().stream()
|
||||
.map(this::toDomainEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteById(String id) {
|
||||
Long longId = Long.parseLong(id);
|
||||
jpaRepository.deleteById(longId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsById(String id) {
|
||||
Long longId = Long.parseLong(id);
|
||||
return jpaRepository.existsById(longId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countByLoreId(String loreId) {
|
||||
return jpaRepository.countByLoreId(Long.parseLong(loreId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LoreNode> searchByName(String query) {
|
||||
return jpaRepository.findByNameContainingIgnoreCase(query).stream()
|
||||
.map(this::toDomainEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private LoreNode toDomainEntity(LoreNodeJpaEntity jpaEntity) {
|
||||
return LoreNode.builder()
|
||||
.id(jpaEntity.getId().toString())
|
||||
.name(jpaEntity.getName())
|
||||
.icon(jpaEntity.getIcon())
|
||||
.parentId(jpaEntity.getParentId() != null ? jpaEntity.getParentId().toString() : null)
|
||||
.loreId(jpaEntity.getLoreId().toString())
|
||||
.createdAt(jpaEntity.getCreatedAt())
|
||||
.updatedAt(jpaEntity.getUpdatedAt())
|
||||
.build();
|
||||
}
|
||||
|
||||
private LoreNodeJpaEntity toJpaEntity(LoreNode loreNode) {
|
||||
Long id = loreNode.getId() != null ? Long.parseLong(loreNode.getId()) : null;
|
||||
Long parentId = loreNode.getParentId() != null && !loreNode.getParentId().isEmpty()
|
||||
? Long.parseLong(loreNode.getParentId())
|
||||
: null;
|
||||
return LoreNodeJpaEntity.builder()
|
||||
.id(id)
|
||||
.name(loreNode.getName())
|
||||
.icon(loreNode.getIcon())
|
||||
.parentId(parentId)
|
||||
.loreId(Long.parseLong(loreNode.getLoreId()))
|
||||
.createdAt(loreNode.getCreatedAt())
|
||||
.updatedAt(loreNode.getUpdatedAt())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.loremind.infrastructure.persistence.postgres;
|
||||
|
||||
import com.loremind.domain.lorecontext.Lore;
|
||||
import com.loremind.domain.lorecontext.ports.LoreRepository;
|
||||
import com.loremind.infrastructure.persistence.entity.LoreJpaEntity;
|
||||
import com.loremind.infrastructure.persistence.jpa.LoreJpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Adaptateur d'infrastructure qui implémente le Port LoreRepository.
|
||||
* Utilise LoreJpaRepository pour interagir avec PostgreSQL.
|
||||
* Fait la conversion entre Lore (domaine) et LoreJpaEntity (JPA).
|
||||
*/
|
||||
@Repository
|
||||
public class PostgresLoreRepository implements LoreRepository {
|
||||
|
||||
private final LoreJpaRepository jpaRepository;
|
||||
|
||||
public PostgresLoreRepository(LoreJpaRepository jpaRepository) {
|
||||
this.jpaRepository = jpaRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Lore save(Lore lore) {
|
||||
LoreJpaEntity jpaEntity = toJpaEntity(lore);
|
||||
LoreJpaEntity saved = jpaRepository.save(jpaEntity);
|
||||
return toDomainEntity(saved);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Lore> findById(String id) {
|
||||
Long longId = Long.parseLong(id);
|
||||
return jpaRepository.findById(longId)
|
||||
.map(this::toDomainEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Lore> findAll() {
|
||||
return jpaRepository.findAll().stream()
|
||||
.map(this::toDomainEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteById(String id) {
|
||||
Long longId = Long.parseLong(id);
|
||||
jpaRepository.deleteById(longId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsById(String id) {
|
||||
Long longId = Long.parseLong(id);
|
||||
return jpaRepository.existsById(longId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Lore> searchByName(String query) {
|
||||
return jpaRepository.findByNameContainingIgnoreCase(query).stream()
|
||||
.map(this::toDomainEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// Méthodes de conversion
|
||||
private Lore toDomainEntity(LoreJpaEntity jpaEntity) {
|
||||
return Lore.builder()
|
||||
.id(jpaEntity.getId().toString())
|
||||
.name(jpaEntity.getName())
|
||||
.description(jpaEntity.getDescription())
|
||||
.createdAt(jpaEntity.getCreatedAt())
|
||||
.updatedAt(jpaEntity.getUpdatedAt())
|
||||
.nodeCount(jpaEntity.getNodeCount())
|
||||
.pageCount(jpaEntity.getPageCount())
|
||||
.build();
|
||||
}
|
||||
|
||||
private LoreJpaEntity toJpaEntity(Lore lore) {
|
||||
Long id = lore.getId() != null ? Long.parseLong(lore.getId()) : null;
|
||||
return LoreJpaEntity.builder()
|
||||
.id(id)
|
||||
.name(lore.getName())
|
||||
.description(lore.getDescription())
|
||||
.createdAt(lore.getCreatedAt())
|
||||
.updatedAt(lore.getUpdatedAt())
|
||||
.nodeCount(lore.getNodeCount())
|
||||
.pageCount(lore.getPageCount())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package com.loremind.infrastructure.persistence.postgres;
|
||||
|
||||
import com.loremind.domain.lorecontext.Page;
|
||||
import com.loremind.domain.lorecontext.ports.PageRepository;
|
||||
import com.loremind.infrastructure.persistence.entity.PageJpaEntity;
|
||||
import com.loremind.infrastructure.persistence.jpa.PageJpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Adaptateur d'infrastructure qui implémente le Port PageRepository.
|
||||
*/
|
||||
@Repository
|
||||
public class PostgresPageRepository implements PageRepository {
|
||||
|
||||
private final PageJpaRepository jpaRepository;
|
||||
|
||||
public PostgresPageRepository(PageJpaRepository jpaRepository) {
|
||||
this.jpaRepository = jpaRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page save(Page page) {
|
||||
PageJpaEntity jpaEntity = toJpaEntity(page);
|
||||
PageJpaEntity saved = jpaRepository.save(jpaEntity);
|
||||
return toDomainEntity(saved);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Page> findById(String id) {
|
||||
Long longId = Long.parseLong(id);
|
||||
return jpaRepository.findById(longId)
|
||||
.map(this::toDomainEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Page> findByLoreId(String loreId) {
|
||||
return jpaRepository.findByLoreId(Long.parseLong(loreId)).stream()
|
||||
.map(this::toDomainEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Page> findByNodeId(String nodeId) {
|
||||
Long longNodeId = Long.parseLong(nodeId);
|
||||
return jpaRepository.findByNodeId(longNodeId).stream()
|
||||
.map(this::toDomainEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Page> findByTemplateId(String templateId) {
|
||||
if (templateId == null || templateId.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
Long longTemplateId = Long.parseLong(templateId);
|
||||
return jpaRepository.findByTemplateId(longTemplateId).stream()
|
||||
.map(this::toDomainEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Page> findAll() {
|
||||
return jpaRepository.findAll().stream()
|
||||
.map(this::toDomainEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteById(String id) {
|
||||
Long longId = Long.parseLong(id);
|
||||
jpaRepository.deleteById(longId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsById(String id) {
|
||||
Long longId = Long.parseLong(id);
|
||||
return jpaRepository.existsById(longId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countByLoreId(String loreId) {
|
||||
return jpaRepository.countByLoreId(Long.parseLong(loreId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Page> searchByTitle(String query) {
|
||||
return jpaRepository.findByTitleContainingIgnoreCase(query).stream()
|
||||
.map(this::toDomainEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Page toDomainEntity(PageJpaEntity e) {
|
||||
return Page.builder()
|
||||
.id(e.getId().toString())
|
||||
.loreId(e.getLoreId() != null ? e.getLoreId().toString() : null)
|
||||
.nodeId(e.getNodeId() != null ? e.getNodeId().toString() : null)
|
||||
.templateId(e.getTemplateId() != null ? e.getTemplateId().toString() : null)
|
||||
.title(e.getTitle())
|
||||
.values(e.getValues() != null ? new HashMap<>(e.getValues()) : new HashMap<>())
|
||||
.notes(e.getNotes())
|
||||
.tags(e.getTags() != null ? new ArrayList<>(e.getTags()) : new ArrayList<>())
|
||||
.relatedPageIds(e.getRelatedPageIds() != null ? new ArrayList<>(e.getRelatedPageIds()) : new ArrayList<>())
|
||||
.createdAt(e.getCreatedAt())
|
||||
.updatedAt(e.getUpdatedAt())
|
||||
.build();
|
||||
}
|
||||
|
||||
private PageJpaEntity toJpaEntity(Page p) {
|
||||
return PageJpaEntity.builder()
|
||||
.id(p.getId() != null ? Long.parseLong(p.getId()) : null)
|
||||
.loreId(p.getLoreId() != null ? Long.parseLong(p.getLoreId()) : null)
|
||||
.nodeId(p.getNodeId() != null ? Long.parseLong(p.getNodeId()) : null)
|
||||
.templateId(p.getTemplateId() != null && !p.getTemplateId().isBlank()
|
||||
? Long.parseLong(p.getTemplateId()) : null)
|
||||
.title(p.getTitle())
|
||||
.values(p.getValues() != null ? new HashMap<>(p.getValues()) : new HashMap<>())
|
||||
.notes(p.getNotes())
|
||||
.tags(p.getTags() != null ? new ArrayList<>(p.getTags()) : new ArrayList<>())
|
||||
.relatedPageIds(p.getRelatedPageIds() != null ? new ArrayList<>(p.getRelatedPageIds()) : new ArrayList<>())
|
||||
.createdAt(p.getCreatedAt())
|
||||
.updatedAt(p.getUpdatedAt())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package com.loremind.infrastructure.persistence.postgres;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Scene;
|
||||
import com.loremind.domain.campaigncontext.ports.SceneRepository;
|
||||
import com.loremind.infrastructure.persistence.entity.SceneJpaEntity;
|
||||
import com.loremind.infrastructure.persistence.jpa.SceneJpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Adaptateur d'infrastructure qui implémente le Port SceneRepository.
|
||||
*/
|
||||
@Repository
|
||||
public class PostgresSceneRepository implements SceneRepository {
|
||||
|
||||
private final SceneJpaRepository jpaRepository;
|
||||
|
||||
public PostgresSceneRepository(SceneJpaRepository jpaRepository) {
|
||||
this.jpaRepository = jpaRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scene save(Scene scene) {
|
||||
SceneJpaEntity jpaEntity = toJpaEntity(scene);
|
||||
SceneJpaEntity saved = jpaRepository.save(jpaEntity);
|
||||
return toDomainEntity(saved);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Scene> findById(String id) {
|
||||
Long longId = Long.parseLong(id);
|
||||
return jpaRepository.findById(longId)
|
||||
.map(this::toDomainEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Scene> findByChapterId(String chapterId) {
|
||||
Long longChapterId = Long.parseLong(chapterId);
|
||||
return jpaRepository.findByChapterId(longChapterId).stream()
|
||||
.map(this::toDomainEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Scene> findAll() {
|
||||
return jpaRepository.findAll().stream()
|
||||
.map(this::toDomainEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteById(String id) {
|
||||
Long longId = Long.parseLong(id);
|
||||
jpaRepository.deleteById(longId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsById(String id) {
|
||||
Long longId = Long.parseLong(id);
|
||||
return jpaRepository.existsById(longId);
|
||||
}
|
||||
|
||||
private Scene toDomainEntity(SceneJpaEntity jpaEntity) {
|
||||
return Scene.builder()
|
||||
.id(jpaEntity.getId().toString())
|
||||
.name(jpaEntity.getName())
|
||||
.description(jpaEntity.getDescription())
|
||||
.chapterId(jpaEntity.getChapterId().toString())
|
||||
.order(jpaEntity.getOrder())
|
||||
.location(jpaEntity.getLocation())
|
||||
.timing(jpaEntity.getTiming())
|
||||
.atmosphere(jpaEntity.getAtmosphere())
|
||||
.playerNarration(jpaEntity.getPlayerNarration())
|
||||
.gmSecretNotes(jpaEntity.getGmSecretNotes())
|
||||
.choicesConsequences(jpaEntity.getChoicesConsequences())
|
||||
.combatDifficulty(jpaEntity.getCombatDifficulty())
|
||||
.enemies(jpaEntity.getEnemies())
|
||||
.relatedPageIds(jpaEntity.getRelatedPageIds() != null
|
||||
? new ArrayList<>(jpaEntity.getRelatedPageIds())
|
||||
: new ArrayList<>())
|
||||
.createdAt(jpaEntity.getCreatedAt())
|
||||
.updatedAt(jpaEntity.getUpdatedAt())
|
||||
.build();
|
||||
}
|
||||
|
||||
private SceneJpaEntity toJpaEntity(Scene scene) {
|
||||
Long id = scene.getId() != null ? Long.parseLong(scene.getId()) : null;
|
||||
return SceneJpaEntity.builder()
|
||||
.id(id)
|
||||
.name(scene.getName())
|
||||
.description(scene.getDescription())
|
||||
.chapterId(Long.parseLong(scene.getChapterId()))
|
||||
.order(scene.getOrder())
|
||||
.location(scene.getLocation())
|
||||
.timing(scene.getTiming())
|
||||
.atmosphere(scene.getAtmosphere())
|
||||
.playerNarration(scene.getPlayerNarration())
|
||||
.gmSecretNotes(scene.getGmSecretNotes())
|
||||
.choicesConsequences(scene.getChoicesConsequences())
|
||||
.combatDifficulty(scene.getCombatDifficulty())
|
||||
.enemies(scene.getEnemies())
|
||||
.relatedPageIds(scene.getRelatedPageIds() != null
|
||||
? new ArrayList<>(scene.getRelatedPageIds())
|
||||
: new ArrayList<>())
|
||||
.createdAt(scene.getCreatedAt())
|
||||
.updatedAt(scene.getUpdatedAt())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.loremind.infrastructure.persistence.postgres;
|
||||
|
||||
import com.loremind.domain.lorecontext.Template;
|
||||
import com.loremind.domain.lorecontext.ports.TemplateRepository;
|
||||
import com.loremind.infrastructure.persistence.entity.TemplateJpaEntity;
|
||||
import com.loremind.infrastructure.persistence.jpa.TemplateJpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Adaptateur d'infrastructure qui implémente le Port TemplateRepository.
|
||||
* Responsable du mapping bidirectionnel entre l'entité de domaine (Template)
|
||||
* et l'entité JPA (TemplateJpaEntity).
|
||||
*/
|
||||
@Repository
|
||||
public class PostgresTemplateRepository implements TemplateRepository {
|
||||
|
||||
private final TemplateJpaRepository jpaRepository;
|
||||
|
||||
public PostgresTemplateRepository(TemplateJpaRepository jpaRepository) {
|
||||
this.jpaRepository = jpaRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Template save(Template template) {
|
||||
TemplateJpaEntity saved = jpaRepository.save(toJpaEntity(template));
|
||||
return toDomainEntity(saved);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Template> findById(String id) {
|
||||
return jpaRepository.findById(Long.parseLong(id)).map(this::toDomainEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Template> findAll() {
|
||||
return jpaRepository.findAll().stream()
|
||||
.map(this::toDomainEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Template> findByLoreId(String loreId) {
|
||||
return jpaRepository.findByLoreId(Long.parseLong(loreId)).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));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Template> searchByName(String query) {
|
||||
return jpaRepository.findByNameContainingIgnoreCase(query).stream()
|
||||
.map(this::toDomainEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// --- Mapping ----------------------------------------------------------
|
||||
|
||||
private Template toDomainEntity(TemplateJpaEntity e) {
|
||||
return Template.builder()
|
||||
.id(e.getId().toString())
|
||||
.loreId(e.getLoreId() != null ? e.getLoreId().toString() : null)
|
||||
.name(e.getName())
|
||||
.description(e.getDescription())
|
||||
.defaultNodeId(e.getDefaultNodeId() != null ? e.getDefaultNodeId().toString() : null)
|
||||
.fields(e.getFields() != null ? new ArrayList<>(e.getFields()) : new ArrayList<>())
|
||||
.createdAt(e.getCreatedAt())
|
||||
.updatedAt(e.getUpdatedAt())
|
||||
.build();
|
||||
}
|
||||
|
||||
private TemplateJpaEntity toJpaEntity(Template t) {
|
||||
return TemplateJpaEntity.builder()
|
||||
.id(t.getId() != null ? Long.parseLong(t.getId()) : null)
|
||||
.loreId(t.getLoreId() != null ? Long.parseLong(t.getLoreId()) : null)
|
||||
.name(t.getName())
|
||||
.description(t.getDescription())
|
||||
.defaultNodeId(t.getDefaultNodeId() != null ? Long.parseLong(t.getDefaultNodeId()) : null)
|
||||
.fields(t.getFields() != null ? new ArrayList<>(t.getFields()) : new ArrayList<>())
|
||||
.createdAt(t.getCreatedAt())
|
||||
.updatedAt(t.getUpdatedAt())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.loremind.infrastructure.web.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
||||
/**
|
||||
* Configuration CORS pour autoriser les requêtes depuis le Frontend Angular.
|
||||
* Adaptateur d'infrastructure qui configure la politique CORS.
|
||||
*/
|
||||
@Configuration
|
||||
public class CorsConfig {
|
||||
|
||||
@Bean
|
||||
public CorsFilter corsFilter() {
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
|
||||
// Autoriser les requêtes depuis localhost:4200 (Angular dev server)
|
||||
config.addAllowedOrigin("http://localhost:4200");
|
||||
config.addAllowedHeader("*");
|
||||
config.addAllowedMethod("*");
|
||||
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
return new CorsFilter(source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.loremind.infrastructure.web.controller;
|
||||
|
||||
import com.loremind.application.campaigncontext.ArcService;
|
||||
import com.loremind.domain.campaigncontext.Arc;
|
||||
import com.loremind.infrastructure.web.dto.campaigncontext.ArcDTO;
|
||||
import com.loremind.infrastructure.web.mapper.ArcMapper;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* REST Controller pour le contexte Arc.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/arcs")
|
||||
public class ArcController {
|
||||
|
||||
private final ArcService arcService;
|
||||
private final ArcMapper arcMapper;
|
||||
|
||||
public ArcController(ArcService arcService, ArcMapper arcMapper) {
|
||||
this.arcService = arcService;
|
||||
this.arcMapper = arcMapper;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<ArcDTO> createArc(@RequestBody ArcDTO arcDTO) {
|
||||
Arc arc = arcMapper.toDomain(arcDTO);
|
||||
Arc createdArc = arcService.createArc(arc.getName(), arc.getDescription(), arc.getCampaignId(), arc.getOrder());
|
||||
return ResponseEntity.ok(arcMapper.toDTO(createdArc));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<ArcDTO> getArcById(@PathVariable String id) {
|
||||
return arcService.getArcById(id)
|
||||
.map(arc -> ResponseEntity.ok(arcMapper.toDTO(arc)))
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<ArcDTO>> getAllArcs() {
|
||||
List<Arc> arcs = arcService.getAllArcs();
|
||||
List<ArcDTO> arcDTOs = arcs.stream()
|
||||
.map(arcMapper::toDTO)
|
||||
.collect(Collectors.toList());
|
||||
return ResponseEntity.ok(arcDTOs);
|
||||
}
|
||||
|
||||
@GetMapping("/campaign/{campaignId}")
|
||||
public ResponseEntity<List<ArcDTO>> getArcsByCampaignId(@PathVariable String campaignId) {
|
||||
List<Arc> arcs = arcService.getArcsByCampaignId(campaignId);
|
||||
List<ArcDTO> arcDTOs = arcs.stream()
|
||||
.map(arcMapper::toDTO)
|
||||
.collect(Collectors.toList());
|
||||
return ResponseEntity.ok(arcDTOs);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<ArcDTO> updateArc(@PathVariable String id, @RequestBody ArcDTO arcDTO) {
|
||||
Arc updatedArc = arcService.updateArc(id, arcMapper.toDomain(arcDTO));
|
||||
return ResponseEntity.ok(arcMapper.toDTO(updatedArc));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Void> deleteArc(@PathVariable String id) {
|
||||
arcService.deleteArc(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.loremind.infrastructure.web.controller;
|
||||
|
||||
import com.loremind.application.campaigncontext.CampaignService;
|
||||
import com.loremind.domain.campaigncontext.Campaign;
|
||||
import com.loremind.infrastructure.web.dto.campaigncontext.CampaignDTO;
|
||||
import com.loremind.infrastructure.web.mapper.CampaignMapper;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* REST Controller pour le contexte Campaign.
|
||||
* Adaptateur d'infrastructure qui expose l'API REST.
|
||||
* Utilise le Service d'application et le Mapper selon l'Architecture Hexagonale.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/campaigns")
|
||||
public class CampaignController {
|
||||
|
||||
private final CampaignService campaignService;
|
||||
private final CampaignMapper campaignMapper;
|
||||
|
||||
public CampaignController(CampaignService campaignService, CampaignMapper campaignMapper) {
|
||||
this.campaignService = campaignService;
|
||||
this.campaignMapper = campaignMapper;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<CampaignDTO> createCampaign(@RequestBody CampaignDTO campaignDTO) {
|
||||
Campaign campaign = campaignMapper.toDomain(campaignDTO);
|
||||
Campaign createdCampaign = campaignService.createCampaign(
|
||||
new CampaignService.CampaignData(campaign.getName(), campaign.getDescription(), campaign.getLoreId())
|
||||
);
|
||||
return ResponseEntity.ok(campaignMapper.toDTO(createdCampaign));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<CampaignDTO> getCampaignById(@PathVariable String id) {
|
||||
return campaignService.getCampaignById(id)
|
||||
.map(campaign -> ResponseEntity.ok(campaignMapper.toDTO(campaign)))
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<CampaignDTO>> getAllCampaigns() {
|
||||
List<Campaign> campaigns = campaignService.getAllCampaigns();
|
||||
List<CampaignDTO> campaignDTOs = campaigns.stream()
|
||||
.map(campaignMapper::toDTO)
|
||||
.collect(Collectors.toList());
|
||||
return ResponseEntity.ok(campaignDTOs);
|
||||
}
|
||||
|
||||
@GetMapping("/search")
|
||||
public ResponseEntity<List<CampaignDTO>> searchCampaigns(@RequestParam("q") String query) {
|
||||
List<CampaignDTO> dtos = campaignService.searchCampaigns(query).stream()
|
||||
.map(campaignMapper::toDTO)
|
||||
.collect(Collectors.toList());
|
||||
return ResponseEntity.ok(dtos);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<CampaignDTO> updateCampaign(@PathVariable String id, @RequestBody CampaignDTO campaignDTO) {
|
||||
Campaign updatedCampaign = campaignService.updateCampaign(
|
||||
id,
|
||||
new CampaignService.CampaignData(campaignDTO.getName(), campaignDTO.getDescription(), campaignDTO.getLoreId())
|
||||
);
|
||||
return ResponseEntity.ok(campaignMapper.toDTO(updatedCampaign));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Void> deleteCampaign(@PathVariable String id) {
|
||||
campaignService.deleteCampaign(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.loremind.infrastructure.web.controller;
|
||||
|
||||
import com.loremind.application.campaigncontext.ChapterService;
|
||||
import com.loremind.domain.campaigncontext.Chapter;
|
||||
import com.loremind.infrastructure.web.dto.campaigncontext.ChapterDTO;
|
||||
import com.loremind.infrastructure.web.mapper.ChapterMapper;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* REST Controller pour le contexte Chapter.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/chapters")
|
||||
public class ChapterController {
|
||||
|
||||
private final ChapterService chapterService;
|
||||
private final ChapterMapper chapterMapper;
|
||||
|
||||
public ChapterController(ChapterService chapterService, ChapterMapper chapterMapper) {
|
||||
this.chapterService = chapterService;
|
||||
this.chapterMapper = chapterMapper;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<ChapterDTO> createChapter(@RequestBody ChapterDTO chapterDTO) {
|
||||
Chapter chapter = chapterMapper.toDomain(chapterDTO);
|
||||
Chapter createdChapter = chapterService.createChapter(chapter.getName(), chapter.getDescription(), chapter.getArcId(), chapter.getOrder());
|
||||
return ResponseEntity.ok(chapterMapper.toDTO(createdChapter));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<ChapterDTO> getChapterById(@PathVariable String id) {
|
||||
return chapterService.getChapterById(id)
|
||||
.map(chapter -> ResponseEntity.ok(chapterMapper.toDTO(chapter)))
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<ChapterDTO>> getAllChapters() {
|
||||
List<Chapter> chapters = chapterService.getAllChapters();
|
||||
List<ChapterDTO> chapterDTOs = chapters.stream()
|
||||
.map(chapterMapper::toDTO)
|
||||
.collect(Collectors.toList());
|
||||
return ResponseEntity.ok(chapterDTOs);
|
||||
}
|
||||
|
||||
@GetMapping("/arc/{arcId}")
|
||||
public ResponseEntity<List<ChapterDTO>> getChaptersByArcId(@PathVariable String arcId) {
|
||||
List<Chapter> chapters = chapterService.getChaptersByArcId(arcId);
|
||||
List<ChapterDTO> chapterDTOs = chapters.stream()
|
||||
.map(chapterMapper::toDTO)
|
||||
.collect(Collectors.toList());
|
||||
return ResponseEntity.ok(chapterDTOs);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<ChapterDTO> updateChapter(@PathVariable String id, @RequestBody ChapterDTO chapterDTO) {
|
||||
Chapter updatedChapter = chapterService.updateChapter(id, chapterMapper.toDomain(chapterDTO));
|
||||
return ResponseEntity.ok(chapterMapper.toDTO(updatedChapter));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Void> deleteChapter(@PathVariable String id) {
|
||||
chapterService.deleteChapter(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.loremind.infrastructure.web.controller;
|
||||
|
||||
import com.loremind.application.lorecontext.LoreService;
|
||||
import com.loremind.domain.lorecontext.Lore;
|
||||
import com.loremind.infrastructure.web.dto.lorecontext.LoreDTO;
|
||||
import com.loremind.infrastructure.web.mapper.LoreMapper;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* REST Controller pour le contexte Lore.
|
||||
* Adaptateur d'infrastructure qui expose l'API REST.
|
||||
* Utilise le Service d'application et le Mapper selon l'Architecture Hexagonale.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/lores")
|
||||
public class LoreController {
|
||||
|
||||
private final LoreService loreService;
|
||||
private final LoreMapper loreMapper;
|
||||
|
||||
public LoreController(LoreService loreService, LoreMapper loreMapper) {
|
||||
this.loreService = loreService;
|
||||
this.loreMapper = loreMapper;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<LoreDTO> createLore(@RequestBody LoreDTO loreDTO) {
|
||||
Lore lore = loreMapper.toDomain(loreDTO);
|
||||
Lore createdLore = loreService.createLore(lore.getName(), lore.getDescription());
|
||||
return ResponseEntity.ok(loreMapper.toDTO(createdLore));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<LoreDTO> getLoreById(@PathVariable String id) {
|
||||
return loreService.getLoreById(id)
|
||||
.map(lore -> ResponseEntity.ok(loreMapper.toDTO(lore)))
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<LoreDTO>> getAllLores() {
|
||||
List<Lore> lores = loreService.getAllLores();
|
||||
List<LoreDTO> loreDTOs = lores.stream()
|
||||
.map(loreMapper::toDTO)
|
||||
.collect(Collectors.toList());
|
||||
return ResponseEntity.ok(loreDTOs);
|
||||
}
|
||||
|
||||
@GetMapping("/search")
|
||||
public ResponseEntity<List<LoreDTO>> searchLores(@RequestParam("q") String query) {
|
||||
List<LoreDTO> dtos = loreService.searchLores(query).stream()
|
||||
.map(loreMapper::toDTO)
|
||||
.collect(Collectors.toList());
|
||||
return ResponseEntity.ok(dtos);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<LoreDTO> updateLore(@PathVariable String id, @RequestBody LoreDTO loreDTO) {
|
||||
Lore updatedLore = loreService.updateLore(id, loreDTO.getName(), loreDTO.getDescription());
|
||||
return ResponseEntity.ok(loreMapper.toDTO(updatedLore));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Void> deleteLore(@PathVariable String id) {
|
||||
loreService.deleteLore(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.loremind.infrastructure.web.controller;
|
||||
|
||||
import com.loremind.application.lorecontext.LoreNodeService;
|
||||
import com.loremind.domain.lorecontext.LoreNode;
|
||||
import com.loremind.infrastructure.web.dto.lorecontext.LoreNodeDTO;
|
||||
import com.loremind.infrastructure.web.mapper.LoreNodeMapper;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* REST Controller pour le contexte LoreNode.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/lore-nodes")
|
||||
public class LoreNodeController {
|
||||
|
||||
private final LoreNodeService loreNodeService;
|
||||
private final LoreNodeMapper loreNodeMapper;
|
||||
|
||||
public LoreNodeController(LoreNodeService loreNodeService, LoreNodeMapper loreNodeMapper) {
|
||||
this.loreNodeService = loreNodeService;
|
||||
this.loreNodeMapper = loreNodeMapper;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<LoreNodeDTO> createLoreNode(@RequestBody LoreNodeDTO loreNodeDTO) {
|
||||
LoreNode changes = loreNodeMapper.toDomain(loreNodeDTO);
|
||||
LoreNode created = loreNodeService.createLoreNode(changes);
|
||||
return ResponseEntity.ok(loreNodeMapper.toDTO(created));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<LoreNodeDTO> getLoreNodeById(@PathVariable String id) {
|
||||
return loreNodeService.getLoreNodeById(id)
|
||||
.map(loreNode -> ResponseEntity.ok(loreNodeMapper.toDTO(loreNode)))
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/lore-nodes → tous les dossiers (usage admin/debug)
|
||||
* GET /api/lore-nodes?loreId=X → uniquement les dossiers du Lore X
|
||||
*
|
||||
* Sans ce filtre, le frontend recevait TOUS les dossiers de TOUS les lores,
|
||||
* ce qui polluait l'affichage quand on switche entre deux lores.
|
||||
* Pattern aligné sur TemplateController et PageController.
|
||||
*/
|
||||
@GetMapping
|
||||
public ResponseEntity<List<LoreNodeDTO>> getLoreNodes(
|
||||
@RequestParam(value = "loreId", required = false) String loreId) {
|
||||
List<LoreNode> loreNodes = (loreId != null && !loreId.isBlank())
|
||||
? loreNodeService.getLoreNodesByLoreId(loreId)
|
||||
: loreNodeService.getAllLoreNodes();
|
||||
List<LoreNodeDTO> loreNodeDTOs = loreNodes.stream()
|
||||
.map(loreNodeMapper::toDTO)
|
||||
.collect(Collectors.toList());
|
||||
return ResponseEntity.ok(loreNodeDTOs);
|
||||
}
|
||||
|
||||
@GetMapping("/search")
|
||||
public ResponseEntity<List<LoreNodeDTO>> searchLoreNodes(@RequestParam("q") String query) {
|
||||
List<LoreNodeDTO> dtos = loreNodeService.searchLoreNodes(query).stream()
|
||||
.map(loreNodeMapper::toDTO)
|
||||
.collect(Collectors.toList());
|
||||
return ResponseEntity.ok(dtos);
|
||||
}
|
||||
|
||||
@GetMapping("/lore/{loreId}")
|
||||
public ResponseEntity<List<LoreNodeDTO>> getLoreNodesByLoreId(@PathVariable String loreId) {
|
||||
List<LoreNode> loreNodes = loreNodeService.getLoreNodesByLoreId(loreId);
|
||||
List<LoreNodeDTO> loreNodeDTOs = loreNodes.stream()
|
||||
.map(loreNodeMapper::toDTO)
|
||||
.collect(Collectors.toList());
|
||||
return ResponseEntity.ok(loreNodeDTOs);
|
||||
}
|
||||
|
||||
@GetMapping("/parent/{parentId}")
|
||||
public ResponseEntity<List<LoreNodeDTO>> getLoreNodesByParentId(@PathVariable String parentId) {
|
||||
List<LoreNode> loreNodes = loreNodeService.getLoreNodesByParentId(parentId);
|
||||
List<LoreNodeDTO> loreNodeDTOs = loreNodes.stream()
|
||||
.map(loreNodeMapper::toDTO)
|
||||
.collect(Collectors.toList());
|
||||
return ResponseEntity.ok(loreNodeDTOs);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<LoreNodeDTO> updateLoreNode(@PathVariable String id, @RequestBody LoreNodeDTO loreNodeDTO) {
|
||||
LoreNode changes = loreNodeMapper.toDomain(loreNodeDTO);
|
||||
LoreNode updated = loreNodeService.updateLoreNode(id, changes);
|
||||
return ResponseEntity.ok(loreNodeMapper.toDTO(updated));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Void> deleteLoreNode(@PathVariable String id) {
|
||||
loreNodeService.deleteLoreNode(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.loremind.infrastructure.web.controller;
|
||||
|
||||
import com.loremind.application.lorecontext.PageService;
|
||||
import com.loremind.domain.lorecontext.Page;
|
||||
import com.loremind.infrastructure.web.dto.lorecontext.PageDTO;
|
||||
import com.loremind.infrastructure.web.mapper.PageMapper;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* REST Controller pour le contexte Page.
|
||||
* - GET /api/pages?loreId=X → toutes les pages d'un lore
|
||||
* - GET /api/pages?nodeId=Y → toutes les pages d'un noeud
|
||||
* - GET /api/pages/{id} → détail
|
||||
* - POST /api/pages → création (loreId, nodeId, templateId, title)
|
||||
* - PUT /api/pages/{id} → mise à jour complète (title, nodeId, values, notes, tags, relatedPageIds)
|
||||
* - DELETE /api/pages/{id} → suppression
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/pages")
|
||||
public class PageController {
|
||||
|
||||
private final PageService pageService;
|
||||
private final PageMapper pageMapper;
|
||||
|
||||
public PageController(PageService pageService, PageMapper pageMapper) {
|
||||
this.pageService = pageService;
|
||||
this.pageMapper = pageMapper;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<PageDTO> createPage(@RequestBody PageDTO dto) {
|
||||
Page created = pageService.createPage(
|
||||
dto.getLoreId(),
|
||||
dto.getNodeId(),
|
||||
dto.getTemplateId(),
|
||||
dto.getTitle()
|
||||
);
|
||||
return ResponseEntity.ok(pageMapper.toDTO(created));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<PageDTO> getPageById(@PathVariable String id) {
|
||||
return pageService.getPageById(id)
|
||||
.map(page -> ResponseEntity.ok(pageMapper.toDTO(page)))
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<PageDTO>> getPages(
|
||||
@RequestParam(value = "loreId", required = false) String loreId,
|
||||
@RequestParam(value = "nodeId", required = false) String nodeId) {
|
||||
List<Page> pages;
|
||||
if (loreId != null && !loreId.isBlank()) {
|
||||
pages = pageService.getPagesByLoreId(loreId);
|
||||
} else if (nodeId != null && !nodeId.isBlank()) {
|
||||
pages = pageService.getPagesByNodeId(nodeId);
|
||||
} else {
|
||||
pages = pageService.getAllPages();
|
||||
}
|
||||
List<PageDTO> dtos = pages.stream().map(pageMapper::toDTO).collect(Collectors.toList());
|
||||
return ResponseEntity.ok(dtos);
|
||||
}
|
||||
|
||||
@GetMapping("/search")
|
||||
public ResponseEntity<List<PageDTO>> searchPages(@RequestParam("q") String query) {
|
||||
List<PageDTO> dtos = pageService.searchPages(query).stream()
|
||||
.map(pageMapper::toDTO)
|
||||
.collect(Collectors.toList());
|
||||
return ResponseEntity.ok(dtos);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<PageDTO> updatePage(@PathVariable String id,
|
||||
@RequestBody PageDTO dto) {
|
||||
Page changes = pageMapper.toDomain(dto);
|
||||
Page updated = pageService.updatePage(id, changes);
|
||||
return ResponseEntity.ok(pageMapper.toDTO(updated));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Void> deletePage(@PathVariable String id) {
|
||||
pageService.deletePage(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.loremind.infrastructure.web.controller;
|
||||
|
||||
import com.loremind.application.campaigncontext.SceneService;
|
||||
import com.loremind.domain.campaigncontext.Scene;
|
||||
import com.loremind.infrastructure.web.dto.campaigncontext.SceneDTO;
|
||||
import com.loremind.infrastructure.web.mapper.SceneMapper;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* REST Controller pour le contexte Scene.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/scenes")
|
||||
public class SceneController {
|
||||
|
||||
private final SceneService sceneService;
|
||||
private final SceneMapper sceneMapper;
|
||||
|
||||
public SceneController(SceneService sceneService, SceneMapper sceneMapper) {
|
||||
this.sceneService = sceneService;
|
||||
this.sceneMapper = sceneMapper;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<SceneDTO> createScene(@RequestBody SceneDTO sceneDTO) {
|
||||
Scene scene = sceneMapper.toDomain(sceneDTO);
|
||||
Scene createdScene = sceneService.createScene(scene.getName(), scene.getDescription(), scene.getChapterId(), scene.getOrder());
|
||||
return ResponseEntity.ok(sceneMapper.toDTO(createdScene));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<SceneDTO> getSceneById(@PathVariable String id) {
|
||||
return sceneService.getSceneById(id)
|
||||
.map(scene -> ResponseEntity.ok(sceneMapper.toDTO(scene)))
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<SceneDTO>> getAllScenes() {
|
||||
List<Scene> scenes = sceneService.getAllScenes();
|
||||
List<SceneDTO> sceneDTOs = scenes.stream()
|
||||
.map(sceneMapper::toDTO)
|
||||
.collect(Collectors.toList());
|
||||
return ResponseEntity.ok(sceneDTOs);
|
||||
}
|
||||
|
||||
@GetMapping("/chapter/{chapterId}")
|
||||
public ResponseEntity<List<SceneDTO>> getScenesByChapterId(@PathVariable String chapterId) {
|
||||
List<Scene> scenes = sceneService.getScenesByChapterId(chapterId);
|
||||
List<SceneDTO> sceneDTOs = scenes.stream()
|
||||
.map(sceneMapper::toDTO)
|
||||
.collect(Collectors.toList());
|
||||
return ResponseEntity.ok(sceneDTOs);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<SceneDTO> updateScene(@PathVariable String id, @RequestBody SceneDTO sceneDTO) {
|
||||
Scene updatedScene = sceneService.updateScene(id, sceneMapper.toDomain(sceneDTO));
|
||||
return ResponseEntity.ok(sceneMapper.toDTO(updatedScene));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Void> deleteScene(@PathVariable String id) {
|
||||
sceneService.deleteScene(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.loremind.infrastructure.web.controller;
|
||||
|
||||
import com.loremind.application.lorecontext.TemplateService;
|
||||
import com.loremind.domain.lorecontext.Template;
|
||||
import com.loremind.infrastructure.web.dto.lorecontext.TemplateDTO;
|
||||
import com.loremind.infrastructure.web.mapper.TemplateMapper;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* REST Controller pour le contexte Template.
|
||||
* Expose également un endpoint filtré par Lore pour alimenter le panneau
|
||||
* "Templates" du sidebar secondaire.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/templates")
|
||||
public class TemplateController {
|
||||
|
||||
private final TemplateService templateService;
|
||||
private final TemplateMapper templateMapper;
|
||||
|
||||
public TemplateController(TemplateService templateService, TemplateMapper templateMapper) {
|
||||
this.templateService = templateService;
|
||||
this.templateMapper = templateMapper;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<TemplateDTO> createTemplate(@RequestBody TemplateDTO dto) {
|
||||
Template created = templateService.createTemplate(
|
||||
dto.getLoreId(),
|
||||
dto.getName(),
|
||||
dto.getDescription(),
|
||||
dto.getDefaultNodeId(),
|
||||
dto.getFields()
|
||||
);
|
||||
return ResponseEntity.ok(templateMapper.toDTO(created));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<TemplateDTO> getTemplateById(@PathVariable String id) {
|
||||
return templateService.getTemplateById(id)
|
||||
.map(template -> ResponseEntity.ok(templateMapper.toDTO(template)))
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<TemplateDTO>> getAllTemplates(
|
||||
@RequestParam(value = "loreId", required = false) String loreId) {
|
||||
List<Template> templates = (loreId != null && !loreId.isBlank())
|
||||
? templateService.getTemplatesByLoreId(loreId)
|
||||
: templateService.getAllTemplates();
|
||||
List<TemplateDTO> dtos = templates.stream()
|
||||
.map(templateMapper::toDTO)
|
||||
.collect(Collectors.toList());
|
||||
return ResponseEntity.ok(dtos);
|
||||
}
|
||||
|
||||
@GetMapping("/search")
|
||||
public ResponseEntity<List<TemplateDTO>> searchTemplates(@RequestParam("q") String query) {
|
||||
List<TemplateDTO> dtos = templateService.searchTemplates(query).stream()
|
||||
.map(templateMapper::toDTO)
|
||||
.collect(Collectors.toList());
|
||||
return ResponseEntity.ok(dtos);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<TemplateDTO> updateTemplate(@PathVariable String id,
|
||||
@RequestBody TemplateDTO dto) {
|
||||
Template changes = templateMapper.toDomain(dto);
|
||||
Template updated = templateService.updateTemplate(id, changes);
|
||||
return ResponseEntity.ok(templateMapper.toDTO(updated));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Void> deleteTemplate(@PathVariable String id) {
|
||||
templateService.deleteTemplate(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.loremind.infrastructure.web.dto.campaigncontext;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* DTO pour l'entité Arc.
|
||||
* Objet de transfert de données pour l'API REST.
|
||||
*/
|
||||
@Data
|
||||
public class ArcDTO {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private String description;
|
||||
private String campaignId;
|
||||
private int order;
|
||||
|
||||
// Champs narratifs enrichis
|
||||
private String themes;
|
||||
private String stakes;
|
||||
private String gmNotes;
|
||||
private String rewards;
|
||||
private String resolution;
|
||||
|
||||
/** IDs des pages du Lore liées à cet arc (weak cross-context references). */
|
||||
private List<String> relatedPageIds = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.loremind.infrastructure.web.dto.campaigncontext;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* DTO pour l'entité Campaign.
|
||||
* Objet de transfert de données pour l'API REST.
|
||||
*/
|
||||
@Data
|
||||
public class CampaignDTO {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private String description;
|
||||
private int arcsCount;
|
||||
/** Nullable : campagne sans univers associé. */
|
||||
private String loreId;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.loremind.infrastructure.web.dto.campaigncontext;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* DTO pour l'entité Chapter.
|
||||
* Objet de transfert de données pour l'API REST.
|
||||
*/
|
||||
@Data
|
||||
public class ChapterDTO {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private String description;
|
||||
private String arcId;
|
||||
private int order;
|
||||
|
||||
// Champs narratifs enrichis
|
||||
private String gmNotes;
|
||||
private String playerObjectives;
|
||||
private String narrativeStakes;
|
||||
|
||||
/** IDs des pages du Lore liées (weak cross-context references). */
|
||||
private List<String> relatedPageIds = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.loremind.infrastructure.web.dto.campaigncontext;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* DTO pour l'entité Scene.
|
||||
* Objet de transfert de données pour l'API REST.
|
||||
*/
|
||||
@Data
|
||||
public class SceneDTO {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private String description;
|
||||
private String chapterId;
|
||||
private int order;
|
||||
|
||||
// Champs narratifs enrichis
|
||||
private String location;
|
||||
private String timing;
|
||||
private String atmosphere;
|
||||
private String playerNarration;
|
||||
private String gmSecretNotes;
|
||||
private String choicesConsequences;
|
||||
private String combatDifficulty;
|
||||
private String enemies;
|
||||
|
||||
/** IDs des pages du Lore liées (weak cross-context references). */
|
||||
private List<String> relatedPageIds = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.loremind.infrastructure.web.dto.lorecontext;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* DTO pour l'entité Lore.
|
||||
* Objet de transfert de données pour l'API REST.
|
||||
* Contient uniquement les champs nécessaires pour le Frontend.
|
||||
*/
|
||||
@Data
|
||||
public class LoreDTO {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private String description;
|
||||
private int nodeCount;
|
||||
private int pageCount;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.loremind.infrastructure.web.dto.lorecontext;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* DTO pour l'entité LoreNode.
|
||||
* Objet de transfert de données pour l'API REST.
|
||||
*/
|
||||
@Data
|
||||
public class LoreNodeDTO {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private String icon;
|
||||
private String parentId;
|
||||
private String loreId;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.loremind.infrastructure.web.dto.lorecontext;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* DTO pour l'entité Page.
|
||||
* Objet de transfert de données pour l'API REST.
|
||||
*/
|
||||
@Data
|
||||
public class PageDTO {
|
||||
|
||||
private String id;
|
||||
private String loreId;
|
||||
private String nodeId;
|
||||
private String templateId;
|
||||
private String title;
|
||||
private Map<String, String> values;
|
||||
private String notes;
|
||||
private List<String> tags;
|
||||
private List<String> relatedPageIds;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.loremind.infrastructure.web.dto.lorecontext;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* DTO pour l'entité Template.
|
||||
* Objet de transfert de données pour l'API REST.
|
||||
* Expose un compteur fieldCount pour éviter aux clients de recalculer fields.size().
|
||||
*/
|
||||
@Data
|
||||
public class TemplateDTO {
|
||||
|
||||
private String id;
|
||||
private String loreId;
|
||||
private String name;
|
||||
private String description;
|
||||
private String defaultNodeId;
|
||||
private List<String> fields;
|
||||
private int fieldCount;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.loremind.infrastructure.web.mapper;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Arc;
|
||||
import com.loremind.infrastructure.web.dto.campaigncontext.ArcDTO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Mapper pour convertir entre Arc (entité de domaine) et ArcDTO.
|
||||
*/
|
||||
@Component
|
||||
public class ArcMapper {
|
||||
|
||||
public ArcDTO toDTO(Arc arc) {
|
||||
if (arc == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ArcDTO dto = new ArcDTO();
|
||||
dto.setId(arc.getId());
|
||||
dto.setName(arc.getName());
|
||||
dto.setDescription(arc.getDescription());
|
||||
dto.setCampaignId(arc.getCampaignId());
|
||||
dto.setOrder(arc.getOrder());
|
||||
dto.setThemes(arc.getThemes());
|
||||
dto.setStakes(arc.getStakes());
|
||||
dto.setGmNotes(arc.getGmNotes());
|
||||
dto.setRewards(arc.getRewards());
|
||||
dto.setResolution(arc.getResolution());
|
||||
dto.setRelatedPageIds(arc.getRelatedPageIds() != null
|
||||
? new ArrayList<>(arc.getRelatedPageIds())
|
||||
: new ArrayList<>());
|
||||
return dto;
|
||||
}
|
||||
|
||||
public Arc toDomain(ArcDTO dto) {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Arc.builder()
|
||||
.id(dto.getId())
|
||||
.name(dto.getName())
|
||||
.description(dto.getDescription())
|
||||
.campaignId(dto.getCampaignId())
|
||||
.order(dto.getOrder())
|
||||
.themes(dto.getThemes())
|
||||
.stakes(dto.getStakes())
|
||||
.gmNotes(dto.getGmNotes())
|
||||
.rewards(dto.getRewards())
|
||||
.resolution(dto.getResolution())
|
||||
.relatedPageIds(dto.getRelatedPageIds() != null
|
||||
? new ArrayList<>(dto.getRelatedPageIds())
|
||||
: new ArrayList<>())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.loremind.infrastructure.web.mapper;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Campaign;
|
||||
import com.loremind.infrastructure.web.dto.campaigncontext.CampaignDTO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Mapper pour convertir entre Campaign (entité de domaine) et CampaignDTO.
|
||||
*/
|
||||
@Component
|
||||
public class CampaignMapper {
|
||||
|
||||
public CampaignDTO toDTO(Campaign campaign) {
|
||||
if (campaign == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CampaignDTO dto = new CampaignDTO();
|
||||
dto.setId(campaign.getId());
|
||||
dto.setName(campaign.getName());
|
||||
dto.setDescription(campaign.getDescription());
|
||||
dto.setArcsCount(campaign.getArcsCount());
|
||||
dto.setLoreId(campaign.getLoreId());
|
||||
return dto;
|
||||
}
|
||||
|
||||
public Campaign toDomain(CampaignDTO dto) {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Campaign.builder()
|
||||
.id(dto.getId())
|
||||
.name(dto.getName())
|
||||
.description(dto.getDescription())
|
||||
.arcsCount(dto.getArcsCount())
|
||||
.loreId(dto.getLoreId())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.loremind.infrastructure.web.mapper;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Chapter;
|
||||
import com.loremind.infrastructure.web.dto.campaigncontext.ChapterDTO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Mapper pour convertir entre Chapter (entité de domaine) et ChapterDTO.
|
||||
*/
|
||||
@Component
|
||||
public class ChapterMapper {
|
||||
|
||||
public ChapterDTO toDTO(Chapter chapter) {
|
||||
if (chapter == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ChapterDTO dto = new ChapterDTO();
|
||||
dto.setId(chapter.getId());
|
||||
dto.setName(chapter.getName());
|
||||
dto.setDescription(chapter.getDescription());
|
||||
dto.setArcId(chapter.getArcId());
|
||||
dto.setOrder(chapter.getOrder());
|
||||
dto.setGmNotes(chapter.getGmNotes());
|
||||
dto.setPlayerObjectives(chapter.getPlayerObjectives());
|
||||
dto.setNarrativeStakes(chapter.getNarrativeStakes());
|
||||
dto.setRelatedPageIds(chapter.getRelatedPageIds() != null
|
||||
? new ArrayList<>(chapter.getRelatedPageIds())
|
||||
: new ArrayList<>());
|
||||
return dto;
|
||||
}
|
||||
|
||||
public Chapter toDomain(ChapterDTO dto) {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Chapter.builder()
|
||||
.id(dto.getId())
|
||||
.name(dto.getName())
|
||||
.description(dto.getDescription())
|
||||
.arcId(dto.getArcId())
|
||||
.order(dto.getOrder())
|
||||
.gmNotes(dto.getGmNotes())
|
||||
.playerObjectives(dto.getPlayerObjectives())
|
||||
.narrativeStakes(dto.getNarrativeStakes())
|
||||
.relatedPageIds(dto.getRelatedPageIds() != null
|
||||
? new ArrayList<>(dto.getRelatedPageIds())
|
||||
: new ArrayList<>())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.loremind.infrastructure.web.mapper;
|
||||
|
||||
import com.loremind.domain.lorecontext.Lore;
|
||||
import com.loremind.infrastructure.web.dto.lorecontext.LoreDTO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Mapper pour convertir entre Lore (entité de domaine) et LoreDTO.
|
||||
* Fait partie de la couche Infrastructure de l'Architecture Hexagonale.
|
||||
*/
|
||||
@Component
|
||||
public class LoreMapper {
|
||||
|
||||
public LoreDTO toDTO(Lore lore) {
|
||||
if (lore == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
LoreDTO dto = new LoreDTO();
|
||||
dto.setId(lore.getId());
|
||||
dto.setName(lore.getName());
|
||||
dto.setDescription(lore.getDescription());
|
||||
dto.setNodeCount(lore.getNodeCount());
|
||||
dto.setPageCount(lore.getPageCount());
|
||||
return dto;
|
||||
}
|
||||
|
||||
public Lore toDomain(LoreDTO dto) {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Lore.builder()
|
||||
.id(dto.getId())
|
||||
.name(dto.getName())
|
||||
.description(dto.getDescription())
|
||||
.nodeCount(dto.getNodeCount())
|
||||
.pageCount(dto.getPageCount())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.loremind.infrastructure.web.mapper;
|
||||
|
||||
import com.loremind.domain.lorecontext.LoreNode;
|
||||
import com.loremind.infrastructure.web.dto.lorecontext.LoreNodeDTO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Mapper pour convertir entre LoreNode (entité de domaine) et LoreNodeDTO.
|
||||
*/
|
||||
@Component
|
||||
public class LoreNodeMapper {
|
||||
|
||||
public LoreNodeDTO toDTO(LoreNode loreNode) {
|
||||
if (loreNode == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
LoreNodeDTO dto = new LoreNodeDTO();
|
||||
dto.setId(loreNode.getId());
|
||||
dto.setName(loreNode.getName());
|
||||
dto.setIcon(loreNode.getIcon());
|
||||
dto.setParentId(loreNode.getParentId());
|
||||
dto.setLoreId(loreNode.getLoreId());
|
||||
return dto;
|
||||
}
|
||||
|
||||
public LoreNode toDomain(LoreNodeDTO dto) {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return LoreNode.builder()
|
||||
.id(dto.getId())
|
||||
.name(dto.getName())
|
||||
.icon(dto.getIcon())
|
||||
.parentId(dto.getParentId())
|
||||
.loreId(dto.getLoreId())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.loremind.infrastructure.web.mapper;
|
||||
|
||||
import com.loremind.domain.lorecontext.Page;
|
||||
import com.loremind.infrastructure.web.dto.lorecontext.PageDTO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Mapper pour convertir entre Page (entité de domaine) et PageDTO.
|
||||
*/
|
||||
@Component
|
||||
public class PageMapper {
|
||||
|
||||
public PageDTO toDTO(Page page) {
|
||||
if (page == null) {
|
||||
return null;
|
||||
}
|
||||
PageDTO dto = new PageDTO();
|
||||
dto.setId(page.getId());
|
||||
dto.setLoreId(page.getLoreId());
|
||||
dto.setNodeId(page.getNodeId());
|
||||
dto.setTemplateId(page.getTemplateId());
|
||||
dto.setTitle(page.getTitle());
|
||||
dto.setValues(page.getValues() != null ? new HashMap<>(page.getValues()) : new HashMap<>());
|
||||
dto.setNotes(page.getNotes());
|
||||
dto.setTags(page.getTags() != null ? new ArrayList<>(page.getTags()) : new ArrayList<>());
|
||||
dto.setRelatedPageIds(page.getRelatedPageIds() != null ? new ArrayList<>(page.getRelatedPageIds()) : new ArrayList<>());
|
||||
return dto;
|
||||
}
|
||||
|
||||
public Page toDomain(PageDTO dto) {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
return Page.builder()
|
||||
.id(dto.getId())
|
||||
.loreId(dto.getLoreId())
|
||||
.nodeId(dto.getNodeId())
|
||||
.templateId(dto.getTemplateId())
|
||||
.title(dto.getTitle())
|
||||
.values(dto.getValues() != null ? new HashMap<>(dto.getValues()) : new HashMap<>())
|
||||
.notes(dto.getNotes())
|
||||
.tags(dto.getTags() != null ? new ArrayList<>(dto.getTags()) : new ArrayList<>())
|
||||
.relatedPageIds(dto.getRelatedPageIds() != null ? new ArrayList<>(dto.getRelatedPageIds()) : new ArrayList<>())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.loremind.infrastructure.web.mapper;
|
||||
|
||||
import com.loremind.domain.campaigncontext.Scene;
|
||||
import com.loremind.infrastructure.web.dto.campaigncontext.SceneDTO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Mapper pour convertir entre Scene (entité de domaine) et SceneDTO.
|
||||
*/
|
||||
@Component
|
||||
public class SceneMapper {
|
||||
|
||||
public SceneDTO toDTO(Scene scene) {
|
||||
if (scene == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SceneDTO dto = new SceneDTO();
|
||||
dto.setId(scene.getId());
|
||||
dto.setName(scene.getName());
|
||||
dto.setDescription(scene.getDescription());
|
||||
dto.setChapterId(scene.getChapterId());
|
||||
dto.setOrder(scene.getOrder());
|
||||
dto.setLocation(scene.getLocation());
|
||||
dto.setTiming(scene.getTiming());
|
||||
dto.setAtmosphere(scene.getAtmosphere());
|
||||
dto.setPlayerNarration(scene.getPlayerNarration());
|
||||
dto.setGmSecretNotes(scene.getGmSecretNotes());
|
||||
dto.setChoicesConsequences(scene.getChoicesConsequences());
|
||||
dto.setCombatDifficulty(scene.getCombatDifficulty());
|
||||
dto.setEnemies(scene.getEnemies());
|
||||
dto.setRelatedPageIds(scene.getRelatedPageIds() != null
|
||||
? new ArrayList<>(scene.getRelatedPageIds())
|
||||
: new ArrayList<>());
|
||||
return dto;
|
||||
}
|
||||
|
||||
public Scene toDomain(SceneDTO dto) {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Scene.builder()
|
||||
.id(dto.getId())
|
||||
.name(dto.getName())
|
||||
.description(dto.getDescription())
|
||||
.chapterId(dto.getChapterId())
|
||||
.order(dto.getOrder())
|
||||
.location(dto.getLocation())
|
||||
.timing(dto.getTiming())
|
||||
.atmosphere(dto.getAtmosphere())
|
||||
.playerNarration(dto.getPlayerNarration())
|
||||
.gmSecretNotes(dto.getGmSecretNotes())
|
||||
.choicesConsequences(dto.getChoicesConsequences())
|
||||
.combatDifficulty(dto.getCombatDifficulty())
|
||||
.enemies(dto.getEnemies())
|
||||
.relatedPageIds(dto.getRelatedPageIds() != null
|
||||
? new ArrayList<>(dto.getRelatedPageIds())
|
||||
: new ArrayList<>())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.loremind.infrastructure.web.mapper;
|
||||
|
||||
import com.loremind.domain.lorecontext.Template;
|
||||
import com.loremind.infrastructure.web.dto.lorecontext.TemplateDTO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Mapper pour convertir entre Template (entité de domaine) et TemplateDTO.
|
||||
*/
|
||||
@Component
|
||||
public class TemplateMapper {
|
||||
|
||||
public TemplateDTO toDTO(Template template) {
|
||||
if (template == null) {
|
||||
return null;
|
||||
}
|
||||
TemplateDTO dto = new TemplateDTO();
|
||||
dto.setId(template.getId());
|
||||
dto.setLoreId(template.getLoreId());
|
||||
dto.setName(template.getName());
|
||||
dto.setDescription(template.getDescription());
|
||||
dto.setDefaultNodeId(template.getDefaultNodeId());
|
||||
dto.setFields(template.getFields() != null
|
||||
? new ArrayList<>(template.getFields())
|
||||
: new ArrayList<>());
|
||||
dto.setFieldCount(template.fieldCount());
|
||||
return dto;
|
||||
}
|
||||
|
||||
public Template toDomain(TemplateDTO dto) {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
return Template.builder()
|
||||
.id(dto.getId())
|
||||
.loreId(dto.getLoreId())
|
||||
.name(dto.getName())
|
||||
.description(dto.getDescription())
|
||||
.defaultNodeId(dto.getDefaultNodeId())
|
||||
.fields(dto.getFields() != null
|
||||
? new ArrayList<>(dto.getFields())
|
||||
: new ArrayList<>())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
20
core/src/main/resources/application.properties
Normal file
20
core/src/main/resources/application.properties
Normal file
@@ -0,0 +1,20 @@
|
||||
# Configuration du serveur
|
||||
server.port=8080
|
||||
|
||||
# Configuration de la base de données PostgreSQL
|
||||
spring.datasource.url=jdbc:postgresql://localhost:5432/loremind
|
||||
spring.datasource.username=ietm64
|
||||
spring.datasource.password=REDACTED
|
||||
spring.datasource.driver-class-name=org.postgresql.Driver
|
||||
|
||||
# Configuration JPA / Hibernate
|
||||
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.jpa.show-sql=true
|
||||
spring.jpa.properties.hibernate.format_sql=true
|
||||
|
||||
# Configuration CORS pour autoriser le Frontend Angular
|
||||
spring.web.cors.allowed-origins=http://localhost:4200
|
||||
spring.web.cors.allowed-methods=GET,POST,PUT,DELETE,OPTIONS
|
||||
spring.web.cors.allowed-headers=*
|
||||
spring.web.cors.allow-credentials=true
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.loremind.infrastructure.persistence.postgres;
|
||||
|
||||
import com.loremind.domain.lorecontext.Lore;
|
||||
import com.loremind.domain.lorecontext.ports.LoreRepository;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Test pour vérifier que PostgresLoreRepository fonctionne correctement.
|
||||
* Utilise PostgreSQL (loremind_test) pour les tests d'intégration.
|
||||
*/
|
||||
@SpringBootTest
|
||||
public class PostgresLoreRepositoryTest {
|
||||
|
||||
@Autowired
|
||||
private LoreRepository loreRepository;
|
||||
|
||||
@Test
|
||||
public void testSaveAndFindLore() {
|
||||
// Créer un Lore
|
||||
Lore lore = Lore.builder()
|
||||
.name("Lore Test")
|
||||
.description("Description test")
|
||||
.nodeCount(0)
|
||||
.pageCount(0)
|
||||
.createdAt(LocalDateTime.now())
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
// Sauvegarder
|
||||
Lore savedLore = loreRepository.save(lore);
|
||||
assertNotNull(savedLore.getId());
|
||||
|
||||
// Récupérer
|
||||
Optional<Lore> foundLore = loreRepository.findById(savedLore.getId());
|
||||
assertTrue(foundLore.isPresent());
|
||||
assertEquals("Lore Test", foundLore.get().getName());
|
||||
|
||||
// Nettoyer
|
||||
loreRepository.deleteById(savedLore.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindAllLores() {
|
||||
// Créer deux Lores
|
||||
Lore lore1 = Lore.builder()
|
||||
.name("Lore 1")
|
||||
.description("Description 1")
|
||||
.nodeCount(0)
|
||||
.pageCount(0)
|
||||
.createdAt(LocalDateTime.now())
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
Lore lore2 = Lore.builder()
|
||||
.name("Lore 2")
|
||||
.description("Description 2")
|
||||
.nodeCount(0)
|
||||
.pageCount(0)
|
||||
.createdAt(LocalDateTime.now())
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
Lore saved1 = loreRepository.save(lore1);
|
||||
Lore saved2 = loreRepository.save(lore2);
|
||||
|
||||
// Récupérer tous
|
||||
List<Lore> allLores = loreRepository.findAll();
|
||||
assertTrue(allLores.size() >= 2);
|
||||
|
||||
// Nettoyer
|
||||
loreRepository.deleteById(saved1.getId());
|
||||
loreRepository.deleteById(saved2.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteLore() {
|
||||
// Créer un Lore
|
||||
Lore lore = Lore.builder()
|
||||
.name("Lore to delete")
|
||||
.description("Description")
|
||||
.nodeCount(0)
|
||||
.pageCount(0)
|
||||
.createdAt(LocalDateTime.now())
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
Lore savedLore = loreRepository.save(lore);
|
||||
|
||||
// Supprimer
|
||||
loreRepository.deleteById(savedLore.getId());
|
||||
|
||||
// Vérifier qu'il n'existe plus
|
||||
assertFalse(loreRepository.existsById(savedLore.getId()));
|
||||
}
|
||||
}
|
||||
10
core/src/test/resources/application.properties
Normal file
10
core/src/test/resources/application.properties
Normal file
@@ -0,0 +1,10 @@
|
||||
# Configuration de test avec PostgreSQL
|
||||
spring.datasource.url=jdbc:postgresql://localhost:5432/loremind_test
|
||||
spring.datasource.username=ietm64
|
||||
spring.datasource.password=REDACTED
|
||||
spring.datasource.driver-class-name=org.postgresql.Driver
|
||||
|
||||
# Configuration JPA pour les tests
|
||||
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
|
||||
spring.jpa.hibernate.ddl-auto=create-drop
|
||||
spring.jpa.show-sql=true
|
||||
Reference in New Issue
Block a user