diff --git a/src/main/java/com/wooteco/wiki/graph/repository/CrewGraphNodeReadModel.java b/src/main/java/com/wooteco/wiki/graph/repository/CrewGraphNodeReadModel.java new file mode 100644 index 0000000..af67a92 --- /dev/null +++ b/src/main/java/com/wooteco/wiki/graph/repository/CrewGraphNodeReadModel.java @@ -0,0 +1,9 @@ +package com.wooteco.wiki.graph.repository; + +import java.util.UUID; + +public record CrewGraphNodeReadModel( + UUID documentUuid, + String title +) { +} diff --git a/src/main/java/com/wooteco/wiki/graph/repository/CrewGraphQueryRepository.java b/src/main/java/com/wooteco/wiki/graph/repository/CrewGraphQueryRepository.java index fd9b8c6..23576e9 100644 --- a/src/main/java/com/wooteco/wiki/graph/repository/CrewGraphQueryRepository.java +++ b/src/main/java/com/wooteco/wiki/graph/repository/CrewGraphQueryRepository.java @@ -5,6 +5,8 @@ public interface CrewGraphQueryRepository { + List findAllGraphNodesByGenerationTitle(String generationTitle); + List findAllCrewDocumentsByGenerationTitle(String generationTitle); List findAllCrewDocumentUuidsByGenerationTitleAndOrganizationDocumentUuid( diff --git a/src/main/java/com/wooteco/wiki/graph/repository/DocumentReferenceReadModel.java b/src/main/java/com/wooteco/wiki/graph/repository/DocumentReferenceReadModel.java new file mode 100644 index 0000000..9ff9a8f --- /dev/null +++ b/src/main/java/com/wooteco/wiki/graph/repository/DocumentReferenceReadModel.java @@ -0,0 +1,9 @@ +package com.wooteco.wiki.graph.repository; + +import java.util.UUID; + +public record DocumentReferenceReadModel( + UUID sourceDocumentUuid, + UUID targetDocumentUuid +) { +} diff --git a/src/main/java/com/wooteco/wiki/graph/repository/DocumentReferenceRepository.java b/src/main/java/com/wooteco/wiki/graph/repository/DocumentReferenceRepository.java index 7a30b29..97572c3 100644 --- a/src/main/java/com/wooteco/wiki/graph/repository/DocumentReferenceRepository.java +++ b/src/main/java/com/wooteco/wiki/graph/repository/DocumentReferenceRepository.java @@ -5,11 +5,36 @@ import com.wooteco.wiki.graph.domain.DocumentReference; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface DocumentReferenceRepository extends JpaRepository { List findAllBySourceDocument(CrewDocument sourceDocument); + @Query(""" + SELECT new com.wooteco.wiki.graph.repository.DocumentReferenceReadModel( + documentReference.sourceDocument.uuid, + documentReference.targetDocument.uuid + ) + FROM DocumentReference documentReference + WHERE documentReference.sourceDocument IN ( + SELECT sourceGenerationLink.crewDocument + FROM DocumentOrganizationLink sourceGenerationLink + WHERE sourceGenerationLink.organizationDocument.title = :generationTitle + ) + AND documentReference.targetDocument IN ( + SELECT targetGenerationLink.crewDocument + FROM DocumentOrganizationLink targetGenerationLink + WHERE targetGenerationLink.organizationDocument.title = :generationTitle + ) + AND TYPE(documentReference.targetDocument) = CrewDocument + ORDER BY documentReference.sourceDocument.uuid, documentReference.targetDocument.uuid + """) + List findAllReadModelsByGenerationTitle( + @Param("generationTitle") String generationTitle + ); + void deleteAllBySourceDocument(CrewDocument sourceDocument); void deleteAllByTargetDocument(Document targetDocument); diff --git a/src/main/java/com/wooteco/wiki/organizationdocument/repository/DocumentOrganizationLinkRepository.java b/src/main/java/com/wooteco/wiki/organizationdocument/repository/DocumentOrganizationLinkRepository.java index b1d34e0..43f2137 100644 --- a/src/main/java/com/wooteco/wiki/organizationdocument/repository/DocumentOrganizationLinkRepository.java +++ b/src/main/java/com/wooteco/wiki/organizationdocument/repository/DocumentOrganizationLinkRepository.java @@ -3,6 +3,7 @@ import com.wooteco.wiki.document.domain.CrewDocument; import com.wooteco.wiki.document.repository.GenerationCrewOrganizationReadModel; import com.wooteco.wiki.document.repository.GenerationCrewQueryRepository; +import com.wooteco.wiki.graph.repository.CrewGraphNodeReadModel; import com.wooteco.wiki.graph.repository.CrewGraphQueryRepository; import com.wooteco.wiki.graph.repository.CrewGraphReadModel; import com.wooteco.wiki.organizationdocument.domain.DocumentOrganizationLink; @@ -57,6 +58,21 @@ List findAllByGenerationTitle( @Param("generationTitle") String generationTitle ); + @Override + @Query(""" + SELECT new com.wooteco.wiki.graph.repository.CrewGraphNodeReadModel( + crewDocument.uuid, + crewDocument.title + ) + FROM DocumentOrganizationLink documentOrganizationLink + JOIN documentOrganizationLink.crewDocument crewDocument + WHERE documentOrganizationLink.organizationDocument.title = :generationTitle + ORDER BY crewDocument.title + """) + List findAllGraphNodesByGenerationTitle( + @Param("generationTitle") String generationTitle + ); + @Override @Query(""" SELECT new com.wooteco.wiki.graph.repository.CrewGraphReadModel( diff --git a/src/test/java/com/wooteco/wiki/graph/repository/CrewGraphQueryRepositoryTest.java b/src/test/java/com/wooteco/wiki/graph/repository/CrewGraphQueryRepositoryTest.java index 0d2277b..77c2242 100644 --- a/src/test/java/com/wooteco/wiki/graph/repository/CrewGraphQueryRepositoryTest.java +++ b/src/test/java/com/wooteco/wiki/graph/repository/CrewGraphQueryRepositoryTest.java @@ -1,6 +1,7 @@ package com.wooteco.wiki.graph.repository; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; import com.wooteco.wiki.document.domain.CrewDocument; import com.wooteco.wiki.document.fixture.CrewDocumentFixture; @@ -34,6 +35,38 @@ class CrewGraphQueryRepositoryTest { @Autowired private DocumentOrganizationLinkRepository documentOrganizationLinkRepository; + @Nested + @DisplayName("기수에 연결된 그래프 노드를 조회할 때") + class FindAllGraphNodesByGenerationTitle { + + @Test + @DisplayName("해당 기수의 크루 문서만 title 순으로 반환한다.") + void findAllGraphNodesByGenerationTitle_success_byCrewDocumentsInGenerationSortedByTitle() { + // given + OrganizationDocument eighthGeneration = saveOrganizationDocument("8기"); + OrganizationDocument seventhGeneration = saveOrganizationDocument("7기"); + CrewDocument firstEighthCrew = saveCrewDocument("가람(8기)"); + CrewDocument secondEighthCrew = saveCrewDocument("나래(8기)"); + CrewDocument seventhCrew = saveCrewDocument("다온(7기)"); + saveCrewDocument("라온(미연결)"); + saveLink(firstEighthCrew, eighthGeneration); + saveLink(secondEighthCrew, eighthGeneration); + saveLink(seventhCrew, seventhGeneration); + + // when + List nodes = crewGraphQueryRepository + .findAllGraphNodesByGenerationTitle("8기"); + + // then + assertThat(nodes) + .extracting(CrewGraphNodeReadModel::documentUuid, CrewGraphNodeReadModel::title) + .containsExactly( + tuple(firstEighthCrew.getUuid(), "가람(8기)"), + tuple(secondEighthCrew.getUuid(), "나래(8기)") + ); + } + } + @Nested @DisplayName("기수와 조직에 모두 연결된 크루 문서 UUID를 조회할 때") class FindAllCrewDocumentUuidsByGenerationTitleAndOrganizationDocumentUuid { diff --git a/src/test/java/com/wooteco/wiki/graph/repository/DocumentReferenceRepositoryTest.java b/src/test/java/com/wooteco/wiki/graph/repository/DocumentReferenceRepositoryTest.java index 67b0d4b..9ab8b9e 100644 --- a/src/test/java/com/wooteco/wiki/graph/repository/DocumentReferenceRepositoryTest.java +++ b/src/test/java/com/wooteco/wiki/graph/repository/DocumentReferenceRepositoryTest.java @@ -1,7 +1,9 @@ package com.wooteco.wiki.graph.repository; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.assertj.core.groups.Tuple.tuple; import com.wooteco.wiki.document.domain.CrewDocument; import com.wooteco.wiki.document.domain.Document; @@ -11,8 +13,11 @@ import com.wooteco.wiki.document.repository.DocumentRepository; import com.wooteco.wiki.graph.domain.DocumentReference; import com.wooteco.wiki.graph.fixture.DocumentReferenceFixture; +import com.wooteco.wiki.organizationdocument.domain.DocumentOrganizationLink; import com.wooteco.wiki.organizationdocument.domain.OrganizationDocument; +import com.wooteco.wiki.organizationdocument.fixture.DocumentOrganizationLinkFixture; import com.wooteco.wiki.organizationdocument.fixture.OrganizationDocumentFixture; +import com.wooteco.wiki.organizationdocument.repository.DocumentOrganizationLinkRepository; import com.wooteco.wiki.organizationdocument.repository.OrganizationDocumentRepository; import jakarta.persistence.EntityManager; import java.util.List; @@ -35,6 +40,9 @@ class DocumentReferenceRepositoryTest { @Autowired private OrganizationDocumentRepository organizationDocumentRepository; + @Autowired + private DocumentOrganizationLinkRepository documentOrganizationLinkRepository; + @Autowired private DocumentRepository documentRepository; @@ -183,6 +191,75 @@ void findAllBySourceDocument_success_byExistingReferences() { } } + @Nested + class FindAllReadModelsByGenerationTitle { + + @Test + void findAllReadModelsByGenerationTitle_success_byDirectionalReferencesSortedByUuid() { + // given + OrganizationDocument generation = saveOrganizationDocument("8기"); + UUID firstSourceUuid = UUID.fromString("00000000-0000-0000-0000-000000000001"); + UUID secondSourceUuid = UUID.fromString("00000000-0000-0000-0000-000000000002"); + CrewDocument firstSourceDocument = saveCrewDocument("first-source", firstSourceUuid); + CrewDocument secondSourceDocument = saveCrewDocument("second-source", secondSourceUuid); + saveLink(firstSourceDocument, generation); + saveLink(secondSourceDocument, generation); + saveAndFlushReference(firstSourceDocument, secondSourceDocument); + saveAndFlushReference(secondSourceDocument, firstSourceDocument); + + // when + List readModels = documentReferenceRepository + .findAllReadModelsByGenerationTitle("8기"); + + // then + assertThat(readModels) + .extracting( + DocumentReferenceReadModel::sourceDocumentUuid, + DocumentReferenceReadModel::targetDocumentUuid + ) + .containsExactly( + tuple(firstSourceUuid, secondSourceUuid), + tuple(secondSourceUuid, firstSourceUuid) + ); + } + + @Test + void findAllReadModelsByGenerationTitle_success_bySameGenerationCrewMembership() { + // given + OrganizationDocument eighthGeneration = saveOrganizationDocument("8기"); + OrganizationDocument seventhGeneration = saveOrganizationDocument("7기"); + CrewDocument sourceDocument = saveCrewDocument("source"); + CrewDocument otherGenerationSourceDocument = saveCrewDocument("other-generation-source"); + CrewDocument sameGenerationTargetDocument = saveCrewDocument("same-generation-target"); + CrewDocument otherGenerationTargetDocument = saveCrewDocument("other-generation-target"); + CrewDocument unlinkedTargetDocument = saveCrewDocument("unlinked-target"); + OrganizationDocument organizationTargetDocument = saveOrganizationDocument("organization-target"); + saveLink(sourceDocument, eighthGeneration); + saveLink(otherGenerationSourceDocument, seventhGeneration); + saveLink(sameGenerationTargetDocument, eighthGeneration); + saveLink(otherGenerationTargetDocument, seventhGeneration); + saveAndFlushReference(sourceDocument, sameGenerationTargetDocument); + saveAndFlushReference(sourceDocument, otherGenerationTargetDocument); + saveAndFlushReference(otherGenerationSourceDocument, sameGenerationTargetDocument); + saveAndFlushReference(sourceDocument, unlinkedTargetDocument); + saveAndFlushReference(sourceDocument, organizationTargetDocument); + + // when + List readModels = documentReferenceRepository + .findAllReadModelsByGenerationTitle("8기"); + + // then + assertThat(readModels) + .extracting( + DocumentReferenceReadModel::sourceDocumentUuid, + DocumentReferenceReadModel::targetDocumentUuid + ) + .containsExactly( + tuple(sourceDocument.getUuid(), sameGenerationTargetDocument.getUuid()) + ); + } + } + @Nested class DeleteAllBySourceDocument { @@ -270,12 +347,16 @@ void delete_success_withoutDocumentCascade() { } private CrewDocument saveCrewDocument(String title) { + return saveCrewDocument(title, UUID.randomUUID()); + } + + private CrewDocument saveCrewDocument(String title, UUID uuid) { CrewDocument crewDocument = CrewDocumentFixture.createCrewDocument( title, "contents", "writer", 10L, - UUID.randomUUID() + uuid ); return crewDocumentRepository.save(crewDocument); } @@ -291,6 +372,17 @@ private OrganizationDocument saveOrganizationDocument(String title) { return organizationDocumentRepository.save(organizationDocument); } + private void saveLink( + CrewDocument crewDocument, + OrganizationDocument organizationDocument + ) { + DocumentOrganizationLink link = DocumentOrganizationLinkFixture.create( + crewDocument, + organizationDocument + ); + documentOrganizationLinkRepository.save(link); + } + private DocumentReference saveReference( CrewDocument sourceDocument, Document targetDocument