From 5c3c58265f9f985da007cdc243dada998e5b96d0 Mon Sep 17 00:00:00 2001 From: "IETM_FIXE\\ietm6" Date: Mon, 6 Jul 2026 14:39:35 +0200 Subject: [PATCH] =?UTF-8?q?Premi=C3=A8re=20fourn=C3=A9e=20correction=20det?= =?UTF-8?q?te=20technique.=20Refactoring=20du=20code=20pour=20am=C3=A9lior?= =?UTF-8?q?er=20la=20lisibilit=C3=A9=20de=20ce=20dernier=20cot=C3=A9=20cor?= =?UTF-8?q?e=20Correction=20sur=20l'export=20/=20import=20:=20les=20images?= =?UTF-8?q?=20n'=C3=A9taient=20pas=20bien=20li=C3=A9es=20=C3=A0=20la=20cam?= =?UTF-8?q?pagne=20lors=20de=20l'export=20/=20import=20et=20provoquait=20u?= =?UTF-8?q?n=20bug=20d'affichage=20des=20images=20lorsqu'on=20voulait=20ou?= =?UTF-8?q?vrir=20une=20campagne=20import=C3=A9e.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../campaigncontext/ArcService.java | 12 +- .../campaigncontext/CampaignBriefBuilder.java | 62 ++-- .../CampaignContextFormatter.java | 40 +++ .../CampaignImportService.java | 152 ++++++---- .../CampaignReadinessAssessment.java | 2 +- .../CampaignReadinessService.java | 268 ++++++++++-------- .../CampaignReferencedFlagsService.java | 4 +- .../campaigncontext/CampaignService.java | 4 +- .../campaigncontext/ChapterService.java | 6 +- .../campaigncontext/CharacterService.java | 26 +- .../campaigncontext/EnemyService.java | 136 +++++---- .../campaigncontext/ItemCatalogService.java | 35 +-- .../campaigncontext/NotebookService.java | 6 +- .../campaigncontext/NpcService.java | 10 +- .../campaigncontext/QuestService.java | 34 +-- .../campaigncontext/QuestStatusEnricher.java | 8 +- .../campaigncontext/RandomTableService.java | 42 +-- .../campaigncontext/ReadinessGap.java | 4 +- .../campaigncontext/SceneService.java | 12 +- .../application/campaigncontext/sedFH3XAk | 45 +++ .../application/files/StoredFileService.java | 9 +- .../GameSystemContextBuilder.java | 9 +- .../gamesystemcontext/GameSystemService.java | 28 +- .../CampaignStructuralContextBuilder.java | 73 ++--- .../generationcontext/DraftScenesUseCase.java | 43 ++- .../GeneratePageValuesUseCase.java | 11 +- .../LoreStructuralContextBuilder.java | 6 +- .../NarrativeAssistFieldsUseCase.java | 54 ++-- .../NarrativeEntityContextBuilder.java | 21 +- .../NarrativeFieldCatalog.java | 29 +- .../SessionStructuralContextBuilder.java | 17 +- .../StreamChatForCampaignUseCase.java | 10 +- .../StreamChatForLoreUseCase.java | 10 +- .../StreamChatForSessionUseCase.java | 10 +- .../application/images/ImageService.java | 3 +- .../application/licensing/LicenseService.java | 36 ++- .../lorecontext/LoreNodeService.java | 2 +- .../application/lorecontext/LoreService.java | 3 +- .../application/lorecontext/PageService.java | 12 +- .../playcontext/SessionEntryService.java | 3 +- .../playcontext/SessionPrepService.java | 19 +- .../playcontext/SessionService.java | 14 +- .../{ => bestiary}/Character.java | 2 +- .../campaigncontext/{ => bestiary}/Enemy.java | 2 +- .../campaigncontext/{ => bestiary}/Npc.java | 2 +- .../CampaignImportProgress.java | 2 +- .../CampaignImportProposal.java | 2 +- .../EntityFieldPatchProposal.java | 2 +- .../{ => generation}/FieldProposal.java | 2 +- .../{ => generation}/SceneDraft.java | 2 +- .../{ => generation}/SceneDraftProposal.java | 2 +- .../{ => itemcatalog}/CatalogItem.java | 2 +- .../{ => itemcatalog}/ItemCatalog.java | 2 +- .../{ => notebook}/Notebook.java | 2 +- .../{ => notebook}/NotebookMessage.java | 2 +- .../{ => notebook}/NotebookSource.java | 2 +- .../campaigncontext/ports/ArcRepository.java | 2 +- .../ports/CampaignPdfImporter.java | 4 +- .../ports/ChapterRepository.java | 2 +- .../ports/CharacterRepository.java | 2 +- .../ports/EnemyRepository.java | 2 +- .../ports/ItemCatalogGenerator.java | 2 +- .../ports/ItemCatalogRepository.java | 2 +- .../ports/NotebookChatStreamer.java | 27 +- .../ports/NotebookRepository.java | 6 +- .../campaigncontext/ports/NpcRepository.java | 2 +- .../ports/QuestRepository.java | 2 +- .../ports/RandomTableGenerator.java | 2 +- .../ports/RandomTableRepository.java | 2 +- .../ports/SceneDraftAssistant.java | 2 +- .../ports/SceneRepository.java | 2 +- .../CampaignImportException.java | 2 +- .../ItemCatalogGenerationException.java | 2 +- .../NarrativeAssistException.java | 2 +- .../{ => exceptions}/NotebookException.java | 2 +- .../RandomTableGenerationException.java | 2 +- .../campaigncontext/{ => quest}/NodeType.java | 2 +- .../{ => quest}/Prerequisite.java | 2 +- .../{ => quest}/PrerequisiteEvaluator.java | 2 +- .../{ => quest}/ProgressionStatus.java | 2 +- .../campaigncontext/{ => quest}/Quest.java | 2 +- .../{ => quest}/QuestNodeRef.java | 2 +- .../{ => quest}/QuestStatus.java | 2 +- .../{ => randomtable}/RandomTable.java | 2 +- .../{ => randomtable}/RandomTableEntry.java | 2 +- .../{ => readiness}/ReadinessEntityType.java | 2 +- .../{ => readiness}/ReadinessSeverity.java | 2 +- .../{ => readiness}/ReadinessStatus.java | 2 +- .../campaigncontext/{ => structure}/Arc.java | 2 +- .../{ => structure}/ArcType.java | 3 +- .../{ => structure}/Chapter.java | 2 +- .../{ => structure}/LinkType.java | 2 +- .../campaigncontext/{ => structure}/Room.java | 2 +- .../{ => structure}/RoomBranch.java | 2 +- .../{ => structure}/Scene.java | 2 +- .../{ => structure}/SceneBattlemap.java | 2 +- .../{ => structure}/SceneBranch.java | 2 +- .../{ => structure}/SceneType.java | 2 +- .../RulesImportException.java | 2 +- .../ChatStreamCallbacks.java | 25 ++ .../ports/AiChatProvider.java | 24 +- .../generationcontext/ports/AiProvider.java | 1 + .../{ => exceptions}/AiProviderException.java | 2 +- .../com/loremind/domain/lorecontext/Page.java | 17 ++ .../domain/playcontext/QuestProgression.java | 2 +- .../ports/QuestProgressionRepository.java | 2 +- .../SessionRecapException.java | 2 +- .../domain/shared/ReorderSupport.java | 13 +- .../infrastructure/ai/BrainAiChatClient.java | 30 +- .../infrastructure/ai/BrainAiClient.java | 2 +- .../ai/BrainCampaignImportClient.java | 16 +- .../ai/BrainItemCatalogClient.java | 4 +- .../ai/BrainNarrativeFieldClient.java | 2 +- .../ai/BrainNotebookChatClient.java | 36 +-- .../ai/BrainNotebookIndexClient.java | 2 +- .../ai/BrainRandomTableClient.java | 4 +- .../ai/BrainRulesImportClient.java | 2 +- .../ai/BrainSceneDraftClient.java | 4 +- .../ai/BrainSessionRecapClient.java | 2 +- .../PrerequisiteListJsonConverter.java | 2 +- .../converter/QuestNodeListJsonConverter.java | 4 +- .../converter/RoomListJsonConverter.java | 4 +- .../SceneBattlemapListJsonConverter.java | 2 +- .../SceneBranchListJsonConverter.java | 2 +- .../persistence/entity/ArcJpaEntity.java | 2 +- .../persistence/entity/QuestJpaEntity.java | 4 +- .../entity/QuestProgressionJpaEntity.java | 2 +- .../persistence/entity/SceneJpaEntity.java | 8 +- .../postgres/PostgresArcRepository.java | 2 +- .../postgres/PostgresChapterRepository.java | 2 +- .../postgres/PostgresCharacterRepository.java | 2 +- .../postgres/PostgresEnemyRepository.java | 2 +- .../PostgresItemCatalogRepository.java | 4 +- .../postgres/PostgresNotebookRepository.java | 6 +- .../postgres/PostgresNpcRepository.java | 2 +- .../PostgresQuestProgressionRepository.java | 2 +- .../postgres/PostgresQuestRepository.java | 2 +- .../PostgresRandomTableRepository.java | 4 +- .../postgres/PostgresSceneRepository.java | 4 +- .../transfer/CampaignContentInserter.java | 34 +-- .../transfer/ExportService.java | 6 +- .../infrastructure/transfer/IdRemapper.java | 84 +++++- .../transfer/ImageImporter.java | 62 ++-- .../infrastructure/transfer/ImportIdMaps.java | 7 + .../transfer/ImportService.java | 14 +- .../transfer/LegacyQuestConverter.java | 6 +- .../transfer/LoreContentInserter.java | 4 +- .../transfer/PlayStateInserter.java | 8 +- .../transfer/StoredFileImporter.java | 52 ++-- .../transfer/dto/ContentExport.java | 8 +- .../foundry/FoundryExportService.java | 8 +- .../transfer/pdf/PdfExportService.java | 16 +- .../web/controller/AiChatController.java | 30 +- .../web/controller/ArcController.java | 2 +- .../controller/CampaignImportController.java | 2 +- .../controller/CampaignTreeController.java | 6 +- .../web/controller/ChapterController.java | 2 +- .../web/controller/CharacterController.java | 2 +- .../web/controller/EnemyController.java | 2 +- .../web/controller/GameSystemController.java | 2 +- .../web/controller/ItemCatalogController.java | 4 +- .../controller/NarrativeAssistController.java | 4 +- .../web/controller/NotebookController.java | 11 +- .../web/controller/NpcController.java | 2 +- .../controller/PageGenerationController.java | 2 +- .../web/controller/QuestController.java | 2 +- .../QuestProgressionController.java | 2 +- .../web/controller/RandomTableController.java | 4 +- .../web/controller/SceneController.java | 2 +- .../web/controller/SceneDraftController.java | 6 +- .../web/controller/SessionController.java | 2 +- .../infrastructure/web/mapper/ArcMapper.java | 4 +- .../web/mapper/ChapterMapper.java | 2 +- .../web/mapper/CharacterMapper.java | 2 +- .../web/mapper/ItemCatalogMapper.java | 4 +- .../infrastructure/web/mapper/NpcMapper.java | 2 +- .../web/mapper/PrerequisiteMapper.java | 2 +- .../web/mapper/QuestMapper.java | 6 +- .../web/mapper/RandomTableMapper.java | 4 +- .../web/mapper/SceneMapper.java | 14 +- .../campaigncontext/ArcServiceTest.java | 8 +- .../CampaignContextFormatterTest.java | 87 ++++++ .../CampaignReadinessServiceTest.java | 28 +- .../CampaignReferencedFlagsServiceTest.java | 4 +- .../campaigncontext/CampaignServiceTest.java | 6 +- .../campaigncontext/ChapterServiceTest.java | 6 +- .../campaigncontext/CharacterServiceTest.java | 2 +- .../ItemCatalogServiceTest.java | 35 +-- .../campaigncontext/NotebookServiceTest.java | 6 +- .../campaigncontext/NpcServiceTest.java | 2 +- .../campaigncontext/QuestServiceTest.java | 14 +- .../QuestStatusEnricherTest.java | 8 +- .../RandomTableServiceTest.java | 37 +-- .../campaigncontext/SceneServiceTest.java | 8 +- .../GameSystemContextBuilderTest.java | 74 +++++ .../CampaignStructuralContextBuilderTest.java | 12 +- .../NarrativeEntityContextBuilderTest.java | 10 +- .../StreamChatForCampaignUseCaseTest.java | 25 +- .../StreamChatForLoreUseCaseTest.java | 25 +- .../playcontext/SessionPrepServiceTest.java | 18 +- .../{ => structure}/ArcTest.java | 2 +- .../{ => structure}/ChapterTest.java | 2 +- .../{ => structure}/SceneBranchTest.java | 2 +- .../{ => structure}/SceneTest.java | 2 +- .../domain/shared/ReorderSupportTest.java | 68 +++++ .../ai/BrainAiChatClientTest.java | 18 +- .../infrastructure/ai/BrainAiClientTest.java | 2 +- .../ai/BrainCampaignImportClientTest.java | 6 +- .../ai/BrainItemCatalogClientTest.java | 4 +- .../ai/BrainNotebookChatClientTest.java | 20 +- .../ai/BrainNotebookIndexClientTest.java | 2 +- .../ai/BrainRandomTableClientTest.java | 4 +- .../ai/BrainRulesImportClientTest.java | 2 +- .../SceneBranchListJsonConverterTest.java | 2 +- .../postgres/PostgresArcRepositoryTest.java | 2 +- .../PostgresChapterRepositoryTest.java | 4 +- .../postgres/PostgresSceneRepositoryTest.java | 10 +- .../ContentExportImportRoundTripTest.java | 14 +- .../transfer/ContentExportV1ImportTest.java | 4 +- .../transfer/IdRemapperTest.java | 6 +- .../transfer/ImageRefRemapOnImportTest.java | 186 ++++++++++++ .../transfer/SelectiveExportTest.java | 4 +- .../foundry/FoundryExportServiceTest.java | 2 +- .../transfer/pdf/PdfExportPreviewTest.java | 18 +- .../pdf/PdfExportServiceRichTest.java | 8 +- .../web/controller/AiChatControllerTest.java | 50 ++-- .../web/controller/ArcControllerTest.java | 2 +- .../CampaignFlagControllerTest.java | 4 +- .../CampaignImportControllerTest.java | 4 +- .../CampaignImportControllerUnitTest.java | 4 +- .../CampaignTreeControllerTest.java | 10 +- .../web/controller/ChapterControllerTest.java | 4 +- .../controller/CharacterControllerTest.java | 2 +- .../web/controller/EnemyControllerTest.java | 2 +- .../controller/GameSystemControllerTest.java | 2 +- .../controller/ItemCatalogControllerTest.java | 6 +- .../controller/NotebookControllerTest.java | 39 ++- .../web/controller/NpcControllerTest.java | 2 +- .../PageGenerationControllerTest.java | 2 +- .../web/controller/QuestControllerTest.java | 2 +- .../QuestProgressionControllerTest.java | 6 +- .../controller/RandomTableControllerTest.java | 6 +- .../web/controller/SceneControllerTest.java | 6 +- 243 files changed, 1877 insertions(+), 1253 deletions(-) create mode 100644 core/src/main/java/com/loremind/application/campaigncontext/CampaignContextFormatter.java create mode 100644 core/src/main/java/com/loremind/application/campaigncontext/sedFH3XAk rename core/src/main/java/com/loremind/domain/campaigncontext/{ => bestiary}/Character.java (98%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => bestiary}/Enemy.java (98%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => bestiary}/Npc.java (98%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => generation}/CampaignImportProgress.java (90%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => generation}/CampaignImportProposal.java (97%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => generation}/EntityFieldPatchProposal.java (94%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => generation}/FieldProposal.java (93%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => generation}/SceneDraft.java (90%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => generation}/SceneDraftProposal.java (90%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => itemcatalog}/CatalogItem.java (90%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => itemcatalog}/ItemCatalog.java (95%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => notebook}/Notebook.java (91%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => notebook}/NotebookMessage.java (91%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => notebook}/NotebookSource.java (91%) rename core/src/main/java/com/loremind/domain/campaigncontext/ports/{ => exceptions}/CampaignImportException.java (86%) rename core/src/main/java/com/loremind/domain/campaigncontext/ports/{ => exceptions}/ItemCatalogGenerationException.java (87%) rename core/src/main/java/com/loremind/domain/campaigncontext/ports/{ => exceptions}/NarrativeAssistException.java (87%) rename core/src/main/java/com/loremind/domain/campaigncontext/ports/{ => exceptions}/NotebookException.java (85%) rename core/src/main/java/com/loremind/domain/campaigncontext/ports/{ => exceptions}/RandomTableGenerationException.java (87%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => quest}/NodeType.java (89%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => quest}/Prerequisite.java (95%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => quest}/PrerequisiteEvaluator.java (98%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => quest}/ProgressionStatus.java (91%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => quest}/Quest.java (98%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => quest}/QuestNodeRef.java (92%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => quest}/QuestStatus.java (91%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => randomtable}/RandomTable.java (96%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => randomtable}/RandomTableEntry.java (93%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => readiness}/ReadinessEntityType.java (85%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => readiness}/ReadinessSeverity.java (91%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => readiness}/ReadinessStatus.java (94%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => structure}/Arc.java (97%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => structure}/ArcType.java (92%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => structure}/Chapter.java (96%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => structure}/LinkType.java (90%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => structure}/Room.java (97%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => structure}/RoomBranch.java (93%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => structure}/Scene.java (98%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => structure}/SceneBattlemap.java (95%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => structure}/SceneBranch.java (96%) rename core/src/main/java/com/loremind/domain/campaigncontext/{ => structure}/SceneType.java (92%) rename core/src/main/java/com/loremind/domain/gamesystemcontext/ports/{ => exceptions}/RulesImportException.java (88%) create mode 100644 core/src/main/java/com/loremind/domain/generationcontext/ChatStreamCallbacks.java rename core/src/main/java/com/loremind/domain/generationcontext/ports/{ => exceptions}/AiProviderException.java (91%) rename core/src/main/java/com/loremind/domain/playcontext/ports/{ => exceptions}/SessionRecapException.java (87%) create mode 100644 core/src/test/java/com/loremind/application/campaigncontext/CampaignContextFormatterTest.java create mode 100644 core/src/test/java/com/loremind/application/gamesystemcontext/GameSystemContextBuilderTest.java rename core/src/test/java/com/loremind/domain/campaigncontext/{ => structure}/ArcTest.java (97%) rename core/src/test/java/com/loremind/domain/campaigncontext/{ => structure}/ChapterTest.java (97%) rename core/src/test/java/com/loremind/domain/campaigncontext/{ => structure}/SceneBranchTest.java (97%) rename core/src/test/java/com/loremind/domain/campaigncontext/{ => structure}/SceneTest.java (98%) create mode 100644 core/src/test/java/com/loremind/domain/shared/ReorderSupportTest.java create mode 100644 core/src/test/java/com/loremind/infrastructure/transfer/ImageRefRemapOnImportTest.java diff --git a/core/src/main/java/com/loremind/application/campaigncontext/ArcService.java b/core/src/main/java/com/loremind/application/campaigncontext/ArcService.java index 5152b80..9c975a6 100644 --- a/core/src/main/java/com/loremind/application/campaigncontext/ArcService.java +++ b/core/src/main/java/com/loremind/application/campaigncontext/ArcService.java @@ -1,9 +1,9 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.Arc; -import com.loremind.domain.campaigncontext.Chapter; -import com.loremind.domain.campaigncontext.FieldProposal; -import com.loremind.domain.campaigncontext.Quest; +import com.loremind.domain.campaigncontext.structure.Arc; +import com.loremind.domain.campaigncontext.structure.Chapter; +import com.loremind.domain.campaigncontext.generation.FieldProposal; +import com.loremind.domain.campaigncontext.quest.Quest; import com.loremind.domain.shared.ReorderSupport; import com.loremind.domain.campaigncontext.ports.ArcRepository; import com.loremind.domain.campaigncontext.ports.ChapterRepository; @@ -169,8 +169,8 @@ public class ArcService { @Transactional public void reorderArcs(List orderedIds) { ReorderSupport.reorder(orderedIds, - id -> arcRepository.findById(id).orElse(null), - (arc, i) -> arc.setOrder(i), + arcRepository::findById, + Arc::setOrder, arcRepository::save); } } diff --git a/core/src/main/java/com/loremind/application/campaigncontext/CampaignBriefBuilder.java b/core/src/main/java/com/loremind/application/campaigncontext/CampaignBriefBuilder.java index fee0b71..cff7c5f 100644 --- a/core/src/main/java/com/loremind/application/campaigncontext/CampaignBriefBuilder.java +++ b/core/src/main/java/com/loremind/application/campaigncontext/CampaignBriefBuilder.java @@ -41,6 +41,16 @@ public class CampaignBriefBuilder { sb.append("# Campagne : ").append(cc.campaignName()).append("\n"); if (notBlank(cc.campaignDescription())) sb.append(cc.campaignDescription()).append("\n"); + appendStructure(sb, cc); + appendNpcs(sb, cc); + + if (campaign.isLinkedToLore()) { + loreContextBuilder.buildOptional(campaign.getLoreId()).ifPresent(lore -> appendLore(sb, lore)); + } + return sb.toString(); + } + + private void appendStructure(StringBuilder sb, CampaignStructuralContext cc) { sb.append("\n## Structure (arcs → chapitres → scènes)\n"); sb.append("_Un arc HUB contient des chapitres parallèles appelés « quêtes » ; ") .append("un arc LINEAR contient des chapitres en séquence._\n"); @@ -48,34 +58,38 @@ public class CampaignBriefBuilder { sb.append("_(aucun arc pour le moment)_\n"); } for (ArcSummary arc : cc.arcs()) { - sb.append(arc.hub() ? "### Arc HUB (à quêtes) : " : "### Arc : ").append(arc.name()); - if (notBlank(arc.description())) sb.append(" — ").append(arc.description()); + appendArc(sb, arc); + } + } + + private void appendArc(StringBuilder sb, ArcSummary arc) { + sb.append(arc.hub() ? "### Arc HUB (à quêtes) : " : "### Arc : ").append(arc.name()); + if (notBlank(arc.description())) sb.append(" — ").append(arc.description()); + sb.append("\n"); + for (ChapterSummary ch : arc.chapters()) { + appendChapter(sb, arc.hub(), ch); + } + } + + private void appendChapter(StringBuilder sb, boolean hub, ChapterSummary ch) { + sb.append(hub ? "- Quête : " : "- Chapitre : ").append(ch.name()); + if (notBlank(ch.description())) sb.append(" — ").append(ch.description()); + sb.append("\n"); + for (SceneSummary sc : ch.scenes()) { + sb.append(" - Scène : ").append(sc.name()); + if (notBlank(sc.description())) sb.append(" — ").append(sc.description()); sb.append("\n"); - for (ChapterSummary ch : arc.chapters()) { - sb.append(arc.hub() ? "- Quête : " : "- Chapitre : ").append(ch.name()); - if (notBlank(ch.description())) sb.append(" — ").append(ch.description()); - sb.append("\n"); - for (SceneSummary sc : ch.scenes()) { - sb.append(" - Scène : ").append(sc.name()); - if (notBlank(sc.description())) sb.append(" — ").append(sc.description()); - sb.append("\n"); - } - } } + } - if (!cc.npcs().isEmpty()) { - sb.append("\n## PNJ existants\n"); - for (NpcSummary n : cc.npcs()) { - sb.append("- ").append(n.name()); - if (notBlank(n.snippet())) sb.append(" : ").append(n.snippet()); - sb.append("\n"); - } + private void appendNpcs(StringBuilder sb, CampaignStructuralContext cc) { + if (cc.npcs().isEmpty()) return; + sb.append("\n## PNJ existants\n"); + for (NpcSummary n : cc.npcs()) { + sb.append("- ").append(n.name()); + if (notBlank(n.snippet())) sb.append(" : ").append(n.snippet()); + sb.append("\n"); } - - if (campaign.isLinkedToLore()) { - loreContextBuilder.buildOptional(campaign.getLoreId()).ifPresent(lore -> appendLore(sb, lore)); - } - return sb.toString(); } private void appendLore(StringBuilder sb, LoreStructuralContext lore) { diff --git a/core/src/main/java/com/loremind/application/campaigncontext/CampaignContextFormatter.java b/core/src/main/java/com/loremind/application/campaigncontext/CampaignContextFormatter.java new file mode 100644 index 0000000..810a4bb --- /dev/null +++ b/core/src/main/java/com/loremind/application/campaigncontext/CampaignContextFormatter.java @@ -0,0 +1,40 @@ +package com.loremind.application.campaigncontext; + +import com.loremind.domain.campaigncontext.Campaign; +import com.loremind.domain.campaigncontext.ports.CampaignRepository; +import com.loremind.domain.gamesystemcontext.ports.GameSystemRepository; +import org.springframework.stereotype.Component; + +/** + * Formateur de contexte campagne pour les prompts IA. + * Centralise la construction du bloc "nom + description + système de jeu". + */ +@Component +public class CampaignContextFormatter { + + private final CampaignRepository campaignRepository; + private final GameSystemRepository gameSystemRepository; + + public CampaignContextFormatter(CampaignRepository campaignRepository, + GameSystemRepository gameSystemRepository) { + this.campaignRepository = campaignRepository; + this.gameSystemRepository = gameSystemRepository; + } + + /** Contexte compact : nom de campagne + description + système de jeu. */ + public String format(String campaignId) { + if (campaignId == null) return ""; + Campaign campaign = campaignRepository.findById(campaignId).orElse(null); + if (campaign == null) return ""; + StringBuilder sb = new StringBuilder(); + sb.append("Campagne : ").append(campaign.getName()); + if (campaign.getDescription() != null && !campaign.getDescription().isBlank()) { + sb.append(" — ").append(campaign.getDescription().trim()); + } + if (campaign.getGameSystemId() != null && !campaign.getGameSystemId().isBlank()) { + gameSystemRepository.findById(campaign.getGameSystemId()) + .ifPresent(gs -> sb.append("\nSystème de jeu : ").append(gs.getName())); + } + return sb.toString(); + } +} diff --git a/core/src/main/java/com/loremind/application/campaigncontext/CampaignImportService.java b/core/src/main/java/com/loremind/application/campaigncontext/CampaignImportService.java index 80cb078..9fad061 100644 --- a/core/src/main/java/com/loremind/application/campaigncontext/CampaignImportService.java +++ b/core/src/main/java/com/loremind/application/campaigncontext/CampaignImportService.java @@ -1,17 +1,17 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.Arc; -import com.loremind.domain.campaigncontext.ArcType; -import com.loremind.domain.campaigncontext.CampaignImportProgress; -import com.loremind.domain.campaigncontext.CampaignImportProposal; -import com.loremind.domain.campaigncontext.CampaignImportProposal.ArcProposal; -import com.loremind.domain.campaigncontext.CampaignImportProposal.ChapterProposal; -import com.loremind.domain.campaigncontext.CampaignImportProposal.NpcProposal; -import com.loremind.domain.campaigncontext.CampaignImportProposal.RoomProposal; -import com.loremind.domain.campaigncontext.CampaignImportProposal.SceneProposal; -import com.loremind.domain.campaigncontext.Chapter; -import com.loremind.domain.campaigncontext.Room; -import com.loremind.domain.campaigncontext.Scene; +import com.loremind.domain.campaigncontext.structure.Arc; +import com.loremind.domain.campaigncontext.structure.ArcType; +import com.loremind.domain.campaigncontext.generation.CampaignImportProgress; +import com.loremind.domain.campaigncontext.generation.CampaignImportProposal; +import com.loremind.domain.campaigncontext.generation.CampaignImportProposal.ArcProposal; +import com.loremind.domain.campaigncontext.generation.CampaignImportProposal.ChapterProposal; +import com.loremind.domain.campaigncontext.generation.CampaignImportProposal.NpcProposal; +import com.loremind.domain.campaigncontext.generation.CampaignImportProposal.RoomProposal; +import com.loremind.domain.campaigncontext.generation.CampaignImportProposal.SceneProposal; +import com.loremind.domain.campaigncontext.structure.Chapter; +import com.loremind.domain.campaigncontext.structure.Room; +import com.loremind.domain.campaigncontext.structure.Scene; import com.loremind.domain.campaigncontext.ports.CampaignPdfImporter; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -83,66 +83,95 @@ public class CampaignImportService { throw new IllegalArgumentException("Campagne introuvable : " + campaignId); } - int arcsCreated = 0, chaptersCreated = 0, scenesCreated = 0; + int arcsCreated = 0; + int chaptersCreated = 0; + int scenesCreated = 0; // Les nouveaux nœuds sont ordonnés APRÈS les frères existants (déjà comptés // via leur existingId dans l'arbre fusionné venu de la revue). int arcOrder = countExisting(proposal.arcs(), ArcProposal::existingId); for (ArcProposal arcP : proposal.arcs()) { if (isBlank(arcP.name())) continue; - String arcId; - if (!isBlank(arcP.existingId())) { - arcId = arcP.existingId(); // arc déjà présent → on s'y rattache - } else { - arcOrder++; - Arc arc = arcService.createArc(Arc.builder() - .name(arcP.name().trim()) - .description(nullIfBlank(arcP.description())) - .campaignId(campaignId) - .order(arcOrder) - .type(parseArcType(arcP.type())) - .build()); - arcId = arc.getId(); - arcsCreated++; - } - - int chapterOrder = countExisting(arcP.chapters(), ChapterProposal::existingId); - for (ChapterProposal chapP : safe(arcP.chapters())) { - if (isBlank(chapP.name())) continue; - String chapId; - if (!isBlank(chapP.existingId())) { - chapId = chapP.existingId(); - } else { - chapterOrder++; - Chapter chapter = chapterService.createChapter( - chapP.name().trim(), nullIfBlank(chapP.description()), arcId, chapterOrder); - chapId = chapter.getId(); - chaptersCreated++; - } - - int sceneOrder = countExisting(chapP.scenes(), SceneProposal::existingId); - for (SceneProposal sceneP : safe(chapP.scenes())) { - if (isBlank(sceneP.name())) continue; - if (!isBlank(sceneP.existingId())) continue; // scène déjà présente - sceneOrder++; - sceneService.createScene(Scene.builder() - .name(sceneP.name().trim()) - .description(nullIfBlank(sceneP.description())) - .playerNarration(nullIfBlank(sceneP.playerNarration())) - .gmSecretNotes(nullIfBlank(sceneP.gmNotes())) - .chapterId(chapId) - .order(sceneOrder) - .rooms(toRooms(sceneP.rooms())) - .build()); - scenesCreated++; - } - } + ArcOutcome outcome = applyArc(campaignId, arcP, arcOrder); + arcOrder = outcome.nextOrder(); + arcsCreated += outcome.arcsCreated(); + chaptersCreated += outcome.chaptersCreated(); + scenesCreated += outcome.scenesCreated(); } int npcsCreated = createNpcs(campaignId, proposal.npcs()); return new ApplyResult(arcsCreated, chaptersCreated, scenesCreated, npcsCreated); } + /** Ordre d'arc suivant + compteurs créés (arc et, en cascade, ses chapitres/scènes). */ + private record ArcOutcome(int nextOrder, int arcsCreated, int chaptersCreated, int scenesCreated) {} + + private ArcOutcome applyArc(String campaignId, ArcProposal arcP, int arcOrder) { + String arcId; + int arcsCreated = 0; + if (!isBlank(arcP.existingId())) { + arcId = arcP.existingId(); // arc déjà présent → on s'y rattache + } else { + arcOrder++; + Arc arc = arcService.createArc(Arc.builder() + .name(arcP.name().trim()) + .description(nullIfBlank(arcP.description())) + .campaignId(campaignId) + .order(arcOrder) + .type(parseArcType(arcP.type())) + .build()); + arcId = arc.getId(); + arcsCreated = 1; + } + + int chapterOrder = countExisting(arcP.chapters(), ChapterProposal::existingId); + int chaptersCreated = 0; + int scenesCreated = 0; + for (ChapterProposal chapP : safe(arcP.chapters())) { + if (isBlank(chapP.name())) continue; + ChapterOutcome outcome = applyChapter(arcId, chapP, chapterOrder); + chapterOrder = outcome.nextOrder(); + chaptersCreated += outcome.chaptersCreated(); + scenesCreated += outcome.scenesCreated(); + } + return new ArcOutcome(arcOrder, arcsCreated, chaptersCreated, scenesCreated); + } + + /** Ordre de chapitre suivant + compteurs créés (chapitre et, en cascade, ses scènes). */ + private record ChapterOutcome(int nextOrder, int chaptersCreated, int scenesCreated) {} + + private ChapterOutcome applyChapter(String arcId, ChapterProposal chapP, int chapterOrder) { + String chapId; + int chaptersCreated = 0; + if (!isBlank(chapP.existingId())) { + chapId = chapP.existingId(); + } else { + chapterOrder++; + Chapter chapter = chapterService.createChapter( + chapP.name().trim(), nullIfBlank(chapP.description()), arcId, chapterOrder); + chapId = chapter.getId(); + chaptersCreated = 1; + } + + int sceneOrder = countExisting(chapP.scenes(), SceneProposal::existingId); + int scenesCreated = 0; + for (SceneProposal sceneP : safe(chapP.scenes())) { + if (isBlank(sceneP.name()) || !isBlank(sceneP.existingId())) continue; // vide ou déjà présente + sceneOrder++; + sceneService.createScene(Scene.builder() + .name(sceneP.name().trim()) + .description(nullIfBlank(sceneP.description())) + .playerNarration(nullIfBlank(sceneP.playerNarration())) + .gmSecretNotes(nullIfBlank(sceneP.gmNotes())) + .chapterId(chapId) + .order(sceneOrder) + .rooms(toRooms(sceneP.rooms())) + .build()); + scenesCreated++; + } + return new ChapterOutcome(chapterOrder, chaptersCreated, scenesCreated); + } + /** * Crée les PNJ proposés (description → values["Description"], même convention * que les cartes d'action des ateliers). Les PNJ portant un nom déjà présent @@ -182,7 +211,8 @@ public class CampaignImportService { /** "HUB" (insensible à la casse) → {@link ArcType#HUB} ; tout le reste → LINEAR. */ private static ArcType parseArcType(String type) { - return "HUB".equalsIgnoreCase(type == null ? "" : type.trim()) ? ArcType.HUB : ArcType.LINEAR; + String normalized = type == null ? "" : type.trim(); + return "HUB".equalsIgnoreCase(normalized) ? ArcType.HUB : ArcType.LINEAR; } /** Convertit les pièces proposées en {@link Room} (ID généré, ordre = index). */ diff --git a/core/src/main/java/com/loremind/application/campaigncontext/CampaignReadinessAssessment.java b/core/src/main/java/com/loremind/application/campaigncontext/CampaignReadinessAssessment.java index 465cad5..0fcd48e 100644 --- a/core/src/main/java/com/loremind/application/campaigncontext/CampaignReadinessAssessment.java +++ b/core/src/main/java/com/loremind/application/campaigncontext/CampaignReadinessAssessment.java @@ -1,6 +1,6 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.ReadinessStatus; +import com.loremind.domain.campaigncontext.readiness.ReadinessStatus; import java.util.List; import java.util.Map; diff --git a/core/src/main/java/com/loremind/application/campaigncontext/CampaignReadinessService.java b/core/src/main/java/com/loremind/application/campaigncontext/CampaignReadinessService.java index 583bd8b..86e4e81 100644 --- a/core/src/main/java/com/loremind/application/campaigncontext/CampaignReadinessService.java +++ b/core/src/main/java/com/loremind/application/campaigncontext/CampaignReadinessService.java @@ -1,20 +1,19 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.Arc; -import com.loremind.domain.campaigncontext.ArcType; +import com.loremind.domain.campaigncontext.structure.Arc; +import com.loremind.domain.campaigncontext.structure.ArcType; import com.loremind.domain.campaigncontext.Campaign; -import com.loremind.domain.campaigncontext.Chapter; -import com.loremind.domain.campaigncontext.Enemy; -import com.loremind.domain.campaigncontext.NodeType; -import com.loremind.domain.campaigncontext.Prerequisite; -import com.loremind.domain.campaigncontext.Quest; -import com.loremind.domain.campaigncontext.ReadinessEntityType; -import com.loremind.domain.campaigncontext.ReadinessSeverity; -import com.loremind.domain.campaigncontext.ReadinessStatus; -import com.loremind.domain.campaigncontext.Room; -import com.loremind.domain.campaigncontext.RoomBranch; -import com.loremind.domain.campaigncontext.Scene; -import com.loremind.domain.campaigncontext.SceneBranch; +import com.loremind.domain.campaigncontext.structure.Chapter; +import com.loremind.domain.campaigncontext.bestiary.Enemy; +import com.loremind.domain.campaigncontext.quest.NodeType; +import com.loremind.domain.campaigncontext.quest.Prerequisite; +import com.loremind.domain.campaigncontext.quest.Quest; +import com.loremind.domain.campaigncontext.readiness.ReadinessEntityType; +import com.loremind.domain.campaigncontext.readiness.ReadinessSeverity; +import com.loremind.domain.campaigncontext.readiness.ReadinessStatus; +import com.loremind.domain.campaigncontext.structure.Room; +import com.loremind.domain.campaigncontext.structure.Scene; +import com.loremind.domain.campaigncontext.structure.SceneBranch; import com.loremind.domain.campaigncontext.ports.ArcRepository; import com.loremind.domain.campaigncontext.ports.CampaignRepository; import com.loremind.domain.campaigncontext.ports.ChapterRepository; @@ -55,6 +54,8 @@ import java.util.stream.Collectors; @Service public class CampaignReadinessService { + private static final String SCENE_FALLBACK_NAME = "Scène"; + private final CampaignRepository campaignRepository; private final ArcRepository arcRepository; private final ChapterRepository chapterRepository; @@ -88,11 +89,6 @@ public class CampaignReadinessService { Set enemyIds = enemyRepository.findByCampaignId(campaignId).stream() .map(Enemy::getId).filter(id -> !isBlank(id)).collect(Collectors.toSet()); - // Index global chapitres / scènes (cibles possibles des nœuds de quête). - Set allChapterIds = new HashSet<>(); - Set allSceneIds = new HashSet<>(); - int totalScenes = 0; - List quests = questRepository.findByCampaignId(campaignId); // Arcs HUB « portés » par ≥1 quête rattachée : ne comptent PAS comme vides // (un arc HUB contient des quêtes, un arc LINÉAIRE des chapitres). @@ -102,50 +98,28 @@ public class CampaignReadinessService { List arcs = new ArrayList<>(arcRepository.findByCampaignId(campaignId)); arcs.sort(Comparator.comparingInt(Arc::getOrder)); + + // Index global chapitres / scènes (cibles possibles des nœuds de quête). + Set allChapterIds = new HashSet<>(); + Set allSceneIds = new HashSet<>(); + int totalScenes = 0; for (Arc arc : arcs) { - List chapters = chapterRepository.findByArcId(arc.getId()); - // Arc SYSTEM (conteneurs des quêtes libres) : jamais « vide » — c'est de la - // plomberie invisible. Ses chapitres restent analysés (CHAP-001 des conteneurs). - boolean hubCoveredByQuest = arc.getType() == ArcType.HUB && arcsWithQuests.contains(arc.getId()); - if (chapters.isEmpty() && !hubCoveredByQuest && arc.getType() != ArcType.SYSTEM) { - String msg = arc.getType() == ArcType.HUB - ? "Arc vide : ajoutez une quête (ou un chapitre), ou supprimez-le." - : "Arc vide : ajoutez un chapitre, ou supprimez-le."; - gaps.add(new ReadinessGap(ReadinessEntityType.ARC, arc.getId(), labelOr(arc.getName(), "Arc"), - "ARC-001-EMPTY", msg, ReadinessSeverity.BLOCKING, arc.getId(), null)); - } - for (Chapter chapter : chapters) { - allChapterIds.add(chapter.getId()); - List scenes = sceneRepository.findByChapterId(chapter.getId()); - if (scenes.isEmpty()) { - gaps.add(new ReadinessGap(ReadinessEntityType.CHAPTER, chapter.getId(), - labelOr(chapter.getName(), "Chapitre"), "CHAP-001-NO-SCENE", - "Chapitre vide : ajoutez au moins une scène pour pouvoir le jouer.", - ReadinessSeverity.BLOCKING, arc.getId(), chapter.getId())); - } - Set chapterSceneIds = scenes.stream() - .map(Scene::getId).filter(Objects::nonNull).collect(Collectors.toSet()); - totalScenes += scenes.size(); - for (Scene scene : scenes) { - allSceneIds.add(scene.getId()); - checkScene(scene, arc.getId(), chapter.getId(), chapterSceneIds, enemyIds, gaps); - } - } + ArcScan scan = checkArc(arc, arcsWithQuests, enemyIds, gaps); + allChapterIds.addAll(scan.chapterIds()); + allSceneIds.addAll(scan.sceneIds()); + totalScenes += scan.sceneCount(); } - Set questIds = quests.stream() - .map(Quest::getId).filter(Objects::nonNull).collect(Collectors.toSet()); - // Campagne vide : ni scène jouable, ni quête porteuse de contenu (couvre le mode plat). - boolean anyQuestWithNodes = quests.stream() - .anyMatch(q -> q.getNodes() != null && !q.getNodes().isEmpty()); - if (totalScenes == 0 && !anyQuestWithNodes) { + if (totalScenes == 0 && !anyQuestHasNodes(quests)) { gaps.add(new ReadinessGap(ReadinessEntityType.CAMPAIGN, campaignId, campaignName, "CAMP-001-NO-CONTENT", "Campagne vide : ajoutez un arc avec une scène, ou créez une quête, pour commencer à jouer.", ReadinessSeverity.BLOCKING, null, null)); } + Set questIds = quests.stream() + .map(Quest::getId).filter(Objects::nonNull).collect(Collectors.toSet()); for (Quest quest : quests) { checkQuest(quest, allChapterIds, allSceneIds, questIds, gaps); } @@ -153,88 +127,149 @@ public class CampaignReadinessService { return aggregate(campaignId, gaps); } + /** Résultat du scan d'un arc : chapitres/scènes indexés (cibles des nœuds de quête) + total scènes. */ + private record ArcScan(Set chapterIds, Set sceneIds, int sceneCount) {} + + private ArcScan checkArc(Arc arc, Set arcsWithQuests, Set enemyIds, List gaps) { + List chapters = chapterRepository.findByArcId(arc.getId()); + // Arc SYSTEM (conteneurs des quêtes libres) : jamais « vide » — c'est de la + // plomberie invisible. Ses chapitres restent analysés (CHAP-001 des conteneurs). + boolean hubCoveredByQuest = arc.getType() == ArcType.HUB && arcsWithQuests.contains(arc.getId()); + if (chapters.isEmpty() && !hubCoveredByQuest && arc.getType() != ArcType.SYSTEM) { + gaps.add(emptyArcGap(arc)); + } + + Set chapterIds = new HashSet<>(); + Set sceneIds = new HashSet<>(); + int sceneCount = 0; + for (Chapter chapter : chapters) { + chapterIds.add(chapter.getId()); + List scenes = sceneRepository.findByChapterId(chapter.getId()); + if (scenes.isEmpty()) { + gaps.add(new ReadinessGap(ReadinessEntityType.CHAPTER, chapter.getId(), + labelOr(chapter.getName(), "Chapitre"), "CHAP-001-NO-SCENE", + "Chapitre vide : ajoutez au moins une scène pour pouvoir le jouer.", + ReadinessSeverity.BLOCKING, arc.getId(), chapter.getId())); + } + Set chapterSceneIds = scenes.stream() + .map(Scene::getId).filter(Objects::nonNull).collect(Collectors.toSet()); + sceneCount += scenes.size(); + for (Scene scene : scenes) { + sceneIds.add(scene.getId()); + checkScene(scene, arc.getId(), chapter.getId(), chapterSceneIds, enemyIds, gaps); + } + } + return new ArcScan(chapterIds, sceneIds, sceneCount); + } + + private ReadinessGap emptyArcGap(Arc arc) { + String msg = arc.getType() == ArcType.HUB + ? "Arc vide : ajoutez une quête (ou un chapitre), ou supprimez-le." + : "Arc vide : ajoutez un chapitre, ou supprimez-le."; + return new ReadinessGap(ReadinessEntityType.ARC, arc.getId(), labelOr(arc.getName(), "Arc"), + "ARC-001-EMPTY", msg, ReadinessSeverity.BLOCKING, arc.getId(), null); + } + + private static boolean anyQuestHasNodes(List quests) { + return quests.stream().anyMatch(q -> q.getNodes() != null && !q.getNodes().isEmpty()); + } + private void checkScene(Scene scene, String arcId, String chapterId, Set chapterSceneIds, Set enemyIds, List gaps) { - String name = labelOr(scene.getName(), "Scène"); + checkSceneName(scene, arcId, chapterId, gaps); + checkSceneBranches(scene, arcId, chapterId, chapterSceneIds, gaps); + checkSceneCombat(scene, arcId, chapterId, enemyIds, gaps); + checkSceneEnemyRefs(scene, arcId, chapterId, enemyIds, gaps); + checkSceneRooms(scene, arcId, chapterId, enemyIds, gaps); + } - // SCENE-001 — scène sans titre. + /** SCENE-001 — scène sans titre. */ + private void checkSceneName(Scene scene, String arcId, String chapterId, List gaps) { if (isBlank(scene.getName())) { gaps.add(sceneGap(scene, arcId, chapterId, "SCENE-001-NO-NAME", "Scène sans titre : donnez-lui un nom pour l'identifier et la jouer.", ReadinessSeverity.BLOCKING)); } + } - // SCENE-010 — branche de sortie cassée (vide / hors chapitre / auto-référence). + /** SCENE-010 — branche de sortie cassée (vide / hors chapitre / auto-référence). */ + private void checkSceneBranches(Scene scene, String arcId, String chapterId, + Set chapterSceneIds, List gaps) { List branches = scene.getBranches(); - if (branches != null && branches.stream().anyMatch(b -> + if (branches == null) return; + boolean invalid = branches.stream().anyMatch(b -> isBlank(b.targetSceneId()) || b.targetSceneId().equals(scene.getId()) - || !chapterSceneIds.contains(b.targetSceneId()))) { + || !chapterSceneIds.contains(b.targetSceneId())); + if (invalid) { gaps.add(sceneGap(scene, arcId, chapterId, "SCENE-010-BRANCH-INVALID", - "Branche cassée : une sortie de « " + name + "Branche cassée : une sortie de « " + labelOr(scene.getName(), SCENE_FALLBACK_NAME) + " » pointe dans le vide, hors du chapitre, ou sur elle-même.", ReadinessSeverity.BLOCKING)); } + } - // SCENE-011 — combat annoncé sans adversaire (règle produit clé). - if (!isBlank(scene.getCombatDifficulty())) { - boolean hasEnemyText = !isBlank(scene.getEnemies()); - boolean hasResolvedEnemy = scene.getEnemyIds() != null - && scene.getEnemyIds().stream().anyMatch(id -> !isBlank(id) && enemyIds.contains(id)); - if (!hasEnemyText && !hasResolvedEnemy) { - gaps.add(sceneGap(scene, arcId, chapterId, "SCENE-011-COMBAT-NO-ENEMY", - "Combat annoncé sans adversaire : ajoutez une fiche du bestiaire ou décrivez les ennemis.", - ReadinessSeverity.RECOMMENDED)); - } + /** SCENE-011 — combat annoncé sans adversaire (règle produit clé). */ + private void checkSceneCombat(Scene scene, String arcId, String chapterId, + Set enemyIds, List gaps) { + if (isBlank(scene.getCombatDifficulty())) return; + boolean hasEnemyText = !isBlank(scene.getEnemies()); + boolean hasResolvedEnemy = scene.getEnemyIds() != null + && scene.getEnemyIds().stream().anyMatch(id -> !isBlank(id) && enemyIds.contains(id)); + if (!hasEnemyText && !hasResolvedEnemy) { + gaps.add(sceneGap(scene, arcId, chapterId, "SCENE-011-COMBAT-NO-ENEMY", + "Combat annoncé sans adversaire : ajoutez une fiche du bestiaire ou décrivez les ennemis.", + ReadinessSeverity.RECOMMENDED)); } + } - // SCENE-012 — référence d'ennemi cassée (fiche supprimée). - if (scene.getEnemyIds() != null - && scene.getEnemyIds().stream().anyMatch(id -> !isBlank(id) && !enemyIds.contains(id))) { + /** SCENE-012 — référence d'ennemi cassée (fiche supprimée). */ + private void checkSceneEnemyRefs(Scene scene, String arcId, String chapterId, + Set enemyIds, List gaps) { + if (scene.getEnemyIds() == null) return; + boolean broken = scene.getEnemyIds().stream().anyMatch(id -> !isBlank(id) && !enemyIds.contains(id)); + if (broken) { gaps.add(sceneGap(scene, arcId, chapterId, "SCENE-012-ENEMY-REF-BROKEN", - "Ennemi introuvable : « " + name + "Ennemi introuvable : « " + labelOr(scene.getName(), SCENE_FALLBACK_NAME) + " » référence une fiche du bestiaire supprimée. Retirez la référence ou recréez la fiche.", ReadinessSeverity.RECOMMENDED)); } + } - // SCENE-041 / SCENE-042 — pièces explorables : portes cassées + ennemis fantômes. + /** SCENE-041 / SCENE-042 — pièces explorables : portes cassées + ennemis fantômes. */ + private void checkSceneRooms(Scene scene, String arcId, String chapterId, + Set enemyIds, List gaps) { List rooms = scene.getRooms(); - if (rooms != null && !rooms.isEmpty()) { - Set roomIds = rooms.stream() - .map(Room::getId).filter(Objects::nonNull).collect(Collectors.toSet()); - boolean roomBranchInvalid = false; - boolean roomEnemyBroken = false; - for (Room room : rooms) { - if (room.getBranches() != null) { - for (RoomBranch rb : room.getBranches()) { - if (isBlank(rb.targetRoomId()) - || rb.targetRoomId().equals(room.getId()) - || !roomIds.contains(rb.targetRoomId())) { - roomBranchInvalid = true; - } - } - } - if (room.getEnemyIds() != null) { - for (String id : room.getEnemyIds()) { - if (!isBlank(id) && !enemyIds.contains(id)) { - roomEnemyBroken = true; - } - } - } - } - if (roomBranchInvalid) { - gaps.add(sceneGap(scene, arcId, chapterId, "SCENE-041-ROOMBRANCH-INVALID", - "Porte cassée : dans « " + name - + " », une sortie de pièce pointe hors de la scène ou dans le vide.", - ReadinessSeverity.BLOCKING)); - } - if (roomEnemyBroken) { - gaps.add(sceneGap(scene, arcId, chapterId, "SCENE-042-ROOM-ENEMY-BROKEN", - "Ennemi introuvable dans une pièce de « " + name - + " » : la référence pointe vers une fiche supprimée.", - ReadinessSeverity.RECOMMENDED)); - } + if (rooms == null || rooms.isEmpty()) return; + String name = labelOr(scene.getName(), SCENE_FALLBACK_NAME); + Set roomIds = rooms.stream() + .map(Room::getId).filter(Objects::nonNull).collect(Collectors.toSet()); + + if (rooms.stream().anyMatch(room -> hasInvalidBranch(room, roomIds))) { + gaps.add(sceneGap(scene, arcId, chapterId, "SCENE-041-ROOMBRANCH-INVALID", + "Porte cassée : dans « " + name + + " », une sortie de pièce pointe hors de la scène ou dans le vide.", + ReadinessSeverity.BLOCKING)); } + if (rooms.stream().anyMatch(room -> hasBrokenEnemyRef(room, enemyIds))) { + gaps.add(sceneGap(scene, arcId, chapterId, "SCENE-042-ROOM-ENEMY-BROKEN", + "Ennemi introuvable dans une pièce de « " + name + + " » : la référence pointe vers une fiche supprimée.", + ReadinessSeverity.RECOMMENDED)); + } + } + + private static boolean hasInvalidBranch(Room room, Set roomIds) { + if (room.getBranches() == null) return false; + return room.getBranches().stream().anyMatch(rb -> + isBlank(rb.targetRoomId()) + || rb.targetRoomId().equals(room.getId()) + || !roomIds.contains(rb.targetRoomId())); + } + + private static boolean hasBrokenEnemyRef(Room room, Set enemyIds) { + if (room.getEnemyIds() == null) return false; + return room.getEnemyIds().stream().anyMatch(id -> !isBlank(id) && !enemyIds.contains(id)); } private void checkQuest(Quest quest, Set allChapterIds, Set allSceneIds, @@ -244,8 +279,7 @@ public class CampaignReadinessService { // QUEST-001 — quête sans nœud ; sinon QUEST-010 — nœud pointant dans le vide. if (quest.getNodes() == null || quest.getNodes().isEmpty()) { gaps.add(questGap(quest, "QUEST-001-NO-NODES", - "Quête sans contenu : ajoutez au moins un chapitre ou une scène à « " + name + " ».", - ReadinessSeverity.BLOCKING)); + "Quête sans contenu : ajoutez au moins un chapitre ou une scène à « " + name + " ».")); } else if (quest.getNodes().stream().anyMatch(n -> n.nodeType() == null || isBlank(n.nodeId()) @@ -253,8 +287,7 @@ public class CampaignReadinessService { || (n.nodeType() == NodeType.SCENE && !allSceneIds.contains(n.nodeId())))) { gaps.add(questGap(quest, "QUEST-010-NODE-REF-BROKEN", "Nœud de quête cassé : dans « " + name - + " », un chapitre ou une scène référencé n'existe plus.", - ReadinessSeverity.BLOCKING)); + + " », un chapitre ou une scène référencé n'existe plus.")); } // CAMP-010 — prérequis QuestCompleted pointant une quête disparue. @@ -264,8 +297,7 @@ public class CampaignReadinessService { .anyMatch(qid -> isBlank(qid) || !questIds.contains(qid))) { gaps.add(questGap(quest, "CAMP-010-DANGLING-QUEST-PREREQ", "Prérequis cassé : « " + name - + " » dépend d'une quête qui n'existe plus. Corrigez la condition de déblocage.", - ReadinessSeverity.BLOCKING)); + + " » dépend d'une quête qui n'existe plus. Corrigez la condition de déblocage.")); } } @@ -294,12 +326,12 @@ public class CampaignReadinessService { private ReadinessGap sceneGap(Scene scene, String arcId, String chapterId, String ruleId, String message, ReadinessSeverity severity) { return new ReadinessGap(ReadinessEntityType.SCENE, scene.getId(), - labelOr(scene.getName(), "Scène"), ruleId, message, severity, arcId, chapterId); + labelOr(scene.getName(), SCENE_FALLBACK_NAME), ruleId, message, severity, arcId, chapterId); } - private ReadinessGap questGap(Quest quest, String ruleId, String message, ReadinessSeverity severity) { + private ReadinessGap questGap(Quest quest, String ruleId, String message) { return new ReadinessGap(ReadinessEntityType.QUEST, quest.getId(), - labelOr(quest.getName(), "Quête"), ruleId, message, severity, null, null); + labelOr(quest.getName(), "Quête"), ruleId, message, ReadinessSeverity.BLOCKING, null, null); } private static int severityRank(ReadinessSeverity severity) { diff --git a/core/src/main/java/com/loremind/application/campaigncontext/CampaignReferencedFlagsService.java b/core/src/main/java/com/loremind/application/campaigncontext/CampaignReferencedFlagsService.java index aeec48c..f833453 100644 --- a/core/src/main/java/com/loremind/application/campaigncontext/CampaignReferencedFlagsService.java +++ b/core/src/main/java/com/loremind/application/campaigncontext/CampaignReferencedFlagsService.java @@ -1,7 +1,7 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.Prerequisite; -import com.loremind.domain.campaigncontext.Quest; +import com.loremind.domain.campaigncontext.quest.Prerequisite; +import com.loremind.domain.campaigncontext.quest.Quest; import com.loremind.domain.campaigncontext.ports.QuestRepository; import org.springframework.stereotype.Service; diff --git a/core/src/main/java/com/loremind/application/campaigncontext/CampaignService.java b/core/src/main/java/com/loremind/application/campaigncontext/CampaignService.java index 860cedf..3a3285e 100644 --- a/core/src/main/java/com/loremind/application/campaigncontext/CampaignService.java +++ b/core/src/main/java/com/loremind/application/campaigncontext/CampaignService.java @@ -1,9 +1,9 @@ package com.loremind.application.campaigncontext; import com.loremind.application.playcontext.PlaythroughService; -import com.loremind.domain.campaigncontext.Arc; +import com.loremind.domain.campaigncontext.structure.Arc; import com.loremind.domain.campaigncontext.Campaign; -import com.loremind.domain.campaigncontext.Chapter; +import com.loremind.domain.campaigncontext.structure.Chapter; import com.loremind.domain.campaigncontext.ports.ArcRepository; import com.loremind.domain.campaigncontext.ports.CampaignRepository; import com.loremind.domain.campaigncontext.ports.ChapterRepository; diff --git a/core/src/main/java/com/loremind/application/campaigncontext/ChapterService.java b/core/src/main/java/com/loremind/application/campaigncontext/ChapterService.java index e1c3d37..c0768a8 100644 --- a/core/src/main/java/com/loremind/application/campaigncontext/ChapterService.java +++ b/core/src/main/java/com/loremind/application/campaigncontext/ChapterService.java @@ -1,7 +1,7 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.Chapter; -import com.loremind.domain.campaigncontext.FieldProposal; +import com.loremind.domain.campaigncontext.structure.Chapter; +import com.loremind.domain.campaigncontext.generation.FieldProposal; import com.loremind.domain.shared.ReorderSupport; import com.loremind.domain.campaigncontext.ports.ChapterRepository; import com.loremind.domain.campaigncontext.ports.SceneRepository; @@ -136,7 +136,7 @@ public class ChapterService { @Transactional public void reorderChapters(String arcId, List orderedIds) { ReorderSupport.reorder(orderedIds, - id -> chapterRepository.findById(id).orElse(null), + chapterRepository::findById, (chapter, i) -> { if (arcId != null && !arcId.isBlank()) chapter.setArcId(arcId); chapter.setOrder(i); diff --git a/core/src/main/java/com/loremind/application/campaigncontext/CharacterService.java b/core/src/main/java/com/loremind/application/campaigncontext/CharacterService.java index 9641457..c7a4c77 100644 --- a/core/src/main/java/com/loremind/application/campaigncontext/CharacterService.java +++ b/core/src/main/java/com/loremind/application/campaigncontext/CharacterService.java @@ -1,6 +1,6 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.Character; +import com.loremind.domain.campaigncontext.bestiary.Character; import com.loremind.domain.campaigncontext.ports.CharacterRepository; import org.springframework.stereotype.Service; @@ -41,9 +41,9 @@ public class CharacterService { .name(data.name()) .portraitImageId(data.portraitImageId()) .headerImageId(data.headerImageId()) - .values(data.values() != null ? new HashMap<>(data.values()) : new HashMap<>()) - .imageValues(data.imageValues() != null ? new HashMap<>(data.imageValues()) : new HashMap<>()) - .keyValueValues(data.keyValueValues() != null ? new HashMap<>(data.keyValueValues()) : new HashMap<>()) + .values(copyStringMap(data.values())) + .imageValues(copyStringListMap(data.imageValues())) + .keyValueValues(copyStringMapMap(data.keyValueValues())) .playthroughId(data.playthroughId()) .order(order) .build(); @@ -64,9 +64,9 @@ public class CharacterService { existing.setName(data.name()); existing.setPortraitImageId(data.portraitImageId()); existing.setHeaderImageId(data.headerImageId()); - existing.setValues(data.values() != null ? new HashMap<>(data.values()) : new HashMap<>()); - existing.setImageValues(data.imageValues() != null ? new HashMap<>(data.imageValues()) : new HashMap<>()); - existing.setKeyValueValues(data.keyValueValues() != null ? new HashMap<>(data.keyValueValues()) : new HashMap<>()); + existing.setValues(copyStringMap(data.values())); + existing.setImageValues(copyStringListMap(data.imageValues())); + existing.setKeyValueValues(copyStringMapMap(data.keyValueValues())); if (data.order() != null) { existing.setOrder(data.order()); } @@ -89,4 +89,16 @@ public class CharacterService { .max() .orElse(-1) + 1; } + + private static Map copyStringMap(Map map) { + return map != null ? new HashMap<>(map) : new HashMap<>(); + } + + private static Map> copyStringListMap(Map> map) { + return map != null ? new HashMap<>(map) : new HashMap<>(); + } + + private static Map> copyStringMapMap(Map> map) { + return map != null ? new HashMap<>(map) : new HashMap<>(); + } } diff --git a/core/src/main/java/com/loremind/application/campaigncontext/EnemyService.java b/core/src/main/java/com/loremind/application/campaigncontext/EnemyService.java index 27dbc8f..eb188d7 100644 --- a/core/src/main/java/com/loremind/application/campaigncontext/EnemyService.java +++ b/core/src/main/java/com/loremind/application/campaigncontext/EnemyService.java @@ -1,6 +1,6 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.Enemy; +import com.loremind.domain.campaigncontext.bestiary.Enemy; import com.loremind.domain.shared.ReorderSupport; import com.loremind.domain.campaigncontext.ports.EnemyRepository; import com.loremind.domain.images.Image; @@ -10,11 +10,7 @@ import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.io.ByteArrayInputStream; -import java.util.Base64; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; /** * Service d'application pour les fiches d'ennemis (bestiaire de campagne). @@ -48,18 +44,10 @@ public class EnemyService { public Enemy createEnemy(EnemyData data) { int order = data.order() != null ? data.order() : nextOrderFor(data.campaignId()); - Enemy enemy = Enemy.builder() - .name(data.name()) - .level(normalize(data.level())) - .folder(normalize(data.folder())) - .portraitImageId(data.portraitImageId()) - .headerImageId(data.headerImageId()) - .values(data.values() != null ? new HashMap<>(data.values()) : new HashMap<>()) - .imageValues(data.imageValues() != null ? new HashMap<>(data.imageValues()) : new HashMap<>()) - .keyValueValues(data.keyValueValues() != null ? new HashMap<>(data.keyValueValues()) : new HashMap<>()) - .campaignId(data.campaignId()) - .order(order) - .build(); + Enemy enemy = Enemy.builder().build(); + applyCommonEnemyFields(enemy, data); + enemy.setCampaignId(data.campaignId()); + enemy.setOrder(order); return enemyRepository.save(enemy); } @@ -74,17 +62,11 @@ public class EnemyService { public Enemy updateEnemy(String id, EnemyData data) { Enemy existing = enemyRepository.findById(id) .orElseThrow(() -> new IllegalArgumentException("Enemy non trouvé avec l'ID: " + id)); - existing.setName(data.name()); - existing.setLevel(normalize(data.level())); - existing.setFolder(normalize(data.folder())); - existing.setPortraitImageId(data.portraitImageId()); - existing.setHeaderImageId(data.headerImageId()); - existing.setValues(data.values() != null ? new HashMap<>(data.values()) : new HashMap<>()); - existing.setImageValues(data.imageValues() != null ? new HashMap<>(data.imageValues()) : new HashMap<>()); - existing.setKeyValueValues(data.keyValueValues() != null ? new HashMap<>(data.keyValueValues()) : new HashMap<>()); + applyCommonEnemyFields(existing, data); if (data.order() != null) { existing.setOrder(data.order()); } + // campaignId immuable après création. return enemyRepository.save(existing); } @@ -100,7 +82,7 @@ public class EnemyService { public void reorderEnemies(String folder, List orderedIds) { String f = normalize(folder); ReorderSupport.reorder(orderedIds, - id -> enemyRepository.findById(id).orElse(null), + enemyRepository::findById, (enemy, i) -> { enemy.setFolder(f); enemy.setOrder(i); }, enemyRepository::save); } @@ -141,9 +123,7 @@ public class EnemyService { return null; } if (bytes.length == 0) return null; - String ext = contentType.contains("png") ? "png" - : contentType.contains("jpeg") || contentType.contains("jpg") ? "jpg" - : contentType.contains("gif") ? "gif" : "webp"; + String ext = pickExtension(contentType); // Type MIME canonique dérivé de l'extension : un data URL "image/jpg" (non // standard) serait rejeté par ImageService (qui n'accepte que "image/jpeg"). String mimeType = "jpg".equals(ext) ? "image/jpeg" : "image/" + ext; @@ -158,6 +138,14 @@ public class EnemyService { } } + /** Extension déduite du content-type ; "webp" par défaut si non reconnu. */ + private static String pickExtension(String contentType) { + if (contentType.contains("png")) return "png"; + if (contentType.contains("jpeg") || contentType.contains("jpg")) return "jpg"; + if (contentType.contains("gif")) return "gif"; + return "webp"; + } + public record MonsterImportResult(int created, int updated) {} /** @@ -174,46 +162,51 @@ public class EnemyService { } int order = existing.stream().mapToInt(Enemy::getOrder).max().orElse(-1) + 1; - int created = 0, updated = 0; + int created = 0; + int updated = 0; for (MonsterImport m : monsters) { - if (m.foundryRef() == null || m.foundryRef().isBlank() - || m.name() == null || m.name().isBlank()) { - continue; - } + if (isBlank(m.foundryRef()) || isBlank(m.name())) continue; Map stats = m.stats() != null ? new HashMap<>(m.stats()) : new HashMap<>(); - String folder = foundryFolder(m.folder()); Enemy ex = byRef.get(m.foundryRef()); if (ex != null) { - ex.setName(m.name()); - ex.setFoundryStats(stats); // rafraîchit le snapshot - ex.setFolder(folder); // ré-aligne sur l'arborescence Foundry - // Portrait : seulement s'il manque (pas de ré-upload à chaque import). - if (ex.getPortraitImageId() == null) { - String portrait = storePortrait(m.imgData(), m.name()); - if (portrait != null) ex.setPortraitImageId(portrait); - } - enemyRepository.save(ex); + updateMonster(ex, m, stats); updated++; } else { - Enemy saved = enemyRepository.save(Enemy.builder() - .name(m.name()) - .foundryRef(m.foundryRef()) - .foundryStats(stats) - .folder(folder) - .portraitImageId(storePortrait(m.imgData(), m.name())) - .campaignId(campaignId) - .order(order++) - .values(new HashMap<>()) - .imageValues(new HashMap<>()) - .keyValueValues(new HashMap<>()) - .build()); - byRef.put(m.foundryRef(), saved); + byRef.put(m.foundryRef(), createMonster(campaignId, m, stats, order++)); created++; } } return new MonsterImportResult(created, updated); } + /** Met à jour un monstre déjà importé : nom, snapshot de stats, dossier, portrait manquant. */ + private void updateMonster(Enemy ex, MonsterImport m, Map stats) { + ex.setName(m.name()); + ex.setFoundryStats(stats); // rafraîchit le snapshot + ex.setFolder(foundryFolder(m.folder())); // ré-aligne sur l'arborescence Foundry + // Portrait : seulement s'il manque (pas de ré-upload à chaque import). + if (ex.getPortraitImageId() == null) { + String portrait = storePortrait(m.imgData(), m.name()); + if (portrait != null) ex.setPortraitImageId(portrait); + } + enemyRepository.save(ex); + } + + private Enemy createMonster(String campaignId, MonsterImport m, Map stats, int order) { + return enemyRepository.save(Enemy.builder() + .name(m.name()) + .foundryRef(m.foundryRef()) + .foundryStats(stats) + .folder(foundryFolder(m.folder())) + .portraitImageId(storePortrait(m.imgData(), m.name())) + .campaignId(campaignId) + .order(order) + .values(new HashMap<>()) + .imageValues(new HashMap<>()) + .keyValueValues(new HashMap<>()) + .build()); + } + public List searchEnemies(String query) { if (query == null || query.isBlank()) return List.of(); return enemyRepository.searchByName(query.trim()); @@ -226,10 +219,37 @@ public class EnemyService { return trimmed.isEmpty() ? null : trimmed; } + private static boolean isBlank(String s) { + return s == null || s.isBlank(); + } + private int nextOrderFor(String campaignId) { return enemyRepository.findByCampaignId(campaignId).stream() .mapToInt(Enemy::getOrder) .max() .orElse(-1) + 1; } + + private static Map copyStringMap(Map map) { + return map != null ? new HashMap<>(map) : new HashMap<>(); + } + + private static Map> copyStringListMap(Map> map) { + return map != null ? new HashMap<>(map) : new HashMap<>(); + } + + private static Map> copyStringMapMap(Map> map) { + return map != null ? new HashMap<>(map) : new HashMap<>(); + } + + private static void applyCommonEnemyFields(Enemy enemy, EnemyData data) { + enemy.setName(data.name()); + enemy.setLevel(normalize(data.level())); + enemy.setFolder(normalize(data.folder())); + enemy.setPortraitImageId(data.portraitImageId()); + enemy.setHeaderImageId(data.headerImageId()); + enemy.setValues(copyStringMap(data.values())); + enemy.setImageValues(copyStringListMap(data.imageValues())); + enemy.setKeyValueValues(copyStringMapMap(data.keyValueValues())); + } } diff --git a/core/src/main/java/com/loremind/application/campaigncontext/ItemCatalogService.java b/core/src/main/java/com/loremind/application/campaigncontext/ItemCatalogService.java index 0a083a8..0806e87 100644 --- a/core/src/main/java/com/loremind/application/campaigncontext/ItemCatalogService.java +++ b/core/src/main/java/com/loremind/application/campaigncontext/ItemCatalogService.java @@ -1,12 +1,9 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.Campaign; -import com.loremind.domain.campaigncontext.CatalogItem; -import com.loremind.domain.campaigncontext.ItemCatalog; -import com.loremind.domain.campaigncontext.ports.CampaignRepository; +import com.loremind.domain.campaigncontext.itemcatalog.CatalogItem; +import com.loremind.domain.campaigncontext.itemcatalog.ItemCatalog; import com.loremind.domain.campaigncontext.ports.ItemCatalogGenerator; import com.loremind.domain.campaigncontext.ports.ItemCatalogRepository; -import com.loremind.domain.gamesystemcontext.ports.GameSystemRepository; import org.springframework.stereotype.Service; import java.util.ArrayList; @@ -21,18 +18,15 @@ public class ItemCatalogService { private final ItemCatalogRepository repository; private final ItemCatalogGenerator generator; - private final CampaignRepository campaignRepository; - private final GameSystemRepository gameSystemRepository; + private final CampaignContextFormatter campaignContextFormatter; public ItemCatalogService( ItemCatalogRepository repository, ItemCatalogGenerator generator, - CampaignRepository campaignRepository, - GameSystemRepository gameSystemRepository) { + CampaignContextFormatter campaignContextFormatter) { this.repository = repository; this.generator = generator; - this.campaignRepository = campaignRepository; - this.gameSystemRepository = gameSystemRepository; + this.campaignContextFormatter = campaignContextFormatter; } public record CatalogData( @@ -89,7 +83,8 @@ public class ItemCatalogService { /** Génère une PROPOSITION de catalogue (non persistée) via l'IA, contextualisée campagne. */ public ItemCatalog generateProposal(String campaignId, String description) { - ItemCatalogGenerator.GeneratedCatalog g = generator.generate(description, buildContext(campaignId)); + ItemCatalogGenerator.GeneratedCatalog g = generator.generate( + description, campaignContextFormatter.format(campaignId)); return ItemCatalog.builder() .name(g.name()) .description(g.description()) @@ -102,22 +97,6 @@ public class ItemCatalogService { return items != null ? new ArrayList<>(items) : new ArrayList<>(); } - private String buildContext(String campaignId) { - if (campaignId == null) return ""; - Campaign campaign = campaignRepository.findById(campaignId).orElse(null); - if (campaign == null) return ""; - StringBuilder sb = new StringBuilder(); - sb.append("Campagne : ").append(campaign.getName()); - if (campaign.getDescription() != null && !campaign.getDescription().isBlank()) { - sb.append(" — ").append(campaign.getDescription().trim()); - } - if (campaign.getGameSystemId() != null && !campaign.getGameSystemId().isBlank()) { - gameSystemRepository.findById(campaign.getGameSystemId()) - .ifPresent(gs -> sb.append("\nSystème de jeu : ").append(gs.getName())); - } - return sb.toString(); - } - private int nextOrderFor(String campaignId) { return repository.findByCampaignId(campaignId).stream() .mapToInt(ItemCatalog::getOrder) diff --git a/core/src/main/java/com/loremind/application/campaigncontext/NotebookService.java b/core/src/main/java/com/loremind/application/campaigncontext/NotebookService.java index a945b58..5b65ae3 100644 --- a/core/src/main/java/com/loremind/application/campaigncontext/NotebookService.java +++ b/core/src/main/java/com/loremind/application/campaigncontext/NotebookService.java @@ -1,9 +1,9 @@ package com.loremind.application.campaigncontext; import com.loremind.domain.campaigncontext.Campaign; -import com.loremind.domain.campaigncontext.Notebook; -import com.loremind.domain.campaigncontext.NotebookMessage; -import com.loremind.domain.campaigncontext.NotebookSource; +import com.loremind.domain.campaigncontext.notebook.Notebook; +import com.loremind.domain.campaigncontext.notebook.NotebookMessage; +import com.loremind.domain.campaigncontext.notebook.NotebookSource; import com.loremind.domain.campaigncontext.ports.CampaignRepository; import com.loremind.domain.campaigncontext.ports.NotebookIndexer; import com.loremind.domain.campaigncontext.ports.NotebookRepository; diff --git a/core/src/main/java/com/loremind/application/campaigncontext/NpcService.java b/core/src/main/java/com/loremind/application/campaigncontext/NpcService.java index 4c1f492..5246049 100644 --- a/core/src/main/java/com/loremind/application/campaigncontext/NpcService.java +++ b/core/src/main/java/com/loremind/application/campaigncontext/NpcService.java @@ -1,15 +1,11 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.Npc; +import com.loremind.domain.campaigncontext.bestiary.Npc; import com.loremind.domain.shared.ReorderSupport; import com.loremind.domain.campaigncontext.ports.NpcRepository; import org.springframework.stereotype.Service; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; /** * Service d'application pour les fiches de PNJ (campagne). @@ -92,7 +88,7 @@ public class NpcService { public void reorderNpcs(String folder, List orderedIds) { String f = normalizeFolder(folder); ReorderSupport.reorder(orderedIds, - id -> npcRepository.findById(id).orElse(null), + npcRepository::findById, (npc, i) -> { npc.setFolder(f); npc.setOrder(i); }, npcRepository::save); } diff --git a/core/src/main/java/com/loremind/application/campaigncontext/QuestService.java b/core/src/main/java/com/loremind/application/campaigncontext/QuestService.java index 75d9af2..6d54aff 100644 --- a/core/src/main/java/com/loremind/application/campaigncontext/QuestService.java +++ b/core/src/main/java/com/loremind/application/campaigncontext/QuestService.java @@ -1,11 +1,11 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.Arc; -import com.loremind.domain.campaigncontext.ArcType; -import com.loremind.domain.campaigncontext.Chapter; -import com.loremind.domain.campaigncontext.NodeType; -import com.loremind.domain.campaigncontext.Quest; -import com.loremind.domain.campaigncontext.QuestNodeRef; +import com.loremind.domain.campaigncontext.structure.Arc; +import com.loremind.domain.campaigncontext.structure.ArcType; +import com.loremind.domain.campaigncontext.structure.Chapter; +import com.loremind.domain.campaigncontext.quest.NodeType; +import com.loremind.domain.campaigncontext.quest.Quest; +import com.loremind.domain.campaigncontext.quest.QuestNodeRef; import com.loremind.domain.campaigncontext.ports.ArcRepository; import com.loremind.domain.campaigncontext.ports.ChapterRepository; import com.loremind.domain.campaigncontext.ports.QuestRepository; @@ -119,15 +119,6 @@ public class QuestService { return questRepository.findByCampaignId(campaignId); } - /** Quêtes rattachées à un arc HUB. */ - public List getQuestsByArcId(String arcId) { - return questRepository.findByArcId(arcId); - } - - public List getAllQuests() { - return questRepository.findAll(); - } - /** Met à jour une Quest (Parameter Object pattern, comme ChapterService). */ @Transactional public Quest updateQuest(String id, Quest updated) { @@ -165,9 +156,10 @@ public class QuestService { /** * Supprime la quête et, en cascade, ses {@code QuestProgression} dans toutes les Parties. * - *

TODO (Phase 5) : signaler/nettoyer les {@code Prerequisite.QuestCompleted} pendants - * d'autres quêtes qui pointaient celle-ci. Échec sûr aujourd'hui : un prérequis vers une - * quête supprimée n'est jamais satisfait → la quête dépendante reste LOCKED (pas de corruption).

+ *

Limite connue (nettoyage prévu Phase 5) : les {@code Prerequisite.QuestCompleted} + * d'autres quêtes qui pointaient celle-ci restent pendants, sans être signalés ni + * nettoyés. Échec sûr aujourd'hui : un prérequis vers une quête supprimée n'est jamais + * satisfait → la quête dépendante reste LOCKED (pas de corruption).

*/ @Transactional public void deleteQuest(String id) { @@ -201,15 +193,11 @@ public class QuestService { return nodes != null ? nodes : List.of(); } - public boolean questExists(String id) { - return questRepository.existsById(id); - } - /** Réordonne les quêtes d'une campagne : {@code order} = position. Transactionnel. */ @Transactional public void reorderQuests(String campaignId, List orderedIds) { ReorderSupport.reorder(orderedIds, - id -> questRepository.findById(id).orElse(null), + questRepository::findById, (quest, i) -> { if (campaignId != null && !campaignId.isBlank()) quest.setCampaignId(campaignId); quest.setOrder(i); diff --git a/core/src/main/java/com/loremind/application/campaigncontext/QuestStatusEnricher.java b/core/src/main/java/com/loremind/application/campaigncontext/QuestStatusEnricher.java index 4c1a70d..8ec3010 100644 --- a/core/src/main/java/com/loremind/application/campaigncontext/QuestStatusEnricher.java +++ b/core/src/main/java/com/loremind/application/campaigncontext/QuestStatusEnricher.java @@ -1,9 +1,9 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.PrerequisiteEvaluator; -import com.loremind.domain.campaigncontext.ProgressionStatus; -import com.loremind.domain.campaigncontext.Quest; -import com.loremind.domain.campaigncontext.QuestStatus; +import com.loremind.domain.campaigncontext.quest.PrerequisiteEvaluator; +import com.loremind.domain.campaigncontext.quest.ProgressionStatus; +import com.loremind.domain.campaigncontext.quest.Quest; +import com.loremind.domain.campaigncontext.quest.QuestStatus; import com.loremind.domain.playcontext.QuestProgression; import com.loremind.domain.playcontext.ports.PlaythroughFlagRepository; import com.loremind.domain.playcontext.ports.PlaythroughRepository; diff --git a/core/src/main/java/com/loremind/application/campaigncontext/RandomTableService.java b/core/src/main/java/com/loremind/application/campaigncontext/RandomTableService.java index 82147a6..39e520c 100644 --- a/core/src/main/java/com/loremind/application/campaigncontext/RandomTableService.java +++ b/core/src/main/java/com/loremind/application/campaigncontext/RandomTableService.java @@ -1,13 +1,10 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.Campaign; -import com.loremind.domain.campaigncontext.RandomTable; +import com.loremind.domain.campaigncontext.randomtable.RandomTable; import com.loremind.domain.shared.ReorderSupport; -import com.loremind.domain.campaigncontext.RandomTableEntry; -import com.loremind.domain.campaigncontext.ports.CampaignRepository; +import com.loremind.domain.campaigncontext.randomtable.RandomTableEntry; import com.loremind.domain.campaigncontext.ports.RandomTableGenerator; import com.loremind.domain.campaigncontext.ports.RandomTableRepository; -import com.loremind.domain.gamesystemcontext.ports.GameSystemRepository; import org.springframework.stereotype.Service; import java.util.ArrayList; @@ -22,18 +19,15 @@ public class RandomTableService { private final RandomTableRepository repository; private final RandomTableGenerator generator; - private final CampaignRepository campaignRepository; - private final GameSystemRepository gameSystemRepository; + private final CampaignContextFormatter campaignContextFormatter; public RandomTableService( RandomTableRepository repository, RandomTableGenerator generator, - CampaignRepository campaignRepository, - GameSystemRepository gameSystemRepository) { + CampaignContextFormatter campaignContextFormatter) { this.repository = repository; this.generator = generator; - this.campaignRepository = campaignRepository; - this.gameSystemRepository = gameSystemRepository; + this.campaignContextFormatter = campaignContextFormatter; } public record TableData( @@ -90,8 +84,8 @@ public class RandomTableService { @org.springframework.transaction.annotation.Transactional public void reorderTables(List orderedIds) { ReorderSupport.reorder(orderedIds, - id -> repository.findById(id).orElse(null), - (table, i) -> table.setOrder(i), + repository::findById, + RandomTable::setOrder, repository::save); } @@ -103,7 +97,8 @@ public class RandomTableService { /** Génère une PROPOSITION de table (non persistée) via l'IA, contextualisée campagne. */ public RandomTable generateProposal(String campaignId, String description, String diceFormula) { String formula = (diceFormula == null || diceFormula.isBlank()) ? "1d20" : diceFormula; - RandomTableGenerator.GeneratedTable g = generator.generate(description, formula, buildContext(campaignId)); + RandomTableGenerator.GeneratedTable g = generator.generate( + description, formula, campaignContextFormatter.format(campaignId)); return RandomTable.builder() .name(g.name()) .description(g.description()) @@ -115,24 +110,7 @@ public class RandomTableService { /** Brode un court récit IA sur un résultat tiré (pour la partie). */ public String improviseRoll(String campaignId, String tableName, String resultLabel, String resultDetail) { - return generator.improvise(tableName, resultLabel, resultDetail, buildContext(campaignId)); - } - - /** Contexte libre (nom de campagne + description + système) pour orienter l'IA. */ - private String buildContext(String campaignId) { - if (campaignId == null) return ""; - Campaign campaign = campaignRepository.findById(campaignId).orElse(null); - if (campaign == null) return ""; - StringBuilder sb = new StringBuilder(); - sb.append("Campagne : ").append(campaign.getName()); - if (campaign.getDescription() != null && !campaign.getDescription().isBlank()) { - sb.append(" — ").append(campaign.getDescription().trim()); - } - if (campaign.getGameSystemId() != null && !campaign.getGameSystemId().isBlank()) { - gameSystemRepository.findById(campaign.getGameSystemId()) - .ifPresent(gs -> sb.append("\nSystème de jeu : ").append(gs.getName())); - } - return sb.toString(); + return generator.improvise(tableName, resultLabel, resultDetail, campaignContextFormatter.format(campaignId)); } private static List copyEntries(List entries) { diff --git a/core/src/main/java/com/loremind/application/campaigncontext/ReadinessGap.java b/core/src/main/java/com/loremind/application/campaigncontext/ReadinessGap.java index 1e72c8a..2239a9f 100644 --- a/core/src/main/java/com/loremind/application/campaigncontext/ReadinessGap.java +++ b/core/src/main/java/com/loremind/application/campaigncontext/ReadinessGap.java @@ -1,7 +1,7 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.ReadinessEntityType; -import com.loremind.domain.campaigncontext.ReadinessSeverity; +import com.loremind.domain.campaigncontext.readiness.ReadinessEntityType; +import com.loremind.domain.campaigncontext.readiness.ReadinessSeverity; /** * Un manque de préparation détecté sur une entité de scénario (read-model, Pilier B). diff --git a/core/src/main/java/com/loremind/application/campaigncontext/SceneService.java b/core/src/main/java/com/loremind/application/campaigncontext/SceneService.java index aac895b..b780f7d 100644 --- a/core/src/main/java/com/loremind/application/campaigncontext/SceneService.java +++ b/core/src/main/java/com/loremind/application/campaigncontext/SceneService.java @@ -1,10 +1,10 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.FieldProposal; -import com.loremind.domain.campaigncontext.Scene; -import com.loremind.domain.campaigncontext.SceneDraft; +import com.loremind.domain.campaigncontext.generation.FieldProposal; +import com.loremind.domain.campaigncontext.structure.Scene; +import com.loremind.domain.campaigncontext.generation.SceneDraft; import com.loremind.domain.shared.ReorderSupport; -import com.loremind.domain.campaigncontext.SceneBranch; +import com.loremind.domain.campaigncontext.structure.SceneBranch; import com.loremind.domain.campaigncontext.ports.SceneRepository; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; @@ -171,7 +171,7 @@ public class SceneService { if (id.equals(sibling.getId()) || branches == null || branches.isEmpty()) continue; List kept = branches.stream() .filter(b -> !id.equals(b.targetSceneId())) - .collect(Collectors.toList()); + .toList(); if (kept.size() != branches.size()) { sibling.setBranches(kept); sceneRepository.save(sibling); @@ -193,7 +193,7 @@ public class SceneService { @Transactional public void reorderScenes(String chapterId, List orderedIds) { ReorderSupport.reorder(orderedIds, - id -> sceneRepository.findById(id).orElse(null), + sceneRepository::findById, (scene, i) -> { if (chapterId != null && !chapterId.isBlank() && !chapterId.equals(scene.getChapterId())) { scene.setChapterId(chapterId); diff --git a/core/src/main/java/com/loremind/application/campaigncontext/sedFH3XAk b/core/src/main/java/com/loremind/application/campaigncontext/sedFH3XAk new file mode 100644 index 0000000..06f148d --- /dev/null +++ b/core/src/main/java/com/loremind/application/campaigncontext/sedFH3XAk @@ -0,0 +1,45 @@ +package com.loremind.application.campaigncontext; + +import com.loremind.domain.campaigncontext.Prerequisite; +import com.loremind.domain.campaigncontext.quest.Quest; +import com.loremind.domain.campaigncontext.ports.QuestRepository; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.TreeSet; + +/** + * Service applicatif : énumère les noms de faits ({@link Prerequisite.FlagSet}) + * référencés par les quêtes d'une Campagne. + * + *

Modèle "déclaration implicite" : il n'existe pas de table de faits déclarés + * globalement. Un fait existe dès qu'au moins une quête le référence dans ses + * prérequis. Ce service expose la liste dédupliquée pour les UIs (toggle dans + * la Partie, autocomplete dans l'éditeur de prérequis de quête).

+ * + *

Niveau 1 : lit désormais les quêtes (entité de première classe), plus les + * chapitres HUB.

+ */ +@Service +public class CampaignReferencedFlagsService { + + private final QuestRepository questRepository; + + public CampaignReferencedFlagsService(QuestRepository questRepository) { + this.questRepository = questRepository; + } + + /** Retourne la liste triée alphabétiquement des noms de faits référencés. */ + public List listForCampaign(String campaignId) { + TreeSet unique = new TreeSet<>(); + for (Quest quest : questRepository.findByCampaignId(campaignId)) { + if (quest.getPrerequisites() == null) continue; + for (Prerequisite p : quest.getPrerequisites()) { + if (p instanceof Prerequisite.FlagSet f && f.flagName() != null && !f.flagName().isBlank()) { + unique.add(f.flagName()); + } + } + } + return List.copyOf(unique); + } +} diff --git a/core/src/main/java/com/loremind/application/files/StoredFileService.java b/core/src/main/java/com/loremind/application/files/StoredFileService.java index 6643865..0b864e8 100644 --- a/core/src/main/java/com/loremind/application/files/StoredFileService.java +++ b/core/src/main/java/com/loremind/application/files/StoredFileService.java @@ -7,6 +7,7 @@ import org.springframework.stereotype.Service; import java.io.InputStream; import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.Optional; import java.util.Set; @@ -24,18 +25,18 @@ import java.util.Set; @Service public class StoredFileService { + private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream"; + /** Types MIME explicitement autorises (en plus de tout {@code image/*} et {@code video/*}). */ private static final Set ALLOWED_EXACT_MIME = Set.of( "application/json", - "application/octet-stream", + DEFAULT_CONTENT_TYPE, "text/plain" ); /** Coherent avec spring.servlet.multipart.max-file-size (application.properties). */ private static final long MAX_SIZE_BYTES = 128L * 1024 * 1024; // 128 Mo - private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream"; - private final StoredFileRepository repository; private final FileStorage storage; @@ -60,7 +61,7 @@ public class StoredFileService { .contentType(resolvedType) .sizeBytes(sizeBytes) .storageKey(storageKey) - .uploadedAt(LocalDateTime.now()) + .uploadedAt(LocalDateTime.now(ZoneId.systemDefault())) .build(); return repository.save(file); } catch (RuntimeException ex) { diff --git a/core/src/main/java/com/loremind/application/gamesystemcontext/GameSystemContextBuilder.java b/core/src/main/java/com/loremind/application/gamesystemcontext/GameSystemContextBuilder.java index 4b05b1f..af497ea 100644 --- a/core/src/main/java/com/loremind/application/gamesystemcontext/GameSystemContextBuilder.java +++ b/core/src/main/java/com/loremind/application/gamesystemcontext/GameSystemContextBuilder.java @@ -27,8 +27,13 @@ import java.util.regex.Pattern; @Service public class GameSystemContextBuilder { - /** Matche "## Titre" en début de ligne (multiline). Capture le titre en groupe 1. */ - private static final Pattern H2_HEADER = Pattern.compile("(?m)^##\\s+(.+?)\\s*$"); + /** + * Matche "## Titre" en début de ligne (multiline). Capture le titre en groupe 1. + * Le titre est forcé à commencer par un caractère non-blanc ({@code \S.*}) : la + * frontière entre les espaces de tête et le titre devient non ambiguë (classes de + * caractères disjointes), ce qui élimine tout risque de backtracking polynomial. + */ + private static final Pattern H2_HEADER = Pattern.compile("(?m)^##\\s+(\\S.*)$"); private final GameSystemRepository gameSystemRepository; diff --git a/core/src/main/java/com/loremind/application/gamesystemcontext/GameSystemService.java b/core/src/main/java/com/loremind/application/gamesystemcontext/GameSystemService.java index 33de436..86b8787 100644 --- a/core/src/main/java/com/loremind/application/gamesystemcontext/GameSystemService.java +++ b/core/src/main/java/com/loremind/application/gamesystemcontext/GameSystemService.java @@ -119,27 +119,29 @@ public class GameSystemService { List template = new ArrayList<>(); Set usedNames = new HashSet<>(); - for (FoundryStructField f : fields == null ? List.of() : fields) { - if (f.path() == null || f.path().isBlank()) continue; - String label = (f.label() != null && !f.label().isBlank()) ? f.label().trim() : f.path(); - // Nom unique : libellé si libre, sinon le chemin (toujours unique). - String name = usedNames.contains(label.toLowerCase()) ? f.path() : label; - if (usedNames.contains(name.toLowerCase())) continue; - usedNames.add(name.toLowerCase()); - FieldType type = "number".equalsIgnoreCase(f.type()) ? FieldType.NUMBER : FieldType.TEXT; - template.add(new TemplateField(name, type, null, null, f.path())); + for (FoundryStructField f : fields != null ? fields : List.of()) { + TemplateField field = toTemplateField(f, usedNames); + if (field != null) template.add(field); } gs.replaceEnemyTemplate(template); gs.setFoundryActorType(normalize(actorType)); return gameSystemRepository.save(gs); } - public void deleteGameSystem(String id) { - gameSystemRepository.deleteById(id); + /** Convertit un champ Foundry en TemplateField, ou null si son chemin est vide ou son nom déjà pris. */ + private static TemplateField toTemplateField(FoundryStructField f, Set usedNames) { + if (f.path() == null || f.path().isBlank()) return null; + String label = (f.label() != null && !f.label().isBlank()) ? f.label().trim() : f.path(); + // Nom unique : libellé si libre, sinon le chemin (toujours unique). + String name = usedNames.contains(label.toLowerCase()) ? f.path() : label; + if (usedNames.contains(name.toLowerCase())) return null; + usedNames.add(name.toLowerCase()); + FieldType type = "number".equalsIgnoreCase(f.type()) ? FieldType.NUMBER : FieldType.TEXT; + return new TemplateField(name, type, null, null, f.path()); } - public boolean gameSystemExists(String id) { - return gameSystemRepository.existsById(id); + public void deleteGameSystem(String id) { + gameSystemRepository.deleteById(id); } public List searchGameSystems(String query) { diff --git a/core/src/main/java/com/loremind/application/generationcontext/CampaignStructuralContextBuilder.java b/core/src/main/java/com/loremind/application/generationcontext/CampaignStructuralContextBuilder.java index 3298ac3..b7670ea 100644 --- a/core/src/main/java/com/loremind/application/generationcontext/CampaignStructuralContextBuilder.java +++ b/core/src/main/java/com/loremind/application/generationcontext/CampaignStructuralContextBuilder.java @@ -1,12 +1,12 @@ package com.loremind.application.generationcontext; -import com.loremind.domain.campaigncontext.Arc; -import com.loremind.domain.campaigncontext.ArcType; +import com.loremind.domain.campaigncontext.structure.Arc; +import com.loremind.domain.campaigncontext.structure.ArcType; import com.loremind.domain.campaigncontext.Campaign; -import com.loremind.domain.campaigncontext.Chapter; -import com.loremind.domain.campaigncontext.Character; -import com.loremind.domain.campaigncontext.Npc; -import com.loremind.domain.campaigncontext.Scene; +import com.loremind.domain.campaigncontext.structure.Chapter; +import com.loremind.domain.campaigncontext.bestiary.Character; +import com.loremind.domain.campaigncontext.bestiary.Npc; +import com.loremind.domain.campaigncontext.structure.Scene; import com.loremind.domain.campaigncontext.ports.ArcRepository; import com.loremind.domain.campaigncontext.ports.CampaignRepository; import com.loremind.domain.campaigncontext.ports.ChapterRepository; @@ -93,26 +93,26 @@ public class CampaignStructuralContextBuilder { // les enemyIds des pièces sans N+1 sur le repo. Map enemyLabelById = enemyRepository.findByCampaignId(campaignId).stream() .collect(Collectors.toMap( - com.loremind.domain.campaigncontext.Enemy::getId, + com.loremind.domain.campaigncontext.bestiary.Enemy::getId, CampaignStructuralContextBuilder::enemyLabel, (a, b) -> a)); List arcs = arcRepository.findByCampaignId(campaignId).stream() .sorted(Comparator.comparingInt(Arc::getOrder)) .map(arc -> toArcSummary(arc, enemyLabelById)) - .collect(Collectors.toList()); + .toList(); List characters = (playthroughId == null || playthroughId.isBlank()) ? List.of() : characterRepository.findByPlaythroughId(playthroughId).stream() .sorted(Comparator.comparingInt(Character::getOrder)) .map(this::toCharacterSummary) - .collect(Collectors.toList()); + .toList(); List npcs = npcRepository.findByCampaignId(campaignId).stream() .sorted(Comparator.comparingInt(Npc::getOrder)) .map(this::toNpcSummary) - .collect(Collectors.toList()); + .toList(); return new CampaignStructuralContext( campaign.getName(), @@ -140,27 +140,36 @@ public class CampaignStructuralContextBuilder { * Snippet pour le resume IA : 1re ligne signifiante de la 1re valeur non vide * du template (refonte 2026-04-30 — remplace l'ancien parsing markdown). */ - private static String extractSnippet(java.util.Map values) { + private static String extractSnippet(Map values) { if (values == null || values.isEmpty()) return ""; - for (String value : values.values()) { - if (value == null || value.isBlank()) continue; - String firstLine = value.lines() - .map(String::strip) - .filter(l -> !l.isEmpty() && !l.startsWith("#")) - .findFirst() - .orElse(""); - if (firstLine.isEmpty()) continue; - if (firstLine.length() <= CHARACTER_SNIPPET_MAX_LEN) return firstLine; - return firstLine.substring(0, CHARACTER_SNIPPET_MAX_LEN - 1).stripTrailing() + "…"; - } - return ""; + return values.values().stream() + .filter(v -> v != null && !v.isBlank()) + .map(CampaignStructuralContextBuilder::firstMeaningfulLine) + .filter(l -> !l.isEmpty()) + .findFirst() + .map(CampaignStructuralContextBuilder::truncateSnippet) + .orElse(""); + } + + /** 1re ligne non vide et non-titre ("# ...") d'une valeur de template, ou "" si aucune. */ + private static String firstMeaningfulLine(String value) { + return value.lines() + .map(String::strip) + .filter(l -> !l.isEmpty() && !l.startsWith("#")) + .findFirst() + .orElse(""); + } + + private static String truncateSnippet(String firstLine) { + if (firstLine.length() <= CHARACTER_SNIPPET_MAX_LEN) return firstLine; + return firstLine.substring(0, CHARACTER_SNIPPET_MAX_LEN - 1).stripTrailing() + "…"; } private ArcSummary toArcSummary(Arc arc, Map enemyLabelById) { List chapters = chapterRepository.findByArcId(arc.getId()).stream() .sorted(Comparator.comparingInt(Chapter::getOrder)) .map(chapter -> toChapterSummary(chapter, enemyLabelById)) - .collect(Collectors.toList()); + .toList(); return new ArcSummary( arc.getName(), arc.getDescription(), @@ -181,7 +190,7 @@ public class CampaignStructuralContextBuilder { List summaries = scenes.stream() .map(s -> toSceneSummary(s, nameById, enemyLabelById)) - .collect(Collectors.toList()); + .toList(); return new ChapterSummary( chapter.getName(), @@ -199,7 +208,7 @@ public class CampaignStructuralContextBuilder { b.label(), nameById.getOrDefault(b.targetSceneId(), "(scène inconnue)"), b.condition())) - .collect(Collectors.toList()); + .toList(); List rooms = toRoomSummaries(scene, enemyLabelById); @@ -221,8 +230,8 @@ public class CampaignStructuralContextBuilder { if (scene.getRooms() == null || scene.getRooms().isEmpty()) return List.of(); Map nameById = scene.getRooms().stream() .collect(Collectors.toMap( - com.loremind.domain.campaigncontext.Room::getId, - com.loremind.domain.campaigncontext.Room::getName, + com.loremind.domain.campaigncontext.structure.Room::getId, + com.loremind.domain.campaigncontext.structure.Room::getName, (a, b) -> a)); return scene.getRooms().stream() .map(r -> { @@ -233,12 +242,12 @@ public class CampaignStructuralContextBuilder { b.label(), nameById.getOrDefault(b.targetRoomId(), "(pièce inconnue)"), b.condition())) - .collect(Collectors.toList()); + .toList(); return new RoomSummary( r.getName(), r.getFloor(), r.getDescription(), roomEnemiesText(r, enemyLabelById), hints); }) - .collect(Collectors.toList()); + .toList(); } /** @@ -247,7 +256,7 @@ public class CampaignStructuralContextBuilder { * libre. L'un ou l'autre peut être vide. */ private static String roomEnemiesText( - com.loremind.domain.campaigncontext.Room room, Map enemyLabelById) { + com.loremind.domain.campaigncontext.structure.Room room, Map enemyLabelById) { String linked = room.getEnemyIds() == null ? "" : room.getEnemyIds().stream() .map(enemyLabelById::get) .filter(l -> l != null && !l.isBlank()) @@ -259,7 +268,7 @@ public class CampaignStructuralContextBuilder { } /** Libellé court d'une fiche du bestiaire : « Nom (niveau) » ou « Nom ». */ - private static String enemyLabel(com.loremind.domain.campaigncontext.Enemy enemy) { + private static String enemyLabel(com.loremind.domain.campaigncontext.bestiary.Enemy enemy) { String level = enemy.getLevel() == null ? "" : enemy.getLevel().strip(); return level.isEmpty() ? enemy.getName() : enemy.getName() + " (" + level + ")"; } diff --git a/core/src/main/java/com/loremind/application/generationcontext/DraftScenesUseCase.java b/core/src/main/java/com/loremind/application/generationcontext/DraftScenesUseCase.java index efda8cb..c61a713 100644 --- a/core/src/main/java/com/loremind/application/generationcontext/DraftScenesUseCase.java +++ b/core/src/main/java/com/loremind/application/generationcontext/DraftScenesUseCase.java @@ -1,14 +1,13 @@ package com.loremind.application.generationcontext; -import com.loremind.domain.campaigncontext.Chapter; -import com.loremind.domain.campaigncontext.Scene; -import com.loremind.domain.campaigncontext.SceneDraft; -import com.loremind.domain.campaigncontext.SceneDraftProposal; -import com.loremind.domain.campaigncontext.ports.CampaignRepository; +import com.loremind.application.campaigncontext.CampaignContextFormatter; +import com.loremind.domain.campaigncontext.structure.Chapter; +import com.loremind.domain.campaigncontext.structure.Scene; +import com.loremind.domain.campaigncontext.generation.SceneDraft; +import com.loremind.domain.campaigncontext.generation.SceneDraftProposal; import com.loremind.domain.campaigncontext.ports.ChapterRepository; import com.loremind.domain.campaigncontext.ports.SceneDraftAssistant; import com.loremind.domain.campaigncontext.ports.SceneRepository; -import com.loremind.domain.gamesystemcontext.ports.GameSystemRepository; import org.springframework.stereotype.Service; import java.util.List; @@ -30,19 +29,16 @@ public class DraftScenesUseCase { private final ChapterRepository chapterRepository; private final SceneRepository sceneRepository; - private final CampaignRepository campaignRepository; - private final GameSystemRepository gameSystemRepository; + private final CampaignContextFormatter campaignContextFormatter; private final SceneDraftAssistant assistant; public DraftScenesUseCase(ChapterRepository chapterRepository, SceneRepository sceneRepository, - CampaignRepository campaignRepository, - GameSystemRepository gameSystemRepository, + CampaignContextFormatter campaignContextFormatter, SceneDraftAssistant assistant) { this.chapterRepository = chapterRepository; this.sceneRepository = sceneRepository; - this.campaignRepository = campaignRepository; - this.gameSystemRepository = gameSystemRepository; + this.campaignContextFormatter = campaignContextFormatter; this.assistant = assistant; } @@ -55,13 +51,13 @@ public class DraftScenesUseCase { List drafts = assistant.draftScenes(context, instruction, n).stream() .filter(d -> d != null && d.name() != null && !d.name().isBlank()) .limit(n) - .collect(Collectors.toList()); + .toList(); return new SceneDraftProposal(chapterId, drafts); } private String buildContext(Chapter chapter, String campaignId) { StringBuilder sb = new StringBuilder(); - sb.append("Chapitre : ").append(blankToLabel(chapter.getName(), "(sans titre)")).append("\n"); + sb.append("Chapitre : ").append(blankToLabel(chapter.getName())).append("\n"); appendIf(sb, "Synopsis", chapter.getDescription()); appendIf(sb, "Objectifs des joueurs", chapter.getPlayerObjectives()); appendIf(sb, "Enjeux narratifs", chapter.getNarrativeStakes()); @@ -78,17 +74,10 @@ public class DraftScenesUseCase { } if (campaignId != null && !campaignId.isBlank()) { - campaignRepository.findById(campaignId).ifPresent(c -> { - sb.append("Campagne : ").append(c.getName()); - if (c.getDescription() != null && !c.getDescription().isBlank()) { - sb.append(" — ").append(c.getDescription().trim()); - } - sb.append("\n"); - if (c.getGameSystemId() != null && !c.getGameSystemId().isBlank()) { - gameSystemRepository.findById(c.getGameSystemId()) - .ifPresent(gs -> sb.append("Système de jeu : ").append(gs.getName()).append("\n")); - } - }); + String campaignBlock = campaignContextFormatter.format(campaignId); + if (!campaignBlock.isBlank()) { + sb.append(campaignBlock).append("\n"); + } } return sb.toString(); } @@ -99,7 +88,7 @@ public class DraftScenesUseCase { } } - private static String blankToLabel(String value, String fallback) { - return value == null || value.isBlank() ? fallback : value; + private static String blankToLabel(String value) { + return value == null || value.isBlank() ? "(sans titre)" : value; } } diff --git a/core/src/main/java/com/loremind/application/generationcontext/GeneratePageValuesUseCase.java b/core/src/main/java/com/loremind/application/generationcontext/GeneratePageValuesUseCase.java index a676055..9f56c66 100644 --- a/core/src/main/java/com/loremind/application/generationcontext/GeneratePageValuesUseCase.java +++ b/core/src/main/java/com/loremind/application/generationcontext/GeneratePageValuesUseCase.java @@ -30,6 +30,8 @@ import java.util.Map; @Service public class GeneratePageValuesUseCase { + private static final String FOR_PAGE = ") pour la page "; + private final PageRepository pageRepository; private final TemplateRepository templateRepository; private final LoreRepository loreRepository; @@ -96,22 +98,19 @@ public class GeneratePageValuesUseCase { } return templateRepository.findById(templateId) .orElseThrow(() -> new IllegalStateException( - "Template introuvable (id=" + templateId - + ") pour la page " + pageId)); + "Template introuvable (id=" + templateId + FOR_PAGE + pageId)); } private Lore loadLore(String loreId, String pageId) { return loreRepository.findById(loreId) .orElseThrow(() -> new IllegalStateException( - "Lore introuvable (id=" + loreId - + ") pour la page " + pageId)); + "Lore introuvable (id=" + loreId + FOR_PAGE + pageId)); } private LoreNode loadFolder(String nodeId, String pageId) { return loreNodeRepository.findById(nodeId) .orElseThrow(() -> new IllegalStateException( - "Dossier parent introuvable (id=" + nodeId - + ") pour la page " + pageId)); + "Dossier parent introuvable (id=" + nodeId + FOR_PAGE + pageId)); } private void requireNonEmptyFields(Template template) { diff --git a/core/src/main/java/com/loremind/application/generationcontext/LoreStructuralContextBuilder.java b/core/src/main/java/com/loremind/application/generationcontext/LoreStructuralContextBuilder.java index 32d58ad..9dd33a2 100644 --- a/core/src/main/java/com/loremind/application/generationcontext/LoreStructuralContextBuilder.java +++ b/core/src/main/java/com/loremind/application/generationcontext/LoreStructuralContextBuilder.java @@ -110,7 +110,7 @@ public class LoreStructuralContextBuilder { return allPages.stream() .filter(p -> nodeId.equals(p.getNodeId())) .map(p -> toPageSummary(p, templateNameById, pageTitleById)) - .collect(Collectors.toList()); + .toList(); } private PageSummary toPageSummary( @@ -160,7 +160,7 @@ public class LoreStructuralContextBuilder { return relatedIds.stream() .map(pageTitleById::get) .filter(title -> title != null && !title.isBlank()) - .collect(Collectors.toList()); + .toList(); } private List extractUniqueTags(List pages) { @@ -168,6 +168,6 @@ public class LoreStructuralContextBuilder { .filter(p -> p.getTags() != null) .flatMap(p -> p.getTags().stream()) .distinct() - .collect(Collectors.toList()); + .toList(); } } diff --git a/core/src/main/java/com/loremind/application/generationcontext/NarrativeAssistFieldsUseCase.java b/core/src/main/java/com/loremind/application/generationcontext/NarrativeAssistFieldsUseCase.java index 1afacff..85b5ed1 100644 --- a/core/src/main/java/com/loremind/application/generationcontext/NarrativeAssistFieldsUseCase.java +++ b/core/src/main/java/com/loremind/application/generationcontext/NarrativeAssistFieldsUseCase.java @@ -1,10 +1,9 @@ package com.loremind.application.generationcontext; -import com.loremind.domain.campaigncontext.EntityFieldPatchProposal; -import com.loremind.domain.campaigncontext.FieldProposal; -import com.loremind.domain.campaigncontext.ports.CampaignRepository; +import com.loremind.application.campaigncontext.CampaignContextFormatter; +import com.loremind.domain.campaigncontext.generation.EntityFieldPatchProposal; +import com.loremind.domain.campaigncontext.generation.FieldProposal; import com.loremind.domain.campaigncontext.ports.NarrativeFieldAssistant; -import com.loremind.domain.gamesystemcontext.ports.GameSystemRepository; import org.springframework.stereotype.Service; import java.util.ArrayList; @@ -26,18 +25,15 @@ import java.util.stream.Collectors; public class NarrativeAssistFieldsUseCase { private final NarrativeFieldCatalog catalog; - private final CampaignRepository campaignRepository; - private final GameSystemRepository gameSystemRepository; + private final CampaignContextFormatter campaignContextFormatter; private final NarrativeFieldAssistant assistant; public NarrativeAssistFieldsUseCase( NarrativeFieldCatalog catalog, - CampaignRepository campaignRepository, - GameSystemRepository gameSystemRepository, + CampaignContextFormatter campaignContextFormatter, NarrativeFieldAssistant assistant) { this.catalog = catalog; - this.campaignRepository = campaignRepository; - this.gameSystemRepository = gameSystemRepository; + this.campaignContextFormatter = campaignContextFormatter; this.assistant = assistant; } @@ -46,7 +42,7 @@ public class NarrativeAssistFieldsUseCase { List specs = snap.defs().stream() .map(d -> new NarrativeFieldAssistant.FieldSpec(d.key(), d.label())) - .collect(Collectors.toList()); + .toList(); Set allowed = snap.defs().stream() .map(NarrativeFieldCatalog.FieldDef::key).collect(Collectors.toSet()); @@ -56,19 +52,26 @@ public class NarrativeAssistFieldsUseCase { List fields = new ArrayList<>(); for (NarrativeFieldAssistant.ProposedField pf : proposed) { - if (pf.key() == null || !allowed.contains(pf.key())) continue; - if (pf.value() == null || pf.value().isBlank()) continue; - String current = snap.current().getOrDefault(pf.key(), ""); - fields.add(new FieldProposal(pf.key(), current, pf.value())); + FieldProposal proposal = toFieldProposal(pf, allowed, snap); + if (proposal != null) fields.add(proposal); } return new EntityFieldPatchProposal(snap.entityType(), entityId, "patch", fields); } + /** Convertit un champ proposé par l'IA en FieldProposal, ou null si sa clé n'est pas autorisée ou sa valeur vide. */ + private static FieldProposal toFieldProposal( + NarrativeFieldAssistant.ProposedField pf, Set allowed, NarrativeFieldCatalog.Snapshot snap) { + if (pf.key() == null || !allowed.contains(pf.key())) return null; + if (pf.value() == null || pf.value().isBlank()) return null; + String current = snap.current().getOrDefault(pf.key(), ""); + return new FieldProposal(pf.key(), current, pf.value()); + } + /** Contexte compact : type + titre + valeurs actuelles non vides + méta campagne. */ private String buildContext(String campaignId, NarrativeFieldCatalog.Snapshot snap) { StringBuilder sb = new StringBuilder(); sb.append(entityLabel(snap.entityType())).append(" : ") - .append(blankToLabel(snap.title(), "(sans titre)")).append("\n"); + .append(blankToLabel(snap.title())).append("\n"); sb.append("État actuel des champs :\n"); boolean any = false; for (Map.Entry e : snap.current().entrySet()) { @@ -82,17 +85,10 @@ public class NarrativeAssistFieldsUseCase { sb.append("- (tous les champs sont vides — à créer de zéro)\n"); } if (campaignId != null && !campaignId.isBlank()) { - campaignRepository.findById(campaignId).ifPresent(c -> { - sb.append("Campagne : ").append(c.getName()); - if (c.getDescription() != null && !c.getDescription().isBlank()) { - sb.append(" — ").append(c.getDescription().trim()); - } - sb.append("\n"); - if (c.getGameSystemId() != null && !c.getGameSystemId().isBlank()) { - gameSystemRepository.findById(c.getGameSystemId()) - .ifPresent(gs -> sb.append("Système de jeu : ").append(gs.getName()).append("\n")); - } - }); + String campaignBlock = campaignContextFormatter.format(campaignId); + if (!campaignBlock.isBlank()) { + sb.append(campaignBlock).append("\n"); + } } return sb.toString(); } @@ -106,7 +102,7 @@ public class NarrativeAssistFieldsUseCase { }; } - private static String blankToLabel(String value, String fallback) { - return value == null || value.isBlank() ? fallback : value; + private static String blankToLabel(String value) { + return value == null || value.isBlank() ? "(sans titre)" : value; } } diff --git a/core/src/main/java/com/loremind/application/generationcontext/NarrativeEntityContextBuilder.java b/core/src/main/java/com/loremind/application/generationcontext/NarrativeEntityContextBuilder.java index 8fddbf7..d9e07c7 100644 --- a/core/src/main/java/com/loremind/application/generationcontext/NarrativeEntityContextBuilder.java +++ b/core/src/main/java/com/loremind/application/generationcontext/NarrativeEntityContextBuilder.java @@ -1,10 +1,10 @@ package com.loremind.application.generationcontext; -import com.loremind.domain.campaigncontext.Arc; -import com.loremind.domain.campaigncontext.Chapter; -import com.loremind.domain.campaigncontext.Character; -import com.loremind.domain.campaigncontext.Npc; -import com.loremind.domain.campaigncontext.Scene; +import com.loremind.domain.campaigncontext.structure.Arc; +import com.loremind.domain.campaigncontext.structure.Chapter; +import com.loremind.domain.campaigncontext.bestiary.Character; +import com.loremind.domain.campaigncontext.bestiary.Npc; +import com.loremind.domain.campaigncontext.structure.Scene; import com.loremind.domain.campaigncontext.ports.ArcRepository; import com.loremind.domain.campaigncontext.ports.ChapterRepository; import com.loremind.domain.campaigncontext.ports.CharacterRepository; @@ -28,6 +28,9 @@ import java.util.Map; @Component public class NarrativeEntityContextBuilder { + /** Longueur max d'une valeur de stat dans le résumé d'ennemi lié (contexte focus compact). */ + private static final int STAT_VALUE_MAX_LEN = 100; + private final ArcRepository arcRepository; private final ChapterRepository chapterRepository; private final SceneRepository sceneRepository; @@ -144,14 +147,14 @@ public class NarrativeEntityContextBuilder { StringBuilder sb = new StringBuilder(); for (String enemyId : s.getEnemyIds()) { enemyRepository.findById(enemyId).ifPresent(e -> { - if (sb.length() > 0) sb.append("\n"); + if (!sb.isEmpty()) sb.append("\n"); sb.append("- ").append(e.getName()); if (e.getLevel() != null && !e.getLevel().isBlank()) { sb.append(" (").append(e.getLevel().trim()).append(")"); } String stats = e.getValues().entrySet().stream() .filter(en -> en.getValue() != null && !en.getValue().isBlank()) - .map(en -> en.getKey() + ": " + truncate(en.getValue().trim(), 100)) + .map(en -> en.getKey() + ": " + truncate(en.getValue().trim())) .collect(java.util.stream.Collectors.joining(" ; ")); if (!stats.isEmpty()) sb.append(" — ").append(stats); }); @@ -159,8 +162,8 @@ public class NarrativeEntityContextBuilder { return sb.toString(); } - private static String truncate(String value, int maxLen) { - return value.length() <= maxLen ? value : value.substring(0, maxLen - 1).stripTrailing() + "…"; + private static String truncate(String value) { + return value.length() <= STAT_VALUE_MAX_LEN ? value : value.substring(0, STAT_VALUE_MAX_LEN - 1).stripTrailing() + "…"; } private NarrativeEntityContext fromCharacter(Character c) { diff --git a/core/src/main/java/com/loremind/application/generationcontext/NarrativeFieldCatalog.java b/core/src/main/java/com/loremind/application/generationcontext/NarrativeFieldCatalog.java index 79af600..548a3f8 100644 --- a/core/src/main/java/com/loremind/application/generationcontext/NarrativeFieldCatalog.java +++ b/core/src/main/java/com/loremind/application/generationcontext/NarrativeFieldCatalog.java @@ -1,8 +1,8 @@ package com.loremind.application.generationcontext; -import com.loremind.domain.campaigncontext.Arc; -import com.loremind.domain.campaigncontext.Chapter; -import com.loremind.domain.campaigncontext.Scene; +import com.loremind.domain.campaigncontext.structure.Arc; +import com.loremind.domain.campaigncontext.structure.Chapter; +import com.loremind.domain.campaigncontext.structure.Scene; import com.loremind.domain.campaigncontext.ports.ArcRepository; import com.loremind.domain.campaigncontext.ports.ChapterRepository; import com.loremind.domain.campaigncontext.ports.SceneRepository; @@ -29,22 +29,25 @@ public class NarrativeFieldCatalog { public record Snapshot(String entityType, String title, LinkedHashMap current, List defs) {} + private static final String FIELD_DESCRIPTION = "description"; + private static final String FIELD_GM_NOTES = "gmNotes"; + private static final List ARC_DEFS = List.of( - new FieldDef("description", "description / synopsis de l'arc"), + new FieldDef(FIELD_DESCRIPTION, "description / synopsis de l'arc"), new FieldDef("themes", "thèmes explorés"), new FieldDef("stakes", "enjeux globaux pour les personnages"), new FieldDef("rewards", "récompenses et progression"), new FieldDef("resolution", "dénouement prévu"), - new FieldDef("gmNotes", "notes privées du MJ")); + new FieldDef(FIELD_GM_NOTES, "notes privées du MJ")); private static final List CHAPTER_DEFS = List.of( - new FieldDef("description", "synopsis du chapitre"), + new FieldDef(FIELD_DESCRIPTION, "synopsis du chapitre"), new FieldDef("playerObjectives", "objectifs des joueurs"), new FieldDef("narrativeStakes", "enjeux narratifs dramatiques"), - new FieldDef("gmNotes", "notes privées du MJ")); + new FieldDef(FIELD_GM_NOTES, "notes privées du MJ")); private static final List SCENE_DEFS = List.of( - new FieldDef("description", "description courte de la scène"), + new FieldDef(FIELD_DESCRIPTION, "description courte de la scène"), new FieldDef("location", "lieu où se déroule la scène"), new FieldDef("timing", "moment / temporalité"), new FieldDef("atmosphere", "ambiance (sons, odeurs, émotions, lumière)"), @@ -80,12 +83,12 @@ public class NarrativeFieldCatalog { Arc a = arcRepository.findById(id) .orElseThrow(() -> new IllegalArgumentException("Arc non trouvé: " + id)); LinkedHashMap cur = new LinkedHashMap<>(); - cur.put("description", nz(a.getDescription())); + cur.put(FIELD_DESCRIPTION, nz(a.getDescription())); cur.put("themes", nz(a.getThemes())); cur.put("stakes", nz(a.getStakes())); cur.put("rewards", nz(a.getRewards())); cur.put("resolution", nz(a.getResolution())); - cur.put("gmNotes", nz(a.getGmNotes())); + cur.put(FIELD_GM_NOTES, nz(a.getGmNotes())); return new Snapshot("arc", a.getName(), cur, ARC_DEFS); } @@ -93,10 +96,10 @@ public class NarrativeFieldCatalog { Chapter c = chapterRepository.findById(id) .orElseThrow(() -> new IllegalArgumentException("Chapitre non trouvé: " + id)); LinkedHashMap cur = new LinkedHashMap<>(); - cur.put("description", nz(c.getDescription())); + cur.put(FIELD_DESCRIPTION, nz(c.getDescription())); cur.put("playerObjectives", nz(c.getPlayerObjectives())); cur.put("narrativeStakes", nz(c.getNarrativeStakes())); - cur.put("gmNotes", nz(c.getGmNotes())); + cur.put(FIELD_GM_NOTES, nz(c.getGmNotes())); return new Snapshot("chapter", c.getName(), cur, CHAPTER_DEFS); } @@ -104,7 +107,7 @@ public class NarrativeFieldCatalog { Scene s = sceneRepository.findById(id) .orElseThrow(() -> new IllegalArgumentException("Scène non trouvée: " + id)); LinkedHashMap cur = new LinkedHashMap<>(); - cur.put("description", nz(s.getDescription())); + cur.put(FIELD_DESCRIPTION, nz(s.getDescription())); cur.put("location", nz(s.getLocation())); cur.put("timing", nz(s.getTiming())); cur.put("atmosphere", nz(s.getAtmosphere())); diff --git a/core/src/main/java/com/loremind/application/generationcontext/SessionStructuralContextBuilder.java b/core/src/main/java/com/loremind/application/generationcontext/SessionStructuralContextBuilder.java index 05b4953..99265ca 100644 --- a/core/src/main/java/com/loremind/application/generationcontext/SessionStructuralContextBuilder.java +++ b/core/src/main/java/com/loremind/application/generationcontext/SessionStructuralContextBuilder.java @@ -1,9 +1,9 @@ package com.loremind.application.generationcontext; -import com.loremind.domain.campaigncontext.PrerequisiteEvaluator; -import com.loremind.domain.campaigncontext.ProgressionStatus; -import com.loremind.domain.campaigncontext.Quest; -import com.loremind.domain.campaigncontext.QuestStatus; +import com.loremind.domain.campaigncontext.quest.PrerequisiteEvaluator; +import com.loremind.domain.campaigncontext.quest.ProgressionStatus; +import com.loremind.domain.campaigncontext.quest.Quest; +import com.loremind.domain.campaigncontext.quest.QuestStatus; import com.loremind.domain.campaigncontext.ports.QuestRepository; import com.loremind.domain.generationcontext.SessionContext; import com.loremind.domain.generationcontext.SessionContext.JournalEntrySummary; @@ -26,7 +26,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.stream.Collectors; /** * Construit le SessionContext injecté dans le prompt IA pendant une partie. @@ -62,10 +61,6 @@ public class SessionStructuralContextBuilder { this.questProgressionRepository = questProgressionRepository; } - public Optional buildOptional(String sessionId) { - return sessionRepository.findById(sessionId).map(this::toContext); - } - public SessionContext build(String sessionId) { Session session = sessionRepository.findById(sessionId) .orElseThrow(() -> new IllegalArgumentException("Session introuvable : " + sessionId)); @@ -97,7 +92,7 @@ public class SessionStructuralContextBuilder { return kept.stream() .map(e -> toSummary(e, null)) - .collect(Collectors.toList()); + .toList(); } /** @@ -209,6 +204,6 @@ public class SessionStructuralContextBuilder { .filter(Map.Entry::getValue) .map(Map.Entry::getKey) .sorted() - .collect(Collectors.toList()); + .toList(); } } diff --git a/core/src/main/java/com/loremind/application/generationcontext/StreamChatForCampaignUseCase.java b/core/src/main/java/com/loremind/application/generationcontext/StreamChatForCampaignUseCase.java index db46444..c92e4d1 100644 --- a/core/src/main/java/com/loremind/application/generationcontext/StreamChatForCampaignUseCase.java +++ b/core/src/main/java/com/loremind/application/generationcontext/StreamChatForCampaignUseCase.java @@ -7,7 +7,7 @@ import com.loremind.domain.gamesystemcontext.GenerationIntent; import com.loremind.domain.generationcontext.CampaignStructuralContext; import com.loremind.domain.generationcontext.ChatMessage; import com.loremind.domain.generationcontext.ChatRequest; -import com.loremind.domain.generationcontext.ChatUsage; +import com.loremind.domain.generationcontext.ChatStreamCallbacks; import com.loremind.domain.generationcontext.GameSystemContext; import com.loremind.domain.generationcontext.LoreStructuralContext; import com.loremind.domain.generationcontext.NarrativeEntityContext; @@ -15,7 +15,6 @@ import com.loremind.domain.generationcontext.ports.AiChatProvider; import org.springframework.stereotype.Service; import java.util.List; -import java.util.function.Consumer; /** * Use case applicatif : chat conversationnel pour une Campagne avec Structural Context. @@ -72,10 +71,7 @@ public class StreamChatForCampaignUseCase { String entityType, String entityId, List messages, - Consumer onUsage, - Consumer onToken, - Runnable onComplete, - Consumer onError) { + ChatStreamCallbacks callbacks) { Campaign campaign = campaignRepository.findById(campaignId) .orElseThrow(() -> new IllegalArgumentException( @@ -94,7 +90,7 @@ public class StreamChatForCampaignUseCase { .gameSystemContext(gameSystemContext) .build(); - aiChatProvider.streamChat(request, onUsage, onToken, onComplete, onError); + aiChatProvider.streamChat(request, callbacks); } /** diff --git a/core/src/main/java/com/loremind/application/generationcontext/StreamChatForLoreUseCase.java b/core/src/main/java/com/loremind/application/generationcontext/StreamChatForLoreUseCase.java index 7c6cfb2..e192fe1 100644 --- a/core/src/main/java/com/loremind/application/generationcontext/StreamChatForLoreUseCase.java +++ b/core/src/main/java/com/loremind/application/generationcontext/StreamChatForLoreUseCase.java @@ -2,7 +2,7 @@ package com.loremind.application.generationcontext; import com.loremind.domain.generationcontext.ChatMessage; import com.loremind.domain.generationcontext.ChatRequest; -import com.loremind.domain.generationcontext.ChatUsage; +import com.loremind.domain.generationcontext.ChatStreamCallbacks; import com.loremind.domain.generationcontext.LoreStructuralContext; import com.loremind.domain.generationcontext.PageContext; import com.loremind.domain.generationcontext.ports.AiChatProvider; @@ -15,7 +15,6 @@ import org.springframework.stereotype.Service; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.function.Consumer; /** * Use case applicatif : chat conversationnel avec Structural Context d'un Lore. @@ -61,10 +60,7 @@ public class StreamChatForLoreUseCase { String loreId, String pageId, List messages, - Consumer onUsage, - Consumer onToken, - Runnable onComplete, - Consumer onError) { + ChatStreamCallbacks callbacks) { LoreStructuralContext loreContext = loreContextBuilder.build(loreId); PageContext pageContext = (pageId == null || pageId.isBlank()) @@ -77,7 +73,7 @@ public class StreamChatForLoreUseCase { .pageContext(pageContext) .build(); - aiChatProvider.streamChat(request, onUsage, onToken, onComplete, onError); + aiChatProvider.streamChat(request, callbacks); } /** diff --git a/core/src/main/java/com/loremind/application/generationcontext/StreamChatForSessionUseCase.java b/core/src/main/java/com/loremind/application/generationcontext/StreamChatForSessionUseCase.java index 0bdd7f8..37d9e9b 100644 --- a/core/src/main/java/com/loremind/application/generationcontext/StreamChatForSessionUseCase.java +++ b/core/src/main/java/com/loremind/application/generationcontext/StreamChatForSessionUseCase.java @@ -7,7 +7,7 @@ import com.loremind.domain.gamesystemcontext.GenerationIntent; import com.loremind.domain.generationcontext.CampaignStructuralContext; import com.loremind.domain.generationcontext.ChatMessage; import com.loremind.domain.generationcontext.ChatRequest; -import com.loremind.domain.generationcontext.ChatUsage; +import com.loremind.domain.generationcontext.ChatStreamCallbacks; import com.loremind.domain.generationcontext.GameSystemContext; import com.loremind.domain.generationcontext.LoreStructuralContext; import com.loremind.domain.generationcontext.SessionContext; @@ -19,7 +19,6 @@ import com.loremind.domain.playcontext.ports.SessionRepository; import org.springframework.stereotype.Service; import java.util.List; -import java.util.function.Consumer; /** * Use case applicatif : chat IA pendant une Session de jeu. @@ -71,10 +70,7 @@ public class StreamChatForSessionUseCase { public void execute( String sessionId, List messages, - Consumer onUsage, - Consumer onToken, - Runnable onComplete, - Consumer onError) { + ChatStreamCallbacks callbacks) { Session session = sessionRepository.findById(sessionId) .orElseThrow(() -> new IllegalArgumentException("Session introuvable : " + sessionId)); @@ -102,7 +98,7 @@ public class StreamChatForSessionUseCase { .sessionContext(sessionContext) .build(); - aiChatProvider.streamChat(request, onUsage, onToken, onComplete, onError); + aiChatProvider.streamChat(request, callbacks); } private LoreStructuralContext loadLoreContextOrNull(Campaign campaign) { diff --git a/core/src/main/java/com/loremind/application/images/ImageService.java b/core/src/main/java/com/loremind/application/images/ImageService.java index 1f1d43e..51c7087 100644 --- a/core/src/main/java/com/loremind/application/images/ImageService.java +++ b/core/src/main/java/com/loremind/application/images/ImageService.java @@ -7,6 +7,7 @@ import org.springframework.stereotype.Service; import java.io.InputStream; import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.List; import java.util.Optional; import java.util.Set; @@ -60,7 +61,7 @@ public class ImageService { .contentType(contentType) .sizeBytes(sizeBytes) .storageKey(storageKey) - .uploadedAt(LocalDateTime.now()) + .uploadedAt(LocalDateTime.now(ZoneId.systemDefault())) .build(); return imageRepository.save(image); } catch (RuntimeException ex) { diff --git a/core/src/main/java/com/loremind/application/licensing/LicenseService.java b/core/src/main/java/com/loremind/application/licensing/LicenseService.java index 454218d..6ee6805 100644 --- a/core/src/main/java/com/loremind/application/licensing/LicenseService.java +++ b/core/src/main/java/com/loremind/application/licensing/LicenseService.java @@ -52,8 +52,8 @@ public class LicenseService { this.repository = repository; this.jwtVerifier = jwtVerifier; this.relay = relay; - this.gracePeriodSeconds = (long) gracePeriodDays * 86_400L; - this.refreshBeforeExpirySeconds = (long) refreshBeforeExpiryDays * 86_400L; + this.gracePeriodSeconds = gracePeriodDays * 86_400L; + this.refreshBeforeExpirySeconds = refreshBeforeExpiryDays * 86_400L; this.licensingEnabled = licensingEnabled; } @@ -134,9 +134,9 @@ public class LicenseService { * Etat courant de la licence pour exposition UI / decision technique. */ public LicenseSnapshot getCurrentSnapshot() { - Optional opt = repository.findCurrent(); - if (opt.isEmpty()) return LicenseSnapshot.none(); - return snapshotOf(opt.get(), Instant.now()); + return repository.findCurrent() + .map(l -> snapshotOf(l, Instant.now())) + .orElseGet(LicenseSnapshot::none); } /** @@ -161,31 +161,32 @@ public class LicenseService { /** * Tente un refresh si la licence est proche de l'expiration. Idempotent. * Appele par le daemon planifie + manuellement via l'UI ("Reessayer"). - * - * @return true si un refresh a ete tente (avec ou sans succes) */ - public boolean refreshIfNeeded() { + public void refreshIfNeeded() { Optional opt = repository.findCurrent(); - if (opt.isEmpty()) return false; + if (opt.isEmpty()) return; License current = opt.get(); Instant now = Instant.now(); long secondsUntilExpiry = Duration.between(now, current.getExpiresAt()).getSeconds(); if (secondsUntilExpiry > refreshBeforeExpirySeconds) { - return false; + return; } - return doRefresh(current, now); + doRefresh(current, now); } /** * Force un refresh immediat (bouton UI "Reessayer maintenant"). */ - public boolean forceRefresh() { - return repository.findCurrent() - .map(license -> doRefresh(license, Instant.now())) - .orElse(false); + public void forceRefresh() { + repository.findCurrent().ifPresent(license -> doRefresh(license, Instant.now())); } - private boolean doRefresh(License current, Instant now) { + /** + * Tente le refresh aupres du relais et persiste le resultat (succes ou echec) sur + * la licence. Le succes/echec est lu depuis {@code lastRefreshSucceeded}, pas + * depuis un retour de methode : le tentative est deja actee cote persistance. + */ + private void doRefresh(License current, Instant now) { log.info("Refreshing Patreon license (current expires {})", current.getExpiresAt()); try { String newJwt = relay.refreshToken(current.getRawJwt()); @@ -199,7 +200,6 @@ public class LicenseService { current.setLastRefreshSucceeded(true); repository.save(current); log.info("License refreshed successfully (new expiry {})", claims.expiresAt()); - return true; } catch (LicenseRelay.RelayException e) { current.setLastRefreshAttemptAt(now); current.setLastRefreshSucceeded(false); @@ -209,13 +209,11 @@ public class LicenseService { } else { log.warn("Relay refresh transient failure ({}): {}", e.getKind(), e.getMessage()); } - return true; } catch (JwtVerifier.JwtVerificationException e) { current.setLastRefreshAttemptAt(now); current.setLastRefreshSucceeded(false); repository.save(current); log.error("Relay returned a JWT that fails verification: {}", e.getMessage()); - return true; } } diff --git a/core/src/main/java/com/loremind/application/lorecontext/LoreNodeService.java b/core/src/main/java/com/loremind/application/lorecontext/LoreNodeService.java index bb99ae9..a6d708b 100644 --- a/core/src/main/java/com/loremind/application/lorecontext/LoreNodeService.java +++ b/core/src/main/java/com/loremind/application/lorecontext/LoreNodeService.java @@ -77,7 +77,7 @@ public class LoreNodeService { public void reorderNodes(String parentId, List orderedIds) { String parent = (parentId == null || parentId.isBlank()) ? null : parentId; ReorderSupport.reorder(orderedIds, - id -> loreNodeRepository.findById(id).orElse(null), + loreNodeRepository::findById, (node, i) -> { if (parent == null || !isSelfOrDescendant(parent, node.getId())) { node.setParentId(parent); diff --git a/core/src/main/java/com/loremind/application/lorecontext/LoreService.java b/core/src/main/java/com/loremind/application/lorecontext/LoreService.java index b4c8963..33bd3bb 100644 --- a/core/src/main/java/com/loremind/application/lorecontext/LoreService.java +++ b/core/src/main/java/com/loremind/application/lorecontext/LoreService.java @@ -15,7 +15,6 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; /** * Service d'application pour le contexte Lore. @@ -72,7 +71,7 @@ public class LoreService { public List getAllLores() { return loreRepository.findAll().stream() .map(this::withCounts) - .collect(Collectors.toList()); + .toList(); } /** diff --git a/core/src/main/java/com/loremind/application/lorecontext/PageService.java b/core/src/main/java/com/loremind/application/lorecontext/PageService.java index 23ff51f..eea6d59 100644 --- a/core/src/main/java/com/loremind/application/lorecontext/PageService.java +++ b/core/src/main/java/com/loremind/application/lorecontext/PageService.java @@ -4,7 +4,6 @@ import com.loremind.domain.lorecontext.Page; import com.loremind.domain.lorecontext.ports.PageRepository; import org.springframework.stereotype.Service; -import com.loremind.domain.shared.CollectionUtils; import com.loremind.domain.shared.ReorderSupport; import java.util.ArrayList; @@ -84,14 +83,7 @@ public class PageService { existing.setTitle(changes.getTitle()); existing.setNodeId(changes.getNodeId()); - existing.setValues(CollectionUtils.copyMap(changes.getValues())); - existing.setImageValues(CollectionUtils.copyMap(changes.getImageValues())); - existing.setImageFraming(CollectionUtils.copyMap(changes.getImageFraming())); - existing.setKeyValueValues(CollectionUtils.copyMap(changes.getKeyValueValues())); - existing.setTableValues(CollectionUtils.copyMap(changes.getTableValues())); - existing.setNotes(changes.getNotes()); - existing.setTags(CollectionUtils.copyList(changes.getTags())); - existing.setRelatedPageIds(CollectionUtils.copyList(changes.getRelatedPageIds())); + existing.applyEditableContent(changes); return pageRepository.save(existing); } @@ -106,7 +98,7 @@ public class PageService { @org.springframework.transaction.annotation.Transactional public void reorderPages(String nodeId, List orderedIds) { ReorderSupport.reorder(orderedIds, - id -> pageRepository.findById(id).orElse(null), + pageRepository::findById, (page, i) -> { if (nodeId != null && !nodeId.isBlank()) page.setNodeId(nodeId); page.setOrder(i); diff --git a/core/src/main/java/com/loremind/application/playcontext/SessionEntryService.java b/core/src/main/java/com/loremind/application/playcontext/SessionEntryService.java index 69d39b0..86fa241 100644 --- a/core/src/main/java/com/loremind/application/playcontext/SessionEntryService.java +++ b/core/src/main/java/com/loremind/application/playcontext/SessionEntryService.java @@ -7,6 +7,7 @@ import com.loremind.domain.playcontext.ports.SessionRepository; import org.springframework.stereotype.Service; import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.List; import java.util.Optional; @@ -38,7 +39,7 @@ public class SessionEntryService { } validateContent(data.content()); - LocalDateTime now = LocalDateTime.now(); + LocalDateTime now = LocalDateTime.now(ZoneId.systemDefault()); SessionEntry entry = SessionEntry.builder() .sessionId(sessionId) .type(data.type() != null ? data.type() : EntryType.NOTE) diff --git a/core/src/main/java/com/loremind/application/playcontext/SessionPrepService.java b/core/src/main/java/com/loremind/application/playcontext/SessionPrepService.java index 05e6089..a635a81 100644 --- a/core/src/main/java/com/loremind/application/playcontext/SessionPrepService.java +++ b/core/src/main/java/com/loremind/application/playcontext/SessionPrepService.java @@ -4,10 +4,10 @@ import com.loremind.application.campaigncontext.CampaignReadinessAssessment; import com.loremind.application.campaigncontext.CampaignReadinessService; import com.loremind.application.campaigncontext.QuestStatusEnricher; import com.loremind.application.campaigncontext.ReadinessGap; -import com.loremind.domain.campaigncontext.Chapter; -import com.loremind.domain.campaigncontext.Quest; -import com.loremind.domain.campaigncontext.QuestNodeRef; -import com.loremind.domain.campaigncontext.QuestStatus; +import com.loremind.domain.campaigncontext.structure.Chapter; +import com.loremind.domain.campaigncontext.quest.Quest; +import com.loremind.domain.campaigncontext.quest.QuestNodeRef; +import com.loremind.domain.campaigncontext.quest.QuestStatus; import com.loremind.domain.campaigncontext.ports.ChapterRepository; import com.loremind.domain.campaigncontext.ports.QuestRepository; import com.loremind.domain.campaigncontext.ports.SceneRepository; @@ -20,7 +20,6 @@ import com.loremind.domain.playcontext.ports.PlaythroughRepository; import com.loremind.domain.playcontext.ports.SessionRepository; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Comparator; import java.util.LinkedHashSet; @@ -111,7 +110,7 @@ public class SessionPrepService { } else { focused = assessment.gaps().stream() .filter(g -> isFocused(g, hotspotChapterIds, hotspotSceneIds, hotspotQuestIds)) - .collect(Collectors.toList()); + .toList(); } int otherGapCount = assessment.gaps().size() - focused.size(); @@ -122,7 +121,7 @@ public class SessionPrepService { .filter(c -> c.getFilled() > 0) .map(c -> new SessionPrepReport.ClockInfo(c.getId(), c.getName(), c.getSegments(), c.getFilled(), c.getFrontId() != null ? frontNames.get(c.getFrontId()) : null)) - .collect(Collectors.toList()); + .toList(); return new SessionPrepReport( playthroughId, @@ -173,7 +172,7 @@ public class SessionPrepService { List sessions = sessionRepository.findByPlaythroughId(playthroughId); return sessions.stream() .max(Comparator.comparing(Session::getStartedAt, - Comparator.nullsFirst(Comparator.naturalOrder()))) + Comparator.nullsFirst(Comparator.naturalOrder()))) .map(s -> new SessionPrepReport.LastSessionInfo( s.getId(), s.getName(), s.getStartedAt(), s.getEndedAt(), s.isActive())) .orElse(null); @@ -183,13 +182,13 @@ public class SessionPrepService { return quests.stream() .filter(q -> statusById.get(q.getId()) == wanted) .sorted(Comparator.comparingInt(Quest::getOrder)) - .collect(Collectors.toList()); + .toList(); } private static List toQuestInfos(List quests) { return quests.stream() .map(q -> new SessionPrepReport.QuestInfo(q.getId(), q.getName(), q.getIcon())) - .collect(Collectors.toList()); + .toList(); } private static List concat(List a, List b) { diff --git a/core/src/main/java/com/loremind/application/playcontext/SessionService.java b/core/src/main/java/com/loremind/application/playcontext/SessionService.java index c937fa5..f31c1f5 100644 --- a/core/src/main/java/com/loremind/application/playcontext/SessionService.java +++ b/core/src/main/java/com/loremind/application/playcontext/SessionService.java @@ -8,6 +8,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; +import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Optional; @@ -23,6 +24,7 @@ import java.util.Optional; public class SessionService { private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + private static final String SESSION_NOT_FOUND = "Session introuvable : "; private final SessionRepository sessionRepository; private final SessionEntryRepository entryRepository; @@ -57,7 +59,7 @@ public class SessionService { "). Termine-la avant d'en lancer une nouvelle."); }); - LocalDateTime now = LocalDateTime.now(); + LocalDateTime now = LocalDateTime.now(ZoneId.systemDefault()); Session session = Session.builder() .name(generateDefaultName(now)) .playthroughId(playthroughId) @@ -68,11 +70,11 @@ public class SessionService { public Session endSession(String id) { Session session = sessionRepository.findById(id) - .orElseThrow(() -> new IllegalArgumentException("Session introuvable : " + id)); + .orElseThrow(() -> new IllegalArgumentException(SESSION_NOT_FOUND + id)); if (!session.isActive()) { throw new IllegalStateException("Cette session est déjà terminée."); } - session.setEndedAt(LocalDateTime.now()); + session.setEndedAt(LocalDateTime.now(ZoneId.systemDefault())); Session saved = sessionRepository.save(session); // Co-MJ : la séance se clôture -> avancer les horloges « fin de séance » de la Partie. clockService.onSessionEnded(saved.getPlaythroughId()); @@ -86,7 +88,7 @@ public class SessionService { */ public Session setCurrentScene(String id, String sceneId) { Session session = sessionRepository.findById(id) - .orElseThrow(() -> new IllegalArgumentException("Session introuvable : " + id)); + .orElseThrow(() -> new IllegalArgumentException(SESSION_NOT_FOUND + id)); session.setCurrentSceneId(sceneId != null && !sceneId.isBlank() ? sceneId : null); return sessionRepository.save(session); } @@ -96,7 +98,7 @@ public class SessionService { throw new IllegalArgumentException("Le nom de la session ne peut pas être vide."); } Session session = sessionRepository.findById(id) - .orElseThrow(() -> new IllegalArgumentException("Session introuvable : " + id)); + .orElseThrow(() -> new IllegalArgumentException(SESSION_NOT_FOUND + id)); session.setName(newName.trim()); return sessionRepository.save(session); } @@ -124,7 +126,7 @@ public class SessionService { @Transactional public void deleteSession(String id) { if (!sessionRepository.existsById(id)) { - throw new IllegalArgumentException("Session introuvable : " + id); + throw new IllegalArgumentException(SESSION_NOT_FOUND + id); } entryRepository.deleteBySessionId(id); sessionRepository.deleteById(id); diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/Character.java b/core/src/main/java/com/loremind/domain/campaigncontext/bestiary/Character.java similarity index 98% rename from core/src/main/java/com/loremind/domain/campaigncontext/Character.java rename to core/src/main/java/com/loremind/domain/campaigncontext/bestiary/Character.java index d351b31..15e19ef 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/Character.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/bestiary/Character.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.bestiary; import lombok.Builder; import lombok.Data; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/Enemy.java b/core/src/main/java/com/loremind/domain/campaigncontext/bestiary/Enemy.java similarity index 98% rename from core/src/main/java/com/loremind/domain/campaigncontext/Enemy.java rename to core/src/main/java/com/loremind/domain/campaigncontext/bestiary/Enemy.java index 401a446..1723b22 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/Enemy.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/bestiary/Enemy.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.bestiary; import lombok.Builder; import lombok.Data; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/Npc.java b/core/src/main/java/com/loremind/domain/campaigncontext/bestiary/Npc.java similarity index 98% rename from core/src/main/java/com/loremind/domain/campaigncontext/Npc.java rename to core/src/main/java/com/loremind/domain/campaigncontext/bestiary/Npc.java index b94ec70..2f18097 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/Npc.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/bestiary/Npc.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.bestiary; import lombok.Builder; import lombok.Data; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/CampaignImportProgress.java b/core/src/main/java/com/loremind/domain/campaigncontext/generation/CampaignImportProgress.java similarity index 90% rename from core/src/main/java/com/loremind/domain/campaigncontext/CampaignImportProgress.java rename to core/src/main/java/com/loremind/domain/campaigncontext/generation/CampaignImportProgress.java index 1fc398c..5ec0723 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/CampaignImportProgress.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/generation/CampaignImportProgress.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.generation; /** * Évènement d'avancement émis pendant l'import streamé d'un PDF de campagne. diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/CampaignImportProposal.java b/core/src/main/java/com/loremind/domain/campaigncontext/generation/CampaignImportProposal.java similarity index 97% rename from core/src/main/java/com/loremind/domain/campaigncontext/CampaignImportProposal.java rename to core/src/main/java/com/loremind/domain/campaigncontext/generation/CampaignImportProposal.java index de411fc..b222ca7 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/CampaignImportProposal.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/generation/CampaignImportProposal.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.generation; import java.util.List; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/EntityFieldPatchProposal.java b/core/src/main/java/com/loremind/domain/campaigncontext/generation/EntityFieldPatchProposal.java similarity index 94% rename from core/src/main/java/com/loremind/domain/campaigncontext/EntityFieldPatchProposal.java rename to core/src/main/java/com/loremind/domain/campaigncontext/generation/EntityFieldPatchProposal.java index c935c01..9172fda 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/EntityFieldPatchProposal.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/generation/EntityFieldPatchProposal.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.generation; import java.util.List; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/FieldProposal.java b/core/src/main/java/com/loremind/domain/campaigncontext/generation/FieldProposal.java similarity index 93% rename from core/src/main/java/com/loremind/domain/campaigncontext/FieldProposal.java rename to core/src/main/java/com/loremind/domain/campaigncontext/generation/FieldProposal.java index c45c914..1092327 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/FieldProposal.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/generation/FieldProposal.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.generation; /** * Proposition IA pour UN champ textuel d'une entité (Pilier A — co-création). diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/SceneDraft.java b/core/src/main/java/com/loremind/domain/campaigncontext/generation/SceneDraft.java similarity index 90% rename from core/src/main/java/com/loremind/domain/campaigncontext/SceneDraft.java rename to core/src/main/java/com/loremind/domain/campaigncontext/generation/SceneDraft.java index 97731a5..b97670a 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/SceneDraft.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/generation/SceneDraft.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.generation; /** * Ébauche de scène proposée par l'IA (Pilier A — capacité « create »). Value Object diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/SceneDraftProposal.java b/core/src/main/java/com/loremind/domain/campaigncontext/generation/SceneDraftProposal.java similarity index 90% rename from core/src/main/java/com/loremind/domain/campaigncontext/SceneDraftProposal.java rename to core/src/main/java/com/loremind/domain/campaigncontext/generation/SceneDraftProposal.java index 77ca4ec..798b95e 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/SceneDraftProposal.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/generation/SceneDraftProposal.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.generation; import java.util.List; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/CatalogItem.java b/core/src/main/java/com/loremind/domain/campaigncontext/itemcatalog/CatalogItem.java similarity index 90% rename from core/src/main/java/com/loremind/domain/campaigncontext/CatalogItem.java rename to core/src/main/java/com/loremind/domain/campaigncontext/itemcatalog/CatalogItem.java index 5164dc5..81a1ad9 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/CatalogItem.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/itemcatalog/CatalogItem.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.itemcatalog; import lombok.Builder; import lombok.Data; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ItemCatalog.java b/core/src/main/java/com/loremind/domain/campaigncontext/itemcatalog/ItemCatalog.java similarity index 95% rename from core/src/main/java/com/loremind/domain/campaigncontext/ItemCatalog.java rename to core/src/main/java/com/loremind/domain/campaigncontext/itemcatalog/ItemCatalog.java index 98d8620..d33d39f 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ItemCatalog.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/itemcatalog/ItemCatalog.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.itemcatalog; import lombok.Builder; import lombok.Data; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/Notebook.java b/core/src/main/java/com/loremind/domain/campaigncontext/notebook/Notebook.java similarity index 91% rename from core/src/main/java/com/loremind/domain/campaigncontext/Notebook.java rename to core/src/main/java/com/loremind/domain/campaigncontext/notebook/Notebook.java index 6fb6344..3f02386 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/Notebook.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/notebook/Notebook.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.notebook; import lombok.Builder; import lombok.Data; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/NotebookMessage.java b/core/src/main/java/com/loremind/domain/campaigncontext/notebook/NotebookMessage.java similarity index 91% rename from core/src/main/java/com/loremind/domain/campaigncontext/NotebookMessage.java rename to core/src/main/java/com/loremind/domain/campaigncontext/notebook/NotebookMessage.java index a56636a..c7ceada 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/NotebookMessage.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/notebook/NotebookMessage.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.notebook; import lombok.Builder; import lombok.Data; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/NotebookSource.java b/core/src/main/java/com/loremind/domain/campaigncontext/notebook/NotebookSource.java similarity index 91% rename from core/src/main/java/com/loremind/domain/campaigncontext/NotebookSource.java rename to core/src/main/java/com/loremind/domain/campaigncontext/notebook/NotebookSource.java index db80b46..9f73567 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/NotebookSource.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/notebook/NotebookSource.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.notebook; import lombok.Builder; import lombok.Data; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ports/ArcRepository.java b/core/src/main/java/com/loremind/domain/campaigncontext/ports/ArcRepository.java index c39fd8d..db30706 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ports/ArcRepository.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/ports/ArcRepository.java @@ -1,6 +1,6 @@ package com.loremind.domain.campaigncontext.ports; -import com.loremind.domain.campaigncontext.Arc; +import com.loremind.domain.campaigncontext.structure.Arc; import java.util.List; import java.util.Optional; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ports/CampaignPdfImporter.java b/core/src/main/java/com/loremind/domain/campaigncontext/ports/CampaignPdfImporter.java index e11ef1d..f0d5e43 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ports/CampaignPdfImporter.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/ports/CampaignPdfImporter.java @@ -1,7 +1,7 @@ package com.loremind.domain.campaigncontext.ports; -import com.loremind.domain.campaigncontext.CampaignImportProgress; -import com.loremind.domain.campaigncontext.CampaignImportProposal; +import com.loremind.domain.campaigncontext.generation.CampaignImportProgress; +import com.loremind.domain.campaigncontext.generation.CampaignImportProposal; import java.util.function.Consumer; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ports/ChapterRepository.java b/core/src/main/java/com/loremind/domain/campaigncontext/ports/ChapterRepository.java index 091a1d1..86837cc 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ports/ChapterRepository.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/ports/ChapterRepository.java @@ -1,6 +1,6 @@ package com.loremind.domain.campaigncontext.ports; -import com.loremind.domain.campaigncontext.Chapter; +import com.loremind.domain.campaigncontext.structure.Chapter; import java.util.List; import java.util.Optional; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ports/CharacterRepository.java b/core/src/main/java/com/loremind/domain/campaigncontext/ports/CharacterRepository.java index 60c6587..076fa92 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ports/CharacterRepository.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/ports/CharacterRepository.java @@ -1,6 +1,6 @@ package com.loremind.domain.campaigncontext.ports; -import com.loremind.domain.campaigncontext.Character; +import com.loremind.domain.campaigncontext.bestiary.Character; import java.util.List; import java.util.Optional; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ports/EnemyRepository.java b/core/src/main/java/com/loremind/domain/campaigncontext/ports/EnemyRepository.java index 4d7174e..e8dad4a 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ports/EnemyRepository.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/ports/EnemyRepository.java @@ -1,6 +1,6 @@ package com.loremind.domain.campaigncontext.ports; -import com.loremind.domain.campaigncontext.Enemy; +import com.loremind.domain.campaigncontext.bestiary.Enemy; import java.util.List; import java.util.Optional; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ports/ItemCatalogGenerator.java b/core/src/main/java/com/loremind/domain/campaigncontext/ports/ItemCatalogGenerator.java index d0711bb..0f130ea 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ports/ItemCatalogGenerator.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/ports/ItemCatalogGenerator.java @@ -1,6 +1,6 @@ package com.loremind.domain.campaigncontext.ports; -import com.loremind.domain.campaigncontext.CatalogItem; +import com.loremind.domain.campaigncontext.itemcatalog.CatalogItem; import java.util.List; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ports/ItemCatalogRepository.java b/core/src/main/java/com/loremind/domain/campaigncontext/ports/ItemCatalogRepository.java index 66fe950..7219c18 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ports/ItemCatalogRepository.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/ports/ItemCatalogRepository.java @@ -1,6 +1,6 @@ package com.loremind.domain.campaigncontext.ports; -import com.loremind.domain.campaigncontext.ItemCatalog; +import com.loremind.domain.campaigncontext.itemcatalog.ItemCatalog; import java.util.List; import java.util.Optional; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ports/NotebookChatStreamer.java b/core/src/main/java/com/loremind/domain/campaigncontext/ports/NotebookChatStreamer.java index 4d74bcf..0166605 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ports/NotebookChatStreamer.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/ports/NotebookChatStreamer.java @@ -16,11 +16,22 @@ public interface NotebookChatStreamer { record Progress(int current, int total) {} /** - * Streame la réponse ancrée sur les sources. Les callbacks sont invoqués au fil - * de l'eau : {@code onSourcesJson} UNE fois avant le premier token (JSON brut des - * passages utilisés — transparence UI), {@code onToken} par fragment, - * {@code onProgress} (mode approfondi uniquement) pendant la lecture du document, - * {@code onDone} à la fin, {@code onError} en cas d'échec. + * Callbacks du stream, invoqués au fil de l'eau : {@code onSourcesJson} UNE fois + * avant le premier token (JSON brut des passages utilisés — transparence UI), + * {@code onToken} par fragment, {@code onProgress} (mode approfondi uniquement) + * pendant la lecture du document, {@code onDone} à la fin, {@code onError} en + * cas d'échec. Regroupés en un seul objet (Argument Object) : ils voyagent + * toujours ensemble entre ce port, son adaptateur et le controller SSE. + */ + record Callbacks( + Consumer onSourcesJson, + Consumer onToken, + Consumer onProgress, + Runnable onDone, + Consumer onError) {} + + /** + * Streame la réponse ancrée sur les sources. * * @param deep true = analyse approfondie (map-reduce sur tout le document) ; * false = chat RAG (top-k). @@ -30,9 +41,5 @@ public interface NotebookChatStreamer { List messages, String context, boolean deep, - Consumer onSourcesJson, - Consumer onToken, - Consumer onProgress, - Runnable onDone, - Consumer onError); + Callbacks callbacks); } diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ports/NotebookRepository.java b/core/src/main/java/com/loremind/domain/campaigncontext/ports/NotebookRepository.java index 3151be5..2bbf35e 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ports/NotebookRepository.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/ports/NotebookRepository.java @@ -1,8 +1,8 @@ package com.loremind.domain.campaigncontext.ports; -import com.loremind.domain.campaigncontext.Notebook; -import com.loremind.domain.campaigncontext.NotebookMessage; -import com.loremind.domain.campaigncontext.NotebookSource; +import com.loremind.domain.campaigncontext.notebook.Notebook; +import com.loremind.domain.campaigncontext.notebook.NotebookMessage; +import com.loremind.domain.campaigncontext.notebook.NotebookSource; import java.util.List; import java.util.Optional; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ports/NpcRepository.java b/core/src/main/java/com/loremind/domain/campaigncontext/ports/NpcRepository.java index 4cdd320..f944af5 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ports/NpcRepository.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/ports/NpcRepository.java @@ -1,6 +1,6 @@ package com.loremind.domain.campaigncontext.ports; -import com.loremind.domain.campaigncontext.Npc; +import com.loremind.domain.campaigncontext.bestiary.Npc; import java.util.List; import java.util.Optional; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ports/QuestRepository.java b/core/src/main/java/com/loremind/domain/campaigncontext/ports/QuestRepository.java index ce22d84..522ff1e 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ports/QuestRepository.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/ports/QuestRepository.java @@ -1,6 +1,6 @@ package com.loremind.domain.campaigncontext.ports; -import com.loremind.domain.campaigncontext.Quest; +import com.loremind.domain.campaigncontext.quest.Quest; import java.util.List; import java.util.Optional; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ports/RandomTableGenerator.java b/core/src/main/java/com/loremind/domain/campaigncontext/ports/RandomTableGenerator.java index 6ef45f8..c037901 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ports/RandomTableGenerator.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/ports/RandomTableGenerator.java @@ -1,6 +1,6 @@ package com.loremind.domain.campaigncontext.ports; -import com.loremind.domain.campaigncontext.RandomTableEntry; +import com.loremind.domain.campaigncontext.randomtable.RandomTableEntry; import java.util.List; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ports/RandomTableRepository.java b/core/src/main/java/com/loremind/domain/campaigncontext/ports/RandomTableRepository.java index fc2d320..8976f49 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ports/RandomTableRepository.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/ports/RandomTableRepository.java @@ -1,6 +1,6 @@ package com.loremind.domain.campaigncontext.ports; -import com.loremind.domain.campaigncontext.RandomTable; +import com.loremind.domain.campaigncontext.randomtable.RandomTable; import java.util.List; import java.util.Optional; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ports/SceneDraftAssistant.java b/core/src/main/java/com/loremind/domain/campaigncontext/ports/SceneDraftAssistant.java index 9df522f..e7a0565 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ports/SceneDraftAssistant.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/ports/SceneDraftAssistant.java @@ -1,6 +1,6 @@ package com.loremind.domain.campaigncontext.ports; -import com.loremind.domain.campaigncontext.SceneDraft; +import com.loremind.domain.campaigncontext.generation.SceneDraft; import java.util.List; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ports/SceneRepository.java b/core/src/main/java/com/loremind/domain/campaigncontext/ports/SceneRepository.java index a4c2dd9..f39436e 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ports/SceneRepository.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/ports/SceneRepository.java @@ -1,6 +1,6 @@ package com.loremind.domain.campaigncontext.ports; -import com.loremind.domain.campaigncontext.Scene; +import com.loremind.domain.campaigncontext.structure.Scene; import java.util.List; import java.util.Optional; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ports/CampaignImportException.java b/core/src/main/java/com/loremind/domain/campaigncontext/ports/exceptions/CampaignImportException.java similarity index 86% rename from core/src/main/java/com/loremind/domain/campaigncontext/ports/CampaignImportException.java rename to core/src/main/java/com/loremind/domain/campaigncontext/ports/exceptions/CampaignImportException.java index 6fdbf8c..22bd37b 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ports/CampaignImportException.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/ports/exceptions/CampaignImportException.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext.ports; +package com.loremind.domain.campaigncontext.ports.exceptions; /** * Erreur de domaine : l'import d'un PDF de campagne a échoué (PDF illisible, diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ports/ItemCatalogGenerationException.java b/core/src/main/java/com/loremind/domain/campaigncontext/ports/exceptions/ItemCatalogGenerationException.java similarity index 87% rename from core/src/main/java/com/loremind/domain/campaigncontext/ports/ItemCatalogGenerationException.java rename to core/src/main/java/com/loremind/domain/campaigncontext/ports/exceptions/ItemCatalogGenerationException.java index ecc85e4..df53da3 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ports/ItemCatalogGenerationException.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/ports/exceptions/ItemCatalogGenerationException.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext.ports; +package com.loremind.domain.campaigncontext.ports.exceptions; /** * Échec de génération IA d'un catalogue d'objets (Brain injoignable, erreur du diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ports/NarrativeAssistException.java b/core/src/main/java/com/loremind/domain/campaigncontext/ports/exceptions/NarrativeAssistException.java similarity index 87% rename from core/src/main/java/com/loremind/domain/campaigncontext/ports/NarrativeAssistException.java rename to core/src/main/java/com/loremind/domain/campaigncontext/ports/exceptions/NarrativeAssistException.java index 935c0d5..0f5015d 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ports/NarrativeAssistException.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/ports/exceptions/NarrativeAssistException.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext.ports; +package com.loremind.domain.campaigncontext.ports.exceptions; /** * Erreur de génération lors de l'étoffage IA d'une entité narrative (Brain injoignable, diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ports/NotebookException.java b/core/src/main/java/com/loremind/domain/campaigncontext/ports/exceptions/NotebookException.java similarity index 85% rename from core/src/main/java/com/loremind/domain/campaigncontext/ports/NotebookException.java rename to core/src/main/java/com/loremind/domain/campaigncontext/ports/exceptions/NotebookException.java index f57b2b5..604fd17 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ports/NotebookException.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/ports/exceptions/NotebookException.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext.ports; +package com.loremind.domain.campaigncontext.ports.exceptions; /** * Échec d'indexation/chat d'un notebook (Brain injoignable, erreur du modèle…). diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ports/RandomTableGenerationException.java b/core/src/main/java/com/loremind/domain/campaigncontext/ports/exceptions/RandomTableGenerationException.java similarity index 87% rename from core/src/main/java/com/loremind/domain/campaigncontext/ports/RandomTableGenerationException.java rename to core/src/main/java/com/loremind/domain/campaigncontext/ports/exceptions/RandomTableGenerationException.java index 5a4edbb..4b716e6 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ports/RandomTableGenerationException.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/ports/exceptions/RandomTableGenerationException.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext.ports; +package com.loremind.domain.campaigncontext.ports.exceptions; /** * Échec de génération/improvisation IA d'une table (Brain injoignable, erreur du diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/NodeType.java b/core/src/main/java/com/loremind/domain/campaigncontext/quest/NodeType.java similarity index 89% rename from core/src/main/java/com/loremind/domain/campaigncontext/NodeType.java rename to core/src/main/java/com/loremind/domain/campaigncontext/quest/NodeType.java index 95ef33b..517cdba 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/NodeType.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/quest/NodeType.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.quest; /** * Type de nœud narratif référencé par une {@link Quest} via {@link QuestNodeRef}. diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/Prerequisite.java b/core/src/main/java/com/loremind/domain/campaigncontext/quest/Prerequisite.java similarity index 95% rename from core/src/main/java/com/loremind/domain/campaigncontext/Prerequisite.java rename to core/src/main/java/com/loremind/domain/campaigncontext/quest/Prerequisite.java index 82ce788..0f8fa8f 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/Prerequisite.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/quest/Prerequisite.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.quest; /** * Condition de déblocage d'une quête (Chapter dans un Arc HUB). diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/PrerequisiteEvaluator.java b/core/src/main/java/com/loremind/domain/campaigncontext/quest/PrerequisiteEvaluator.java similarity index 98% rename from core/src/main/java/com/loremind/domain/campaigncontext/PrerequisiteEvaluator.java rename to core/src/main/java/com/loremind/domain/campaigncontext/quest/PrerequisiteEvaluator.java index 86d3045..e47fb6f 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/PrerequisiteEvaluator.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/quest/PrerequisiteEvaluator.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.quest; import java.util.List; import java.util.Map; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ProgressionStatus.java b/core/src/main/java/com/loremind/domain/campaigncontext/quest/ProgressionStatus.java similarity index 91% rename from core/src/main/java/com/loremind/domain/campaigncontext/ProgressionStatus.java rename to core/src/main/java/com/loremind/domain/campaigncontext/quest/ProgressionStatus.java index 1bf1213..9ae0fbd 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ProgressionStatus.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/quest/ProgressionStatus.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.quest; /** * Statut de progression d'une quête (= Chapter dans un Arc HUB), piloté manuellement par le MJ. diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/Quest.java b/core/src/main/java/com/loremind/domain/campaigncontext/quest/Quest.java similarity index 98% rename from core/src/main/java/com/loremind/domain/campaigncontext/Quest.java rename to core/src/main/java/com/loremind/domain/campaigncontext/quest/Quest.java index f878774..962a36c 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/Quest.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/quest/Quest.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.quest; import lombok.Builder; import lombok.Data; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/QuestNodeRef.java b/core/src/main/java/com/loremind/domain/campaigncontext/quest/QuestNodeRef.java similarity index 92% rename from core/src/main/java/com/loremind/domain/campaigncontext/QuestNodeRef.java rename to core/src/main/java/com/loremind/domain/campaigncontext/quest/QuestNodeRef.java index b9c9a42..9e988f9 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/QuestNodeRef.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/quest/QuestNodeRef.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.quest; /** * Value Object : lien faible d'une {@link Quest} vers un nœud narratif diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/QuestStatus.java b/core/src/main/java/com/loremind/domain/campaigncontext/quest/QuestStatus.java similarity index 91% rename from core/src/main/java/com/loremind/domain/campaigncontext/QuestStatus.java rename to core/src/main/java/com/loremind/domain/campaigncontext/quest/QuestStatus.java index 6347a1e..c4cfe0a 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/QuestStatus.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/quest/QuestStatus.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.quest; /** * Statut effectif d'une quête tel qu'affiché dans la vue Hub. diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/RandomTable.java b/core/src/main/java/com/loremind/domain/campaigncontext/randomtable/RandomTable.java similarity index 96% rename from core/src/main/java/com/loremind/domain/campaigncontext/RandomTable.java rename to core/src/main/java/com/loremind/domain/campaigncontext/randomtable/RandomTable.java index e1fa78d..a3d8128 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/RandomTable.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/randomtable/RandomTable.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.randomtable; import lombok.Builder; import lombok.Data; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/RandomTableEntry.java b/core/src/main/java/com/loremind/domain/campaigncontext/randomtable/RandomTableEntry.java similarity index 93% rename from core/src/main/java/com/loremind/domain/campaigncontext/RandomTableEntry.java rename to core/src/main/java/com/loremind/domain/campaigncontext/randomtable/RandomTableEntry.java index 2e14d2b..ff062c0 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/RandomTableEntry.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/randomtable/RandomTableEntry.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.randomtable; import lombok.Builder; import lombok.Data; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ReadinessEntityType.java b/core/src/main/java/com/loremind/domain/campaigncontext/readiness/ReadinessEntityType.java similarity index 85% rename from core/src/main/java/com/loremind/domain/campaigncontext/ReadinessEntityType.java rename to core/src/main/java/com/loremind/domain/campaigncontext/readiness/ReadinessEntityType.java index 7c69598..fb5b806 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ReadinessEntityType.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/readiness/ReadinessEntityType.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.readiness; /** * Type de l'entité de scénario ciblée par un manque de readiness. Le front s'en diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ReadinessSeverity.java b/core/src/main/java/com/loremind/domain/campaigncontext/readiness/ReadinessSeverity.java similarity index 91% rename from core/src/main/java/com/loremind/domain/campaigncontext/ReadinessSeverity.java rename to core/src/main/java/com/loremind/domain/campaigncontext/readiness/ReadinessSeverity.java index a5cd93c..48c769e 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ReadinessSeverity.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/readiness/ReadinessSeverity.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.readiness; /** * Gravité d'un manque de préparation détecté par le guidage (Pilier B). diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ReadinessStatus.java b/core/src/main/java/com/loremind/domain/campaigncontext/readiness/ReadinessStatus.java similarity index 94% rename from core/src/main/java/com/loremind/domain/campaigncontext/ReadinessStatus.java rename to core/src/main/java/com/loremind/domain/campaigncontext/readiness/ReadinessStatus.java index 0321b61..8e4eace 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ReadinessStatus.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/readiness/ReadinessStatus.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.readiness; /** * Statut de PRÉPARATION d'une entité de scénario (Pilier B « guidage / readiness »). diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/Arc.java b/core/src/main/java/com/loremind/domain/campaigncontext/structure/Arc.java similarity index 97% rename from core/src/main/java/com/loremind/domain/campaigncontext/Arc.java rename to core/src/main/java/com/loremind/domain/campaigncontext/structure/Arc.java index 7188067..9f88bba 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/Arc.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/structure/Arc.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.structure; import lombok.Builder; import lombok.Data; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/ArcType.java b/core/src/main/java/com/loremind/domain/campaigncontext/structure/ArcType.java similarity index 92% rename from core/src/main/java/com/loremind/domain/campaigncontext/ArcType.java rename to core/src/main/java/com/loremind/domain/campaigncontext/structure/ArcType.java index f4266fb..3719eeb 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/ArcType.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/structure/ArcType.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.structure; /** * Type structurel d'un Arc. @@ -9,7 +9,6 @@ package com.loremind.domain.campaigncontext; * narration dans l'arbre (ses quêtes s'affichent sous « Quêtes ») ; dans * les exports (PDF / Foundry / backup) il apparaît sous son nom — ses * scènes sont du vrai contenu jouable. - * * Value Object du domaine (Bounded Context : Campaign). */ public enum ArcType { diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/Chapter.java b/core/src/main/java/com/loremind/domain/campaigncontext/structure/Chapter.java similarity index 96% rename from core/src/main/java/com/loremind/domain/campaigncontext/Chapter.java rename to core/src/main/java/com/loremind/domain/campaigncontext/structure/Chapter.java index e3ddd92..b6d5cc1 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/Chapter.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/structure/Chapter.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.structure; import lombok.Builder; import lombok.Data; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/LinkType.java b/core/src/main/java/com/loremind/domain/campaigncontext/structure/LinkType.java similarity index 90% rename from core/src/main/java/com/loremind/domain/campaigncontext/LinkType.java rename to core/src/main/java/com/loremind/domain/campaigncontext/structure/LinkType.java index dd84f62..d6b878b 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/LinkType.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/structure/LinkType.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.structure; /** * Type d'un lien narratif entre nœuds ({@link SceneBranch}) — Niveau 2. diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/Room.java b/core/src/main/java/com/loremind/domain/campaigncontext/structure/Room.java similarity index 97% rename from core/src/main/java/com/loremind/domain/campaigncontext/Room.java rename to core/src/main/java/com/loremind/domain/campaigncontext/structure/Room.java index 56f249e..b682d1a 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/Room.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/structure/Room.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.structure; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/RoomBranch.java b/core/src/main/java/com/loremind/domain/campaigncontext/structure/RoomBranch.java similarity index 93% rename from core/src/main/java/com/loremind/domain/campaigncontext/RoomBranch.java rename to core/src/main/java/com/loremind/domain/campaigncontext/structure/RoomBranch.java index ef6cecf..738430c 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/RoomBranch.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/structure/RoomBranch.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.structure; /** * Sortie d'une pièce vers une autre pièce de la même Scene explorable. diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/Scene.java b/core/src/main/java/com/loremind/domain/campaigncontext/structure/Scene.java similarity index 98% rename from core/src/main/java/com/loremind/domain/campaigncontext/Scene.java rename to core/src/main/java/com/loremind/domain/campaigncontext/structure/Scene.java index 6740078..487779e 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/Scene.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/structure/Scene.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.structure; import lombok.Builder; import lombok.Data; diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/SceneBattlemap.java b/core/src/main/java/com/loremind/domain/campaigncontext/structure/SceneBattlemap.java similarity index 95% rename from core/src/main/java/com/loremind/domain/campaigncontext/SceneBattlemap.java rename to core/src/main/java/com/loremind/domain/campaigncontext/structure/SceneBattlemap.java index 6e7bea6..551a704 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/SceneBattlemap.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/structure/SceneBattlemap.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.structure; /** * Value Object représentant UNE battlemap d'une Scene destinée à l'export Foundry : diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/SceneBranch.java b/core/src/main/java/com/loremind/domain/campaigncontext/structure/SceneBranch.java similarity index 96% rename from core/src/main/java/com/loremind/domain/campaigncontext/SceneBranch.java rename to core/src/main/java/com/loremind/domain/campaigncontext/structure/SceneBranch.java index ff61d45..aa72b2e 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/SceneBranch.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/structure/SceneBranch.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.structure; /** * Value Object représentant une "sortie" narrative depuis une Scene. diff --git a/core/src/main/java/com/loremind/domain/campaigncontext/SceneType.java b/core/src/main/java/com/loremind/domain/campaigncontext/structure/SceneType.java similarity index 92% rename from core/src/main/java/com/loremind/domain/campaigncontext/SceneType.java rename to core/src/main/java/com/loremind/domain/campaigncontext/structure/SceneType.java index b80cc26..11b7e5a 100644 --- a/core/src/main/java/com/loremind/domain/campaigncontext/SceneType.java +++ b/core/src/main/java/com/loremind/domain/campaigncontext/structure/SceneType.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.structure; /** * Type narratif d'un nœud (Scène) — Niveau 2 (graphe de nœuds typés). diff --git a/core/src/main/java/com/loremind/domain/gamesystemcontext/ports/RulesImportException.java b/core/src/main/java/com/loremind/domain/gamesystemcontext/ports/exceptions/RulesImportException.java similarity index 88% rename from core/src/main/java/com/loremind/domain/gamesystemcontext/ports/RulesImportException.java rename to core/src/main/java/com/loremind/domain/gamesystemcontext/ports/exceptions/RulesImportException.java index 3822f2b..3220d0c 100644 --- a/core/src/main/java/com/loremind/domain/gamesystemcontext/ports/RulesImportException.java +++ b/core/src/main/java/com/loremind/domain/gamesystemcontext/ports/exceptions/RulesImportException.java @@ -1,4 +1,4 @@ -package com.loremind.domain.gamesystemcontext.ports; +package com.loremind.domain.gamesystemcontext.ports.exceptions; /** * Erreur de domaine : l'import d'un PDF de règles a échoué (PDF illisible, diff --git a/core/src/main/java/com/loremind/domain/generationcontext/ChatStreamCallbacks.java b/core/src/main/java/com/loremind/domain/generationcontext/ChatStreamCallbacks.java new file mode 100644 index 0000000..c55d8ec --- /dev/null +++ b/core/src/main/java/com/loremind/domain/generationcontext/ChatStreamCallbacks.java @@ -0,0 +1,25 @@ +package com.loremind.domain.generationcontext; + +import java.util.function.Consumer; + +/** + * Callbacks du streaming chat IA, regroupés en un seul objet (Argument Object) : + * ils voyagent toujours ensemble, dans le même ordre, entre le port + * {@link com.loremind.domain.generationcontext.ports.AiChatProvider}, ses use + * cases appelants et le controller SSE. + * + * @param onUsage invoqué une fois au début du stream avec le bilan d'occupation + * de la fenêtre de contexte (tokens system / history / current / + * max). Peut ne jamais être invoqué si le provider ne supporte + * pas le comptage. + * @param onToken invoqué à chaque token reçu du LLM (peut être appelé de + * nombreuses fois) + * @param onComplete invoqué une fois le stream terminé avec succès + * @param onError invoqué en cas d'erreur (Brain injoignable, timeout, réponse + * invalide). Exclusif avec onComplete. + */ +public record ChatStreamCallbacks( + Consumer onUsage, + Consumer onToken, + Runnable onComplete, + Consumer onError) {} diff --git a/core/src/main/java/com/loremind/domain/generationcontext/ports/AiChatProvider.java b/core/src/main/java/com/loremind/domain/generationcontext/ports/AiChatProvider.java index 3533520..09fff10 100644 --- a/core/src/main/java/com/loremind/domain/generationcontext/ports/AiChatProvider.java +++ b/core/src/main/java/com/loremind/domain/generationcontext/ports/AiChatProvider.java @@ -1,9 +1,7 @@ package com.loremind.domain.generationcontext.ports; import com.loremind.domain.generationcontext.ChatRequest; -import com.loremind.domain.generationcontext.ChatUsage; - -import java.util.function.Consumer; +import com.loremind.domain.generationcontext.ChatStreamCallbacks; /** * Port de sortie pour le chat streamé avec un LLM. @@ -26,22 +24,8 @@ public interface AiChatProvider { * de l'exécuter dans un thread adapté (ex: thread dédié à la requête * HTTP côté controller SSE). * - * @param request messages + contexte Lore - * @param onUsage invoqué une fois au début du stream avec le bilan - * d'occupation de la fenêtre de contexte (tokens system / - * history / current / max). Peut ne jamais être invoqué - * si le provider ne supporte pas le comptage. - * @param onToken invoqué à chaque token reçu du LLM (peut être appelé - * de nombreuses fois) - * @param onComplete invoqué une fois le stream terminé avec succès - * @param onError invoqué en cas d'erreur (Brain injoignable, timeout, - * réponse invalide). Exclusif avec onComplete. + * @param request messages + contexte Lore + * @param callbacks onUsage / onToken / onComplete / onError du stream */ - void streamChat( - ChatRequest request, - Consumer onUsage, - Consumer onToken, - Runnable onComplete, - Consumer onError - ); + void streamChat(ChatRequest request, ChatStreamCallbacks callbacks); } diff --git a/core/src/main/java/com/loremind/domain/generationcontext/ports/AiProvider.java b/core/src/main/java/com/loremind/domain/generationcontext/ports/AiProvider.java index 4dc7a3a..46b5312 100644 --- a/core/src/main/java/com/loremind/domain/generationcontext/ports/AiProvider.java +++ b/core/src/main/java/com/loremind/domain/generationcontext/ports/AiProvider.java @@ -2,6 +2,7 @@ package com.loremind.domain.generationcontext.ports; import com.loremind.domain.generationcontext.GenerationContext; import com.loremind.domain.generationcontext.GenerationResult; +import com.loremind.domain.generationcontext.ports.exceptions.AiProviderException; /** * Port de sortie pour la génération IA. diff --git a/core/src/main/java/com/loremind/domain/generationcontext/ports/AiProviderException.java b/core/src/main/java/com/loremind/domain/generationcontext/ports/exceptions/AiProviderException.java similarity index 91% rename from core/src/main/java/com/loremind/domain/generationcontext/ports/AiProviderException.java rename to core/src/main/java/com/loremind/domain/generationcontext/ports/exceptions/AiProviderException.java index e209bef..fde29a0 100644 --- a/core/src/main/java/com/loremind/domain/generationcontext/ports/AiProviderException.java +++ b/core/src/main/java/com/loremind/domain/generationcontext/ports/exceptions/AiProviderException.java @@ -1,4 +1,4 @@ -package com.loremind.domain.generationcontext.ports; +package com.loremind.domain.generationcontext.ports.exceptions; /** * Exception de domaine signalant un échec du fournisseur IA. diff --git a/core/src/main/java/com/loremind/domain/lorecontext/Page.java b/core/src/main/java/com/loremind/domain/lorecontext/Page.java index e27ccec..67d6383 100644 --- a/core/src/main/java/com/loremind/domain/lorecontext/Page.java +++ b/core/src/main/java/com/loremind/domain/lorecontext/Page.java @@ -1,5 +1,6 @@ package com.loremind.domain.lorecontext; +import com.loremind.domain.shared.CollectionUtils; import lombok.Builder; import lombok.Data; @@ -78,4 +79,20 @@ public class Page { public boolean hasTemplate() { return templateId != null && !templateId.isBlank(); } + + /** + * Applique le contenu éditable (valeurs de champs + métadonnées) depuis une + * autre Page, avec copies défensives. Ne touche pas aux champs structurels + * (id, loreId, nodeId, templateId, order) : à la charge de l'appelant. + */ + public void applyEditableContent(Page changes) { + this.values = CollectionUtils.copyMap(changes.values); + this.imageValues = CollectionUtils.copyMap(changes.imageValues); + this.imageFraming = CollectionUtils.copyMap(changes.imageFraming); + this.keyValueValues = CollectionUtils.copyMap(changes.keyValueValues); + this.tableValues = CollectionUtils.copyMap(changes.tableValues); + this.notes = changes.notes; + this.tags = CollectionUtils.copyList(changes.tags); + this.relatedPageIds = CollectionUtils.copyList(changes.relatedPageIds); + } } diff --git a/core/src/main/java/com/loremind/domain/playcontext/QuestProgression.java b/core/src/main/java/com/loremind/domain/playcontext/QuestProgression.java index 10b8d25..f9a6055 100644 --- a/core/src/main/java/com/loremind/domain/playcontext/QuestProgression.java +++ b/core/src/main/java/com/loremind/domain/playcontext/QuestProgression.java @@ -1,6 +1,6 @@ package com.loremind.domain.playcontext; -import com.loremind.domain.campaigncontext.ProgressionStatus; +import com.loremind.domain.campaigncontext.quest.ProgressionStatus; import lombok.Builder; import lombok.Data; diff --git a/core/src/main/java/com/loremind/domain/playcontext/ports/QuestProgressionRepository.java b/core/src/main/java/com/loremind/domain/playcontext/ports/QuestProgressionRepository.java index 2cb5b2b..46e9cec 100644 --- a/core/src/main/java/com/loremind/domain/playcontext/ports/QuestProgressionRepository.java +++ b/core/src/main/java/com/loremind/domain/playcontext/ports/QuestProgressionRepository.java @@ -1,6 +1,6 @@ package com.loremind.domain.playcontext.ports; -import com.loremind.domain.campaigncontext.ProgressionStatus; +import com.loremind.domain.campaigncontext.quest.ProgressionStatus; import com.loremind.domain.playcontext.QuestProgression; import java.util.List; diff --git a/core/src/main/java/com/loremind/domain/playcontext/ports/SessionRecapException.java b/core/src/main/java/com/loremind/domain/playcontext/ports/exceptions/SessionRecapException.java similarity index 87% rename from core/src/main/java/com/loremind/domain/playcontext/ports/SessionRecapException.java rename to core/src/main/java/com/loremind/domain/playcontext/ports/exceptions/SessionRecapException.java index 99fbbfd..2afb802 100644 --- a/core/src/main/java/com/loremind/domain/playcontext/ports/SessionRecapException.java +++ b/core/src/main/java/com/loremind/domain/playcontext/ports/exceptions/SessionRecapException.java @@ -1,4 +1,4 @@ -package com.loremind.domain.playcontext.ports; +package com.loremind.domain.playcontext.ports.exceptions; /** * Erreur de génération du récap de séance (Brain injoignable, réponse vide…). diff --git a/core/src/main/java/com/loremind/domain/shared/ReorderSupport.java b/core/src/main/java/com/loremind/domain/shared/ReorderSupport.java index 96e0af1..d7bc8a1 100644 --- a/core/src/main/java/com/loremind/domain/shared/ReorderSupport.java +++ b/core/src/main/java/com/loremind/domain/shared/ReorderSupport.java @@ -1,6 +1,7 @@ package com.loremind.domain.shared; import java.util.List; +import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.ObjIntConsumer; @@ -17,22 +18,22 @@ public final class ReorderSupport { /** * @param orderedIds liste ordonnée des ids (null = no-op) - * @param finder id → entité (ou null si introuvable) + * @param finder id → entité, absente si introuvable (typiquement {@code repository::findById}) * @param applyOrder (entité, index) → pose l'ordre et l'éventuel parent * @param saver persiste l'entité */ public static void reorder(List orderedIds, - Function finder, + Function> finder, ObjIntConsumer applyOrder, Consumer saver) { if (orderedIds == null) return; int i = 0; for (String id : orderedIds) { - T entity = finder.apply(id); - if (entity != null) { - applyOrder.accept(entity, i); + int index = i; + finder.apply(id).ifPresent(entity -> { + applyOrder.accept(entity, index); saver.accept(entity); - } + }); i++; } } diff --git a/core/src/main/java/com/loremind/infrastructure/ai/BrainAiChatClient.java b/core/src/main/java/com/loremind/infrastructure/ai/BrainAiChatClient.java index 051d655..85cbc91 100644 --- a/core/src/main/java/com/loremind/infrastructure/ai/BrainAiChatClient.java +++ b/core/src/main/java/com/loremind/infrastructure/ai/BrainAiChatClient.java @@ -1,9 +1,10 @@ package com.loremind.infrastructure.ai; import com.loremind.domain.generationcontext.ChatRequest; +import com.loremind.domain.generationcontext.ChatStreamCallbacks; import com.loremind.domain.generationcontext.ChatUsage; import com.loremind.domain.generationcontext.ports.AiChatProvider; -import com.loremind.domain.generationcontext.ports.AiProviderException; +import com.loremind.domain.generationcontext.ports.exceptions.AiProviderException; import com.loremind.infrastructure.web.config.UserLanguageHolder; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.ParameterizedTypeReference; @@ -15,7 +16,6 @@ import reactor.core.publisher.Flux; import java.time.Duration; import java.util.Map; -import java.util.function.Consumer; /** * Adapter de sortie (Architecture Hexagonale) : implémente AiChatProvider @@ -54,13 +54,7 @@ public class BrainAiChatClient implements AiChatProvider { } @Override - public void streamChat( - ChatRequest request, - Consumer onUsage, - Consumer onToken, - Runnable onComplete, - Consumer onError) { - + public void streamChat(ChatRequest request, ChatStreamCallbacks callbacks) { Map payload = payloadBuilder.build(request); Flux> flux = webClient.post() @@ -77,26 +71,22 @@ public class BrainAiChatClient implements AiChatProvider { // au contrat synchrone du port. L'appelant choisit le thread. flux .timeout(Duration.ofSeconds(120)) - .doOnNext(sse -> handleEvent(sse, onUsage, onToken, onError)) + .doOnNext(sse -> handleEvent(sse, callbacks)) .blockLast(); - onComplete.run(); + callbacks.onComplete().run(); } catch (Exception e) { - onError.accept(new AiProviderException( + callbacks.onError().accept(new AiProviderException( "Erreur lors du streaming chat depuis le Brain.", e)); } } /** Dispatch selon le type d'évènement SSE (data par défaut, done, error, usage). */ - private void handleEvent( - ServerSentEvent sse, - Consumer onUsage, - Consumer onToken, - Consumer onError) { + private void handleEvent(ServerSentEvent sse, ChatStreamCallbacks callbacks) { String event = sse.event(); // null si pas d'event: xxx -> data par défaut String data = sse.data(); if ("error".equals(event)) { - onError.accept(new AiProviderException( + callbacks.onError().accept(new AiProviderException( "Le Brain a signalé une erreur : " + data)); return; } @@ -105,13 +95,13 @@ public class BrainAiChatClient implements AiChatProvider { } if ("usage".equals(event)) { ChatUsage usage = sseParser.parseUsage(data); - if (usage != null) onUsage.accept(usage); + if (usage != null) callbacks.onUsage().accept(usage); return; } // Défaut : évènement data avec JSON {"token":"..."}. String token = sseParser.parseToken(data); if (token != null && !token.isEmpty()) { - onToken.accept(token); + callbacks.onToken().accept(token); } } } diff --git a/core/src/main/java/com/loremind/infrastructure/ai/BrainAiClient.java b/core/src/main/java/com/loremind/infrastructure/ai/BrainAiClient.java index 9e4b6d9..7190b5c 100644 --- a/core/src/main/java/com/loremind/infrastructure/ai/BrainAiClient.java +++ b/core/src/main/java/com/loremind/infrastructure/ai/BrainAiClient.java @@ -3,7 +3,7 @@ package com.loremind.infrastructure.ai; import com.loremind.domain.generationcontext.GenerationContext; import com.loremind.domain.generationcontext.GenerationResult; import com.loremind.domain.generationcontext.ports.AiProvider; -import com.loremind.domain.generationcontext.ports.AiProviderException; +import com.loremind.domain.generationcontext.ports.exceptions.AiProviderException; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; diff --git a/core/src/main/java/com/loremind/infrastructure/ai/BrainCampaignImportClient.java b/core/src/main/java/com/loremind/infrastructure/ai/BrainCampaignImportClient.java index 687e919..b9717c2 100644 --- a/core/src/main/java/com/loremind/infrastructure/ai/BrainCampaignImportClient.java +++ b/core/src/main/java/com/loremind/infrastructure/ai/BrainCampaignImportClient.java @@ -2,14 +2,14 @@ package com.loremind.infrastructure.ai; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.loremind.domain.campaigncontext.CampaignImportProgress; -import com.loremind.domain.campaigncontext.CampaignImportProposal; -import com.loremind.domain.campaigncontext.CampaignImportProposal.ArcProposal; -import com.loremind.domain.campaigncontext.CampaignImportProposal.ChapterProposal; -import com.loremind.domain.campaigncontext.CampaignImportProposal.NpcProposal; -import com.loremind.domain.campaigncontext.CampaignImportProposal.RoomProposal; -import com.loremind.domain.campaigncontext.CampaignImportProposal.SceneProposal; -import com.loremind.domain.campaigncontext.ports.CampaignImportException; +import com.loremind.domain.campaigncontext.generation.CampaignImportProgress; +import com.loremind.domain.campaigncontext.generation.CampaignImportProposal; +import com.loremind.domain.campaigncontext.generation.CampaignImportProposal.ArcProposal; +import com.loremind.domain.campaigncontext.generation.CampaignImportProposal.ChapterProposal; +import com.loremind.domain.campaigncontext.generation.CampaignImportProposal.NpcProposal; +import com.loremind.domain.campaigncontext.generation.CampaignImportProposal.RoomProposal; +import com.loremind.domain.campaigncontext.generation.CampaignImportProposal.SceneProposal; +import com.loremind.domain.campaigncontext.ports.exceptions.CampaignImportException; import com.loremind.domain.campaigncontext.ports.CampaignPdfImporter; import com.loremind.infrastructure.web.config.UserLanguageHolder; import org.springframework.beans.factory.annotation.Value; diff --git a/core/src/main/java/com/loremind/infrastructure/ai/BrainItemCatalogClient.java b/core/src/main/java/com/loremind/infrastructure/ai/BrainItemCatalogClient.java index 45e711e..d93e120 100644 --- a/core/src/main/java/com/loremind/infrastructure/ai/BrainItemCatalogClient.java +++ b/core/src/main/java/com/loremind/infrastructure/ai/BrainItemCatalogClient.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.ai; -import com.loremind.domain.campaigncontext.CatalogItem; -import com.loremind.domain.campaigncontext.ports.ItemCatalogGenerationException; +import com.loremind.domain.campaigncontext.itemcatalog.CatalogItem; +import com.loremind.domain.campaigncontext.ports.exceptions.ItemCatalogGenerationException; import com.loremind.domain.campaigncontext.ports.ItemCatalogGenerator; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; diff --git a/core/src/main/java/com/loremind/infrastructure/ai/BrainNarrativeFieldClient.java b/core/src/main/java/com/loremind/infrastructure/ai/BrainNarrativeFieldClient.java index e31fac1..5c557f1 100644 --- a/core/src/main/java/com/loremind/infrastructure/ai/BrainNarrativeFieldClient.java +++ b/core/src/main/java/com/loremind/infrastructure/ai/BrainNarrativeFieldClient.java @@ -1,6 +1,6 @@ package com.loremind.infrastructure.ai; -import com.loremind.domain.campaigncontext.ports.NarrativeAssistException; +import com.loremind.domain.campaigncontext.ports.exceptions.NarrativeAssistException; import com.loremind.domain.campaigncontext.ports.NarrativeFieldAssistant; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; diff --git a/core/src/main/java/com/loremind/infrastructure/ai/BrainNotebookChatClient.java b/core/src/main/java/com/loremind/infrastructure/ai/BrainNotebookChatClient.java index 5cdf1a4..a2f415e 100644 --- a/core/src/main/java/com/loremind/infrastructure/ai/BrainNotebookChatClient.java +++ b/core/src/main/java/com/loremind/infrastructure/ai/BrainNotebookChatClient.java @@ -3,7 +3,7 @@ package com.loremind.infrastructure.ai; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.loremind.domain.campaigncontext.ports.NotebookChatStreamer; -import com.loremind.domain.campaigncontext.ports.NotebookException; +import com.loremind.domain.campaigncontext.ports.exceptions.NotebookException; import com.loremind.infrastructure.web.config.UserLanguageHolder; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.ParameterizedTypeReference; @@ -17,7 +17,6 @@ import java.time.Duration; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.function.Consumer; import java.util.stream.Collectors; /** @@ -52,11 +51,7 @@ public class BrainNotebookChatClient implements NotebookChatStreamer { List messages, String context, boolean deep, - Consumer onSourcesJson, - Consumer onToken, - Consumer onProgress, - Runnable onDone, - Consumer onError) { + Callbacks callbacks) { Map payload = new LinkedHashMap<>(); payload.put("source_ids", sourceIds); @@ -78,48 +73,39 @@ public class BrainNotebookChatClient implements NotebookChatStreamer { try { flux .timeout(Duration.ofSeconds(timeoutSeconds)) - .doOnNext(sse -> handleEvent( - sse, terminated, onSourcesJson, onToken, onProgress, onDone, onError)) + .doOnNext(sse -> handleEvent(sse, terminated, callbacks)) .blockLast(); if (!terminated[0]) { - onDone.run(); // flux terminé sans event done explicite + callbacks.onDone().run(); // flux terminé sans event done explicite } } catch (Exception e) { if (!terminated[0]) { String cause = e.getClass().getSimpleName() + (e.getMessage() != null ? " — " + e.getMessage() : ""); - onError.accept(new NotebookException( + callbacks.onError().accept(new NotebookException( "Erreur lors du streaming du chat depuis le Brain : " + cause, e)); } } } - private void handleEvent( - ServerSentEvent sse, - boolean[] terminated, - Consumer onSourcesJson, - Consumer onToken, - Consumer onProgress, - Runnable onDone, - Consumer onError) { - + private void handleEvent(ServerSentEvent sse, boolean[] terminated, Callbacks callbacks) { String event = sse.event(); String data = sse.data() == null ? "" : sse.data(); if ("token".equals(event)) { String token = readField(data, "token"); - if (token != null && !token.isEmpty()) onToken.accept(token); + if (token != null && !token.isEmpty()) callbacks.onToken().accept(token); } else if ("sources".equals(event)) { // Passages utilisés par le RAG : relayés tels quels (JSON brut) — le // Core n'a pas besoin de les comprendre, seulement de les transmettre. - onSourcesJson.accept(data); + callbacks.onSourcesJson().accept(data); } else if ("progress".equals(event)) { - onProgress.accept(new Progress(readInt(data, "current"), readInt(data, "total"))); + callbacks.onProgress().accept(new Progress(readInt(data, "current"), readInt(data, "total"))); } else if ("done".equals(event)) { terminated[0] = true; - onDone.run(); + callbacks.onDone().run(); } else if ("error".equals(event)) { terminated[0] = true; - onError.accept(new NotebookException("Le Brain a signalé une erreur : " + readMessage(data))); + callbacks.onError().accept(new NotebookException("Le Brain a signalé une erreur : " + readMessage(data))); } } diff --git a/core/src/main/java/com/loremind/infrastructure/ai/BrainNotebookIndexClient.java b/core/src/main/java/com/loremind/infrastructure/ai/BrainNotebookIndexClient.java index 983fa8e..fe19fca 100644 --- a/core/src/main/java/com/loremind/infrastructure/ai/BrainNotebookIndexClient.java +++ b/core/src/main/java/com/loremind/infrastructure/ai/BrainNotebookIndexClient.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.ai; import com.fasterxml.jackson.annotation.JsonProperty; -import com.loremind.domain.campaigncontext.ports.NotebookException; +import com.loremind.domain.campaigncontext.ports.exceptions.NotebookException; import com.loremind.domain.campaigncontext.ports.NotebookIndexer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/core/src/main/java/com/loremind/infrastructure/ai/BrainRandomTableClient.java b/core/src/main/java/com/loremind/infrastructure/ai/BrainRandomTableClient.java index 4e7edc5..820a3b8 100644 --- a/core/src/main/java/com/loremind/infrastructure/ai/BrainRandomTableClient.java +++ b/core/src/main/java/com/loremind/infrastructure/ai/BrainRandomTableClient.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.ai; -import com.loremind.domain.campaigncontext.RandomTableEntry; -import com.loremind.domain.campaigncontext.ports.RandomTableGenerationException; +import com.loremind.domain.campaigncontext.randomtable.RandomTableEntry; +import com.loremind.domain.campaigncontext.ports.exceptions.RandomTableGenerationException; import com.loremind.domain.campaigncontext.ports.RandomTableGenerator; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; diff --git a/core/src/main/java/com/loremind/infrastructure/ai/BrainRulesImportClient.java b/core/src/main/java/com/loremind/infrastructure/ai/BrainRulesImportClient.java index ea84e2a..82b5cfc 100644 --- a/core/src/main/java/com/loremind/infrastructure/ai/BrainRulesImportClient.java +++ b/core/src/main/java/com/loremind/infrastructure/ai/BrainRulesImportClient.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.loremind.domain.gamesystemcontext.RulesImportProgress; import com.loremind.domain.gamesystemcontext.RulesImportResult; -import com.loremind.domain.gamesystemcontext.ports.RulesImportException; +import com.loremind.domain.gamesystemcontext.ports.exceptions.RulesImportException; import com.loremind.domain.gamesystemcontext.ports.RulesPdfImporter; import com.loremind.infrastructure.web.config.UserLanguageHolder; import org.springframework.beans.factory.annotation.Qualifier; diff --git a/core/src/main/java/com/loremind/infrastructure/ai/BrainSceneDraftClient.java b/core/src/main/java/com/loremind/infrastructure/ai/BrainSceneDraftClient.java index 8b4d3ec..dd18f7d 100644 --- a/core/src/main/java/com/loremind/infrastructure/ai/BrainSceneDraftClient.java +++ b/core/src/main/java/com/loremind/infrastructure/ai/BrainSceneDraftClient.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.ai; -import com.loremind.domain.campaigncontext.SceneDraft; -import com.loremind.domain.campaigncontext.ports.NarrativeAssistException; +import com.loremind.domain.campaigncontext.generation.SceneDraft; +import com.loremind.domain.campaigncontext.ports.exceptions.NarrativeAssistException; import com.loremind.domain.campaigncontext.ports.SceneDraftAssistant; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; diff --git a/core/src/main/java/com/loremind/infrastructure/ai/BrainSessionRecapClient.java b/core/src/main/java/com/loremind/infrastructure/ai/BrainSessionRecapClient.java index dd2c8f6..a093bb3 100644 --- a/core/src/main/java/com/loremind/infrastructure/ai/BrainSessionRecapClient.java +++ b/core/src/main/java/com/loremind/infrastructure/ai/BrainSessionRecapClient.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.ai; import com.loremind.domain.playcontext.ports.SessionRecapAssistant; -import com.loremind.domain.playcontext.ports.SessionRecapException; +import com.loremind.domain.playcontext.ports.exceptions.SessionRecapException; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; diff --git a/core/src/main/java/com/loremind/infrastructure/persistence/converter/PrerequisiteListJsonConverter.java b/core/src/main/java/com/loremind/infrastructure/persistence/converter/PrerequisiteListJsonConverter.java index a80ace2..77e0d38 100644 --- a/core/src/main/java/com/loremind/infrastructure/persistence/converter/PrerequisiteListJsonConverter.java +++ b/core/src/main/java/com/loremind/infrastructure/persistence/converter/PrerequisiteListJsonConverter.java @@ -2,7 +2,7 @@ package com.loremind.infrastructure.persistence.converter; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import com.loremind.domain.campaigncontext.Prerequisite; +import com.loremind.domain.campaigncontext.quest.Prerequisite; import jakarta.persistence.AttributeConverter; import jakarta.persistence.Converter; diff --git a/core/src/main/java/com/loremind/infrastructure/persistence/converter/QuestNodeListJsonConverter.java b/core/src/main/java/com/loremind/infrastructure/persistence/converter/QuestNodeListJsonConverter.java index c32da4c..0508719 100644 --- a/core/src/main/java/com/loremind/infrastructure/persistence/converter/QuestNodeListJsonConverter.java +++ b/core/src/main/java/com/loremind/infrastructure/persistence/converter/QuestNodeListJsonConverter.java @@ -2,8 +2,8 @@ package com.loremind.infrastructure.persistence.converter; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import com.loremind.domain.campaigncontext.NodeType; -import com.loremind.domain.campaigncontext.QuestNodeRef; +import com.loremind.domain.campaigncontext.quest.NodeType; +import com.loremind.domain.campaigncontext.quest.QuestNodeRef; import jakarta.persistence.AttributeConverter; import jakarta.persistence.Converter; diff --git a/core/src/main/java/com/loremind/infrastructure/persistence/converter/RoomListJsonConverter.java b/core/src/main/java/com/loremind/infrastructure/persistence/converter/RoomListJsonConverter.java index 32b296e..8a6f314 100644 --- a/core/src/main/java/com/loremind/infrastructure/persistence/converter/RoomListJsonConverter.java +++ b/core/src/main/java/com/loremind/infrastructure/persistence/converter/RoomListJsonConverter.java @@ -2,7 +2,7 @@ package com.loremind.infrastructure.persistence.converter; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import com.loremind.domain.campaigncontext.Room; +import com.loremind.domain.campaigncontext.structure.Room; import jakarta.persistence.AttributeConverter; import jakarta.persistence.Converter; @@ -12,7 +12,7 @@ import java.util.List; /** * Convertit la liste de pièces explorables d'une Scene en JSON pour la persistance. * - *

Une Room contient elle-même une liste de {@link com.loremind.domain.campaigncontext.RoomBranch} + *

Une Room contient elle-même une liste de {@link com.loremind.domain.campaigncontext.structure.RoomBranch} * (record Java) ; Jackson 2.12+ sait sérialiser/désérialiser les records nativement * via le constructeur canonique, donc rien de spécial à faire pour les branches.

*/ diff --git a/core/src/main/java/com/loremind/infrastructure/persistence/converter/SceneBattlemapListJsonConverter.java b/core/src/main/java/com/loremind/infrastructure/persistence/converter/SceneBattlemapListJsonConverter.java index 830fd0b..8834fcb 100644 --- a/core/src/main/java/com/loremind/infrastructure/persistence/converter/SceneBattlemapListJsonConverter.java +++ b/core/src/main/java/com/loremind/infrastructure/persistence/converter/SceneBattlemapListJsonConverter.java @@ -2,7 +2,7 @@ package com.loremind.infrastructure.persistence.converter; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import com.loremind.domain.campaigncontext.SceneBattlemap; +import com.loremind.domain.campaigncontext.structure.SceneBattlemap; import jakarta.persistence.AttributeConverter; import jakarta.persistence.Converter; diff --git a/core/src/main/java/com/loremind/infrastructure/persistence/converter/SceneBranchListJsonConverter.java b/core/src/main/java/com/loremind/infrastructure/persistence/converter/SceneBranchListJsonConverter.java index 1bde658..59cf3c9 100644 --- a/core/src/main/java/com/loremind/infrastructure/persistence/converter/SceneBranchListJsonConverter.java +++ b/core/src/main/java/com/loremind/infrastructure/persistence/converter/SceneBranchListJsonConverter.java @@ -2,7 +2,7 @@ package com.loremind.infrastructure.persistence.converter; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import com.loremind.domain.campaigncontext.SceneBranch; +import com.loremind.domain.campaigncontext.structure.SceneBranch; import jakarta.persistence.AttributeConverter; import jakarta.persistence.Converter; diff --git a/core/src/main/java/com/loremind/infrastructure/persistence/entity/ArcJpaEntity.java b/core/src/main/java/com/loremind/infrastructure/persistence/entity/ArcJpaEntity.java index 70ed888..b24cf2f 100644 --- a/core/src/main/java/com/loremind/infrastructure/persistence/entity/ArcJpaEntity.java +++ b/core/src/main/java/com/loremind/infrastructure/persistence/entity/ArcJpaEntity.java @@ -1,6 +1,6 @@ package com.loremind.infrastructure.persistence.entity; -import com.loremind.domain.campaigncontext.ArcType; +import com.loremind.domain.campaigncontext.structure.ArcType; import com.loremind.infrastructure.persistence.converter.StringListJsonConverter; import jakarta.persistence.*; import org.hibernate.annotations.ColumnDefault; diff --git a/core/src/main/java/com/loremind/infrastructure/persistence/entity/QuestJpaEntity.java b/core/src/main/java/com/loremind/infrastructure/persistence/entity/QuestJpaEntity.java index 1ce5ba6..c99ddfb 100644 --- a/core/src/main/java/com/loremind/infrastructure/persistence/entity/QuestJpaEntity.java +++ b/core/src/main/java/com/loremind/infrastructure/persistence/entity/QuestJpaEntity.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.persistence.entity; -import com.loremind.domain.campaigncontext.Prerequisite; -import com.loremind.domain.campaigncontext.QuestNodeRef; +import com.loremind.domain.campaigncontext.quest.Prerequisite; +import com.loremind.domain.campaigncontext.quest.QuestNodeRef; import com.loremind.infrastructure.persistence.converter.PrerequisiteListJsonConverter; import com.loremind.infrastructure.persistence.converter.QuestNodeListJsonConverter; import com.loremind.infrastructure.persistence.converter.StringListJsonConverter; diff --git a/core/src/main/java/com/loremind/infrastructure/persistence/entity/QuestProgressionJpaEntity.java b/core/src/main/java/com/loremind/infrastructure/persistence/entity/QuestProgressionJpaEntity.java index da1c729..7dc26d6 100644 --- a/core/src/main/java/com/loremind/infrastructure/persistence/entity/QuestProgressionJpaEntity.java +++ b/core/src/main/java/com/loremind/infrastructure/persistence/entity/QuestProgressionJpaEntity.java @@ -1,6 +1,6 @@ package com.loremind.infrastructure.persistence.entity; -import com.loremind.domain.campaigncontext.ProgressionStatus; +import com.loremind.domain.campaigncontext.quest.ProgressionStatus; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/core/src/main/java/com/loremind/infrastructure/persistence/entity/SceneJpaEntity.java b/core/src/main/java/com/loremind/infrastructure/persistence/entity/SceneJpaEntity.java index 47e6c61..8874158 100644 --- a/core/src/main/java/com/loremind/infrastructure/persistence/entity/SceneJpaEntity.java +++ b/core/src/main/java/com/loremind/infrastructure/persistence/entity/SceneJpaEntity.java @@ -1,9 +1,9 @@ package com.loremind.infrastructure.persistence.entity; -import com.loremind.domain.campaigncontext.Room; -import com.loremind.domain.campaigncontext.SceneBattlemap; -import com.loremind.domain.campaigncontext.SceneBranch; -import com.loremind.domain.campaigncontext.SceneType; +import com.loremind.domain.campaigncontext.structure.Room; +import com.loremind.domain.campaigncontext.structure.SceneBattlemap; +import com.loremind.domain.campaigncontext.structure.SceneBranch; +import com.loremind.domain.campaigncontext.structure.SceneType; import com.loremind.infrastructure.persistence.converter.RoomListJsonConverter; import com.loremind.infrastructure.persistence.converter.SceneBattlemapListJsonConverter; import com.loremind.infrastructure.persistence.converter.SceneBranchListJsonConverter; diff --git a/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresArcRepository.java b/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresArcRepository.java index 162ce03..376016b 100644 --- a/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresArcRepository.java +++ b/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresArcRepository.java @@ -1,6 +1,6 @@ package com.loremind.infrastructure.persistence.postgres; -import com.loremind.domain.campaigncontext.Arc; +import com.loremind.domain.campaigncontext.structure.Arc; import com.loremind.domain.campaigncontext.ports.ArcRepository; import com.loremind.infrastructure.persistence.entity.ArcJpaEntity; import com.loremind.infrastructure.persistence.jpa.ArcJpaRepository; diff --git a/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresChapterRepository.java b/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresChapterRepository.java index 1c75306..78e8394 100644 --- a/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresChapterRepository.java +++ b/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresChapterRepository.java @@ -1,6 +1,6 @@ package com.loremind.infrastructure.persistence.postgres; -import com.loremind.domain.campaigncontext.Chapter; +import com.loremind.domain.campaigncontext.structure.Chapter; import com.loremind.domain.campaigncontext.ports.ChapterRepository; import com.loremind.infrastructure.persistence.entity.ChapterJpaEntity; import com.loremind.infrastructure.persistence.jpa.ChapterJpaRepository; diff --git a/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresCharacterRepository.java b/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresCharacterRepository.java index 4e060e4..a2a63bb 100644 --- a/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresCharacterRepository.java +++ b/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresCharacterRepository.java @@ -1,6 +1,6 @@ package com.loremind.infrastructure.persistence.postgres; -import com.loremind.domain.campaigncontext.Character; +import com.loremind.domain.campaigncontext.bestiary.Character; import com.loremind.domain.campaigncontext.ports.CharacterRepository; import com.loremind.infrastructure.persistence.entity.CharacterJpaEntity; import com.loremind.infrastructure.persistence.jpa.CharacterJpaRepository; diff --git a/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresEnemyRepository.java b/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresEnemyRepository.java index 5a9db02..9b5aef5 100644 --- a/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresEnemyRepository.java +++ b/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresEnemyRepository.java @@ -1,6 +1,6 @@ package com.loremind.infrastructure.persistence.postgres; -import com.loremind.domain.campaigncontext.Enemy; +import com.loremind.domain.campaigncontext.bestiary.Enemy; import com.loremind.domain.campaigncontext.ports.EnemyRepository; import com.loremind.infrastructure.persistence.entity.EnemyJpaEntity; import com.loremind.infrastructure.persistence.jpa.EnemyJpaRepository; diff --git a/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresItemCatalogRepository.java b/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresItemCatalogRepository.java index 30438cd..d7a6f39 100644 --- a/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresItemCatalogRepository.java +++ b/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresItemCatalogRepository.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.persistence.postgres; -import com.loremind.domain.campaigncontext.CatalogItem; -import com.loremind.domain.campaigncontext.ItemCatalog; +import com.loremind.domain.campaigncontext.itemcatalog.CatalogItem; +import com.loremind.domain.campaigncontext.itemcatalog.ItemCatalog; import com.loremind.domain.campaigncontext.ports.ItemCatalogRepository; import com.loremind.infrastructure.persistence.entity.CatalogItemJpaEntity; import com.loremind.infrastructure.persistence.entity.ItemCatalogJpaEntity; diff --git a/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresNotebookRepository.java b/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresNotebookRepository.java index f16d1d8..55a9a59 100644 --- a/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresNotebookRepository.java +++ b/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresNotebookRepository.java @@ -1,8 +1,8 @@ package com.loremind.infrastructure.persistence.postgres; -import com.loremind.domain.campaigncontext.Notebook; -import com.loremind.domain.campaigncontext.NotebookMessage; -import com.loremind.domain.campaigncontext.NotebookSource; +import com.loremind.domain.campaigncontext.notebook.Notebook; +import com.loremind.domain.campaigncontext.notebook.NotebookMessage; +import com.loremind.domain.campaigncontext.notebook.NotebookSource; import com.loremind.domain.campaigncontext.ports.NotebookRepository; import com.loremind.infrastructure.persistence.entity.NotebookJpaEntity; import com.loremind.infrastructure.persistence.entity.NotebookMessageJpaEntity; diff --git a/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresNpcRepository.java b/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresNpcRepository.java index 2a5fac3..ad9b147 100644 --- a/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresNpcRepository.java +++ b/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresNpcRepository.java @@ -1,6 +1,6 @@ package com.loremind.infrastructure.persistence.postgres; -import com.loremind.domain.campaigncontext.Npc; +import com.loremind.domain.campaigncontext.bestiary.Npc; import com.loremind.domain.campaigncontext.ports.NpcRepository; import com.loremind.infrastructure.persistence.entity.NpcJpaEntity; import com.loremind.infrastructure.persistence.jpa.NpcJpaRepository; diff --git a/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresQuestProgressionRepository.java b/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresQuestProgressionRepository.java index 680afab..dcc78d7 100644 --- a/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresQuestProgressionRepository.java +++ b/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresQuestProgressionRepository.java @@ -1,6 +1,6 @@ package com.loremind.infrastructure.persistence.postgres; -import com.loremind.domain.campaigncontext.ProgressionStatus; +import com.loremind.domain.campaigncontext.quest.ProgressionStatus; import com.loremind.domain.playcontext.QuestProgression; import com.loremind.domain.playcontext.ports.QuestProgressionRepository; import com.loremind.infrastructure.persistence.entity.QuestProgressionJpaEntity; diff --git a/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresQuestRepository.java b/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresQuestRepository.java index f2bded8..0ec32d1 100644 --- a/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresQuestRepository.java +++ b/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresQuestRepository.java @@ -1,6 +1,6 @@ package com.loremind.infrastructure.persistence.postgres; -import com.loremind.domain.campaigncontext.Quest; +import com.loremind.domain.campaigncontext.quest.Quest; import com.loremind.domain.campaigncontext.ports.QuestRepository; import com.loremind.infrastructure.persistence.entity.QuestJpaEntity; import com.loremind.infrastructure.persistence.jpa.QuestJpaRepository; diff --git a/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresRandomTableRepository.java b/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresRandomTableRepository.java index 8453eb1..8d472ef 100644 --- a/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresRandomTableRepository.java +++ b/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresRandomTableRepository.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.persistence.postgres; -import com.loremind.domain.campaigncontext.RandomTable; -import com.loremind.domain.campaigncontext.RandomTableEntry; +import com.loremind.domain.campaigncontext.randomtable.RandomTable; +import com.loremind.domain.campaigncontext.randomtable.RandomTableEntry; import com.loremind.domain.campaigncontext.ports.RandomTableRepository; import com.loremind.infrastructure.persistence.entity.RandomTableEntryJpaEntity; import com.loremind.infrastructure.persistence.entity.RandomTableJpaEntity; diff --git a/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresSceneRepository.java b/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresSceneRepository.java index 9e27cb2..6847bd6 100644 --- a/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresSceneRepository.java +++ b/core/src/main/java/com/loremind/infrastructure/persistence/postgres/PostgresSceneRepository.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.persistence.postgres; -import com.loremind.domain.campaigncontext.Scene; -import com.loremind.domain.campaigncontext.SceneType; +import com.loremind.domain.campaigncontext.structure.Scene; +import com.loremind.domain.campaigncontext.structure.SceneType; import com.loremind.domain.campaigncontext.ports.SceneRepository; import com.loremind.infrastructure.persistence.entity.SceneJpaEntity; import com.loremind.infrastructure.persistence.jpa.SceneJpaRepository; diff --git a/core/src/main/java/com/loremind/infrastructure/transfer/CampaignContentInserter.java b/core/src/main/java/com/loremind/infrastructure/transfer/CampaignContentInserter.java index d8fca08..00ed559 100644 --- a/core/src/main/java/com/loremind/infrastructure/transfer/CampaignContentInserter.java +++ b/core/src/main/java/com/loremind/infrastructure/transfer/CampaignContentInserter.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.transfer; -import com.loremind.domain.campaigncontext.SceneBattlemap; -import com.loremind.domain.campaigncontext.SceneType; +import com.loremind.domain.campaigncontext.structure.SceneBattlemap; +import com.loremind.domain.campaigncontext.structure.SceneType; import com.loremind.infrastructure.persistence.converter.PrerequisiteListJsonConverter; import com.loremind.infrastructure.persistence.converter.QuestNodeListJsonConverter; import com.loremind.infrastructure.persistence.entity.ArcJpaEntity; @@ -106,7 +106,7 @@ class CampaignContentInserter { e.setRewards(d.rewards()); e.setResolution(d.resolution()); e.setRelatedPageIds(d.relatedPageIds()); // remappe en 2e passe - e.setIllustrationImageIds(d.illustrationImageIds()); + e.setIllustrationImageIds(IdRemapper.remapStringList(maps.imageMap, d.illustrationImageIds())); maps.arcMap.put(d.id(), arcRepo.save(e).getId()); } result.count("arcs", maps.arcMap.size()); @@ -181,7 +181,7 @@ class CampaignContentInserter { e.setPlayerObjectives(d.playerObjectives()); e.setNarrativeStakes(d.narrativeStakes()); e.setRelatedPageIds(d.relatedPageIds()); // remappe en 2e passe - e.setIllustrationImageIds(d.illustrationImageIds()); + e.setIllustrationImageIds(IdRemapper.remapStringList(maps.imageMap, d.illustrationImageIds())); maps.chapterMap.put(d.id(), chapterRepo.save(e).getId()); } result.count("chapters", maps.chapterMap.size()); @@ -202,7 +202,7 @@ class CampaignContentInserter { e.setPlayerObjectives(d.playerObjectives()); e.setNarrativeStakes(d.narrativeStakes()); e.setRelatedPageIds(d.relatedPageIds()); // remappé 2e passe - e.setIllustrationImageIds(d.illustrationImageIds()); + e.setIllustrationImageIds(IdRemapper.remapStringList(maps.imageMap, d.illustrationImageIds())); maps.questMap.put(d.id(), questRepo.save(e).getId()); } @@ -218,10 +218,10 @@ class CampaignContentInserter { for (ContentExport.NpcDto d : nullSafe(export.npcs())) { NpcJpaEntity e = new NpcJpaEntity(); e.setName(d.name()); - e.setPortraitImageId(d.portraitImageId()); - e.setHeaderImageId(d.headerImageId()); + e.setPortraitImageId(IdRemapper.remapStringId(maps.imageMap, d.portraitImageId())); + e.setHeaderImageId(IdRemapper.remapStringId(maps.imageMap, d.headerImageId())); e.setValues(d.values()); - e.setImageValues(d.imageValues()); + e.setImageValues(IdRemapper.remapImageValues(maps.imageMap, d.imageValues())); e.setKeyValueValues(d.keyValueValues()); e.setCampaignId(IdRemapper.remapId(maps.campaignMap, d.campaignId())); e.setRelatedPageIds(d.relatedPageIds()); // remappe en 2e passe @@ -237,10 +237,10 @@ class CampaignContentInserter { e.setName(d.name()); e.setLevel(d.level()); e.setFolder(d.folder()); - e.setPortraitImageId(d.portraitImageId()); - e.setHeaderImageId(d.headerImageId()); + e.setPortraitImageId(IdRemapper.remapStringId(maps.imageMap, d.portraitImageId())); + e.setHeaderImageId(IdRemapper.remapStringId(maps.imageMap, d.headerImageId())); e.setValues(d.values()); - e.setImageValues(d.imageValues()); + e.setImageValues(IdRemapper.remapImageValues(maps.imageMap, d.imageValues())); e.setKeyValueValues(d.keyValueValues()); e.setCampaignId(IdRemapper.remapId(maps.campaignMap, d.campaignId())); e.setFoundryRef(d.foundryRef()); // ref externe Foundry : conservee telle quelle @@ -269,20 +269,22 @@ class CampaignContentInserter { e.setEnemies(d.enemies()); e.setEnemyIds(d.enemyIds()); // remappe en 2e passe e.setRelatedPageIds(d.relatedPageIds()); // remappe en 2e passe - e.setIllustrationImageIds(d.illustrationImageIds()); - // Battlemaps : ids StoredFile passes tels quels (meme logique que les refs - // d'images illustration, non remappees). Les exports anterieurs a V22 - // portaient une paire unique -> reconstituee en premiere entree de liste. + e.setIllustrationImageIds(IdRemapper.remapStringList(maps.imageMap, d.illustrationImageIds())); + // Battlemaps : ids StoredFile remappes via storedFileMap. Les exports anterieurs + // a V22 portaient une paire unique -> reconstituee en premiere entree de liste. List battlemaps = d.battlemaps(); if ((battlemaps == null || battlemaps.isEmpty()) && (d.battlemapMediaFileId() != null || d.battlemapDataFileId() != null)) { battlemaps = List.of(new SceneBattlemap("", d.battlemapMediaFileId(), d.battlemapDataFileId())); } + battlemaps = IdRemapper.remapBattlemaps(maps.storedFileMap, battlemaps); e.setBattlemaps(battlemaps != null ? battlemaps : List.of()); e.setGraphX(d.graphX()); e.setGraphY(d.graphY()); e.setBranches(d.branches()); // remappe en 2e passe - e.setRooms(d.rooms()); // Rooms: UUID, non remappes + // Rooms : l'id (UUID) et les branches restent tels quels ; seules leurs refs + // d'images (galerie + plan) sont remappees vers les images importees. + e.setRooms(IdRemapper.remapRoomImages(maps.imageMap, d.rooms())); maps.sceneMap.put(d.id(), sceneRepo.save(e).getId()); } result.count("scenes", maps.sceneMap.size()); diff --git a/core/src/main/java/com/loremind/infrastructure/transfer/ExportService.java b/core/src/main/java/com/loremind/infrastructure/transfer/ExportService.java index e25c508..624bab5 100644 --- a/core/src/main/java/com/loremind/infrastructure/transfer/ExportService.java +++ b/core/src/main/java/com/loremind/infrastructure/transfer/ExportService.java @@ -2,7 +2,7 @@ package com.loremind.infrastructure.transfer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; -import com.loremind.domain.campaigncontext.Room; +import com.loremind.domain.campaigncontext.structure.Room; import com.loremind.domain.files.ports.FileStorage; import com.loremind.domain.images.ports.ImageStorage; import com.loremind.infrastructure.persistence.converter.PrerequisiteListJsonConverter; @@ -240,6 +240,7 @@ public class ExportService { Set imageRefs = new LinkedHashSet<>(); arcEntities.forEach(a -> addAll(imageRefs, a.getIllustrationImageIds())); chapterEntities.forEach(c -> addAll(imageRefs, c.getIllustrationImageIds())); + campaignQuests.forEach(q -> addAll(imageRefs, q.getIllustrationImageIds())); sceneEntities.forEach(s -> addAll(imageRefs, s.getIllustrationImageIds())); sceneEntities.forEach(s -> addRoomImageRefs(imageRefs, s.getRooms())); npcEntities.forEach(n -> { add(imageRefs, n.getPortraitImageId()); add(imageRefs, n.getHeaderImageId()); addImageValues(imageRefs, n.getImageValues()); }); @@ -398,6 +399,9 @@ public class ExportService { Set refs = new LinkedHashSet<>(); for (ContentExport.ArcDto a : export.arcs()) addAll(refs, a.illustrationImageIds()); for (ContentExport.ChapterDto c : export.chapters()) addAll(refs, c.illustrationImageIds()); + if (export.quests() != null) { + for (ContentExport.QuestDto q : export.quests()) addAll(refs, q.illustrationImageIds()); + } for (ContentExport.SceneDto s : export.scenes()) addAll(refs, s.illustrationImageIds()); for (ContentExport.SceneDto s : export.scenes()) addRoomImageRefs(refs, s.rooms()); for (ContentExport.CharacterDto c : export.characters()) { diff --git a/core/src/main/java/com/loremind/infrastructure/transfer/IdRemapper.java b/core/src/main/java/com/loremind/infrastructure/transfer/IdRemapper.java index 44b5919..7300a46 100644 --- a/core/src/main/java/com/loremind/infrastructure/transfer/IdRemapper.java +++ b/core/src/main/java/com/loremind/infrastructure/transfer/IdRemapper.java @@ -1,12 +1,15 @@ package com.loremind.infrastructure.transfer; -import com.loremind.domain.campaigncontext.ArcType; -import com.loremind.domain.campaigncontext.NodeType; -import com.loremind.domain.campaigncontext.Prerequisite; -import com.loremind.domain.campaigncontext.QuestNodeRef; -import com.loremind.domain.campaigncontext.SceneBranch; +import com.loremind.domain.campaigncontext.structure.ArcType; +import com.loremind.domain.campaigncontext.quest.NodeType; +import com.loremind.domain.campaigncontext.quest.Prerequisite; +import com.loremind.domain.campaigncontext.quest.QuestNodeRef; +import com.loremind.domain.campaigncontext.structure.Room; +import com.loremind.domain.campaigncontext.structure.SceneBattlemap; +import com.loremind.domain.campaigncontext.structure.SceneBranch; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -49,6 +52,77 @@ final class IdRemapper { return out; } + /** + * Remap des valeurs d'un champ image « multi » ({@code slot → [imageId...]}, ex. galeries + * de PNJ/pages) : chaque id d'image est réécrit via {@code imageMap}, clés (slots) inchangées. + */ + static Map> remapImageValues(Map imageMap, + Map> imageValues) { + if (imageValues == null) return null; + Map> out = new LinkedHashMap<>(); + for (Map.Entry> e : imageValues.entrySet()) { + out.put(e.getKey(), remapStringList(imageMap, e.getValue())); + } + return out; + } + + /** + * Remap du cadrage d'images d'une page ({@code fieldKey → imageId → cadrage}) : seule la + * clé INTERNE (imageId) est réécrite via {@code imageMap}, la clé externe (fieldKey) et la + * valeur de cadrage sont inchangées. Générique sur la valeur pour éviter d'importer le type + * de cadrage ici. Sans ce remap, le cadrage resterait indexé sous l'ancien id et l'image + * importée perdrait son pan/zoom. + */ + static Map> remapImageFraming(Map imageMap, + Map> framing) { + if (framing == null) return null; + Map> out = new LinkedHashMap<>(); + for (Map.Entry> outer : framing.entrySet()) { + Map inner = outer.getValue(); + if (inner == null) { + out.put(outer.getKey(), null); + continue; + } + Map remappedInner = new LinkedHashMap<>(); + for (Map.Entry e : inner.entrySet()) { + remappedInner.put(remapStringId(imageMap, e.getKey()), e.getValue()); + } + out.put(outer.getKey(), remappedInner); + } + return out; + } + + /** + * Remap des réfs d'images portées par les salles (Room) d'une scène : galerie + * ({@code illustrationImageIds}) + plan ({@code mapImageId}). Room étant un value object + * mutable (Lombok), on réécrit EN PLACE — la liste vient d'une désérialisation jetable. + */ + static List remapRoomImages(Map imageMap, List rooms) { + if (rooms == null) return null; + for (Room r : rooms) { + if (r == null) continue; + r.setIllustrationImageIds(remapStringList(imageMap, r.getIllustrationImageIds())); + r.setMapImageId(remapStringId(imageMap, r.getMapImageId())); + } + return rooms; + } + + /** + * Remap des réfs de fichiers (StoredFile) des battlemaps d'une scène : {@code mediaFileId} + * + {@code dataFileId} réécrits via {@code fileMap}. SceneBattlemap est un record immuable, + * on reconstruit chaque entrée. + */ + static List remapBattlemaps(Map fileMap, List battlemaps) { + if (battlemaps == null) return null; + List out = new ArrayList<>(battlemaps.size()); + for (SceneBattlemap bm : battlemaps) { + out.add(new SceneBattlemap(bm.label(), + remapStringId(fileMap, bm.mediaFileId()), + remapStringId(fileMap, bm.dataFileId()))); + } + return out; + } + /** * Remappe {@code QuestCompleted.questId} via {@code idMap}. L'appelant fournit la map * sémantiquement correcte selon le contexte : {@code questMap} pour des prérequis de diff --git a/core/src/main/java/com/loremind/infrastructure/transfer/ImageImporter.java b/core/src/main/java/com/loremind/infrastructure/transfer/ImageImporter.java index ff290f9..a406d87 100644 --- a/core/src/main/java/com/loremind/infrastructure/transfer/ImageImporter.java +++ b/core/src/main/java/com/loremind/infrastructure/transfer/ImageImporter.java @@ -7,7 +7,6 @@ import com.loremind.infrastructure.transfer.dto.ContentExport; import org.springframework.stereotype.Component; import java.io.ByteArrayInputStream; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -17,6 +16,12 @@ import java.util.Map; * Les binaires sont stockés sous LEUR CLÉ D'ORIGINE (pas de remapping de clé) : * une image dont la clé existe déjà est RÉUTILISÉE (pas de réupload), pour éviter * les doublons quand on agrège plusieurs exports dans la même base. + *

+ * En revanche l'ID de ligne {@code images} est REMAPPÉ (comme tout le reste en mode + * fusion) : on alimente {@link ImportIdMaps#imageMap} {@code ancienId → nouvelId} pour + * que les entités importées (portraits, illustrations, galeries, plans de salle) + * pointent la bonne image sur la machine cible. Sans ce remap, un export repris sur + * une autre base montre des images absentes ou mélangées. */ @Component class ImageImporter { @@ -30,47 +35,62 @@ class ImageImporter { } /** - * Réécrit les binaires d'images (clé préservée) et leurs métadonnées. + * Réécrit les binaires d'images (clé préservée) + métadonnées, et remplit la map de + * remapping des ids d'images. + *

+ * On itère sur les MÉTADONNÉES ({@code export.images()}) et non sur les binaires, car + * c'est là que vit l'{@code ancien id} nécessaire au remapping. Une image déjà présente + * (même clé) est réutilisée : son id EXISTANT devient la cible du remap. * - * @param export contenu importé (source des métadonnées par clé) - * @param imageBinaries {@code storageKey → binaire} lus depuis le zip - * @param result compteurs d'images (uploadées / réutilisées) à incrémenter + * @param export contenu importé (source des métadonnées, dont l'ancien id) + * @param imageBinaries {@code storageKey → binaire} lus depuis le zip + * @param maps état de remapping : {@code imageMap} est alimentée ici + * @param result compteurs d'images (uploadées / réutilisées) à incrémenter */ void importImages(ContentExport export, Map imageBinaries, + ImportIdMaps maps, ImportResult.Builder result) { - // Index des métadonnées d'image par clé (depuis le data.json). - Map metaByKey = new HashMap<>(); - for (ContentExport.ImageDto img : nullSafe(export.images())) { - if (img.storageKey() != null) metaByKey.put(img.storageKey(), img); - } + for (ContentExport.ImageDto meta : nullSafe(export.images())) { + String storageKey = meta.storageKey(); + if (storageKey == null || storageKey.isBlank()) continue; - for (Map.Entry bin : imageBinaries.entrySet()) { - String storageKey = bin.getKey(); - byte[] data = bin.getValue(); - if (imageRepo.findByStorageKey(storageKey).isPresent()) { - // Image déjà présente : on réutilise, pas de réupload (éviter doublon). + var existing = imageRepo.findByStorageKey(storageKey); + if (existing.isPresent()) { + // Image déjà présente : réutilisée (pas de réupload). L'ancien id pointe + // désormais la ligne existante. + mapId(maps, meta.id(), existing.get().getId()); result.imageReused(); continue; } - ContentExport.ImageDto meta = metaByKey.get(storageKey); - String contentType = meta != null && meta.contentType() != null + + byte[] data = imageBinaries.get(storageKey); + if (data == null) { + // Métadonnée sans binaire (ex. image orpheline non embarquée) : rien à + // matérialiser, pas de cible de remap. + continue; + } + + String contentType = meta.contentType() != null ? meta.contentType() : guessContentType(storageKey); - long size = meta != null ? meta.sizeBytes() : data.length; + long size = meta.sizeBytes() > 0 ? meta.sizeBytes() : data.length; imageStorage.store(storageKey, contentType, new ByteArrayInputStream(data), data.length); ImageJpaEntity e = new ImageJpaEntity(); - e.setFilename(meta != null && meta.filename() != null - ? meta.filename() : fileNameOf(storageKey)); + e.setFilename(meta.filename() != null ? meta.filename() : fileNameOf(storageKey)); e.setContentType(contentType); e.setSizeBytes(size); e.setStorageKey(storageKey); - imageRepo.save(e); + mapId(maps, meta.id(), imageRepo.save(e).getId()); result.imageUploaded(); } } + private static void mapId(ImportIdMaps maps, Long oldId, Long newId) { + if (oldId != null && newId != null) maps.imageMap.put(oldId, newId); + } + private static List nullSafe(List list) { return list != null ? list : List.of(); } diff --git a/core/src/main/java/com/loremind/infrastructure/transfer/ImportIdMaps.java b/core/src/main/java/com/loremind/infrastructure/transfer/ImportIdMaps.java index f53842e..fe9bc9a 100644 --- a/core/src/main/java/com/loremind/infrastructure/transfer/ImportIdMaps.java +++ b/core/src/main/java/com/loremind/infrastructure/transfer/ImportIdMaps.java @@ -16,6 +16,13 @@ import java.util.Map; */ final class ImportIdMaps { + // Images/fichiers : remappés dès la 1re phase (importés AVANT les inserters), pour + // que toute réf d'image (portrait, illustration, imageValues, plan de salle) ou de + // fichier (battlemap) porte le NOUVEL id dans la base cible. Sans ça, un import + // inter-machines laisse des refs pendantes ou pointant l'image d'un autre id. + final Map imageMap = new HashMap<>(); + final Map storedFileMap = new HashMap<>(); + final Map gameSystemMap = new HashMap<>(); final Map loreMap = new HashMap<>(); final Map loreNodeMap = new HashMap<>(); diff --git a/core/src/main/java/com/loremind/infrastructure/transfer/ImportService.java b/core/src/main/java/com/loremind/infrastructure/transfer/ImportService.java index 8334392..d266d01 100644 --- a/core/src/main/java/com/loremind/infrastructure/transfer/ImportService.java +++ b/core/src/main/java/com/loremind/infrastructure/transfer/ImportService.java @@ -19,7 +19,10 @@ import java.io.InputStream; *

  • {@link ImportArchiveParser} : {@code data.json} -> {@link ContentExport}, * binaires gardes en memoire.
  • *
  • {@link ImageImporter} / {@link StoredFileImporter} : reecrit les binaires - * sous LEUR CLE D'ORIGINE (pas de remapping de cle) ; skip si la cle existe deja.
  • + * sous LEUR CLE D'ORIGINE (pas de remapping de cle) ; skip si la cle existe deja. + * L'id de ligne image/fichier est en revanche REMAPPE oldId -> newId (maps + * {@link ImportIdMaps#imageMap}/{@link ImportIdMaps#storedFileMap}) pour que les + * entites inserees ensuite pointent la bonne image/fichier dans la base cible. *
  • Insertion top-down en construisant les maps de remapping par type * ({@link ImportIdMaps}) : {@link LoreContentInserter} (referentiel lore + systemes), * {@link CampaignContentInserter} (contenu de campagne, quetes legacy comprises), @@ -65,13 +68,14 @@ public class ImportService { ContentExport export = archive.export(); ImportResult.Builder result = new ImportResult.Builder(); + ImportIdMaps maps = new ImportIdMaps(); - // 2. Reecriture des images + fichiers (cle preservee). - imageImporter.importImages(export, archive.imageBinaries(), result); - storedFileImporter.importFiles(export, archive.fileBinaries(), result); + // 2. Reecriture des images + fichiers (cle preservee) + remap des ids (image/fichier) + // dans maps, consomme par les inserters ci-dessous. + imageImporter.importImages(export, archive.imageBinaries(), maps, result); + storedFileImporter.importFiles(export, archive.fileBinaries(), maps, result); // 3. Insertion top-down + maps de remapping. - ImportIdMaps maps = new ImportIdMaps(); loreContentInserter.insert(export, maps, result); campaignContentInserter.insert(export, maps, result); playStateInserter.insert(export, maps, result); diff --git a/core/src/main/java/com/loremind/infrastructure/transfer/LegacyQuestConverter.java b/core/src/main/java/com/loremind/infrastructure/transfer/LegacyQuestConverter.java index 016b764..e15fdeb 100644 --- a/core/src/main/java/com/loremind/infrastructure/transfer/LegacyQuestConverter.java +++ b/core/src/main/java/com/loremind/infrastructure/transfer/LegacyQuestConverter.java @@ -1,8 +1,8 @@ package com.loremind.infrastructure.transfer; -import com.loremind.domain.campaigncontext.NodeType; -import com.loremind.domain.campaigncontext.Prerequisite; -import com.loremind.domain.campaigncontext.QuestNodeRef; +import com.loremind.domain.campaigncontext.quest.NodeType; +import com.loremind.domain.campaigncontext.quest.Prerequisite; +import com.loremind.domain.campaigncontext.quest.QuestNodeRef; import com.loremind.infrastructure.persistence.converter.PrerequisiteListJsonConverter; import com.loremind.infrastructure.persistence.entity.QuestJpaEntity; import com.loremind.infrastructure.persistence.jpa.QuestJpaRepository; diff --git a/core/src/main/java/com/loremind/infrastructure/transfer/LoreContentInserter.java b/core/src/main/java/com/loremind/infrastructure/transfer/LoreContentInserter.java index 4c1ee2c..f395b71 100644 --- a/core/src/main/java/com/loremind/infrastructure/transfer/LoreContentInserter.java +++ b/core/src/main/java/com/loremind/infrastructure/transfer/LoreContentInserter.java @@ -104,8 +104,8 @@ class LoreContentInserter { e.setTemplateId(IdRemapper.remapId(maps.templateMap, d.templateId())); e.setTitle(d.title()); e.setValues(d.values()); - e.setImageValues(d.imageValues()); - e.setImageFraming(d.imageFraming()); + e.setImageValues(IdRemapper.remapImageValues(maps.imageMap, d.imageValues())); + e.setImageFraming(IdRemapper.remapImageFraming(maps.imageMap, d.imageFraming())); e.setKeyValueValues(d.keyValueValues()); e.setTableValues(d.tableValues()); e.setNotes(d.notes()); diff --git a/core/src/main/java/com/loremind/infrastructure/transfer/PlayStateInserter.java b/core/src/main/java/com/loremind/infrastructure/transfer/PlayStateInserter.java index d105dee..e964f4a 100644 --- a/core/src/main/java/com/loremind/infrastructure/transfer/PlayStateInserter.java +++ b/core/src/main/java/com/loremind/infrastructure/transfer/PlayStateInserter.java @@ -1,6 +1,6 @@ package com.loremind.infrastructure.transfer; -import com.loremind.domain.campaigncontext.ProgressionStatus; +import com.loremind.domain.campaigncontext.quest.ProgressionStatus; import com.loremind.domain.playcontext.ClockTrigger; import com.loremind.domain.playcontext.EntryType; import com.loremind.infrastructure.persistence.entity.CharacterJpaEntity; @@ -166,10 +166,10 @@ class PlayStateInserter { for (ContentExport.CharacterDto d : nullSafe(export.characters())) { CharacterJpaEntity e = new CharacterJpaEntity(); e.setName(d.name()); - e.setPortraitImageId(d.portraitImageId()); - e.setHeaderImageId(d.headerImageId()); + e.setPortraitImageId(IdRemapper.remapStringId(maps.imageMap, d.portraitImageId())); + e.setHeaderImageId(IdRemapper.remapStringId(maps.imageMap, d.headerImageId())); e.setValues(d.values()); - e.setImageValues(d.imageValues()); + e.setImageValues(IdRemapper.remapImageValues(maps.imageMap, d.imageValues())); e.setKeyValueValues(d.keyValueValues()); e.setCampaignId(IdRemapper.remapId(maps.campaignMap, d.campaignId())); // playthroughId remappe vers la Partie importee (ou null si le jeu n'etait pas diff --git a/core/src/main/java/com/loremind/infrastructure/transfer/StoredFileImporter.java b/core/src/main/java/com/loremind/infrastructure/transfer/StoredFileImporter.java index ce4145d..8dedb8a 100644 --- a/core/src/main/java/com/loremind/infrastructure/transfer/StoredFileImporter.java +++ b/core/src/main/java/com/loremind/infrastructure/transfer/StoredFileImporter.java @@ -7,7 +7,6 @@ import com.loremind.infrastructure.transfer.dto.ContentExport; import org.springframework.stereotype.Component; import java.io.ByteArrayInputStream; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,7 +15,9 @@ import java.util.Map; *

    * Pendant de {@link ImageImporter} pour la table {@code stored_files}. Les binaires * sont stockes sous LEUR CLE D'ORIGINE ; un fichier dont la cle existe deja est - * REUTILISE (pas de reupload). + * REUTILISE (pas de reupload). L'id de ligne est REMAPPE dans + * {@link ImportIdMaps#storedFileMap} pour que les battlemaps des scenes importees + * pointent le bon fichier sur la machine cible. */ @Component class StoredFileImporter { @@ -30,46 +31,57 @@ class StoredFileImporter { } /** - * Reecrit les binaires de fichiers (cle preservee) et leurs metadonnees. + * Reecrit les binaires de fichiers (cle preservee) + metadonnees, et remplit la map de + * remapping des ids de fichiers. Itere sur les METADONNEES (source de l'ancien id), + * pas sur les binaires (meme logique que {@link ImageImporter}). * - * @param export contenu importe (source des metadonnees par cle) + * @param export contenu importe (source des metadonnees, dont l'ancien id) * @param fileBinaries {@code storageKey -> binaire} lus depuis le zip (prefixe files/) + * @param maps etat de remapping : {@code storedFileMap} est alimentee ici * @param result compteurs a incrementer */ void importFiles(ContentExport export, Map fileBinaries, + ImportIdMaps maps, ImportResult.Builder result) { - Map metaByKey = new HashMap<>(); - for (ContentExport.StoredFileDto f : nullSafe(export.storedFiles())) { - if (f.storageKey() != null) metaByKey.put(f.storageKey(), f); - } - int imported = 0; - for (Map.Entry bin : fileBinaries.entrySet()) { - String storageKey = bin.getKey(); - byte[] data = bin.getValue(); - if (fileRepo.findByStorageKey(storageKey).isPresent()) { - continue; // deja present : reutilise, pas de reupload + for (ContentExport.StoredFileDto meta : nullSafe(export.storedFiles())) { + String storageKey = meta.storageKey(); + if (storageKey == null || storageKey.isBlank()) continue; + + var existing = fileRepo.findByStorageKey(storageKey); + if (existing.isPresent()) { + // Deja present : reutilise (pas de reupload). L'ancien id pointe la ligne existante. + mapId(maps, meta.id(), existing.get().getId()); + continue; } - ContentExport.StoredFileDto meta = metaByKey.get(storageKey); - String contentType = meta != null && meta.contentType() != null + + byte[] data = fileBinaries.get(storageKey); + if (data == null) { + continue; // metadonnee sans binaire : rien a materialiser + } + + String contentType = meta.contentType() != null ? meta.contentType() : "application/octet-stream"; - long size = meta != null ? meta.sizeBytes() : data.length; + long size = meta.sizeBytes() > 0 ? meta.sizeBytes() : data.length; fileStorage.store(storageKey, contentType, new ByteArrayInputStream(data), data.length); StoredFileJpaEntity e = new StoredFileJpaEntity(); - e.setFilename(meta != null && meta.filename() != null - ? meta.filename() : fileNameOf(storageKey)); + e.setFilename(meta.filename() != null ? meta.filename() : fileNameOf(storageKey)); e.setContentType(contentType); e.setSizeBytes(size); e.setStorageKey(storageKey); - fileRepo.save(e); + mapId(maps, meta.id(), fileRepo.save(e).getId()); imported++; } result.count("storedFiles", imported); } + private static void mapId(ImportIdMaps maps, Long oldId, Long newId) { + if (oldId != null && newId != null) maps.storedFileMap.put(oldId, newId); + } + private static List nullSafe(List list) { return list != null ? list : List.of(); } diff --git a/core/src/main/java/com/loremind/infrastructure/transfer/dto/ContentExport.java b/core/src/main/java/com/loremind/infrastructure/transfer/dto/ContentExport.java index 58a2d2d..24a65b8 100644 --- a/core/src/main/java/com/loremind/infrastructure/transfer/dto/ContentExport.java +++ b/core/src/main/java/com/loremind/infrastructure/transfer/dto/ContentExport.java @@ -1,9 +1,9 @@ package com.loremind.infrastructure.transfer.dto; -import com.loremind.domain.campaigncontext.Room; -import com.loremind.domain.campaigncontext.SceneBattlemap; -import com.loremind.domain.campaigncontext.SceneBranch; -import com.loremind.domain.campaigncontext.SceneType; +import com.loremind.domain.campaigncontext.structure.Room; +import com.loremind.domain.campaigncontext.structure.SceneBattlemap; +import com.loremind.domain.campaigncontext.structure.SceneBranch; +import com.loremind.domain.campaigncontext.structure.SceneType; import com.loremind.domain.shared.template.TemplateField; import java.util.List; diff --git a/core/src/main/java/com/loremind/infrastructure/transfer/foundry/FoundryExportService.java b/core/src/main/java/com/loremind/infrastructure/transfer/foundry/FoundryExportService.java index f45b317..e8c6e56 100644 --- a/core/src/main/java/com/loremind/infrastructure/transfer/foundry/FoundryExportService.java +++ b/core/src/main/java/com/loremind/infrastructure/transfer/foundry/FoundryExportService.java @@ -3,10 +3,10 @@ package com.loremind.infrastructure.transfer.foundry; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; -import com.loremind.domain.campaigncontext.Room; -import com.loremind.domain.campaigncontext.RoomBranch; -import com.loremind.domain.campaigncontext.SceneBattlemap; -import com.loremind.domain.campaigncontext.SceneBranch; +import com.loremind.domain.campaigncontext.structure.Room; +import com.loremind.domain.campaigncontext.structure.RoomBranch; +import com.loremind.domain.campaigncontext.structure.SceneBattlemap; +import com.loremind.domain.campaigncontext.structure.SceneBranch; import com.loremind.domain.shared.template.FieldType; import com.loremind.domain.shared.template.TemplateField; import com.loremind.infrastructure.persistence.entity.*; diff --git a/core/src/main/java/com/loremind/infrastructure/transfer/pdf/PdfExportService.java b/core/src/main/java/com/loremind/infrastructure/transfer/pdf/PdfExportService.java index edbeae6..04e2313 100644 --- a/core/src/main/java/com/loremind/infrastructure/transfer/pdf/PdfExportService.java +++ b/core/src/main/java/com/loremind/infrastructure/transfer/pdf/PdfExportService.java @@ -1,13 +1,13 @@ package com.loremind.infrastructure.transfer.pdf; -import com.loremind.domain.campaigncontext.ArcType; -import com.loremind.domain.campaigncontext.LinkType; -import com.loremind.domain.campaigncontext.NodeType; -import com.loremind.domain.campaigncontext.Prerequisite; -import com.loremind.domain.campaigncontext.QuestNodeRef; -import com.loremind.domain.campaigncontext.Room; -import com.loremind.domain.campaigncontext.RoomBranch; -import com.loremind.domain.campaigncontext.SceneBranch; +import com.loremind.domain.campaigncontext.structure.ArcType; +import com.loremind.domain.campaigncontext.structure.LinkType; +import com.loremind.domain.campaigncontext.quest.NodeType; +import com.loremind.domain.campaigncontext.quest.Prerequisite; +import com.loremind.domain.campaigncontext.quest.QuestNodeRef; +import com.loremind.domain.campaigncontext.structure.Room; +import com.loremind.domain.campaigncontext.structure.RoomBranch; +import com.loremind.domain.campaigncontext.structure.SceneBranch; import com.loremind.domain.files.ports.FileStorage; import com.loremind.domain.images.ports.ImageStorage; import com.loremind.domain.shared.template.FieldType; diff --git a/core/src/main/java/com/loremind/infrastructure/web/controller/AiChatController.java b/core/src/main/java/com/loremind/infrastructure/web/controller/AiChatController.java index a73ef25..3ae44a4 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/controller/AiChatController.java +++ b/core/src/main/java/com/loremind/infrastructure/web/controller/AiChatController.java @@ -4,6 +4,7 @@ import com.loremind.application.generationcontext.StreamChatForCampaignUseCase; import com.loremind.application.generationcontext.StreamChatForLoreUseCase; import com.loremind.application.generationcontext.StreamChatForSessionUseCase; import com.loremind.domain.generationcontext.ChatMessage; +import com.loremind.domain.generationcontext.ChatStreamCallbacks; import com.loremind.domain.generationcontext.ChatUsage; import com.loremind.infrastructure.web.dto.generationcontext.ChatMessageDTO; import com.loremind.infrastructure.web.dto.generationcontext.ChatStreamCampaignRequestDTO; @@ -98,12 +99,7 @@ public class AiChatController { private void runLoreStreaming( SseEmitter emitter, String loreId, String pageId, List messages) { try { - streamChatForLoreUseCase.execute( - loreId, pageId, messages, - usage -> sendUsage(emitter, usage), - token -> sendToken(emitter, token), - () -> complete(emitter), - error -> fail(emitter, error)); + streamChatForLoreUseCase.execute(loreId, pageId, messages, callbacksFor(emitter)); } catch (IllegalArgumentException e) { // Lore ou Page introuvable : on envoie un event error puis on termine proprement. fail(emitter, e); @@ -120,11 +116,7 @@ public class AiChatController { List messages) { try { streamChatForCampaignUseCase.execute( - campaignId, entityType, entityId, messages, - usage -> sendUsage(emitter, usage), - token -> sendToken(emitter, token), - () -> complete(emitter), - error -> fail(emitter, error)); + campaignId, entityType, entityId, messages, callbacksFor(emitter)); } catch (Exception e) { fail(emitter, e); } @@ -135,17 +127,21 @@ public class AiChatController { String sessionId, List messages) { try { - streamChatForSessionUseCase.execute( - sessionId, messages, - usage -> sendUsage(emitter, usage), - token -> sendToken(emitter, token), - () -> complete(emitter), - error -> fail(emitter, error)); + streamChatForSessionUseCase.execute(sessionId, messages, callbacksFor(emitter)); } catch (Exception e) { fail(emitter, e); } } + /** Callbacks de streaming qui écrivent chaque évènement sur cet emitter SSE. */ + private ChatStreamCallbacks callbacksFor(SseEmitter emitter) { + return new ChatStreamCallbacks( + usage -> sendUsage(emitter, usage), + token -> sendToken(emitter, token), + () -> complete(emitter), + error -> fail(emitter, error)); + } + // --- Helpers SSE (un seul point d'écriture par type d'événement) -------- private void sendUsage(SseEmitter emitter, ChatUsage usage) { diff --git a/core/src/main/java/com/loremind/infrastructure/web/controller/ArcController.java b/core/src/main/java/com/loremind/infrastructure/web/controller/ArcController.java index 7cd3bf8..2d8fe36 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/controller/ArcController.java +++ b/core/src/main/java/com/loremind/infrastructure/web/controller/ArcController.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.web.controller; import com.loremind.application.campaigncontext.ArcService; -import com.loremind.domain.campaigncontext.Arc; +import com.loremind.domain.campaigncontext.structure.Arc; import com.loremind.infrastructure.web.dto.campaigncontext.ArcDTO; import com.loremind.infrastructure.web.mapper.ArcMapper; import org.springframework.http.ResponseEntity; diff --git a/core/src/main/java/com/loremind/infrastructure/web/controller/CampaignImportController.java b/core/src/main/java/com/loremind/infrastructure/web/controller/CampaignImportController.java index e0d7c2e..d8b9923 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/controller/CampaignImportController.java +++ b/core/src/main/java/com/loremind/infrastructure/web/controller/CampaignImportController.java @@ -2,7 +2,7 @@ package com.loremind.infrastructure.web.controller; import com.fasterxml.jackson.databind.ObjectMapper; import com.loremind.application.campaigncontext.CampaignImportService; -import com.loremind.domain.campaigncontext.CampaignImportProposal; +import com.loremind.domain.campaigncontext.generation.CampaignImportProposal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; diff --git a/core/src/main/java/com/loremind/infrastructure/web/controller/CampaignTreeController.java b/core/src/main/java/com/loremind/infrastructure/web/controller/CampaignTreeController.java index 0aae56a..4854241 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/controller/CampaignTreeController.java +++ b/core/src/main/java/com/loremind/infrastructure/web/controller/CampaignTreeController.java @@ -2,9 +2,9 @@ package com.loremind.infrastructure.web.controller; import com.loremind.application.campaigncontext.CampaignReadinessAssessment; import com.loremind.application.campaigncontext.CampaignReadinessService; -import com.loremind.domain.campaigncontext.Arc; -import com.loremind.domain.campaigncontext.Chapter; -import com.loremind.domain.campaigncontext.Enemy; +import com.loremind.domain.campaigncontext.structure.Arc; +import com.loremind.domain.campaigncontext.structure.Chapter; +import com.loremind.domain.campaigncontext.bestiary.Enemy; import com.loremind.domain.campaigncontext.ports.ArcRepository; import com.loremind.domain.campaigncontext.ports.CampaignRepository; import com.loremind.domain.campaigncontext.ports.ChapterRepository; diff --git a/core/src/main/java/com/loremind/infrastructure/web/controller/ChapterController.java b/core/src/main/java/com/loremind/infrastructure/web/controller/ChapterController.java index 6060c43..9d01258 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/controller/ChapterController.java +++ b/core/src/main/java/com/loremind/infrastructure/web/controller/ChapterController.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.web.controller; import com.loremind.application.campaigncontext.ChapterService; -import com.loremind.domain.campaigncontext.Chapter; +import com.loremind.domain.campaigncontext.structure.Chapter; import com.loremind.infrastructure.web.dto.campaigncontext.ChapterDTO; import com.loremind.infrastructure.web.mapper.ChapterMapper; import org.springframework.http.ResponseEntity; diff --git a/core/src/main/java/com/loremind/infrastructure/web/controller/CharacterController.java b/core/src/main/java/com/loremind/infrastructure/web/controller/CharacterController.java index fbe66ad..1876ad0 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/controller/CharacterController.java +++ b/core/src/main/java/com/loremind/infrastructure/web/controller/CharacterController.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.web.controller; import com.loremind.application.campaigncontext.CharacterService; -import com.loremind.domain.campaigncontext.Character; +import com.loremind.domain.campaigncontext.bestiary.Character; import com.loremind.infrastructure.web.dto.campaigncontext.CharacterDTO; import com.loremind.infrastructure.web.mapper.CharacterMapper; import org.springframework.http.ResponseEntity; diff --git a/core/src/main/java/com/loremind/infrastructure/web/controller/EnemyController.java b/core/src/main/java/com/loremind/infrastructure/web/controller/EnemyController.java index 16e5412..bd04de2 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/controller/EnemyController.java +++ b/core/src/main/java/com/loremind/infrastructure/web/controller/EnemyController.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.web.controller; import com.loremind.application.campaigncontext.EnemyService; -import com.loremind.domain.campaigncontext.Enemy; +import com.loremind.domain.campaigncontext.bestiary.Enemy; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; diff --git a/core/src/main/java/com/loremind/infrastructure/web/controller/GameSystemController.java b/core/src/main/java/com/loremind/infrastructure/web/controller/GameSystemController.java index 787faea..2486679 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/controller/GameSystemController.java +++ b/core/src/main/java/com/loremind/infrastructure/web/controller/GameSystemController.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.loremind.application.gamesystemcontext.GameSystemService; import com.loremind.domain.gamesystemcontext.GameSystem; import com.loremind.domain.gamesystemcontext.RulesImportResult; -import com.loremind.domain.gamesystemcontext.ports.RulesImportException; +import com.loremind.domain.gamesystemcontext.ports.exceptions.RulesImportException; import com.loremind.infrastructure.web.dto.gamesystemcontext.GameSystemDTO; import com.loremind.infrastructure.web.dto.gamesystemcontext.RulesImportResponseDTO; import com.loremind.infrastructure.web.mapper.GameSystemMapper; diff --git a/core/src/main/java/com/loremind/infrastructure/web/controller/ItemCatalogController.java b/core/src/main/java/com/loremind/infrastructure/web/controller/ItemCatalogController.java index f5b158a..c13cc29 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/controller/ItemCatalogController.java +++ b/core/src/main/java/com/loremind/infrastructure/web/controller/ItemCatalogController.java @@ -1,8 +1,8 @@ package com.loremind.infrastructure.web.controller; import com.loremind.application.campaigncontext.ItemCatalogService; -import com.loremind.domain.campaigncontext.ItemCatalog; -import com.loremind.domain.campaigncontext.ports.ItemCatalogGenerationException; +import com.loremind.domain.campaigncontext.itemcatalog.ItemCatalog; +import com.loremind.domain.campaigncontext.ports.exceptions.ItemCatalogGenerationException; import com.loremind.infrastructure.web.dto.campaigncontext.ItemCatalogDTO; import com.loremind.infrastructure.web.mapper.ItemCatalogMapper; import org.springframework.http.HttpStatus; diff --git a/core/src/main/java/com/loremind/infrastructure/web/controller/NarrativeAssistController.java b/core/src/main/java/com/loremind/infrastructure/web/controller/NarrativeAssistController.java index 5d06d84..63c1f3b 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/controller/NarrativeAssistController.java +++ b/core/src/main/java/com/loremind/infrastructure/web/controller/NarrativeAssistController.java @@ -4,8 +4,8 @@ import com.loremind.application.campaigncontext.ArcService; import com.loremind.application.campaigncontext.ChapterService; import com.loremind.application.campaigncontext.SceneService; import com.loremind.application.generationcontext.NarrativeAssistFieldsUseCase; -import com.loremind.domain.campaigncontext.EntityFieldPatchProposal; -import com.loremind.domain.campaigncontext.ports.NarrativeAssistException; +import com.loremind.domain.campaigncontext.generation.EntityFieldPatchProposal; +import com.loremind.domain.campaigncontext.ports.exceptions.NarrativeAssistException; import com.loremind.infrastructure.web.mapper.ArcMapper; import com.loremind.infrastructure.web.mapper.ChapterMapper; import com.loremind.infrastructure.web.mapper.SceneMapper; diff --git a/core/src/main/java/com/loremind/infrastructure/web/controller/NotebookController.java b/core/src/main/java/com/loremind/infrastructure/web/controller/NotebookController.java index a71aa98..d811256 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/controller/NotebookController.java +++ b/core/src/main/java/com/loremind/infrastructure/web/controller/NotebookController.java @@ -1,10 +1,10 @@ package com.loremind.infrastructure.web.controller; import com.loremind.application.campaigncontext.NotebookService; -import com.loremind.domain.campaigncontext.Notebook; -import com.loremind.domain.campaigncontext.NotebookSource; +import com.loremind.domain.campaigncontext.notebook.Notebook; +import com.loremind.domain.campaigncontext.notebook.NotebookSource; import com.loremind.domain.campaigncontext.ports.NotebookChatStreamer; -import com.loremind.domain.campaigncontext.ports.NotebookException; +import com.loremind.domain.campaigncontext.ports.exceptions.NotebookException; import com.loremind.infrastructure.web.sse.SseJson; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.task.TaskExecutor; @@ -186,8 +186,7 @@ public class NotebookController { boolean deep = req.deep() != null && req.deep(); taskExecutor.execute(() -> { StringBuilder assistant = new StringBuilder(); - chatStreamer.stream( - sourceIds, history, context, deep, + chatStreamer.stream(sourceIds, history, context, deep, new NotebookChatStreamer.Callbacks( sourcesJson -> sendSources(emitter, sourcesJson), token -> { assistant.append(token); sendToken(emitter, token); }, progress -> sendProgress(emitter, progress), @@ -198,7 +197,7 @@ public class NotebookController { } complete(emitter); }, - error -> fail(emitter, error)); + error -> fail(emitter, error))); }); return emitter; } diff --git a/core/src/main/java/com/loremind/infrastructure/web/controller/NpcController.java b/core/src/main/java/com/loremind/infrastructure/web/controller/NpcController.java index f4479fe..37eca20 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/controller/NpcController.java +++ b/core/src/main/java/com/loremind/infrastructure/web/controller/NpcController.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.web.controller; import com.loremind.application.campaigncontext.NpcService; -import com.loremind.domain.campaigncontext.Npc; +import com.loremind.domain.campaigncontext.bestiary.Npc; import com.loremind.infrastructure.web.dto.campaigncontext.NpcDTO; import com.loremind.infrastructure.web.mapper.NpcMapper; import org.springframework.http.ResponseEntity; diff --git a/core/src/main/java/com/loremind/infrastructure/web/controller/PageGenerationController.java b/core/src/main/java/com/loremind/infrastructure/web/controller/PageGenerationController.java index bea8e5b..f975de1 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/controller/PageGenerationController.java +++ b/core/src/main/java/com/loremind/infrastructure/web/controller/PageGenerationController.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.web.controller; import com.loremind.application.generationcontext.GeneratePageValuesUseCase; -import com.loremind.domain.generationcontext.ports.AiProviderException; +import com.loremind.domain.generationcontext.ports.exceptions.AiProviderException; import com.loremind.infrastructure.web.dto.generationcontext.GenerationSuggestionsDTO; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; diff --git a/core/src/main/java/com/loremind/infrastructure/web/controller/QuestController.java b/core/src/main/java/com/loremind/infrastructure/web/controller/QuestController.java index 8d805bf..5cd21b0 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/controller/QuestController.java +++ b/core/src/main/java/com/loremind/infrastructure/web/controller/QuestController.java @@ -2,7 +2,7 @@ package com.loremind.infrastructure.web.controller; import com.loremind.application.campaigncontext.QuestService; import com.loremind.application.campaigncontext.QuestStatusEnricher; -import com.loremind.domain.campaigncontext.Quest; +import com.loremind.domain.campaigncontext.quest.Quest; import com.loremind.infrastructure.web.dto.campaigncontext.QuestDTO; import com.loremind.infrastructure.web.mapper.QuestMapper; import org.springframework.http.ResponseEntity; diff --git a/core/src/main/java/com/loremind/infrastructure/web/controller/QuestProgressionController.java b/core/src/main/java/com/loremind/infrastructure/web/controller/QuestProgressionController.java index 16c33d7..9f6ae19 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/controller/QuestProgressionController.java +++ b/core/src/main/java/com/loremind/infrastructure/web/controller/QuestProgressionController.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.web.controller; import com.loremind.application.playcontext.ClockService; -import com.loremind.domain.campaigncontext.ProgressionStatus; +import com.loremind.domain.campaigncontext.quest.ProgressionStatus; import com.loremind.domain.playcontext.ports.QuestProgressionRepository; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; diff --git a/core/src/main/java/com/loremind/infrastructure/web/controller/RandomTableController.java b/core/src/main/java/com/loremind/infrastructure/web/controller/RandomTableController.java index 47021b8..55cb607 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/controller/RandomTableController.java +++ b/core/src/main/java/com/loremind/infrastructure/web/controller/RandomTableController.java @@ -1,8 +1,8 @@ package com.loremind.infrastructure.web.controller; import com.loremind.application.campaigncontext.RandomTableService; -import com.loremind.domain.campaigncontext.RandomTable; -import com.loremind.domain.campaigncontext.ports.RandomTableGenerationException; +import com.loremind.domain.campaigncontext.randomtable.RandomTable; +import com.loremind.domain.campaigncontext.ports.exceptions.RandomTableGenerationException; import com.loremind.infrastructure.web.dto.campaigncontext.RandomTableDTO; import com.loremind.infrastructure.web.mapper.RandomTableMapper; import org.springframework.http.HttpStatus; diff --git a/core/src/main/java/com/loremind/infrastructure/web/controller/SceneController.java b/core/src/main/java/com/loremind/infrastructure/web/controller/SceneController.java index ef3b776..6169c4f 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/controller/SceneController.java +++ b/core/src/main/java/com/loremind/infrastructure/web/controller/SceneController.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.web.controller; import com.loremind.application.campaigncontext.SceneService; -import com.loremind.domain.campaigncontext.Scene; +import com.loremind.domain.campaigncontext.structure.Scene; import com.loremind.infrastructure.web.dto.campaigncontext.SceneDTO; import com.loremind.infrastructure.web.mapper.SceneMapper; import org.springframework.http.ResponseEntity; diff --git a/core/src/main/java/com/loremind/infrastructure/web/controller/SceneDraftController.java b/core/src/main/java/com/loremind/infrastructure/web/controller/SceneDraftController.java index 09b43e0..cc060ac 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/controller/SceneDraftController.java +++ b/core/src/main/java/com/loremind/infrastructure/web/controller/SceneDraftController.java @@ -3,9 +3,9 @@ package com.loremind.infrastructure.web.controller; import com.loremind.application.campaigncontext.ChapterService; import com.loremind.application.campaigncontext.SceneService; import com.loremind.application.generationcontext.DraftScenesUseCase; -import com.loremind.domain.campaigncontext.Scene; -import com.loremind.domain.campaigncontext.SceneDraftProposal; -import com.loremind.domain.campaigncontext.ports.NarrativeAssistException; +import com.loremind.domain.campaigncontext.structure.Scene; +import com.loremind.domain.campaigncontext.generation.SceneDraftProposal; +import com.loremind.domain.campaigncontext.ports.exceptions.NarrativeAssistException; import com.loremind.infrastructure.web.dto.campaigncontext.SceneDTO; import com.loremind.infrastructure.web.mapper.SceneMapper; import org.springframework.http.HttpStatus; diff --git a/core/src/main/java/com/loremind/infrastructure/web/controller/SessionController.java b/core/src/main/java/com/loremind/infrastructure/web/controller/SessionController.java index edd1095..7df74b6 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/controller/SessionController.java +++ b/core/src/main/java/com/loremind/infrastructure/web/controller/SessionController.java @@ -3,7 +3,7 @@ package com.loremind.infrastructure.web.controller; import com.loremind.application.playcontext.SessionRecapService; import com.loremind.application.playcontext.SessionService; import com.loremind.domain.playcontext.Session; -import com.loremind.domain.playcontext.ports.SessionRecapException; +import com.loremind.domain.playcontext.ports.exceptions.SessionRecapException; import com.loremind.infrastructure.web.dto.playcontext.SessionDTO; import com.loremind.infrastructure.web.mapper.SessionMapper; import org.springframework.http.HttpStatus; diff --git a/core/src/main/java/com/loremind/infrastructure/web/mapper/ArcMapper.java b/core/src/main/java/com/loremind/infrastructure/web/mapper/ArcMapper.java index c3d1cd9..9748fed 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/mapper/ArcMapper.java +++ b/core/src/main/java/com/loremind/infrastructure/web/mapper/ArcMapper.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.web.mapper; -import com.loremind.domain.campaigncontext.Arc; -import com.loremind.domain.campaigncontext.ArcType; +import com.loremind.domain.campaigncontext.structure.Arc; +import com.loremind.domain.campaigncontext.structure.ArcType; import com.loremind.infrastructure.web.dto.campaigncontext.ArcDTO; import org.springframework.stereotype.Component; diff --git a/core/src/main/java/com/loremind/infrastructure/web/mapper/ChapterMapper.java b/core/src/main/java/com/loremind/infrastructure/web/mapper/ChapterMapper.java index d703813..7bdb997 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/mapper/ChapterMapper.java +++ b/core/src/main/java/com/loremind/infrastructure/web/mapper/ChapterMapper.java @@ -1,6 +1,6 @@ package com.loremind.infrastructure.web.mapper; -import com.loremind.domain.campaigncontext.Chapter; +import com.loremind.domain.campaigncontext.structure.Chapter; import com.loremind.infrastructure.web.dto.campaigncontext.ChapterDTO; import org.springframework.stereotype.Component; diff --git a/core/src/main/java/com/loremind/infrastructure/web/mapper/CharacterMapper.java b/core/src/main/java/com/loremind/infrastructure/web/mapper/CharacterMapper.java index 7b78f4f..b04f455 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/mapper/CharacterMapper.java +++ b/core/src/main/java/com/loremind/infrastructure/web/mapper/CharacterMapper.java @@ -1,6 +1,6 @@ package com.loremind.infrastructure.web.mapper; -import com.loremind.domain.campaigncontext.Character; +import com.loremind.domain.campaigncontext.bestiary.Character; import com.loremind.infrastructure.web.dto.campaigncontext.CharacterDTO; import org.springframework.stereotype.Component; diff --git a/core/src/main/java/com/loremind/infrastructure/web/mapper/ItemCatalogMapper.java b/core/src/main/java/com/loremind/infrastructure/web/mapper/ItemCatalogMapper.java index 89b43fe..e301a87 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/mapper/ItemCatalogMapper.java +++ b/core/src/main/java/com/loremind/infrastructure/web/mapper/ItemCatalogMapper.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.web.mapper; -import com.loremind.domain.campaigncontext.CatalogItem; -import com.loremind.domain.campaigncontext.ItemCatalog; +import com.loremind.domain.campaigncontext.itemcatalog.CatalogItem; +import com.loremind.domain.campaigncontext.itemcatalog.ItemCatalog; import com.loremind.infrastructure.web.dto.campaigncontext.CatalogItemDTO; import com.loremind.infrastructure.web.dto.campaigncontext.ItemCatalogDTO; import org.springframework.stereotype.Component; diff --git a/core/src/main/java/com/loremind/infrastructure/web/mapper/NpcMapper.java b/core/src/main/java/com/loremind/infrastructure/web/mapper/NpcMapper.java index db5dafc..f93f561 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/mapper/NpcMapper.java +++ b/core/src/main/java/com/loremind/infrastructure/web/mapper/NpcMapper.java @@ -1,6 +1,6 @@ package com.loremind.infrastructure.web.mapper; -import com.loremind.domain.campaigncontext.Npc; +import com.loremind.domain.campaigncontext.bestiary.Npc; import com.loremind.infrastructure.web.dto.campaigncontext.NpcDTO; import org.springframework.stereotype.Component; diff --git a/core/src/main/java/com/loremind/infrastructure/web/mapper/PrerequisiteMapper.java b/core/src/main/java/com/loremind/infrastructure/web/mapper/PrerequisiteMapper.java index cdcdfc3..2cd0d9d 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/mapper/PrerequisiteMapper.java +++ b/core/src/main/java/com/loremind/infrastructure/web/mapper/PrerequisiteMapper.java @@ -1,6 +1,6 @@ package com.loremind.infrastructure.web.mapper; -import com.loremind.domain.campaigncontext.Prerequisite; +import com.loremind.domain.campaigncontext.quest.Prerequisite; import com.loremind.infrastructure.web.dto.campaigncontext.PrerequisiteDTO; import org.springframework.stereotype.Component; diff --git a/core/src/main/java/com/loremind/infrastructure/web/mapper/QuestMapper.java b/core/src/main/java/com/loremind/infrastructure/web/mapper/QuestMapper.java index 735a8f5..ea8902d 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/mapper/QuestMapper.java +++ b/core/src/main/java/com/loremind/infrastructure/web/mapper/QuestMapper.java @@ -1,8 +1,8 @@ package com.loremind.infrastructure.web.mapper; -import com.loremind.domain.campaigncontext.NodeType; -import com.loremind.domain.campaigncontext.Quest; -import com.loremind.domain.campaigncontext.QuestNodeRef; +import com.loremind.domain.campaigncontext.quest.NodeType; +import com.loremind.domain.campaigncontext.quest.Quest; +import com.loremind.domain.campaigncontext.quest.QuestNodeRef; import com.loremind.infrastructure.web.dto.campaigncontext.QuestDTO; import com.loremind.infrastructure.web.dto.campaigncontext.QuestNodeRefDTO; import org.springframework.stereotype.Component; diff --git a/core/src/main/java/com/loremind/infrastructure/web/mapper/RandomTableMapper.java b/core/src/main/java/com/loremind/infrastructure/web/mapper/RandomTableMapper.java index ffa584f..b9498d5 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/mapper/RandomTableMapper.java +++ b/core/src/main/java/com/loremind/infrastructure/web/mapper/RandomTableMapper.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.web.mapper; -import com.loremind.domain.campaigncontext.RandomTable; -import com.loremind.domain.campaigncontext.RandomTableEntry; +import com.loremind.domain.campaigncontext.randomtable.RandomTable; +import com.loremind.domain.campaigncontext.randomtable.RandomTableEntry; import com.loremind.infrastructure.web.dto.campaigncontext.RandomTableDTO; import com.loremind.infrastructure.web.dto.campaigncontext.RandomTableEntryDTO; import org.springframework.stereotype.Component; diff --git a/core/src/main/java/com/loremind/infrastructure/web/mapper/SceneMapper.java b/core/src/main/java/com/loremind/infrastructure/web/mapper/SceneMapper.java index c20e342..651a940 100644 --- a/core/src/main/java/com/loremind/infrastructure/web/mapper/SceneMapper.java +++ b/core/src/main/java/com/loremind/infrastructure/web/mapper/SceneMapper.java @@ -1,12 +1,12 @@ package com.loremind.infrastructure.web.mapper; -import com.loremind.domain.campaigncontext.LinkType; -import com.loremind.domain.campaigncontext.Room; -import com.loremind.domain.campaigncontext.RoomBranch; -import com.loremind.domain.campaigncontext.Scene; -import com.loremind.domain.campaigncontext.SceneBattlemap; -import com.loremind.domain.campaigncontext.SceneBranch; -import com.loremind.domain.campaigncontext.SceneType; +import com.loremind.domain.campaigncontext.structure.LinkType; +import com.loremind.domain.campaigncontext.structure.Room; +import com.loremind.domain.campaigncontext.structure.RoomBranch; +import com.loremind.domain.campaigncontext.structure.Scene; +import com.loremind.domain.campaigncontext.structure.SceneBattlemap; +import com.loremind.domain.campaigncontext.structure.SceneBranch; +import com.loremind.domain.campaigncontext.structure.SceneType; import com.loremind.infrastructure.web.dto.campaigncontext.RoomBranchDTO; import com.loremind.infrastructure.web.dto.campaigncontext.RoomDTO; import com.loremind.infrastructure.web.dto.campaigncontext.SceneBattlemapDTO; diff --git a/core/src/test/java/com/loremind/application/campaigncontext/ArcServiceTest.java b/core/src/test/java/com/loremind/application/campaigncontext/ArcServiceTest.java index 32f4581..2a0b567 100644 --- a/core/src/test/java/com/loremind/application/campaigncontext/ArcServiceTest.java +++ b/core/src/test/java/com/loremind/application/campaigncontext/ArcServiceTest.java @@ -1,9 +1,9 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.Arc; -import com.loremind.domain.campaigncontext.Chapter; -import com.loremind.domain.campaigncontext.FieldProposal; -import com.loremind.domain.campaigncontext.Scene; +import com.loremind.domain.campaigncontext.structure.Arc; +import com.loremind.domain.campaigncontext.structure.Chapter; +import com.loremind.domain.campaigncontext.generation.FieldProposal; +import com.loremind.domain.campaigncontext.structure.Scene; import com.loremind.domain.campaigncontext.ports.ArcRepository; import com.loremind.domain.campaigncontext.ports.ChapterRepository; import com.loremind.domain.campaigncontext.ports.QuestRepository; diff --git a/core/src/test/java/com/loremind/application/campaigncontext/CampaignContextFormatterTest.java b/core/src/test/java/com/loremind/application/campaigncontext/CampaignContextFormatterTest.java new file mode 100644 index 0000000..d237ac0 --- /dev/null +++ b/core/src/test/java/com/loremind/application/campaigncontext/CampaignContextFormatterTest.java @@ -0,0 +1,87 @@ +package com.loremind.application.campaigncontext; + +import com.loremind.domain.campaigncontext.Campaign; +import com.loremind.domain.campaigncontext.ports.CampaignRepository; +import com.loremind.domain.gamesystemcontext.GameSystem; +import com.loremind.domain.gamesystemcontext.ports.GameSystemRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +/** + * Test unitaire pour CampaignContextFormatter. + * Mocks des ports (campagne, système de jeu). + */ +@ExtendWith(MockitoExtension.class) +class CampaignContextFormatterTest { + + @Mock + private CampaignRepository campaignRepository; + @Mock + private GameSystemRepository gameSystemRepository; + + @InjectMocks + private CampaignContextFormatter formatter; + + @Test + void testFormat_NullCampaignId_ReturnsEmpty() { + assertEquals("", formatter.format(null)); + } + + @Test + void testFormat_CampaignNotFound_ReturnsEmpty() { + when(campaignRepository.findById("camp-1")).thenReturn(Optional.empty()); + + assertEquals("", formatter.format("camp-1")); + } + + @Test + void testFormat_NameOnly() { + when(campaignRepository.findById("camp-1")) + .thenReturn(Optional.of(Campaign.builder().id("camp-1").name("Camp").build())); + + assertEquals("Campagne : Camp", formatter.format("camp-1")); + } + + @Test + void testFormat_WithDescription_TrimsIt() { + when(campaignRepository.findById("camp-1")).thenReturn(Optional.of(Campaign.builder() + .id("camp-1").name("Camp").description(" Aventure ").build())); + + assertEquals("Campagne : Camp — Aventure", formatter.format("camp-1")); + } + + @Test + void testFormat_BlankDescription_Ignored() { + when(campaignRepository.findById("camp-1")).thenReturn(Optional.of(Campaign.builder() + .id("camp-1").name("Camp").description(" ").build())); + + assertEquals("Campagne : Camp", formatter.format("camp-1")); + } + + @Test + void testFormat_WithGameSystem_AppendsIt() { + when(campaignRepository.findById("camp-1")).thenReturn(Optional.of(Campaign.builder() + .id("camp-1").name("Camp").description("Aventure").gameSystemId("gs-1").build())); + when(gameSystemRepository.findById("gs-1")) + .thenReturn(Optional.of(GameSystem.builder().id("gs-1").name("Pathfinder").build())); + + assertEquals("Campagne : Camp — Aventure\nSystème de jeu : Pathfinder", formatter.format("camp-1")); + } + + @Test + void testFormat_GameSystemNotFound_Ignored() { + when(campaignRepository.findById("camp-1")).thenReturn(Optional.of(Campaign.builder() + .id("camp-1").name("Camp").gameSystemId("gs-1").build())); + when(gameSystemRepository.findById("gs-1")).thenReturn(Optional.empty()); + + assertEquals("Campagne : Camp", formatter.format("camp-1")); + } +} diff --git a/core/src/test/java/com/loremind/application/campaigncontext/CampaignReadinessServiceTest.java b/core/src/test/java/com/loremind/application/campaigncontext/CampaignReadinessServiceTest.java index d48c18e..263b8b3 100644 --- a/core/src/test/java/com/loremind/application/campaigncontext/CampaignReadinessServiceTest.java +++ b/core/src/test/java/com/loremind/application/campaigncontext/CampaignReadinessServiceTest.java @@ -1,19 +1,19 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.Arc; -import com.loremind.domain.campaigncontext.ArcType; -import com.loremind.domain.campaigncontext.Chapter; -import com.loremind.domain.campaigncontext.Enemy; -import com.loremind.domain.campaigncontext.NodeType; -import com.loremind.domain.campaigncontext.Prerequisite; -import com.loremind.domain.campaigncontext.Quest; -import com.loremind.domain.campaigncontext.QuestNodeRef; -import com.loremind.domain.campaigncontext.ReadinessSeverity; -import com.loremind.domain.campaigncontext.ReadinessStatus; -import com.loremind.domain.campaigncontext.Room; -import com.loremind.domain.campaigncontext.RoomBranch; -import com.loremind.domain.campaigncontext.Scene; -import com.loremind.domain.campaigncontext.SceneBranch; +import com.loremind.domain.campaigncontext.structure.Arc; +import com.loremind.domain.campaigncontext.structure.ArcType; +import com.loremind.domain.campaigncontext.structure.Chapter; +import com.loremind.domain.campaigncontext.bestiary.Enemy; +import com.loremind.domain.campaigncontext.quest.NodeType; +import com.loremind.domain.campaigncontext.quest.Prerequisite; +import com.loremind.domain.campaigncontext.quest.Quest; +import com.loremind.domain.campaigncontext.quest.QuestNodeRef; +import com.loremind.domain.campaigncontext.readiness.ReadinessSeverity; +import com.loremind.domain.campaigncontext.readiness.ReadinessStatus; +import com.loremind.domain.campaigncontext.structure.Room; +import com.loremind.domain.campaigncontext.structure.RoomBranch; +import com.loremind.domain.campaigncontext.structure.Scene; +import com.loremind.domain.campaigncontext.structure.SceneBranch; import com.loremind.domain.campaigncontext.ports.ArcRepository; import com.loremind.domain.campaigncontext.ports.CampaignRepository; import com.loremind.domain.campaigncontext.ports.ChapterRepository; diff --git a/core/src/test/java/com/loremind/application/campaigncontext/CampaignReferencedFlagsServiceTest.java b/core/src/test/java/com/loremind/application/campaigncontext/CampaignReferencedFlagsServiceTest.java index d2faccc..43200a8 100644 --- a/core/src/test/java/com/loremind/application/campaigncontext/CampaignReferencedFlagsServiceTest.java +++ b/core/src/test/java/com/loremind/application/campaigncontext/CampaignReferencedFlagsServiceTest.java @@ -1,7 +1,7 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.Prerequisite; -import com.loremind.domain.campaigncontext.Quest; +import com.loremind.domain.campaigncontext.quest.Prerequisite; +import com.loremind.domain.campaigncontext.quest.Quest; import com.loremind.domain.campaigncontext.ports.QuestRepository; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/core/src/test/java/com/loremind/application/campaigncontext/CampaignServiceTest.java b/core/src/test/java/com/loremind/application/campaigncontext/CampaignServiceTest.java index 72898f8..845bda1 100644 --- a/core/src/test/java/com/loremind/application/campaigncontext/CampaignServiceTest.java +++ b/core/src/test/java/com/loremind/application/campaigncontext/CampaignServiceTest.java @@ -1,10 +1,10 @@ package com.loremind.application.campaigncontext; import com.loremind.application.playcontext.PlaythroughService; -import com.loremind.domain.campaigncontext.Arc; +import com.loremind.domain.campaigncontext.structure.Arc; import com.loremind.domain.campaigncontext.Campaign; -import com.loremind.domain.campaigncontext.Chapter; -import com.loremind.domain.campaigncontext.Scene; +import com.loremind.domain.campaigncontext.structure.Chapter; +import com.loremind.domain.campaigncontext.structure.Scene; import com.loremind.domain.campaigncontext.ports.ArcRepository; import com.loremind.domain.campaigncontext.ports.CampaignRepository; import com.loremind.domain.campaigncontext.ports.ChapterRepository; diff --git a/core/src/test/java/com/loremind/application/campaigncontext/ChapterServiceTest.java b/core/src/test/java/com/loremind/application/campaigncontext/ChapterServiceTest.java index b4d7db0..06193a9 100644 --- a/core/src/test/java/com/loremind/application/campaigncontext/ChapterServiceTest.java +++ b/core/src/test/java/com/loremind/application/campaigncontext/ChapterServiceTest.java @@ -1,8 +1,8 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.Chapter; -import com.loremind.domain.campaigncontext.FieldProposal; -import com.loremind.domain.campaigncontext.Scene; +import com.loremind.domain.campaigncontext.structure.Chapter; +import com.loremind.domain.campaigncontext.generation.FieldProposal; +import com.loremind.domain.campaigncontext.structure.Scene; import com.loremind.domain.campaigncontext.ports.ChapterRepository; import com.loremind.domain.campaigncontext.ports.SceneRepository; import org.junit.jupiter.api.BeforeEach; diff --git a/core/src/test/java/com/loremind/application/campaigncontext/CharacterServiceTest.java b/core/src/test/java/com/loremind/application/campaigncontext/CharacterServiceTest.java index a64d383..19aab7b 100644 --- a/core/src/test/java/com/loremind/application/campaigncontext/CharacterServiceTest.java +++ b/core/src/test/java/com/loremind/application/campaigncontext/CharacterServiceTest.java @@ -1,6 +1,6 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.Character; +import com.loremind.domain.campaigncontext.bestiary.Character; import com.loremind.domain.campaigncontext.ports.CharacterRepository; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/core/src/test/java/com/loremind/application/campaigncontext/ItemCatalogServiceTest.java b/core/src/test/java/com/loremind/application/campaigncontext/ItemCatalogServiceTest.java index 0b8678b..126d871 100644 --- a/core/src/test/java/com/loremind/application/campaigncontext/ItemCatalogServiceTest.java +++ b/core/src/test/java/com/loremind/application/campaigncontext/ItemCatalogServiceTest.java @@ -1,16 +1,11 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.Campaign; -import com.loremind.domain.campaigncontext.CatalogItem; -import com.loremind.domain.campaigncontext.ItemCatalog; -import com.loremind.domain.campaigncontext.ports.CampaignRepository; +import com.loremind.domain.campaigncontext.itemcatalog.CatalogItem; +import com.loremind.domain.campaigncontext.itemcatalog.ItemCatalog; import com.loremind.domain.campaigncontext.ports.ItemCatalogGenerator; import com.loremind.domain.campaigncontext.ports.ItemCatalogRepository; -import com.loremind.domain.gamesystemcontext.GameSystem; -import com.loremind.domain.gamesystemcontext.ports.GameSystemRepository; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -36,9 +31,7 @@ public class ItemCatalogServiceTest { @Mock private ItemCatalogGenerator generator; @Mock - private CampaignRepository campaignRepository; - @Mock - private GameSystemRepository gameSystemRepository; + private CampaignContextFormatter campaignContextFormatter; @InjectMocks private ItemCatalogService service; @@ -189,10 +182,9 @@ public class ItemCatalogServiceTest { @Test void testGenerateProposal_CopiesItemsAndContext() { CatalogItem item = CatalogItem.builder().name("Potion").build(); - when(generator.generate(eq("desc"), anyString())) + when(campaignContextFormatter.format("camp-1")).thenReturn("Campagne : Camp"); + when(generator.generate(eq("desc"), eq("Campagne : Camp"))) .thenReturn(new ItemCatalogGenerator.GeneratedCatalog("Nom IA", "Desc IA", List.of(item))); - when(campaignRepository.findById("camp-1")) - .thenReturn(Optional.of(Campaign.builder().id("camp-1").name("Camp").build())); ItemCatalog result = service.generateProposal("camp-1", "desc"); @@ -203,9 +195,9 @@ public class ItemCatalogServiceTest { @Test void testGenerateProposal_NullGeneratedItems() { + when(campaignContextFormatter.format("camp-1")).thenReturn(""); when(generator.generate(anyString(), anyString())) .thenReturn(new ItemCatalogGenerator.GeneratedCatalog("N", "D", null)); - when(campaignRepository.findById("camp-1")).thenReturn(Optional.empty()); ItemCatalog result = service.generateProposal("camp-1", "desc"); @@ -214,21 +206,14 @@ public class ItemCatalogServiceTest { } @Test - void testGenerateProposal_BuildsContextWithGameSystem() { - when(campaignRepository.findById("camp-1")).thenReturn(Optional.of(Campaign.builder() - .id("camp-1").name("Camp").description(" Aventure ").gameSystemId("gs-1").build())); - when(gameSystemRepository.findById("gs-1")) - .thenReturn(Optional.of(GameSystem.builder().id("gs-1").name("Pathfinder").build())); + void testGenerateProposal_DelegatesContextToFormatter() { + when(campaignContextFormatter.format("camp-1")) + .thenReturn("Campagne : Camp — Aventure\nSystème de jeu : Pathfinder"); when(generator.generate(anyString(), any())) .thenReturn(new ItemCatalogGenerator.GeneratedCatalog("N", "D", List.of())); service.generateProposal("camp-1", "desc"); - ArgumentCaptor ctxCaptor = ArgumentCaptor.forClass(String.class); - verify(generator).generate(eq("desc"), ctxCaptor.capture()); - String ctx = ctxCaptor.getValue(); - assertTrue(ctx.contains("Camp")); - assertTrue(ctx.contains("Aventure")); - assertTrue(ctx.contains("Pathfinder")); + verify(generator).generate(eq("desc"), eq("Campagne : Camp — Aventure\nSystème de jeu : Pathfinder")); } } diff --git a/core/src/test/java/com/loremind/application/campaigncontext/NotebookServiceTest.java b/core/src/test/java/com/loremind/application/campaigncontext/NotebookServiceTest.java index 227a677..601e6fa 100644 --- a/core/src/test/java/com/loremind/application/campaigncontext/NotebookServiceTest.java +++ b/core/src/test/java/com/loremind/application/campaigncontext/NotebookServiceTest.java @@ -1,9 +1,9 @@ package com.loremind.application.campaigncontext; import com.loremind.domain.campaigncontext.Campaign; -import com.loremind.domain.campaigncontext.Notebook; -import com.loremind.domain.campaigncontext.NotebookMessage; -import com.loremind.domain.campaigncontext.NotebookSource; +import com.loremind.domain.campaigncontext.notebook.Notebook; +import com.loremind.domain.campaigncontext.notebook.NotebookMessage; +import com.loremind.domain.campaigncontext.notebook.NotebookSource; import com.loremind.domain.campaigncontext.ports.CampaignRepository; import com.loremind.domain.campaigncontext.ports.NotebookIndexer; import com.loremind.domain.campaigncontext.ports.NotebookRepository; diff --git a/core/src/test/java/com/loremind/application/campaigncontext/NpcServiceTest.java b/core/src/test/java/com/loremind/application/campaigncontext/NpcServiceTest.java index 08e7559..6fdf203 100644 --- a/core/src/test/java/com/loremind/application/campaigncontext/NpcServiceTest.java +++ b/core/src/test/java/com/loremind/application/campaigncontext/NpcServiceTest.java @@ -1,6 +1,6 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.Npc; +import com.loremind.domain.campaigncontext.bestiary.Npc; import com.loremind.domain.campaigncontext.ports.CampaignRepository; import com.loremind.domain.campaigncontext.ports.NpcRepository; import org.junit.jupiter.api.BeforeEach; diff --git a/core/src/test/java/com/loremind/application/campaigncontext/QuestServiceTest.java b/core/src/test/java/com/loremind/application/campaigncontext/QuestServiceTest.java index b27c346..4857928 100644 --- a/core/src/test/java/com/loremind/application/campaigncontext/QuestServiceTest.java +++ b/core/src/test/java/com/loremind/application/campaigncontext/QuestServiceTest.java @@ -1,12 +1,12 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.Arc; -import com.loremind.domain.campaigncontext.ArcType; -import com.loremind.domain.campaigncontext.Chapter; -import com.loremind.domain.campaigncontext.NodeType; -import com.loremind.domain.campaigncontext.Quest; -import com.loremind.domain.campaigncontext.QuestNodeRef; -import com.loremind.domain.campaigncontext.Scene; +import com.loremind.domain.campaigncontext.structure.Arc; +import com.loremind.domain.campaigncontext.structure.ArcType; +import com.loremind.domain.campaigncontext.structure.Chapter; +import com.loremind.domain.campaigncontext.quest.NodeType; +import com.loremind.domain.campaigncontext.quest.Quest; +import com.loremind.domain.campaigncontext.quest.QuestNodeRef; +import com.loremind.domain.campaigncontext.structure.Scene; import com.loremind.domain.campaigncontext.ports.ArcRepository; import com.loremind.domain.campaigncontext.ports.ChapterRepository; import com.loremind.domain.campaigncontext.ports.QuestRepository; diff --git a/core/src/test/java/com/loremind/application/campaigncontext/QuestStatusEnricherTest.java b/core/src/test/java/com/loremind/application/campaigncontext/QuestStatusEnricherTest.java index 023df39..571761e 100644 --- a/core/src/test/java/com/loremind/application/campaigncontext/QuestStatusEnricherTest.java +++ b/core/src/test/java/com/loremind/application/campaigncontext/QuestStatusEnricherTest.java @@ -1,9 +1,9 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.Prerequisite; -import com.loremind.domain.campaigncontext.ProgressionStatus; -import com.loremind.domain.campaigncontext.Quest; -import com.loremind.domain.campaigncontext.QuestStatus; +import com.loremind.domain.campaigncontext.quest.Prerequisite; +import com.loremind.domain.campaigncontext.quest.ProgressionStatus; +import com.loremind.domain.campaigncontext.quest.Quest; +import com.loremind.domain.campaigncontext.quest.QuestStatus; import com.loremind.domain.playcontext.Playthrough; import com.loremind.domain.playcontext.QuestProgression; import com.loremind.domain.playcontext.Session; diff --git a/core/src/test/java/com/loremind/application/campaigncontext/RandomTableServiceTest.java b/core/src/test/java/com/loremind/application/campaigncontext/RandomTableServiceTest.java index 74e0893..7a7556a 100644 --- a/core/src/test/java/com/loremind/application/campaigncontext/RandomTableServiceTest.java +++ b/core/src/test/java/com/loremind/application/campaigncontext/RandomTableServiceTest.java @@ -1,16 +1,11 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.Campaign; -import com.loremind.domain.campaigncontext.RandomTable; -import com.loremind.domain.campaigncontext.RandomTableEntry; -import com.loremind.domain.campaigncontext.ports.CampaignRepository; +import com.loremind.domain.campaigncontext.randomtable.RandomTable; +import com.loremind.domain.campaigncontext.randomtable.RandomTableEntry; import com.loremind.domain.campaigncontext.ports.RandomTableGenerator; import com.loremind.domain.campaigncontext.ports.RandomTableRepository; -import com.loremind.domain.gamesystemcontext.GameSystem; -import com.loremind.domain.gamesystemcontext.ports.GameSystemRepository; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -36,9 +31,7 @@ public class RandomTableServiceTest { @Mock private RandomTableGenerator generator; @Mock - private CampaignRepository campaignRepository; - @Mock - private GameSystemRepository gameSystemRepository; + private CampaignContextFormatter campaignContextFormatter; @InjectMocks private RandomTableService service; @@ -208,10 +201,9 @@ public class RandomTableServiceTest { @Test void testGenerateProposal_DefaultsFormulaAndCopiesEntries() { RandomTableEntry e = RandomTableEntry.builder().minRoll(1).maxRoll(1).label("X").build(); - when(generator.generate(eq("desc"), eq("1d20"), anyString())) + when(campaignContextFormatter.format("camp-1")).thenReturn("Campagne : Ma Campagne"); + when(generator.generate(eq("desc"), eq("1d20"), eq("Campagne : Ma Campagne"))) .thenReturn(new RandomTableGenerator.GeneratedTable("Nom IA", "Desc IA", List.of(e))); - when(campaignRepository.findById("camp-1")) - .thenReturn(Optional.of(Campaign.builder().id("camp-1").name("Ma Campagne").build())); RandomTable result = service.generateProposal("camp-1", "desc", " "); @@ -223,9 +215,9 @@ public class RandomTableServiceTest { @Test void testGenerateProposal_NullGeneratedEntries() { + when(campaignContextFormatter.format("camp-1")).thenReturn(""); when(generator.generate(anyString(), eq("2d6"), anyString())) .thenReturn(new RandomTableGenerator.GeneratedTable("N", "D", null)); - when(campaignRepository.findById("camp-1")).thenReturn(Optional.empty()); RandomTable result = service.generateProposal("camp-1", "desc", "2d6"); @@ -234,29 +226,22 @@ public class RandomTableServiceTest { } @Test - void testGenerateProposal_BuildsContextWithGameSystem() { - when(campaignRepository.findById("camp-1")).thenReturn(Optional.of(Campaign.builder() - .id("camp-1").name("Camp").description(" Une aventure ").gameSystemId("gs-1").build())); - when(gameSystemRepository.findById("gs-1")) - .thenReturn(Optional.of(GameSystem.builder().id("gs-1").name("D&D 5e").build())); + void testGenerateProposal_DelegatesContextToFormatter() { + when(campaignContextFormatter.format("camp-1")) + .thenReturn("Campagne : Camp — Une aventure\nSystème de jeu : D&D 5e"); when(generator.generate(anyString(), anyString(), any())) .thenReturn(new RandomTableGenerator.GeneratedTable("N", "D", List.of())); service.generateProposal("camp-1", "desc", "1d8"); - ArgumentCaptor ctxCaptor = ArgumentCaptor.forClass(String.class); - verify(generator).generate(eq("desc"), eq("1d8"), ctxCaptor.capture()); - String ctx = ctxCaptor.getValue(); - assertTrue(ctx.contains("Camp")); - assertTrue(ctx.contains("Une aventure")); - assertTrue(ctx.contains("D&D 5e")); + verify(generator).generate(eq("desc"), eq("1d8"), eq("Campagne : Camp — Une aventure\nSystème de jeu : D&D 5e")); } // --- improviseRoll --- @Test void testImproviseRoll_DelegatesToGenerator() { - when(campaignRepository.findById("camp-1")).thenReturn(Optional.empty()); + when(campaignContextFormatter.format("camp-1")).thenReturn(""); when(generator.improvise(eq("Rencontres"), eq("Gobelins"), eq("detail"), anyString())) .thenReturn("Un récit improvisé."); diff --git a/core/src/test/java/com/loremind/application/campaigncontext/SceneServiceTest.java b/core/src/test/java/com/loremind/application/campaigncontext/SceneServiceTest.java index 751ab00..bac4222 100644 --- a/core/src/test/java/com/loremind/application/campaigncontext/SceneServiceTest.java +++ b/core/src/test/java/com/loremind/application/campaigncontext/SceneServiceTest.java @@ -1,9 +1,9 @@ package com.loremind.application.campaigncontext; -import com.loremind.domain.campaigncontext.FieldProposal; -import com.loremind.domain.campaigncontext.Scene; -import com.loremind.domain.campaigncontext.SceneBranch; -import com.loremind.domain.campaigncontext.SceneDraft; +import com.loremind.domain.campaigncontext.generation.FieldProposal; +import com.loremind.domain.campaigncontext.structure.Scene; +import com.loremind.domain.campaigncontext.structure.SceneBranch; +import com.loremind.domain.campaigncontext.generation.SceneDraft; import com.loremind.domain.campaigncontext.ports.SceneRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/core/src/test/java/com/loremind/application/gamesystemcontext/GameSystemContextBuilderTest.java b/core/src/test/java/com/loremind/application/gamesystemcontext/GameSystemContextBuilderTest.java new file mode 100644 index 0000000..6fd6b51 --- /dev/null +++ b/core/src/test/java/com/loremind/application/gamesystemcontext/GameSystemContextBuilderTest.java @@ -0,0 +1,74 @@ +package com.loremind.application.gamesystemcontext; + +import com.loremind.domain.gamesystemcontext.ports.GameSystemRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test unitaire pour {@link GameSystemContextBuilder#parseH2Sections}. Couvre en + * particulier les titres avec espaces de fin, qui motivaient l'ajustement du regex + * (retrait du {@code \s*} final, redondant avec le {@code .trim()} déjà appliqué). + */ +@ExtendWith(MockitoExtension.class) +class GameSystemContextBuilderTest { + + @Mock + private GameSystemRepository gameSystemRepository; + + @InjectMocks + private GameSystemContextBuilder builder; + + @Test + void parseH2Sections_nullOrBlank_returnsEmptyMap() { + assertTrue(builder.parseH2Sections(null).isEmpty()); + assertTrue(builder.parseH2Sections(" ").isEmpty()); + } + + @Test + void parseH2Sections_ignoresPreambleBeforeFirstHeader() { + String md = "Préambule libre\nignoré.\n## Combat\nRègles de combat."; + + Map sections = builder.parseH2Sections(md); + + assertEquals(1, sections.size()); + assertEquals("Règles de combat.", sections.get("Combat")); + } + + @Test + void parseH2Sections_multipleSections_preservesOrderAndContent() { + String md = "## Combat\nRègles de combat.\n## Magie\nRègles de magie."; + + Map sections = builder.parseH2Sections(md); + + assertEquals(java.util.List.of("Combat", "Magie"), java.util.List.copyOf(sections.keySet())); + assertEquals("Règles de combat.", sections.get("Combat")); + assertEquals("Règles de magie.", sections.get("Magie")); + } + + @Test + void parseH2Sections_trailingSpacesOnHeaderLine_titleIsTrimmed() { + String md = "## Combat \nRègles de combat."; + + Map sections = builder.parseH2Sections(md); + + assertEquals(1, sections.size()); + assertTrue(sections.containsKey("Combat")); + } + + @Test + void parseH2Sections_trimsContentWhitespace() { + String md = "## Combat\n\n Règles de combat. \n\n## Magie\nRègles de magie."; + + Map sections = builder.parseH2Sections(md); + + assertEquals("Règles de combat.", sections.get("Combat")); + } +} diff --git a/core/src/test/java/com/loremind/application/generationcontext/CampaignStructuralContextBuilderTest.java b/core/src/test/java/com/loremind/application/generationcontext/CampaignStructuralContextBuilderTest.java index 2d4e752..91dd43d 100644 --- a/core/src/test/java/com/loremind/application/generationcontext/CampaignStructuralContextBuilderTest.java +++ b/core/src/test/java/com/loremind/application/generationcontext/CampaignStructuralContextBuilderTest.java @@ -1,12 +1,12 @@ package com.loremind.application.generationcontext; -import com.loremind.domain.campaigncontext.Arc; +import com.loremind.domain.campaigncontext.structure.Arc; import com.loremind.domain.campaigncontext.Campaign; -import com.loremind.domain.campaigncontext.Chapter; -import com.loremind.domain.campaigncontext.Character; -import com.loremind.domain.campaigncontext.Npc; -import com.loremind.domain.campaigncontext.Scene; -import com.loremind.domain.campaigncontext.SceneBranch; +import com.loremind.domain.campaigncontext.structure.Chapter; +import com.loremind.domain.campaigncontext.bestiary.Character; +import com.loremind.domain.campaigncontext.bestiary.Npc; +import com.loremind.domain.campaigncontext.structure.Scene; +import com.loremind.domain.campaigncontext.structure.SceneBranch; import com.loremind.domain.campaigncontext.ports.ArcRepository; import com.loremind.domain.campaigncontext.ports.CampaignRepository; import com.loremind.domain.campaigncontext.ports.ChapterRepository; diff --git a/core/src/test/java/com/loremind/application/generationcontext/NarrativeEntityContextBuilderTest.java b/core/src/test/java/com/loremind/application/generationcontext/NarrativeEntityContextBuilderTest.java index 0bd2dbd..83db27b 100644 --- a/core/src/test/java/com/loremind/application/generationcontext/NarrativeEntityContextBuilderTest.java +++ b/core/src/test/java/com/loremind/application/generationcontext/NarrativeEntityContextBuilderTest.java @@ -1,10 +1,10 @@ package com.loremind.application.generationcontext; -import com.loremind.domain.campaigncontext.Arc; -import com.loremind.domain.campaigncontext.Chapter; -import com.loremind.domain.campaigncontext.Character; -import com.loremind.domain.campaigncontext.Npc; -import com.loremind.domain.campaigncontext.Scene; +import com.loremind.domain.campaigncontext.structure.Arc; +import com.loremind.domain.campaigncontext.structure.Chapter; +import com.loremind.domain.campaigncontext.bestiary.Character; +import com.loremind.domain.campaigncontext.bestiary.Npc; +import com.loremind.domain.campaigncontext.structure.Scene; import com.loremind.domain.campaigncontext.ports.ArcRepository; import com.loremind.domain.campaigncontext.ports.ChapterRepository; import com.loremind.domain.campaigncontext.ports.CharacterRepository; diff --git a/core/src/test/java/com/loremind/application/generationcontext/StreamChatForCampaignUseCaseTest.java b/core/src/test/java/com/loremind/application/generationcontext/StreamChatForCampaignUseCaseTest.java index 6d797e0..b3a6cee 100644 --- a/core/src/test/java/com/loremind/application/generationcontext/StreamChatForCampaignUseCaseTest.java +++ b/core/src/test/java/com/loremind/application/generationcontext/StreamChatForCampaignUseCaseTest.java @@ -5,6 +5,7 @@ import com.loremind.domain.campaigncontext.ports.CampaignRepository; import com.loremind.domain.generationcontext.CampaignStructuralContext; import com.loremind.domain.generationcontext.ChatMessage; import com.loremind.domain.generationcontext.ChatRequest; +import com.loremind.domain.generationcontext.ChatStreamCallbacks; import com.loremind.domain.generationcontext.ChatUsage; import com.loremind.domain.generationcontext.LoreStructuralContext; import com.loremind.domain.generationcontext.NarrativeEntityContext; @@ -51,6 +52,7 @@ public class StreamChatForCampaignUseCaseTest { private Consumer onToken; private Runnable onComplete; private Consumer onError; + private ChatStreamCallbacks callbacks; @SuppressWarnings("unchecked") @BeforeEach @@ -61,6 +63,7 @@ public class StreamChatForCampaignUseCaseTest { onToken = mock(Consumer.class); onComplete = mock(Runnable.class); onError = mock(Consumer.class); + callbacks = new ChatStreamCallbacks(onUsage, onToken, onComplete, onError); } @Test @@ -68,7 +71,7 @@ public class StreamChatForCampaignUseCaseTest { when(campaignRepository.findById("missing")).thenReturn(Optional.empty()); assertThrows(IllegalArgumentException.class, - () -> useCase.execute("missing", null, null, messages, onUsage, onToken, onComplete, onError)); + () -> useCase.execute("missing", null, null, messages, callbacks)); verifyNoInteractions(aiChatProvider); } @@ -78,10 +81,10 @@ public class StreamChatForCampaignUseCaseTest { when(campaignRepository.findById("c-1")).thenReturn(Optional.of(standalone)); when(campaignContextBuilder.build("c-1")).thenReturn(campaignCtx); - useCase.execute("c-1", null, null, messages, onUsage, onToken, onComplete, onError); + useCase.execute("c-1", null, null, messages, callbacks); ArgumentCaptor captor = ArgumentCaptor.forClass(ChatRequest.class); - verify(aiChatProvider).streamChat(captor.capture(), eq(onUsage), eq(onToken), eq(onComplete), eq(onError)); + verify(aiChatProvider).streamChat(captor.capture(), eq(callbacks)); ChatRequest req = captor.getValue(); assertSame(campaignCtx, req.campaignContext()); assertNull(req.loreContext()); @@ -101,10 +104,10 @@ public class StreamChatForCampaignUseCaseTest { when(campaignContextBuilder.build("c-1")).thenReturn(campaignCtx); when(loreContextBuilder.buildOptional("lore-1")).thenReturn(Optional.of(loreCtx)); - useCase.execute("c-1", null, null, messages, onUsage, onToken, onComplete, onError); + useCase.execute("c-1", null, null, messages, callbacks); ArgumentCaptor captor = ArgumentCaptor.forClass(ChatRequest.class); - verify(aiChatProvider).streamChat(captor.capture(), any(), any(), any(), any()); + verify(aiChatProvider).streamChat(captor.capture(), any()); assertSame(loreCtx, captor.getValue().loreContext()); } @@ -116,10 +119,10 @@ public class StreamChatForCampaignUseCaseTest { when(campaignContextBuilder.build("c-1")).thenReturn(campaignCtx); when(loreContextBuilder.buildOptional("lore-ghost")).thenReturn(Optional.empty()); - useCase.execute("c-1", null, null, messages, onUsage, onToken, onComplete, onError); + useCase.execute("c-1", null, null, messages, callbacks); ArgumentCaptor captor = ArgumentCaptor.forClass(ChatRequest.class); - verify(aiChatProvider).streamChat(captor.capture(), any(), any(), any(), any()); + verify(aiChatProvider).streamChat(captor.capture(), any()); assertNull(captor.getValue().loreContext()); // La requete doit tout de meme partir (pas d'exception). } @@ -133,10 +136,10 @@ public class StreamChatForCampaignUseCaseTest { when(campaignContextBuilder.build("c-1")).thenReturn(campaignCtx); when(narrativeEntityContextBuilder.build("scene", "s-1")).thenReturn(entity); - useCase.execute("c-1", "scene", "s-1", messages, onUsage, onToken, onComplete, onError); + useCase.execute("c-1", "scene", "s-1", messages, callbacks); ArgumentCaptor captor = ArgumentCaptor.forClass(ChatRequest.class); - verify(aiChatProvider).streamChat(captor.capture(), any(), any(), any(), any()); + verify(aiChatProvider).streamChat(captor.capture(), any()); assertSame(entity, captor.getValue().narrativeEntity()); } @@ -146,10 +149,10 @@ public class StreamChatForCampaignUseCaseTest { when(campaignRepository.findById("c-1")).thenReturn(Optional.of(standalone)); when(campaignContextBuilder.build("c-1")).thenReturn(campaignCtx); - useCase.execute("c-1", "scene", " ", messages, onUsage, onToken, onComplete, onError); + useCase.execute("c-1", "scene", " ", messages, callbacks); ArgumentCaptor captor = ArgumentCaptor.forClass(ChatRequest.class); - verify(aiChatProvider).streamChat(captor.capture(), any(), any(), any(), any()); + verify(aiChatProvider).streamChat(captor.capture(), any()); assertNull(captor.getValue().narrativeEntity()); verifyNoInteractions(narrativeEntityContextBuilder); } diff --git a/core/src/test/java/com/loremind/application/generationcontext/StreamChatForLoreUseCaseTest.java b/core/src/test/java/com/loremind/application/generationcontext/StreamChatForLoreUseCaseTest.java index 0db356b..f84b7b5 100644 --- a/core/src/test/java/com/loremind/application/generationcontext/StreamChatForLoreUseCaseTest.java +++ b/core/src/test/java/com/loremind/application/generationcontext/StreamChatForLoreUseCaseTest.java @@ -2,6 +2,7 @@ package com.loremind.application.generationcontext; import com.loremind.domain.generationcontext.ChatMessage; import com.loremind.domain.generationcontext.ChatRequest; +import com.loremind.domain.generationcontext.ChatStreamCallbacks; import com.loremind.domain.generationcontext.ChatUsage; import com.loremind.domain.generationcontext.LoreStructuralContext; import com.loremind.domain.generationcontext.ports.AiChatProvider; @@ -51,6 +52,7 @@ public class StreamChatForLoreUseCaseTest { private Consumer onToken; private Runnable onComplete; private Consumer onError; + private ChatStreamCallbacks callbacks; @SuppressWarnings("unchecked") @BeforeEach @@ -61,16 +63,17 @@ public class StreamChatForLoreUseCaseTest { onToken = mock(Consumer.class); onComplete = mock(Runnable.class); onError = mock(Consumer.class); + callbacks = new ChatStreamCallbacks(onUsage, onToken, onComplete, onError); } @Test void testExecute_NoPageId_SendsRequestWithoutPageContext() { when(loreContextBuilder.build("lore-1")).thenReturn(loreCtx); - useCase.execute("lore-1", null, messages, onUsage, onToken, onComplete, onError); + useCase.execute("lore-1", null, messages, callbacks); ArgumentCaptor captor = ArgumentCaptor.forClass(ChatRequest.class); - verify(aiChatProvider).streamChat(captor.capture(), eq(onUsage), eq(onToken), eq(onComplete), eq(onError)); + verify(aiChatProvider).streamChat(captor.capture(), eq(callbacks)); ChatRequest req = captor.getValue(); assertSame(loreCtx, req.loreContext()); assertNull(req.pageContext()); @@ -81,10 +84,10 @@ public class StreamChatForLoreUseCaseTest { void testExecute_BlankPageId_TreatedAsNoPage() { when(loreContextBuilder.build("lore-1")).thenReturn(loreCtx); - useCase.execute("lore-1", " ", messages, onUsage, onToken, onComplete, onError); + useCase.execute("lore-1", " ", messages, callbacks); ArgumentCaptor captor = ArgumentCaptor.forClass(ChatRequest.class); - verify(aiChatProvider).streamChat(captor.capture(), any(), any(), any(), any()); + verify(aiChatProvider).streamChat(captor.capture(), any()); assertNull(captor.getValue().pageContext()); verifyNoInteractions(pageRepository); } @@ -108,10 +111,10 @@ public class StreamChatForLoreUseCaseTest { when(pageRepository.findById("p-1")).thenReturn(Optional.of(page)); when(templateRepository.findById("tpl-1")).thenReturn(Optional.of(tpl)); - useCase.execute("lore-1", "p-1", messages, onUsage, onToken, onComplete, onError); + useCase.execute("lore-1", "p-1", messages, callbacks); ArgumentCaptor captor = ArgumentCaptor.forClass(ChatRequest.class); - verify(aiChatProvider).streamChat(captor.capture(), any(), any(), any(), any()); + verify(aiChatProvider).streamChat(captor.capture(), any()); ChatRequest req = captor.getValue(); assertNotNull(req.pageContext()); assertEquals("Alice", req.pageContext().title()); @@ -130,10 +133,10 @@ public class StreamChatForLoreUseCaseTest { when(loreContextBuilder.build("lore-1")).thenReturn(loreCtx); when(pageRepository.findById("p-1")).thenReturn(Optional.of(page)); - useCase.execute("lore-1", "p-1", messages, onUsage, onToken, onComplete, onError); + useCase.execute("lore-1", "p-1", messages, callbacks); ArgumentCaptor captor = ArgumentCaptor.forClass(ChatRequest.class); - verify(aiChatProvider).streamChat(captor.capture(), any(), any(), any(), any()); + verify(aiChatProvider).streamChat(captor.capture(), any()); var pageCtx = captor.getValue().pageContext(); assertNotNull(pageCtx); assertEquals("Orphan", pageCtx.title()); @@ -153,10 +156,10 @@ public class StreamChatForLoreUseCaseTest { when(pageRepository.findById("p-1")).thenReturn(Optional.of(page)); when(templateRepository.findById("tpl-ghost")).thenReturn(Optional.empty()); - useCase.execute("lore-1", "p-1", messages, onUsage, onToken, onComplete, onError); + useCase.execute("lore-1", "p-1", messages, callbacks); ArgumentCaptor captor = ArgumentCaptor.forClass(ChatRequest.class); - verify(aiChatProvider).streamChat(captor.capture(), any(), any(), any(), any()); + verify(aiChatProvider).streamChat(captor.capture(), any()); var pageCtx = captor.getValue().pageContext(); assertEquals("?", pageCtx.templateName()); assertTrue(pageCtx.templateFields().isEmpty()); @@ -168,7 +171,7 @@ public class StreamChatForLoreUseCaseTest { when(pageRepository.findById("missing")).thenReturn(Optional.empty()); assertThrows(IllegalArgumentException.class, - () -> useCase.execute("lore-1", "missing", messages, onUsage, onToken, onComplete, onError)); + () -> useCase.execute("lore-1", "missing", messages, callbacks)); verifyNoInteractions(aiChatProvider); } } diff --git a/core/src/test/java/com/loremind/application/playcontext/SessionPrepServiceTest.java b/core/src/test/java/com/loremind/application/playcontext/SessionPrepServiceTest.java index b0e6a99..e58e5ca 100644 --- a/core/src/test/java/com/loremind/application/playcontext/SessionPrepServiceTest.java +++ b/core/src/test/java/com/loremind/application/playcontext/SessionPrepServiceTest.java @@ -4,15 +4,15 @@ import com.loremind.application.campaigncontext.CampaignReadinessAssessment; import com.loremind.application.campaigncontext.CampaignReadinessService; import com.loremind.application.campaigncontext.QuestStatusEnricher; import com.loremind.application.campaigncontext.ReadinessGap; -import com.loremind.domain.campaigncontext.Chapter; -import com.loremind.domain.campaigncontext.NodeType; -import com.loremind.domain.campaigncontext.Quest; -import com.loremind.domain.campaigncontext.QuestNodeRef; -import com.loremind.domain.campaigncontext.QuestStatus; -import com.loremind.domain.campaigncontext.ReadinessEntityType; -import com.loremind.domain.campaigncontext.ReadinessSeverity; -import com.loremind.domain.campaigncontext.ReadinessStatus; -import com.loremind.domain.campaigncontext.Scene; +import com.loremind.domain.campaigncontext.structure.Chapter; +import com.loremind.domain.campaigncontext.quest.NodeType; +import com.loremind.domain.campaigncontext.quest.Quest; +import com.loremind.domain.campaigncontext.quest.QuestNodeRef; +import com.loremind.domain.campaigncontext.quest.QuestStatus; +import com.loremind.domain.campaigncontext.readiness.ReadinessEntityType; +import com.loremind.domain.campaigncontext.readiness.ReadinessSeverity; +import com.loremind.domain.campaigncontext.readiness.ReadinessStatus; +import com.loremind.domain.campaigncontext.structure.Scene; import com.loremind.domain.campaigncontext.ports.ChapterRepository; import com.loremind.domain.campaigncontext.ports.QuestRepository; import com.loremind.domain.campaigncontext.ports.SceneRepository; diff --git a/core/src/test/java/com/loremind/domain/campaigncontext/ArcTest.java b/core/src/test/java/com/loremind/domain/campaigncontext/structure/ArcTest.java similarity index 97% rename from core/src/test/java/com/loremind/domain/campaigncontext/ArcTest.java rename to core/src/test/java/com/loremind/domain/campaigncontext/structure/ArcTest.java index b2af7a7..dab0b36 100644 --- a/core/src/test/java/com/loremind/domain/campaigncontext/ArcTest.java +++ b/core/src/test/java/com/loremind/domain/campaigncontext/structure/ArcTest.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.structure; import org.junit.jupiter.api.Test; diff --git a/core/src/test/java/com/loremind/domain/campaigncontext/ChapterTest.java b/core/src/test/java/com/loremind/domain/campaigncontext/structure/ChapterTest.java similarity index 97% rename from core/src/test/java/com/loremind/domain/campaigncontext/ChapterTest.java rename to core/src/test/java/com/loremind/domain/campaigncontext/structure/ChapterTest.java index de6ddc8..00034c4 100644 --- a/core/src/test/java/com/loremind/domain/campaigncontext/ChapterTest.java +++ b/core/src/test/java/com/loremind/domain/campaigncontext/structure/ChapterTest.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.structure; import org.junit.jupiter.api.Test; diff --git a/core/src/test/java/com/loremind/domain/campaigncontext/SceneBranchTest.java b/core/src/test/java/com/loremind/domain/campaigncontext/structure/SceneBranchTest.java similarity index 97% rename from core/src/test/java/com/loremind/domain/campaigncontext/SceneBranchTest.java rename to core/src/test/java/com/loremind/domain/campaigncontext/structure/SceneBranchTest.java index 8e8a505..d97da44 100644 --- a/core/src/test/java/com/loremind/domain/campaigncontext/SceneBranchTest.java +++ b/core/src/test/java/com/loremind/domain/campaigncontext/structure/SceneBranchTest.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.structure; import org.junit.jupiter.api.Test; diff --git a/core/src/test/java/com/loremind/domain/campaigncontext/SceneTest.java b/core/src/test/java/com/loremind/domain/campaigncontext/structure/SceneTest.java similarity index 98% rename from core/src/test/java/com/loremind/domain/campaigncontext/SceneTest.java rename to core/src/test/java/com/loremind/domain/campaigncontext/structure/SceneTest.java index 0ad1315..f9b0e91 100644 --- a/core/src/test/java/com/loremind/domain/campaigncontext/SceneTest.java +++ b/core/src/test/java/com/loremind/domain/campaigncontext/structure/SceneTest.java @@ -1,4 +1,4 @@ -package com.loremind.domain.campaigncontext; +package com.loremind.domain.campaigncontext.structure; import org.junit.jupiter.api.Test; diff --git a/core/src/test/java/com/loremind/domain/shared/ReorderSupportTest.java b/core/src/test/java/com/loremind/domain/shared/ReorderSupportTest.java new file mode 100644 index 0000000..c8c09df --- /dev/null +++ b/core/src/test/java/com/loremind/domain/shared/ReorderSupportTest.java @@ -0,0 +1,68 @@ +package com.loremind.domain.shared; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test unitaire pour {@link ReorderSupport}. Couvre en particulier les ids inconnus, + * qui doivent être ignorés silencieusement (contrat documenté du finder) et non lever. + */ +class ReorderSupportTest { + + private record Item(String id) { + } + + @Test + void reorder_assignsPositionsInOrderedIdsOrder() { + Map positions = new HashMap<>(); + Map byId = Map.of("a", new Item("a"), "b", new Item("b"), "c", new Item("c")); + List saved = new ArrayList<>(); + + ReorderSupport.reorder(List.of("c", "a", "b"), + id -> Optional.ofNullable(byId.get(id)), + (item, i) -> positions.put(item.id(), i), + saved::add); + + assertEquals(0, positions.get("c")); + assertEquals(1, positions.get("a")); + assertEquals(2, positions.get("b")); + assertEquals(3, saved.size()); + } + + @Test + void reorder_unknownId_isSkippedWithoutThrowing() { + Map positions = new HashMap<>(); + Map byId = Map.of("a", new Item("a")); + List saved = new ArrayList<>(); + + ReorderSupport.reorder(List.of("a", "ghost"), + id -> Optional.ofNullable(byId.get(id)), + (item, i) -> positions.put(item.id(), i), + saved::add); + + assertEquals(1, saved.size()); + assertEquals(0, positions.get("a")); + assertFalse(positions.containsKey("ghost")); + } + + @Test + void reorder_nullOrderedIds_isNoOp() { + List saved = new ArrayList<>(); + + ReorderSupport.reorder(null, + id -> Optional.of(new Item(id)), + (item, i) -> { }, + saved::add); + + assertTrue(saved.isEmpty()); + } +} diff --git a/core/src/test/java/com/loremind/infrastructure/ai/BrainAiChatClientTest.java b/core/src/test/java/com/loremind/infrastructure/ai/BrainAiChatClientTest.java index ae64793..bb56b8b 100644 --- a/core/src/test/java/com/loremind/infrastructure/ai/BrainAiChatClientTest.java +++ b/core/src/test/java/com/loremind/infrastructure/ai/BrainAiChatClientTest.java @@ -1,8 +1,9 @@ package com.loremind.infrastructure.ai; import com.loremind.domain.generationcontext.ChatRequest; +import com.loremind.domain.generationcontext.ChatStreamCallbacks; import com.loremind.domain.generationcontext.ChatUsage; -import com.loremind.domain.generationcontext.ports.AiProviderException; +import com.loremind.domain.generationcontext.ports.exceptions.AiProviderException; import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -71,6 +72,7 @@ class BrainAiChatClientTest { private final Consumer onToken = tokens::add; private final Runnable onComplete = () -> completed.set(true); private final Consumer onError = error::set; + private final ChatStreamCallbacks callbacks = new ChatStreamCallbacks(onUsage, onToken, onComplete, onError); @Test void flux_complet_parse_usage_et_token_puis_complete() { @@ -80,7 +82,7 @@ class BrainAiChatClientTest { "event:done\ndata:{}\n\n"; BrainAiChatClient client = clientWithSse(sse); - client.streamChat(minimalRequest(), onUsage, onToken, onComplete, onError); + client.streamChat(minimalRequest(), callbacks); // usage parsé et propagé assertThat(usages).hasSize(1); @@ -101,7 +103,7 @@ class BrainAiChatClientTest { "event:done\ndata:{}\n\n"; BrainAiChatClient client = clientWithSse(sse); - client.streamChat(minimalRequest(), onUsage, onToken, onComplete, onError); + client.streamChat(minimalRequest(), callbacks); assertThat(tokens).containsExactly("Bon", "jour"); assertThat(completed).isTrue(); @@ -115,7 +117,7 @@ class BrainAiChatClientTest { "event:done\ndata:{}\n\n"; BrainAiChatClient client = clientWithSse(sse); - client.streamChat(minimalRequest(), onUsage, onToken, onComplete, onError); + client.streamChat(minimalRequest(), callbacks); assertThat(error.get()) .isInstanceOf(AiProviderException.class) @@ -131,7 +133,7 @@ class BrainAiChatClientTest { "event:done\ndata:{}\n\n"; BrainAiChatClient client = clientWithSse(sse); - client.streamChat(minimalRequest(), onUsage, onToken, onComplete, onError); + client.streamChat(minimalRequest(), callbacks); assertThat(tokens).isEmpty(); assertThat(completed).isTrue(); @@ -146,7 +148,7 @@ class BrainAiChatClientTest { "event:done\ndata:{}\n\n"; BrainAiChatClient client = clientWithSse(sse); - client.streamChat(minimalRequest(), onUsage, onToken, onComplete, onError); + client.streamChat(minimalRequest(), callbacks); // Les champs absents tombent à 0 (parser tolérant). assertThat(usages).containsExactly(new ChatUsage(5, 0, 0, 0)); @@ -156,7 +158,7 @@ class BrainAiChatClientTest { void erreur_transport_declenche_onError_avec_AiProviderException() { BrainAiChatClient client = clientErroring(); - client.streamChat(minimalRequest(), onUsage, onToken, onComplete, onError); + client.streamChat(minimalRequest(), callbacks); assertThat(error.get()) .isInstanceOf(AiProviderException.class) @@ -170,7 +172,7 @@ class BrainAiChatClientTest { // Flux SSE vide : aucun évènement, blockLast renvoie null, onComplete appelé. BrainAiChatClient client = clientWithSse(""); - client.streamChat(minimalRequest(), onUsage, onToken, onComplete, onError); + client.streamChat(minimalRequest(), callbacks); assertThat(tokens).isEmpty(); assertThat(usages).isEmpty(); diff --git a/core/src/test/java/com/loremind/infrastructure/ai/BrainAiClientTest.java b/core/src/test/java/com/loremind/infrastructure/ai/BrainAiClientTest.java index 1824056..eb354f5 100644 --- a/core/src/test/java/com/loremind/infrastructure/ai/BrainAiClientTest.java +++ b/core/src/test/java/com/loremind/infrastructure/ai/BrainAiClientTest.java @@ -2,7 +2,7 @@ package com.loremind.infrastructure.ai; import com.loremind.domain.generationcontext.GenerationContext; import com.loremind.domain.generationcontext.GenerationResult; -import com.loremind.domain.generationcontext.ports.AiProviderException; +import com.loremind.domain.generationcontext.ports.exceptions.AiProviderException; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.springframework.http.HttpEntity; diff --git a/core/src/test/java/com/loremind/infrastructure/ai/BrainCampaignImportClientTest.java b/core/src/test/java/com/loremind/infrastructure/ai/BrainCampaignImportClientTest.java index 675591b..e4e5a7f 100644 --- a/core/src/test/java/com/loremind/infrastructure/ai/BrainCampaignImportClientTest.java +++ b/core/src/test/java/com/loremind/infrastructure/ai/BrainCampaignImportClientTest.java @@ -1,9 +1,9 @@ package com.loremind.infrastructure.ai; import com.fasterxml.jackson.databind.ObjectMapper; -import com.loremind.domain.campaigncontext.CampaignImportProgress; -import com.loremind.domain.campaigncontext.CampaignImportProposal; -import com.loremind.domain.campaigncontext.ports.CampaignImportException; +import com.loremind.domain.campaigncontext.generation.CampaignImportProgress; +import com.loremind.domain.campaigncontext.generation.CampaignImportProposal; +import com.loremind.domain.campaigncontext.ports.exceptions.CampaignImportException; import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; diff --git a/core/src/test/java/com/loremind/infrastructure/ai/BrainItemCatalogClientTest.java b/core/src/test/java/com/loremind/infrastructure/ai/BrainItemCatalogClientTest.java index ecdd90b..0acf5e1 100644 --- a/core/src/test/java/com/loremind/infrastructure/ai/BrainItemCatalogClientTest.java +++ b/core/src/test/java/com/loremind/infrastructure/ai/BrainItemCatalogClientTest.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.ai; -import com.loremind.domain.campaigncontext.CatalogItem; -import com.loremind.domain.campaigncontext.ports.ItemCatalogGenerationException; +import com.loremind.domain.campaigncontext.itemcatalog.CatalogItem; +import com.loremind.domain.campaigncontext.ports.exceptions.ItemCatalogGenerationException; import com.loremind.domain.campaigncontext.ports.ItemCatalogGenerator.GeneratedCatalog; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/core/src/test/java/com/loremind/infrastructure/ai/BrainNotebookChatClientTest.java b/core/src/test/java/com/loremind/infrastructure/ai/BrainNotebookChatClientTest.java index c18e1fd..7846fd6 100644 --- a/core/src/test/java/com/loremind/infrastructure/ai/BrainNotebookChatClientTest.java +++ b/core/src/test/java/com/loremind/infrastructure/ai/BrainNotebookChatClientTest.java @@ -3,7 +3,7 @@ package com.loremind.infrastructure.ai; import com.fasterxml.jackson.databind.ObjectMapper; import com.loremind.domain.campaigncontext.ports.NotebookChatStreamer.Msg; import com.loremind.domain.campaigncontext.ports.NotebookChatStreamer.Progress; -import com.loremind.domain.campaigncontext.ports.NotebookException; +import com.loremind.domain.campaigncontext.ports.exceptions.NotebookException; import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -66,11 +66,12 @@ class BrainNotebookChatClientTest { List.of(new Msg("user", "Bonjour")), "ctx", deep, - sources::set, - tokens::append, - progresses::add, - () -> done.set(true), - error::set); + new BrainNotebookChatClient.Callbacks( + sources::set, + tokens::append, + progresses::add, + () -> done.set(true), + error::set)); } } @@ -190,11 +191,8 @@ class BrainNotebookChatClientTest { List.of(new Msg("user", "hi")), null, false, - json -> {}, - tok -> {}, - p -> {}, - () -> done.set(true), - err -> {}); + new BrainNotebookChatClient.Callbacks( + json -> {}, tok -> {}, p -> {}, () -> done.set(true), err -> {})); assertTrue(done.get()); } diff --git a/core/src/test/java/com/loremind/infrastructure/ai/BrainNotebookIndexClientTest.java b/core/src/test/java/com/loremind/infrastructure/ai/BrainNotebookIndexClientTest.java index d454348..a3d11cc 100644 --- a/core/src/test/java/com/loremind/infrastructure/ai/BrainNotebookIndexClientTest.java +++ b/core/src/test/java/com/loremind/infrastructure/ai/BrainNotebookIndexClientTest.java @@ -1,6 +1,6 @@ package com.loremind.infrastructure.ai; -import com.loremind.domain.campaigncontext.ports.NotebookException; +import com.loremind.domain.campaigncontext.ports.exceptions.NotebookException; import com.loremind.domain.campaigncontext.ports.NotebookIndexer.IndexResult; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/core/src/test/java/com/loremind/infrastructure/ai/BrainRandomTableClientTest.java b/core/src/test/java/com/loremind/infrastructure/ai/BrainRandomTableClientTest.java index 3a64043..578b66e 100644 --- a/core/src/test/java/com/loremind/infrastructure/ai/BrainRandomTableClientTest.java +++ b/core/src/test/java/com/loremind/infrastructure/ai/BrainRandomTableClientTest.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.ai; -import com.loremind.domain.campaigncontext.RandomTableEntry; -import com.loremind.domain.campaigncontext.ports.RandomTableGenerationException; +import com.loremind.domain.campaigncontext.randomtable.RandomTableEntry; +import com.loremind.domain.campaigncontext.ports.exceptions.RandomTableGenerationException; import com.loremind.domain.campaigncontext.ports.RandomTableGenerator.GeneratedTable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/core/src/test/java/com/loremind/infrastructure/ai/BrainRulesImportClientTest.java b/core/src/test/java/com/loremind/infrastructure/ai/BrainRulesImportClientTest.java index 6b629da..f17aafc 100644 --- a/core/src/test/java/com/loremind/infrastructure/ai/BrainRulesImportClientTest.java +++ b/core/src/test/java/com/loremind/infrastructure/ai/BrainRulesImportClientTest.java @@ -3,7 +3,7 @@ package com.loremind.infrastructure.ai; import com.fasterxml.jackson.databind.ObjectMapper; import com.loremind.domain.gamesystemcontext.RulesImportProgress; import com.loremind.domain.gamesystemcontext.RulesImportResult; -import com.loremind.domain.gamesystemcontext.ports.RulesImportException; +import com.loremind.domain.gamesystemcontext.ports.exceptions.RulesImportException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; diff --git a/core/src/test/java/com/loremind/infrastructure/persistence/converter/SceneBranchListJsonConverterTest.java b/core/src/test/java/com/loremind/infrastructure/persistence/converter/SceneBranchListJsonConverterTest.java index a83187b..8cd63a2 100644 --- a/core/src/test/java/com/loremind/infrastructure/persistence/converter/SceneBranchListJsonConverterTest.java +++ b/core/src/test/java/com/loremind/infrastructure/persistence/converter/SceneBranchListJsonConverterTest.java @@ -1,6 +1,6 @@ package com.loremind.infrastructure.persistence.converter; -import com.loremind.domain.campaigncontext.SceneBranch; +import com.loremind.domain.campaigncontext.structure.SceneBranch; import org.junit.jupiter.api.Test; import java.util.List; diff --git a/core/src/test/java/com/loremind/infrastructure/persistence/postgres/PostgresArcRepositoryTest.java b/core/src/test/java/com/loremind/infrastructure/persistence/postgres/PostgresArcRepositoryTest.java index 6cf158b..def9c32 100644 --- a/core/src/test/java/com/loremind/infrastructure/persistence/postgres/PostgresArcRepositoryTest.java +++ b/core/src/test/java/com/loremind/infrastructure/persistence/postgres/PostgresArcRepositoryTest.java @@ -1,6 +1,6 @@ package com.loremind.infrastructure.persistence.postgres; -import com.loremind.domain.campaigncontext.Arc; +import com.loremind.domain.campaigncontext.structure.Arc; import com.loremind.domain.campaigncontext.Campaign; import com.loremind.domain.campaigncontext.ports.ArcRepository; import com.loremind.domain.campaigncontext.ports.CampaignRepository; diff --git a/core/src/test/java/com/loremind/infrastructure/persistence/postgres/PostgresChapterRepositoryTest.java b/core/src/test/java/com/loremind/infrastructure/persistence/postgres/PostgresChapterRepositoryTest.java index 8674e9a..b1f2491 100644 --- a/core/src/test/java/com/loremind/infrastructure/persistence/postgres/PostgresChapterRepositoryTest.java +++ b/core/src/test/java/com/loremind/infrastructure/persistence/postgres/PostgresChapterRepositoryTest.java @@ -1,8 +1,8 @@ package com.loremind.infrastructure.persistence.postgres; -import com.loremind.domain.campaigncontext.Arc; +import com.loremind.domain.campaigncontext.structure.Arc; import com.loremind.domain.campaigncontext.Campaign; -import com.loremind.domain.campaigncontext.Chapter; +import com.loremind.domain.campaigncontext.structure.Chapter; import com.loremind.domain.campaigncontext.ports.ArcRepository; import com.loremind.domain.campaigncontext.ports.CampaignRepository; import com.loremind.domain.campaigncontext.ports.ChapterRepository; diff --git a/core/src/test/java/com/loremind/infrastructure/persistence/postgres/PostgresSceneRepositoryTest.java b/core/src/test/java/com/loremind/infrastructure/persistence/postgres/PostgresSceneRepositoryTest.java index 4ca76ac..eb51176 100644 --- a/core/src/test/java/com/loremind/infrastructure/persistence/postgres/PostgresSceneRepositoryTest.java +++ b/core/src/test/java/com/loremind/infrastructure/persistence/postgres/PostgresSceneRepositoryTest.java @@ -1,11 +1,11 @@ package com.loremind.infrastructure.persistence.postgres; -import com.loremind.domain.campaigncontext.Arc; +import com.loremind.domain.campaigncontext.structure.Arc; import com.loremind.domain.campaigncontext.Campaign; -import com.loremind.domain.campaigncontext.Chapter; -import com.loremind.domain.campaigncontext.Scene; -import com.loremind.domain.campaigncontext.SceneBattlemap; -import com.loremind.domain.campaigncontext.SceneBranch; +import com.loremind.domain.campaigncontext.structure.Chapter; +import com.loremind.domain.campaigncontext.structure.Scene; +import com.loremind.domain.campaigncontext.structure.SceneBattlemap; +import com.loremind.domain.campaigncontext.structure.SceneBranch; import com.loremind.domain.campaigncontext.ports.ArcRepository; import com.loremind.domain.campaigncontext.ports.CampaignRepository; import com.loremind.domain.campaigncontext.ports.ChapterRepository; diff --git a/core/src/test/java/com/loremind/infrastructure/transfer/ContentExportImportRoundTripTest.java b/core/src/test/java/com/loremind/infrastructure/transfer/ContentExportImportRoundTripTest.java index a804396..d788508 100644 --- a/core/src/test/java/com/loremind/infrastructure/transfer/ContentExportImportRoundTripTest.java +++ b/core/src/test/java/com/loremind/infrastructure/transfer/ContentExportImportRoundTripTest.java @@ -1,12 +1,12 @@ package com.loremind.infrastructure.transfer; -import com.loremind.domain.campaigncontext.ArcType; -import com.loremind.domain.campaigncontext.NodeType; -import com.loremind.domain.campaigncontext.Prerequisite; -import com.loremind.domain.campaigncontext.QuestNodeRef; -import com.loremind.domain.campaigncontext.ProgressionStatus; -import com.loremind.domain.campaigncontext.SceneBattlemap; -import com.loremind.domain.campaigncontext.SceneBranch; +import com.loremind.domain.campaigncontext.structure.ArcType; +import com.loremind.domain.campaigncontext.quest.NodeType; +import com.loremind.domain.campaigncontext.quest.Prerequisite; +import com.loremind.domain.campaigncontext.quest.QuestNodeRef; +import com.loremind.domain.campaigncontext.quest.ProgressionStatus; +import com.loremind.domain.campaigncontext.structure.SceneBattlemap; +import com.loremind.domain.campaigncontext.structure.SceneBranch; import com.loremind.domain.files.ports.FileStorage; import com.loremind.domain.images.ports.ImageStorage; import com.loremind.domain.playcontext.ClockTrigger; diff --git a/core/src/test/java/com/loremind/infrastructure/transfer/ContentExportV1ImportTest.java b/core/src/test/java/com/loremind/infrastructure/transfer/ContentExportV1ImportTest.java index cd9cc35..ed48f59 100644 --- a/core/src/test/java/com/loremind/infrastructure/transfer/ContentExportV1ImportTest.java +++ b/core/src/test/java/com/loremind/infrastructure/transfer/ContentExportV1ImportTest.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.transfer; -import com.loremind.domain.campaigncontext.NodeType; -import com.loremind.domain.campaigncontext.Prerequisite; +import com.loremind.domain.campaigncontext.quest.NodeType; +import com.loremind.domain.campaigncontext.quest.Prerequisite; import com.loremind.infrastructure.persistence.entity.ChapterJpaEntity; import com.loremind.infrastructure.persistence.entity.QuestJpaEntity; import com.loremind.infrastructure.persistence.entity.QuestProgressionJpaEntity; diff --git a/core/src/test/java/com/loremind/infrastructure/transfer/IdRemapperTest.java b/core/src/test/java/com/loremind/infrastructure/transfer/IdRemapperTest.java index 3a3aced..57658ac 100644 --- a/core/src/test/java/com/loremind/infrastructure/transfer/IdRemapperTest.java +++ b/core/src/test/java/com/loremind/infrastructure/transfer/IdRemapperTest.java @@ -1,8 +1,8 @@ package com.loremind.infrastructure.transfer; -import com.loremind.domain.campaigncontext.ArcType; -import com.loremind.domain.campaigncontext.Prerequisite; -import com.loremind.domain.campaigncontext.SceneBranch; +import com.loremind.domain.campaigncontext.structure.ArcType; +import com.loremind.domain.campaigncontext.quest.Prerequisite; +import com.loremind.domain.campaigncontext.structure.SceneBranch; import org.junit.jupiter.api.Test; import java.util.List; diff --git a/core/src/test/java/com/loremind/infrastructure/transfer/ImageRefRemapOnImportTest.java b/core/src/test/java/com/loremind/infrastructure/transfer/ImageRefRemapOnImportTest.java new file mode 100644 index 0000000..8d82bbe --- /dev/null +++ b/core/src/test/java/com/loremind/infrastructure/transfer/ImageRefRemapOnImportTest.java @@ -0,0 +1,186 @@ +package com.loremind.infrastructure.transfer; + +import com.loremind.domain.campaigncontext.structure.Room; +import com.loremind.domain.campaigncontext.structure.SceneBattlemap; +import com.loremind.domain.files.ports.FileStorage; +import com.loremind.domain.images.ports.ImageStorage; +import com.loremind.domain.lorecontext.ImageFraming; +import com.loremind.infrastructure.persistence.entity.ArcJpaEntity; +import com.loremind.infrastructure.persistence.entity.CampaignJpaEntity; +import com.loremind.infrastructure.persistence.entity.ChapterJpaEntity; +import com.loremind.infrastructure.persistence.entity.ImageJpaEntity; +import com.loremind.infrastructure.persistence.entity.LoreJpaEntity; +import com.loremind.infrastructure.persistence.entity.LoreNodeJpaEntity; +import com.loremind.infrastructure.persistence.entity.NpcJpaEntity; +import com.loremind.infrastructure.persistence.entity.PageJpaEntity; +import com.loremind.infrastructure.persistence.entity.SceneJpaEntity; +import com.loremind.infrastructure.persistence.entity.StoredFileJpaEntity; +import com.loremind.infrastructure.persistence.jpa.ArcJpaRepository; +import com.loremind.infrastructure.persistence.jpa.CampaignJpaRepository; +import com.loremind.infrastructure.persistence.jpa.ChapterJpaRepository; +import com.loremind.infrastructure.persistence.jpa.ImageJpaRepository; +import com.loremind.infrastructure.persistence.jpa.LoreJpaRepository; +import com.loremind.infrastructure.persistence.jpa.LoreNodeJpaRepository; +import com.loremind.infrastructure.persistence.jpa.NpcJpaRepository; +import com.loremind.infrastructure.persistence.jpa.PageJpaRepository; +import com.loremind.infrastructure.persistence.jpa.SceneJpaRepository; +import com.loremind.infrastructure.persistence.jpa.StoredFileJpaRepository; +import com.loremind.infrastructure.transfer.dto.ContentExport; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.transaction.annotation.Transactional; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.mockito.Mockito.when; + +/** + * Régression : import d'un export sur une base où les images/fichiers reçoivent un + * NOUVEL id (cas réel « fixe → portable »). Le round-trip classique réimporte dans la + * MÊME base : la clé de stockage existe déjà, l'id d'image ne bouge pas, et le bug de + * refs non remappées reste invisible. + *

    + * Ici on exporte, puis on SUPPRIME les lignes {@code images}/{@code stored_files} (le + * binaire reste fourni par le mock de stockage). Réimportées, elles obtiennent un id + * différent (les colonnes IDENTITY H2 ne réutilisent pas un id libéré) — exactement ce + * qui se passe sur une autre machine. On vérifie alors que TOUTES les réfs d'images et + * de fichiers des entités importées pointent le nouvel id, pas l'ancien (images absentes + * / mélangées avant le correctif). + */ +@SpringBootTest +@Transactional +class ImageRefRemapOnImportTest { + + @Autowired private ExportService exportService; + @Autowired private ImportService importService; + + @Autowired private CampaignJpaRepository campaignRepo; + @Autowired private ArcJpaRepository arcRepo; + @Autowired private ChapterJpaRepository chapterRepo; + @Autowired private SceneJpaRepository sceneRepo; + @Autowired private NpcJpaRepository npcRepo; + @Autowired private LoreJpaRepository loreRepo; + @Autowired private LoreNodeJpaRepository loreNodeRepo; + @Autowired private PageJpaRepository pageRepo; + @Autowired private ImageJpaRepository imageRepo; + @Autowired private StoredFileJpaRepository storedFileRepo; + + @MockitoBean private ImageStorage imageStorage; + @MockitoBean private FileStorage fileStorage; + + private static final String IMG_KEY = "images/remap-portrait.png"; + private static final String FILE_KEY = "files/remap-map.json"; + + @Test + void importIntoDbWithFreshIds_remapsEveryImageAndFileRef() throws IOException { + // ----- Seed : image + fichier référencés par un peu tout ----- + ImageJpaEntity image = imageRepo.save(ImageJpaEntity.builder() + .filename("portrait.png").contentType("image/png").sizeBytes(4).storageKey(IMG_KEY).build()); + String oldImageRef = String.valueOf(image.getId()); + StoredFileJpaEntity file = storedFileRepo.save(StoredFileJpaEntity.builder() + .filename("map.json").contentType("application/json").sizeBytes(2).storageKey(FILE_KEY).build()); + String oldFileRef = String.valueOf(file.getId()); + + CampaignJpaEntity campaign = campaignRepo.save(CampaignJpaEntity.builder() + .name("Remap Campaign").description("d").arcsCount(1).build()); + + Long originalNpcId = npcRepo.save(NpcJpaEntity.builder() + .name("Remap Npc").campaignId(campaign.getId()) + .portraitImageId(oldImageRef) + .headerImageId(oldImageRef) + .imageValues(Map.of("gallery", List.of(oldImageRef))) + .folder("Ville").order(0).build()).getId(); + + ArcJpaEntity arc = arcRepo.save(ArcJpaEntity.builder() + .name("Remap Arc").campaignId(campaign.getId()).order(0) + .illustrationImageIds(new ArrayList<>(List.of(oldImageRef))).build()); + ChapterJpaEntity chapter = chapterRepo.save(ChapterJpaEntity.builder() + .name("Remap Chapter").arcId(arc.getId()).order(0).build()); + + Room room = Room.builder().id("room-uuid-1").name("Crypte").order(0) + .mapImageId(oldImageRef) + .illustrationImageIds(new ArrayList<>(List.of(oldImageRef))).build(); + Long originalSceneId = sceneRepo.save(SceneJpaEntity.builder() + .name("Remap Scene").chapterId(chapter.getId()).order(0) + .illustrationImageIds(new ArrayList<>(List.of(oldImageRef))) + .battlemaps(new ArrayList<>(List.of(new SceneBattlemap("Jour", oldFileRef, null)))) + .rooms(new ArrayList<>(List.of(room))).build()).getId(); + + // Page de lore : galerie + cadrage (fieldKey -> imageId -> pan/zoom) indexé par id d'image. + LoreJpaEntity lore = loreRepo.save(LoreJpaEntity.builder().name("Remap Lore").description("d").build()); + LoreNodeJpaEntity node = loreNodeRepo.save(LoreNodeJpaEntity.builder() + .name("Remap Node").loreId(lore.getId()).order(0).build()); + Long originalPageId = pageRepo.save(PageJpaEntity.builder() + .loreId(lore.getId()).nodeId(node.getId()).title("Remap Page") + .imageValues(Map.of("gallery", List.of(oldImageRef))) + .imageFraming(Map.of("gallery", Map.of(oldImageRef, new ImageFraming(50.0, 30.0, 1.4)))) + .build()).getId(); + + when(imageStorage.download(IMG_KEY)).thenAnswer(inv -> new ByteArrayInputStream("PNG!".getBytes())); + when(fileStorage.download(FILE_KEY)).thenAnswer(inv -> new ByteArrayInputStream("{}".getBytes())); + + // ----- Export + zip ----- + ContentExport export = exportService.buildExport("2026-01-02T00:00:00Z"); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + exportService.writeZip(export, baos); + + // ----- Simule une base cible : les clés n'existent plus -> id neuf à la réinsertion ----- + imageRepo.deleteById(image.getId()); + storedFileRepo.deleteById(file.getId()); + imageRepo.flush(); + storedFileRepo.flush(); + + // ----- Import ----- + importService.importZip(new ByteArrayInputStream(baos.toByteArray())); + + // Image/fichier recréés sous leur clé, mais avec un NOUVEL id. + ImageJpaEntity newImage = imageRepo.findByStorageKey(IMG_KEY).orElseThrow(); + String newImageRef = String.valueOf(newImage.getId()); + assertNotEquals(oldImageRef, newImageRef, "l'image importée doit avoir un id neuf"); + StoredFileJpaEntity newFile = storedFileRepo.findByStorageKey(FILE_KEY).orElseThrow(); + String newFileRef = String.valueOf(newFile.getId()); + assertNotEquals(oldFileRef, newFileRef, "le fichier importé doit avoir un id neuf"); + + // NPC importé : portrait + header + galerie pointent la nouvelle image. + NpcJpaEntity importedNpc = npcRepo.findAll().stream() + .filter(n -> "Remap Npc".equals(n.getName()) && !n.getId().equals(originalNpcId)) + .findFirst().orElseThrow(); + assertEquals(newImageRef, importedNpc.getPortraitImageId(), "portrait NPC remappé"); + assertEquals(newImageRef, importedNpc.getHeaderImageId(), "header NPC remappé"); + assertEquals(List.of(newImageRef), importedNpc.getImageValues().get("gallery"), "galerie NPC remappée"); + + // Arc importé : illustration remappée. + ArcJpaEntity importedArc = arcRepo.findAll().stream() + .filter(a -> "Remap Arc".equals(a.getName()) && !a.getId().equals(arc.getId())) + .findFirst().orElseThrow(); + assertEquals(List.of(newImageRef), importedArc.getIllustrationImageIds(), "illustration Arc remappée"); + + // Scène importée : illustration + battlemap (fichier) + images de salle remappées. + SceneJpaEntity importedScene = sceneRepo.findAll().stream() + .filter(s -> "Remap Scene".equals(s.getName()) && !s.getId().equals(originalSceneId)) + .findFirst().orElseThrow(); + assertEquals(List.of(newImageRef), importedScene.getIllustrationImageIds(), "illustration Scène remappée"); + assertEquals(newFileRef, importedScene.getBattlemaps().get(0).mediaFileId(), "battlemap Scène remappée"); + Room importedRoom = importedScene.getRooms().get(0); + assertEquals(newImageRef, importedRoom.getMapImageId(), "plan de salle remappé"); + assertEquals(List.of(newImageRef), importedRoom.getIllustrationImageIds(), "galerie de salle remappée"); + + // Page importée : galerie ET cadrage (clé interne = id d'image) remappés vers la nouvelle image. + PageJpaEntity importedPage = pageRepo.findAll().stream() + .filter(p -> "Remap Page".equals(p.getTitle()) && !p.getId().equals(originalPageId)) + .findFirst().orElseThrow(); + assertEquals(List.of(newImageRef), importedPage.getImageValues().get("gallery"), "galerie Page remappée"); + assertEquals(Map.of(newImageRef, new ImageFraming(50.0, 30.0, 1.4)), + importedPage.getImageFraming().get("gallery"), + "le cadrage doit être ré-indexé sous le nouvel id d'image"); + } +} diff --git a/core/src/test/java/com/loremind/infrastructure/transfer/SelectiveExportTest.java b/core/src/test/java/com/loremind/infrastructure/transfer/SelectiveExportTest.java index e5cf881..cee8545 100644 --- a/core/src/test/java/com/loremind/infrastructure/transfer/SelectiveExportTest.java +++ b/core/src/test/java/com/loremind/infrastructure/transfer/SelectiveExportTest.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.transfer; -import com.loremind.domain.campaigncontext.ArcType; -import com.loremind.domain.campaigncontext.Room; +import com.loremind.domain.campaigncontext.structure.ArcType; +import com.loremind.domain.campaigncontext.structure.Room; import com.loremind.infrastructure.persistence.entity.*; import com.loremind.infrastructure.persistence.jpa.*; import com.loremind.infrastructure.transfer.dto.ContentExport; diff --git a/core/src/test/java/com/loremind/infrastructure/transfer/foundry/FoundryExportServiceTest.java b/core/src/test/java/com/loremind/infrastructure/transfer/foundry/FoundryExportServiceTest.java index 9a5c5eb..0bad122 100644 --- a/core/src/test/java/com/loremind/infrastructure/transfer/foundry/FoundryExportServiceTest.java +++ b/core/src/test/java/com/loremind/infrastructure/transfer/foundry/FoundryExportServiceTest.java @@ -1,6 +1,6 @@ package com.loremind.infrastructure.transfer.foundry; -import com.loremind.domain.campaigncontext.SceneBattlemap; +import com.loremind.domain.campaigncontext.structure.SceneBattlemap; import com.loremind.domain.shared.template.TemplateField; import com.loremind.infrastructure.persistence.entity.*; import com.loremind.infrastructure.persistence.jpa.*; diff --git a/core/src/test/java/com/loremind/infrastructure/transfer/pdf/PdfExportPreviewTest.java b/core/src/test/java/com/loremind/infrastructure/transfer/pdf/PdfExportPreviewTest.java index 8fcf47e..78cecce 100644 --- a/core/src/test/java/com/loremind/infrastructure/transfer/pdf/PdfExportPreviewTest.java +++ b/core/src/test/java/com/loremind/infrastructure/transfer/pdf/PdfExportPreviewTest.java @@ -1,14 +1,14 @@ package com.loremind.infrastructure.transfer.pdf; -import com.loremind.domain.campaigncontext.ArcType; -import com.loremind.domain.campaigncontext.LinkType; -import com.loremind.domain.campaigncontext.NodeType; -import com.loremind.domain.campaigncontext.Prerequisite; -import com.loremind.domain.campaigncontext.QuestNodeRef; -import com.loremind.domain.campaigncontext.Room; -import com.loremind.domain.campaigncontext.RoomBranch; -import com.loremind.domain.campaigncontext.SceneBattlemap; -import com.loremind.domain.campaigncontext.SceneBranch; +import com.loremind.domain.campaigncontext.structure.ArcType; +import com.loremind.domain.campaigncontext.structure.LinkType; +import com.loremind.domain.campaigncontext.quest.NodeType; +import com.loremind.domain.campaigncontext.quest.Prerequisite; +import com.loremind.domain.campaigncontext.quest.QuestNodeRef; +import com.loremind.domain.campaigncontext.structure.Room; +import com.loremind.domain.campaigncontext.structure.RoomBranch; +import com.loremind.domain.campaigncontext.structure.SceneBattlemap; +import com.loremind.domain.campaigncontext.structure.SceneBranch; import com.loremind.domain.files.ports.FileStorage; import com.loremind.domain.images.ports.ImageStorage; import com.loremind.domain.shared.template.TemplateField; diff --git a/core/src/test/java/com/loremind/infrastructure/transfer/pdf/PdfExportServiceRichTest.java b/core/src/test/java/com/loremind/infrastructure/transfer/pdf/PdfExportServiceRichTest.java index 358b6fa..28bd911 100644 --- a/core/src/test/java/com/loremind/infrastructure/transfer/pdf/PdfExportServiceRichTest.java +++ b/core/src/test/java/com/loremind/infrastructure/transfer/pdf/PdfExportServiceRichTest.java @@ -1,9 +1,9 @@ package com.loremind.infrastructure.transfer.pdf; -import com.loremind.domain.campaigncontext.NodeType; -import com.loremind.domain.campaigncontext.Prerequisite; -import com.loremind.domain.campaigncontext.QuestNodeRef; -import com.loremind.domain.campaigncontext.SceneBattlemap; +import com.loremind.domain.campaigncontext.quest.NodeType; +import com.loremind.domain.campaigncontext.quest.Prerequisite; +import com.loremind.domain.campaigncontext.quest.QuestNodeRef; +import com.loremind.domain.campaigncontext.structure.SceneBattlemap; import com.loremind.domain.files.ports.FileStorage; import com.loremind.domain.images.ports.ImageStorage; import com.loremind.domain.shared.template.FieldType; diff --git a/core/src/test/java/com/loremind/infrastructure/web/controller/AiChatControllerTest.java b/core/src/test/java/com/loremind/infrastructure/web/controller/AiChatControllerTest.java index 3ff97d1..9b21d09 100644 --- a/core/src/test/java/com/loremind/infrastructure/web/controller/AiChatControllerTest.java +++ b/core/src/test/java/com/loremind/infrastructure/web/controller/AiChatControllerTest.java @@ -5,6 +5,7 @@ import com.loremind.application.generationcontext.StreamChatForCampaignUseCase; import com.loremind.application.generationcontext.StreamChatForLoreUseCase; import com.loremind.application.generationcontext.StreamChatForSessionUseCase; import com.loremind.domain.generationcontext.ChatMessage; +import com.loremind.domain.generationcontext.ChatStreamCallbacks; import com.loremind.domain.generationcontext.ChatUsage; import com.loremind.infrastructure.web.dto.generationcontext.ChatMessageDTO; import com.loremind.infrastructure.web.dto.generationcontext.ChatStreamCampaignRequestDTO; @@ -22,7 +23,6 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import java.util.List; -import java.util.function.Consumer; import static org.hamcrest.Matchers.containsString; import static org.mockito.ArgumentMatchers.any; @@ -79,14 +79,12 @@ class AiChatControllerTest { void chatStream_streamsUsageTokenDone() throws Exception { // Le use case mocke joue : usage -> 1 token -> fin. doAnswer(inv -> { - Consumer onUsage = inv.getArgument(3); - Consumer onToken = inv.getArgument(4); - Runnable onComplete = inv.getArgument(5); - onUsage.accept(new ChatUsage(10, 20, 30, 8000)); - onToken.accept("Bonjour"); - onComplete.run(); + ChatStreamCallbacks callbacks = inv.getArgument(3); + callbacks.onUsage().accept(new ChatUsage(10, 20, 30, 8000)); + callbacks.onToken().accept("Bonjour"); + callbacks.onComplete().run(); return null; - }).when(loreUseCase).execute(any(), any(), any(), any(), any(), any(), any()); + }).when(loreUseCase).execute(any(), any(), any(), any()); ChatStreamRequestDTO body = new ChatStreamRequestDTO(); body.setLoreId("lore-1"); @@ -104,8 +102,8 @@ class AiChatControllerTest { @Test void chatStream_passesDomainMessagesToUseCase() throws Exception { - doAnswer(inv -> { ((Runnable) inv.getArgument(5)).run(); return null; }) - .when(loreUseCase).execute(any(), any(), any(), any(), any(), any(), any()); + doAnswer(inv -> { ((ChatStreamCallbacks) inv.getArgument(3)).onComplete().run(); return null; }) + .when(loreUseCase).execute(any(), any(), any(), any()); ChatStreamRequestDTO body = new ChatStreamRequestDTO(); body.setLoreId("lore-1"); @@ -123,7 +121,7 @@ class AiChatControllerTest { verify(loreUseCase).execute( org.mockito.ArgumentMatchers.eq("lore-1"), org.mockito.ArgumentMatchers.eq("page-9"), - captor.capture(), any(), any(), any(), any()); + captor.capture(), any()); List passed = captor.getValue(); org.junit.jupiter.api.Assertions.assertEquals(2, passed.size()); org.junit.jupiter.api.Assertions.assertEquals("user", passed.get(0).role()); @@ -133,10 +131,10 @@ class AiChatControllerTest { @Test void chatStream_useCaseInvokesError_emitsError() throws Exception { doAnswer(inv -> { - Consumer onError = inv.getArgument(6); - onError.accept(new RuntimeException("brain HS")); + ChatStreamCallbacks callbacks = inv.getArgument(3); + callbacks.onError().accept(new RuntimeException("brain HS")); return null; - }).when(loreUseCase).execute(any(), any(), any(), any(), any(), any(), any()); + }).when(loreUseCase).execute(any(), any(), any(), any()); ChatStreamRequestDTO body = new ChatStreamRequestDTO(); body.setLoreId("lore-1"); @@ -152,7 +150,7 @@ class AiChatControllerTest { void chatStream_useCaseThrows_emitsError() throws Exception { // Lore introuvable -> le use case leve, le controller catch et fail(emitter). doThrow(new IllegalArgumentException("Lore introuvable")) - .when(loreUseCase).execute(any(), any(), any(), any(), any(), any(), any()); + .when(loreUseCase).execute(any(), any(), any(), any()); ChatStreamRequestDTO body = new ChatStreamRequestDTO(); body.setLoreId("missing"); @@ -168,12 +166,11 @@ class AiChatControllerTest { @Test void chatStreamCampaign_streamsTokenThenDone() throws Exception { doAnswer(inv -> { - Consumer onToken = inv.getArgument(5); - Runnable onComplete = inv.getArgument(6); - onToken.accept("Campagne"); - onComplete.run(); + ChatStreamCallbacks callbacks = inv.getArgument(4); + callbacks.onToken().accept("Campagne"); + callbacks.onComplete().run(); return null; - }).when(campaignUseCase).execute(any(), any(), any(), any(), any(), any(), any(), any()); + }).when(campaignUseCase).execute(any(), any(), any(), any(), any()); ChatStreamCampaignRequestDTO body = new ChatStreamCampaignRequestDTO(); body.setCampaignId("camp-1"); @@ -191,7 +188,7 @@ class AiChatControllerTest { @Test void chatStreamCampaign_useCaseThrows_emitsError() throws Exception { doThrow(new IllegalArgumentException("Campagne introuvable")) - .when(campaignUseCase).execute(any(), any(), any(), any(), any(), any(), any(), any()); + .when(campaignUseCase).execute(any(), any(), any(), any(), any()); ChatStreamCampaignRequestDTO body = new ChatStreamCampaignRequestDTO(); body.setCampaignId("missing"); @@ -207,12 +204,11 @@ class AiChatControllerTest { @Test void chatStreamSession_streamsTokenThenDone() throws Exception { doAnswer(inv -> { - Consumer onToken = inv.getArgument(3); - Runnable onComplete = inv.getArgument(4); - onToken.accept("Session"); - onComplete.run(); + ChatStreamCallbacks callbacks = inv.getArgument(2); + callbacks.onToken().accept("Session"); + callbacks.onComplete().run(); return null; - }).when(sessionUseCase).execute(any(), any(), any(), any(), any(), any()); + }).when(sessionUseCase).execute(any(), any(), any()); ChatStreamSessionRequestDTO body = new ChatStreamSessionRequestDTO(); body.setSessionId("sess-1"); @@ -228,7 +224,7 @@ class AiChatControllerTest { @Test void chatStreamSession_useCaseThrows_emitsError() throws Exception { doThrow(new IllegalArgumentException("Session introuvable")) - .when(sessionUseCase).execute(any(), any(), any(), any(), any(), any()); + .when(sessionUseCase).execute(any(), any(), any()); ChatStreamSessionRequestDTO body = new ChatStreamSessionRequestDTO(); body.setSessionId("missing"); diff --git a/core/src/test/java/com/loremind/infrastructure/web/controller/ArcControllerTest.java b/core/src/test/java/com/loremind/infrastructure/web/controller/ArcControllerTest.java index 1eca1b6..b12b6a7 100644 --- a/core/src/test/java/com/loremind/infrastructure/web/controller/ArcControllerTest.java +++ b/core/src/test/java/com/loremind/infrastructure/web/controller/ArcControllerTest.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.web.controller; import com.fasterxml.jackson.databind.ObjectMapper; -import com.loremind.domain.campaigncontext.Arc; +import com.loremind.domain.campaigncontext.structure.Arc; import com.loremind.domain.campaigncontext.Campaign; import com.loremind.domain.campaigncontext.ports.ArcRepository; import com.loremind.domain.campaigncontext.ports.CampaignRepository; diff --git a/core/src/test/java/com/loremind/infrastructure/web/controller/CampaignFlagControllerTest.java b/core/src/test/java/com/loremind/infrastructure/web/controller/CampaignFlagControllerTest.java index f4f73b9..51fb043 100644 --- a/core/src/test/java/com/loremind/infrastructure/web/controller/CampaignFlagControllerTest.java +++ b/core/src/test/java/com/loremind/infrastructure/web/controller/CampaignFlagControllerTest.java @@ -1,8 +1,8 @@ package com.loremind.infrastructure.web.controller; import com.loremind.domain.campaigncontext.Campaign; -import com.loremind.domain.campaigncontext.Prerequisite; -import com.loremind.domain.campaigncontext.Quest; +import com.loremind.domain.campaigncontext.quest.Prerequisite; +import com.loremind.domain.campaigncontext.quest.Quest; import com.loremind.domain.campaigncontext.ports.CampaignRepository; import com.loremind.domain.campaigncontext.ports.QuestRepository; import org.junit.jupiter.api.BeforeEach; diff --git a/core/src/test/java/com/loremind/infrastructure/web/controller/CampaignImportControllerTest.java b/core/src/test/java/com/loremind/infrastructure/web/controller/CampaignImportControllerTest.java index dbc3acf..b995fb8 100644 --- a/core/src/test/java/com/loremind/infrastructure/web/controller/CampaignImportControllerTest.java +++ b/core/src/test/java/com/loremind/infrastructure/web/controller/CampaignImportControllerTest.java @@ -3,8 +3,8 @@ package com.loremind.infrastructure.web.controller; import com.fasterxml.jackson.databind.ObjectMapper; import com.loremind.application.campaigncontext.CampaignImportService; import com.loremind.application.campaigncontext.CampaignImportService.ApplyResult; -import com.loremind.domain.campaigncontext.CampaignImportProgress; -import com.loremind.domain.campaigncontext.CampaignImportProposal; +import com.loremind.domain.campaigncontext.generation.CampaignImportProgress; +import com.loremind.domain.campaigncontext.generation.CampaignImportProposal; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/core/src/test/java/com/loremind/infrastructure/web/controller/CampaignImportControllerUnitTest.java b/core/src/test/java/com/loremind/infrastructure/web/controller/CampaignImportControllerUnitTest.java index 3e96a6f..34c827f 100644 --- a/core/src/test/java/com/loremind/infrastructure/web/controller/CampaignImportControllerUnitTest.java +++ b/core/src/test/java/com/loremind/infrastructure/web/controller/CampaignImportControllerUnitTest.java @@ -2,8 +2,8 @@ package com.loremind.infrastructure.web.controller; import com.fasterxml.jackson.databind.ObjectMapper; import com.loremind.application.campaigncontext.CampaignImportService; -import com.loremind.domain.campaigncontext.CampaignImportProgress; -import com.loremind.domain.campaigncontext.CampaignImportProposal; +import com.loremind.domain.campaigncontext.generation.CampaignImportProgress; +import com.loremind.domain.campaigncontext.generation.CampaignImportProposal; import org.junit.jupiter.api.Test; import org.springframework.core.task.TaskExecutor; import org.springframework.mock.web.MockMultipartFile; diff --git a/core/src/test/java/com/loremind/infrastructure/web/controller/CampaignTreeControllerTest.java b/core/src/test/java/com/loremind/infrastructure/web/controller/CampaignTreeControllerTest.java index e945ca6..0b8f0aa 100644 --- a/core/src/test/java/com/loremind/infrastructure/web/controller/CampaignTreeControllerTest.java +++ b/core/src/test/java/com/loremind/infrastructure/web/controller/CampaignTreeControllerTest.java @@ -1,11 +1,11 @@ package com.loremind.infrastructure.web.controller; -import com.loremind.domain.campaigncontext.Arc; -import com.loremind.domain.campaigncontext.ArcType; +import com.loremind.domain.campaigncontext.structure.Arc; +import com.loremind.domain.campaigncontext.structure.ArcType; import com.loremind.domain.campaigncontext.Campaign; -import com.loremind.domain.campaigncontext.Chapter; -import com.loremind.domain.campaigncontext.Quest; -import com.loremind.domain.campaigncontext.Scene; +import com.loremind.domain.campaigncontext.structure.Chapter; +import com.loremind.domain.campaigncontext.quest.Quest; +import com.loremind.domain.campaigncontext.structure.Scene; import com.loremind.domain.campaigncontext.ports.ArcRepository; import com.loremind.domain.campaigncontext.ports.CampaignRepository; import com.loremind.domain.campaigncontext.ports.ChapterRepository; diff --git a/core/src/test/java/com/loremind/infrastructure/web/controller/ChapterControllerTest.java b/core/src/test/java/com/loremind/infrastructure/web/controller/ChapterControllerTest.java index 78e1236..15e043f 100644 --- a/core/src/test/java/com/loremind/infrastructure/web/controller/ChapterControllerTest.java +++ b/core/src/test/java/com/loremind/infrastructure/web/controller/ChapterControllerTest.java @@ -1,9 +1,9 @@ package com.loremind.infrastructure.web.controller; import com.fasterxml.jackson.databind.ObjectMapper; -import com.loremind.domain.campaigncontext.Arc; +import com.loremind.domain.campaigncontext.structure.Arc; import com.loremind.domain.campaigncontext.Campaign; -import com.loremind.domain.campaigncontext.Chapter; +import com.loremind.domain.campaigncontext.structure.Chapter; import com.loremind.domain.campaigncontext.ports.ArcRepository; import com.loremind.domain.campaigncontext.ports.CampaignRepository; import com.loremind.domain.campaigncontext.ports.ChapterRepository; diff --git a/core/src/test/java/com/loremind/infrastructure/web/controller/CharacterControllerTest.java b/core/src/test/java/com/loremind/infrastructure/web/controller/CharacterControllerTest.java index 137b2b1..ada78f5 100644 --- a/core/src/test/java/com/loremind/infrastructure/web/controller/CharacterControllerTest.java +++ b/core/src/test/java/com/loremind/infrastructure/web/controller/CharacterControllerTest.java @@ -2,7 +2,7 @@ package com.loremind.infrastructure.web.controller; import com.fasterxml.jackson.databind.ObjectMapper; import com.loremind.domain.campaigncontext.Campaign; -import com.loremind.domain.campaigncontext.Character; +import com.loremind.domain.campaigncontext.bestiary.Character; import com.loremind.domain.campaigncontext.ports.CampaignRepository; import com.loremind.domain.campaigncontext.ports.CharacterRepository; import com.loremind.domain.playcontext.Playthrough; diff --git a/core/src/test/java/com/loremind/infrastructure/web/controller/EnemyControllerTest.java b/core/src/test/java/com/loremind/infrastructure/web/controller/EnemyControllerTest.java index bc51b38..fbfc620 100644 --- a/core/src/test/java/com/loremind/infrastructure/web/controller/EnemyControllerTest.java +++ b/core/src/test/java/com/loremind/infrastructure/web/controller/EnemyControllerTest.java @@ -2,7 +2,7 @@ package com.loremind.infrastructure.web.controller; import com.fasterxml.jackson.databind.ObjectMapper; import com.loremind.domain.campaigncontext.Campaign; -import com.loremind.domain.campaigncontext.Enemy; +import com.loremind.domain.campaigncontext.bestiary.Enemy; import com.loremind.domain.campaigncontext.ports.CampaignRepository; import com.loremind.domain.campaigncontext.ports.EnemyRepository; import org.junit.jupiter.api.BeforeEach; diff --git a/core/src/test/java/com/loremind/infrastructure/web/controller/GameSystemControllerTest.java b/core/src/test/java/com/loremind/infrastructure/web/controller/GameSystemControllerTest.java index b477b85..6d929a1 100644 --- a/core/src/test/java/com/loremind/infrastructure/web/controller/GameSystemControllerTest.java +++ b/core/src/test/java/com/loremind/infrastructure/web/controller/GameSystemControllerTest.java @@ -3,7 +3,7 @@ package com.loremind.infrastructure.web.controller; import com.fasterxml.jackson.databind.ObjectMapper; import com.loremind.domain.gamesystemcontext.RulesImportProgress; import com.loremind.domain.gamesystemcontext.RulesImportResult; -import com.loremind.domain.gamesystemcontext.ports.RulesImportException; +import com.loremind.domain.gamesystemcontext.ports.exceptions.RulesImportException; import com.loremind.domain.gamesystemcontext.ports.RulesPdfImporter; import com.loremind.infrastructure.web.dto.gamesystemcontext.GameSystemDTO; import com.loremind.infrastructure.web.dto.shared.TemplateFieldDTO; diff --git a/core/src/test/java/com/loremind/infrastructure/web/controller/ItemCatalogControllerTest.java b/core/src/test/java/com/loremind/infrastructure/web/controller/ItemCatalogControllerTest.java index 09f083d..4abe69a 100644 --- a/core/src/test/java/com/loremind/infrastructure/web/controller/ItemCatalogControllerTest.java +++ b/core/src/test/java/com/loremind/infrastructure/web/controller/ItemCatalogControllerTest.java @@ -2,10 +2,10 @@ package com.loremind.infrastructure.web.controller; import com.fasterxml.jackson.databind.ObjectMapper; import com.loremind.domain.campaigncontext.Campaign; -import com.loremind.domain.campaigncontext.CatalogItem; -import com.loremind.domain.campaigncontext.ItemCatalog; +import com.loremind.domain.campaigncontext.itemcatalog.CatalogItem; +import com.loremind.domain.campaigncontext.itemcatalog.ItemCatalog; import com.loremind.domain.campaigncontext.ports.CampaignRepository; -import com.loremind.domain.campaigncontext.ports.ItemCatalogGenerationException; +import com.loremind.domain.campaigncontext.ports.exceptions.ItemCatalogGenerationException; import com.loremind.domain.campaigncontext.ports.ItemCatalogGenerator; import com.loremind.domain.campaigncontext.ports.ItemCatalogRepository; import com.loremind.infrastructure.web.controller.ItemCatalogController.GenerateRequest; diff --git a/core/src/test/java/com/loremind/infrastructure/web/controller/NotebookControllerTest.java b/core/src/test/java/com/loremind/infrastructure/web/controller/NotebookControllerTest.java index 25e9230..9a69b2e 100644 --- a/core/src/test/java/com/loremind/infrastructure/web/controller/NotebookControllerTest.java +++ b/core/src/test/java/com/loremind/infrastructure/web/controller/NotebookControllerTest.java @@ -2,11 +2,11 @@ package com.loremind.infrastructure.web.controller; import com.fasterxml.jackson.databind.ObjectMapper; import com.loremind.domain.campaigncontext.Campaign; -import com.loremind.domain.campaigncontext.Notebook; -import com.loremind.domain.campaigncontext.NotebookSource; +import com.loremind.domain.campaigncontext.notebook.Notebook; +import com.loremind.domain.campaigncontext.notebook.NotebookSource; import com.loremind.domain.campaigncontext.ports.CampaignRepository; import com.loremind.domain.campaigncontext.ports.NotebookChatStreamer; -import com.loremind.domain.campaigncontext.ports.NotebookException; +import com.loremind.domain.campaigncontext.ports.exceptions.NotebookException; import com.loremind.domain.campaigncontext.ports.NotebookIndexer; import com.loremind.domain.campaigncontext.ports.NotebookRepository; import org.junit.jupiter.api.BeforeEach; @@ -216,13 +216,11 @@ class NotebookControllerTest { Notebook nb = persistNotebook(); // Le streamer mocke joue : 1 token puis fin. doAnswer(inv -> { - java.util.function.Consumer onToken = inv.getArgument(5); - Runnable onDone = inv.getArgument(7); - onToken.accept("Bonjour"); - onDone.run(); + NotebookChatStreamer.Callbacks callbacks = inv.getArgument(4); + callbacks.onToken().accept("Bonjour"); + callbacks.onDone().run(); return null; - }).when(chatStreamer).stream(any(), any(), any(), org.mockito.ArgumentMatchers.anyBoolean(), - any(), any(), any(), any(), any()); + }).when(chatStreamer).stream(any(), any(), any(), org.mockito.ArgumentMatchers.anyBoolean(), any()); var req = new NotebookController.ChatRequest("Salut ?", false, null, null); MvcResult result = mockMvc.perform(post("/api/notebooks/{id}/chat/stream", nb.getId()) @@ -261,11 +259,10 @@ class NotebookControllerTest { void chatStream_streamerError_emitsError() throws Exception { Notebook nb = persistNotebook(); doAnswer(inv -> { - java.util.function.Consumer onError = inv.getArgument(8); - onError.accept(new RuntimeException("boom")); + NotebookChatStreamer.Callbacks callbacks = inv.getArgument(4); + callbacks.onError().accept(new RuntimeException("boom")); return null; - }).when(chatStreamer).stream(any(), any(), any(), org.mockito.ArgumentMatchers.anyBoolean(), - any(), any(), any(), any(), any()); + }).when(chatStreamer).stream(any(), any(), any(), org.mockito.ArgumentMatchers.anyBoolean(), any()); var req = new NotebookController.ChatRequest("Salut ?", false, null, null); MvcResult result = mockMvc.perform(post("/api/notebooks/{id}/chat/stream", nb.getId()) @@ -297,17 +294,13 @@ class NotebookControllerTest { // Le streamer mocke joue, en mode approfondi : sources -> progress -> token -> fin. doAnswer(inv -> { - java.util.function.Consumer onSourcesJson = inv.getArgument(4); - java.util.function.Consumer onToken = inv.getArgument(5); - java.util.function.Consumer onProgress = inv.getArgument(6); - Runnable onDone = inv.getArgument(7); - onSourcesJson.accept("{\"sources\":[{\"source_id\":\"" + src.getId() + "\",\"page\":1}]}"); - onProgress.accept(new NotebookChatStreamer.Progress(1, 3)); - onToken.accept("Reponse"); - onDone.run(); + NotebookChatStreamer.Callbacks callbacks = inv.getArgument(4); + callbacks.onSourcesJson().accept("{\"sources\":[{\"source_id\":\"" + src.getId() + "\",\"page\":1}]}"); + callbacks.onProgress().accept(new NotebookChatStreamer.Progress(1, 3)); + callbacks.onToken().accept("Reponse"); + callbacks.onDone().run(); return null; - }).when(chatStreamer).stream(any(), any(), any(), org.mockito.ArgumentMatchers.anyBoolean(), - any(), any(), any(), any(), any()); + }).when(chatStreamer).stream(any(), any(), any(), org.mockito.ArgumentMatchers.anyBoolean(), any()); // deep=true + sourceIds (filtrage) + archiveIds non nuls (branche buildArchiveContext). var req = new NotebookController.ChatRequest( diff --git a/core/src/test/java/com/loremind/infrastructure/web/controller/NpcControllerTest.java b/core/src/test/java/com/loremind/infrastructure/web/controller/NpcControllerTest.java index 4dd2390..9c51591 100644 --- a/core/src/test/java/com/loremind/infrastructure/web/controller/NpcControllerTest.java +++ b/core/src/test/java/com/loremind/infrastructure/web/controller/NpcControllerTest.java @@ -2,7 +2,7 @@ package com.loremind.infrastructure.web.controller; import com.fasterxml.jackson.databind.ObjectMapper; import com.loremind.domain.campaigncontext.Campaign; -import com.loremind.domain.campaigncontext.Npc; +import com.loremind.domain.campaigncontext.bestiary.Npc; import com.loremind.domain.campaigncontext.ports.CampaignRepository; import com.loremind.domain.campaigncontext.ports.NpcRepository; import com.loremind.infrastructure.web.dto.campaigncontext.NpcDTO; diff --git a/core/src/test/java/com/loremind/infrastructure/web/controller/PageGenerationControllerTest.java b/core/src/test/java/com/loremind/infrastructure/web/controller/PageGenerationControllerTest.java index e1916e8..ccdfa53 100644 --- a/core/src/test/java/com/loremind/infrastructure/web/controller/PageGenerationControllerTest.java +++ b/core/src/test/java/com/loremind/infrastructure/web/controller/PageGenerationControllerTest.java @@ -1,7 +1,7 @@ package com.loremind.infrastructure.web.controller; import com.loremind.application.generationcontext.GeneratePageValuesUseCase; -import com.loremind.domain.generationcontext.ports.AiProviderException; +import com.loremind.domain.generationcontext.ports.exceptions.AiProviderException; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; diff --git a/core/src/test/java/com/loremind/infrastructure/web/controller/QuestControllerTest.java b/core/src/test/java/com/loremind/infrastructure/web/controller/QuestControllerTest.java index 17809d6..a95a496 100644 --- a/core/src/test/java/com/loremind/infrastructure/web/controller/QuestControllerTest.java +++ b/core/src/test/java/com/loremind/infrastructure/web/controller/QuestControllerTest.java @@ -2,7 +2,7 @@ package com.loremind.infrastructure.web.controller; import com.fasterxml.jackson.databind.ObjectMapper; import com.loremind.domain.campaigncontext.Campaign; -import com.loremind.domain.campaigncontext.Quest; +import com.loremind.domain.campaigncontext.quest.Quest; import com.loremind.domain.campaigncontext.ports.CampaignRepository; import com.loremind.domain.campaigncontext.ports.QuestRepository; import com.loremind.domain.playcontext.Playthrough; diff --git a/core/src/test/java/com/loremind/infrastructure/web/controller/QuestProgressionControllerTest.java b/core/src/test/java/com/loremind/infrastructure/web/controller/QuestProgressionControllerTest.java index fe99e4a..45159e2 100644 --- a/core/src/test/java/com/loremind/infrastructure/web/controller/QuestProgressionControllerTest.java +++ b/core/src/test/java/com/loremind/infrastructure/web/controller/QuestProgressionControllerTest.java @@ -1,10 +1,10 @@ package com.loremind.infrastructure.web.controller; import com.fasterxml.jackson.databind.ObjectMapper; -import com.loremind.domain.campaigncontext.Arc; +import com.loremind.domain.campaigncontext.structure.Arc; import com.loremind.domain.campaigncontext.Campaign; -import com.loremind.domain.campaigncontext.Chapter; -import com.loremind.domain.campaigncontext.ProgressionStatus; +import com.loremind.domain.campaigncontext.structure.Chapter; +import com.loremind.domain.campaigncontext.quest.ProgressionStatus; import com.loremind.domain.campaigncontext.ports.ArcRepository; import com.loremind.domain.campaigncontext.ports.CampaignRepository; import com.loremind.domain.campaigncontext.ports.ChapterRepository; diff --git a/core/src/test/java/com/loremind/infrastructure/web/controller/RandomTableControllerTest.java b/core/src/test/java/com/loremind/infrastructure/web/controller/RandomTableControllerTest.java index 8d2960e..1ee45a6 100644 --- a/core/src/test/java/com/loremind/infrastructure/web/controller/RandomTableControllerTest.java +++ b/core/src/test/java/com/loremind/infrastructure/web/controller/RandomTableControllerTest.java @@ -2,10 +2,10 @@ package com.loremind.infrastructure.web.controller; import com.fasterxml.jackson.databind.ObjectMapper; import com.loremind.domain.campaigncontext.Campaign; -import com.loremind.domain.campaigncontext.RandomTable; -import com.loremind.domain.campaigncontext.RandomTableEntry; +import com.loremind.domain.campaigncontext.randomtable.RandomTable; +import com.loremind.domain.campaigncontext.randomtable.RandomTableEntry; import com.loremind.domain.campaigncontext.ports.CampaignRepository; -import com.loremind.domain.campaigncontext.ports.RandomTableGenerationException; +import com.loremind.domain.campaigncontext.ports.exceptions.RandomTableGenerationException; import com.loremind.domain.campaigncontext.ports.RandomTableGenerator; import com.loremind.domain.campaigncontext.ports.RandomTableRepository; import com.loremind.infrastructure.web.controller.RandomTableController.GenerateRequest; diff --git a/core/src/test/java/com/loremind/infrastructure/web/controller/SceneControllerTest.java b/core/src/test/java/com/loremind/infrastructure/web/controller/SceneControllerTest.java index 12e9c84..daca1c8 100644 --- a/core/src/test/java/com/loremind/infrastructure/web/controller/SceneControllerTest.java +++ b/core/src/test/java/com/loremind/infrastructure/web/controller/SceneControllerTest.java @@ -1,10 +1,10 @@ package com.loremind.infrastructure.web.controller; import com.fasterxml.jackson.databind.ObjectMapper; -import com.loremind.domain.campaigncontext.Arc; +import com.loremind.domain.campaigncontext.structure.Arc; import com.loremind.domain.campaigncontext.Campaign; -import com.loremind.domain.campaigncontext.Chapter; -import com.loremind.domain.campaigncontext.Scene; +import com.loremind.domain.campaigncontext.structure.Chapter; +import com.loremind.domain.campaigncontext.structure.Scene; import com.loremind.domain.campaigncontext.ports.ArcRepository; import com.loremind.domain.campaigncontext.ports.CampaignRepository; import com.loremind.domain.campaigncontext.ports.ChapterRepository;