Initial commit - LoreMind project
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
@@ -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
@@ -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
@@ -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
@@ -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=M&Ipourlavie64
|
||||||
|
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
@@ -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=M&Ipourlavie64
|
||||||
|
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
|
||||||
BIN
docs/Documentation_projet.docx
Normal file
33
docs/loremind-contexte.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# LoreMind - Document de Contexte et d'Architecture
|
||||||
|
|
||||||
|
## 1. Vision du Produit
|
||||||
|
LoreMind est une application web d'aide aux Maîtres de Jeu (JDR). Elle permet de centraliser la gestion de l'univers (Lore) et le suivi des campagnes.
|
||||||
|
**Fonctionnalité clé :** Intégration d'un moteur IA capable de générer des pages de contenu complètes (PNJ, Villes, Quêtes) à partir de templates prédéfinis.
|
||||||
|
**Objectif final :** Permettre l'export structuré de ces données vers FoundryVTT.
|
||||||
|
|
||||||
|
## 2. Stack Technologique Globale
|
||||||
|
L'application suit une architecture distribuée pour séparer les responsabilités :
|
||||||
|
* **Frontend :** Angular (Interface utilisateur, affichage du lore, formulaires de templates).
|
||||||
|
* **Backend Core (Métier & Données) :** Java (Spring Boot recommandé). Orchestre l'application, gère la persistance et l'export VTT.
|
||||||
|
* **Backend IA (Cognitif) :** Python. Dédié au traitement des LLM et à la génération de contenu selon les gabarits.
|
||||||
|
* **Base de Données :** PostgreSQL. Stockage relationnel classique, avec utilisation intensive du type `JSONB` pour stocker les templates flexibles.
|
||||||
|
|
||||||
|
## 3. Architecture du Backend Java (Règles strictes)
|
||||||
|
Le développement du Backend Core doit IMPÉRATIVEMENT respecter le **Domain-Driven Design (DDD)** et l'**Architecture Hexagonale (Ports et Adaptateurs)**.
|
||||||
|
|
||||||
|
### 3.1. Contextes Délimités (Bounded Contexts)
|
||||||
|
Le code doit être séparé selon ces domaines autonomes :
|
||||||
|
* `LoreContext` : Gestion de l'encyclopédie de l'univers (Lieux, Factions, PNJ).
|
||||||
|
* `CampaignContext` : Suivi des sessions, actions des joueurs, chronologie.
|
||||||
|
* `GenerationContext` : Gestion des requêtes IA, traitement des templates et communication avec le Backend Python.
|
||||||
|
|
||||||
|
### 3.2. Règles d'Architecture Hexagonale
|
||||||
|
* **Domaine (Core) :** Contient les entités métier pures et les interfaces (Ports). Il ne doit avoir **aucune** dépendance technique (ni Spring, ni annotations JPA, ni base de données).
|
||||||
|
* **Application (Use Cases) :** Orchestre les flux de données (ex: `GenerateNpcUseCase`).
|
||||||
|
* **Infrastructure (Adapters) :** Implémente les interfaces du domaine (ex: `PostgresLoreRepository`, `PythonAiClient`, `RestControllers`).
|
||||||
|
|
||||||
|
## 4. Directives pour l'IA (Windsurf)
|
||||||
|
* Utilise toujours le vocabulaire du domaine (*Ubiquitous Language*) dans le nommage des classes et méthodes (ex: `LoreNode`, `Template`, `Campaign`).
|
||||||
|
* Assure-toi que les entités du domaine ne soient jamais anémiques (la logique métier est dans l'entité, pas dans des services externes).
|
||||||
|
* Pour la base de données PostgreSQL, utilise la spécification JSONB dès qu'une structure de donnée est liée à un template modulable.
|
||||||
|
* Commente uniquement la logique métier complexe, évite les commentaires évidents.
|
||||||
BIN
docs/maquettes/campagne/Accueil partie campagne.png
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
docs/maquettes/campagne/Assistance IA.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
docs/maquettes/campagne/Création d'un nouvel arc.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
docs/maquettes/campagne/Création de chapitre.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
docs/maquettes/campagne/Création de scène.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
docs/maquettes/campagne/Ecran de création de campagne.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
docs/maquettes/campagne/Modification d'arc.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
docs/maquettes/campagne/Modification d'un chapitre.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
docs/maquettes/campagne/Modification de scène.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
docs/maquettes/campagne/Sauvegarde de la campagne.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
docs/maquettes/campagne/détail/Modification arc détail 2.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
docs/maquettes/campagne/détail/Modification arc détail 3.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 99 KiB |