From df92752b3254fa4f7d8bc51e21543ea558d8be85 Mon Sep 17 00:00:00 2001 From: java-saeng Date: Wed, 2 Aug 2023 16:49:44 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat=20:=20=EC=BB=A4=EB=A6=AC=ED=81=98?= =?UTF-8?q?=EB=9F=BC=20ID=EB=A1=9C=20=ED=82=A4=EC=9B=8C=EB=93=9C=EB=93=A4?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #1461 --- .../roadmap/application/RoadMapService.java | 41 ++++++++++++ .../application/dto/KeywordsResponse.java | 8 +++ .../domain/repository/KeywordRepository.java | 3 + .../prolog/roadmap/ui/RoadmapController.java | 20 ++++++ .../application/RoadMapServiceTest.java | 67 +++++++++++++++++++ .../repository/KeywordRepositoryTest.java | 57 +++++++++++++++- 6 files changed, 194 insertions(+), 2 deletions(-) create mode 100644 backend/src/main/java/wooteco/prolog/roadmap/application/RoadMapService.java create mode 100644 backend/src/main/java/wooteco/prolog/roadmap/ui/RoadmapController.java create mode 100644 backend/src/test/java/wooteco/prolog/roadmap/application/RoadMapServiceTest.java diff --git a/backend/src/main/java/wooteco/prolog/roadmap/application/RoadMapService.java b/backend/src/main/java/wooteco/prolog/roadmap/application/RoadMapService.java new file mode 100644 index 000000000..a8f1b30c1 --- /dev/null +++ b/backend/src/main/java/wooteco/prolog/roadmap/application/RoadMapService.java @@ -0,0 +1,41 @@ +package wooteco.prolog.roadmap.application; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import wooteco.prolog.roadmap.application.dto.KeywordsResponse; +import wooteco.prolog.roadmap.domain.Curriculum; +import wooteco.prolog.roadmap.domain.Keyword; +import wooteco.prolog.roadmap.domain.repository.CurriculumRepository; +import wooteco.prolog.roadmap.domain.repository.KeywordRepository; +import wooteco.prolog.session.domain.Session; +import wooteco.prolog.session.domain.repository.SessionRepository; + +@RequiredArgsConstructor +@Transactional +@Service +public class RoadMapService { + + private final CurriculumRepository curriculumRepository; + private final SessionRepository sessionRepository; + private final KeywordRepository keywordRepository; + + @Transactional(readOnly = true) + public KeywordsResponse findAllKeywords(final Long curriculumId) { + final Curriculum curriculum = curriculumRepository.findById(curriculumId) + .orElseThrow(() -> new IllegalArgumentException( + "해당 커리큘럼이 존재하지 않습니다. curriculumId = " + curriculumId)); + + final Set sessionIds = sessionRepository.findAllByCurriculumId(curriculum.getId()) + .stream() + .map(Session::getId) + .collect(Collectors.toSet()); + + final List keywords = keywordRepository.findBySessionIdIn(sessionIds); + + return KeywordsResponse.createResponseWithChildren(keywords); + } +} diff --git a/backend/src/main/java/wooteco/prolog/roadmap/application/dto/KeywordsResponse.java b/backend/src/main/java/wooteco/prolog/roadmap/application/dto/KeywordsResponse.java index aee3cd48b..bbd9c3018 100644 --- a/backend/src/main/java/wooteco/prolog/roadmap/application/dto/KeywordsResponse.java +++ b/backend/src/main/java/wooteco/prolog/roadmap/application/dto/KeywordsResponse.java @@ -23,4 +23,12 @@ public static KeywordsResponse createResponse(final List keywords) { .collect(Collectors.toList()); return new KeywordsResponse(keywordsResponse); } + + public static KeywordsResponse createResponseWithChildren(final List keywords) { + List keywordsResponse = keywords.stream() + .filter(it -> it.getParent() == null) + .map(KeywordResponse::createWithAllChildResponse) + .collect(Collectors.toList()); + return new KeywordsResponse(keywordsResponse); + } } diff --git a/backend/src/main/java/wooteco/prolog/roadmap/domain/repository/KeywordRepository.java b/backend/src/main/java/wooteco/prolog/roadmap/domain/repository/KeywordRepository.java index 3ef198afc..d15d4e7ae 100644 --- a/backend/src/main/java/wooteco/prolog/roadmap/domain/repository/KeywordRepository.java +++ b/backend/src/main/java/wooteco/prolog/roadmap/domain/repository/KeywordRepository.java @@ -1,6 +1,7 @@ package wooteco.prolog.roadmap.domain.repository; import java.util.List; +import java.util.Set; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -17,4 +18,6 @@ public interface KeywordRepository extends JpaRepository { @Query("SELECT k FROM Keyword k " + "WHERE k.sessionId = :sessionId AND k.parent IS NULL") List findBySessionIdAndParentIsNull(@Param("sessionId") Long sessionId); + + List findBySessionIdIn(final Set sessionIds); } diff --git a/backend/src/main/java/wooteco/prolog/roadmap/ui/RoadmapController.java b/backend/src/main/java/wooteco/prolog/roadmap/ui/RoadmapController.java new file mode 100644 index 000000000..16500a192 --- /dev/null +++ b/backend/src/main/java/wooteco/prolog/roadmap/ui/RoadmapController.java @@ -0,0 +1,20 @@ +package wooteco.prolog.roadmap.ui; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import wooteco.prolog.roadmap.application.RoadMapService; +import wooteco.prolog.roadmap.application.dto.KeywordsResponse; + +@RequiredArgsConstructor +@RestController +public class RoadmapController { + + private final RoadMapService roadMapService; + + @GetMapping("/roadmaps") + public KeywordsResponse findKeywords(@RequestParam final Long curriculumId) { + return roadMapService.findAllKeywords(curriculumId); + } +} diff --git a/backend/src/test/java/wooteco/prolog/roadmap/application/RoadMapServiceTest.java b/backend/src/test/java/wooteco/prolog/roadmap/application/RoadMapServiceTest.java new file mode 100644 index 000000000..469f81277 --- /dev/null +++ b/backend/src/test/java/wooteco/prolog/roadmap/application/RoadMapServiceTest.java @@ -0,0 +1,67 @@ +package wooteco.prolog.roadmap.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.DisplayName; +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 wooteco.prolog.roadmap.application.dto.KeywordsResponse; +import wooteco.prolog.roadmap.domain.Curriculum; +import wooteco.prolog.roadmap.domain.Keyword; +import wooteco.prolog.roadmap.domain.repository.CurriculumRepository; +import wooteco.prolog.roadmap.domain.repository.KeywordRepository; +import wooteco.prolog.session.domain.Session; +import wooteco.prolog.session.domain.repository.SessionRepository; + +@ExtendWith(MockitoExtension.class) +class RoadMapServiceTest { + + @Mock + private CurriculumRepository curriculumRepository; + @Mock + private SessionRepository sessionRepository; + @Mock + private KeywordRepository keywordRepository; + @InjectMocks + private RoadMapService roadMapService; + + @Test + @DisplayName("curriculumId가 주어지면 해당 커리큘럼의 키워드들을 전부 조회할 수 있다.") + void findAllKeywords() throws Exception { + //given + final Curriculum curriculum = new Curriculum(1L, "커리큘럼1"); + final Session session = new Session(1L, curriculum.getId(), "세션1"); + final List sessions = Arrays.asList(session); + final Keyword keyword = Keyword.createKeyword("자바1", "자바 설명1", 1, 5, session.getId(), + null); + + when(curriculumRepository.findById(anyLong())) + .thenReturn(Optional.of(curriculum)); + + when(sessionRepository.findAllByCurriculumId(anyLong())) + .thenReturn(sessions); + + when(keywordRepository.findBySessionIdIn(any())) + .thenReturn(Arrays.asList(keyword)); + + final KeywordsResponse expected = KeywordsResponse.createResponse(Arrays.asList(keyword)); + + //when + final KeywordsResponse actual = + roadMapService.findAllKeywords(curriculum.getId()); + + //then + assertThat(actual) + .usingRecursiveComparison() + .isEqualTo(expected); + } +} diff --git a/backend/src/test/java/wooteco/prolog/roadmap/repository/KeywordRepositoryTest.java b/backend/src/test/java/wooteco/prolog/roadmap/repository/KeywordRepositoryTest.java index c010def07..70255a8ba 100644 --- a/backend/src/test/java/wooteco/prolog/roadmap/repository/KeywordRepositoryTest.java +++ b/backend/src/test/java/wooteco/prolog/roadmap/repository/KeywordRepositoryTest.java @@ -3,12 +3,16 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; import javax.persistence.EntityManager; -import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import wooteco.prolog.roadmap.domain.Curriculum; import wooteco.prolog.roadmap.domain.Keyword; +import wooteco.prolog.roadmap.domain.repository.CurriculumRepository; import wooteco.prolog.roadmap.domain.repository.KeywordRepository; import wooteco.prolog.session.domain.Session; import wooteco.prolog.session.domain.repository.SessionRepository; @@ -23,6 +27,8 @@ class KeywordRepositoryTest { private SessionRepository sessionRepository; @Autowired private EntityManager em; + @Autowired + private CurriculumRepository curriculumRepository; @Test void 부모_키워드와_1뎁스까지의_자식_키워드를_함께_조회할_수_있다() { @@ -105,7 +111,54 @@ class KeywordRepositoryTest { sessionId); // then - Assertions.assertThat(extractParentKeyword.size()).isEqualTo(1); + assertThat(extractParentKeyword.size()).isEqualTo(1); + } + + @Test + @DisplayName("세션 ID 리스트로 키워드 리스트를 조회한다") + void findBySessionIdIn() { + //given + final Curriculum curriculum = curriculumRepository.save(new Curriculum("커리큘럼1")); + + final Session session1 = sessionRepository.save(new Session(curriculum.getId(), "세션1")); + final Session session2 = sessionRepository.save(new Session(curriculum.getId(), "세션2")); + final Session session3 = sessionRepository.save(new Session(curriculum.getId(), "세션3")); + final Session session4 = sessionRepository.save(new Session(curriculum.getId(), "세션4")); + final Session session5 = sessionRepository.save(new Session(curriculum.getId(), "세션5")); + + final Keyword keyword1 = keywordRepository.save( + Keyword.createKeyword("자바1", "자바 설명1", 1, 5, session1.getId(), null)); + final Keyword keyword2 = keywordRepository.save( + Keyword.createKeyword("자바2", "자바 설명2", 2, 5, session1.getId(), keyword1)); + final Keyword keyword3 = keywordRepository.save( + Keyword.createKeyword("자바3", "자바 설명3", 3, 5, session1.getId(), null)); + final Keyword keyword4 = keywordRepository.save( + Keyword.createKeyword("자바4", "자바 설명4", 4, 5, session1.getId(), keyword3)); + keywordRepository.save( + Keyword.createKeyword("자바5", "자바 설명5", 5, 5, session2.getId(), null)); + keywordRepository.save( + Keyword.createKeyword("자바6", "자바 설명6", 6, 5, session2.getId(), keyword1)); + keywordRepository.save( + Keyword.createKeyword("자바7", "자바 설명7", 7, 5, session2.getId(), null)); + keywordRepository.save( + Keyword.createKeyword("자바8", "자바 설명8", 8, 5, session3.getId(), keyword2)); + final Keyword keyword9 = keywordRepository.save( + Keyword.createKeyword("자바9", "자바 설명9", 9, 5, session4.getId(), keyword2)); + final Keyword keyword10 = keywordRepository.save( + Keyword.createKeyword("자바10", "자바 설명10", 10, 5, session5.getId(), null)); + + final HashSet sessionIds = new HashSet<>( + Arrays.asList(session1.getId(), session4.getId(), session5.getId()) + ); + + //when + final List keywords = keywordRepository.findBySessionIdIn(sessionIds); + + //then + assertThat(keywords) + .usingRecursiveComparison() + .ignoringFields("id", "parent.id") + .isEqualTo(Arrays.asList(keyword1, keyword2, keyword3, keyword4, keyword9, keyword10)); } private Keyword createKeywordParent(final Keyword keyword) { From d45a3d6553345d43cea0ad862057537c34ca9436 Mon Sep 17 00:00:00 2001 From: java-saeng Date: Wed, 2 Aug 2023 17:46:27 +0900 Subject: [PATCH 2/4] =?UTF-8?q?fix=20:=20=EB=8B=A8=EC=9C=84=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존 코드 수정에 따라서 단위 테스트도 수정 #1461 --- .../prolog/roadmap/application/RoadMapServiceTest.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/backend/src/test/java/wooteco/prolog/roadmap/application/RoadMapServiceTest.java b/backend/src/test/java/wooteco/prolog/roadmap/application/RoadMapServiceTest.java index 469f81277..2fb659eb7 100644 --- a/backend/src/test/java/wooteco/prolog/roadmap/application/RoadMapServiceTest.java +++ b/backend/src/test/java/wooteco/prolog/roadmap/application/RoadMapServiceTest.java @@ -6,6 +6,7 @@ import static org.mockito.Mockito.when; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Optional; import org.junit.jupiter.api.DisplayName; @@ -36,13 +37,13 @@ class RoadMapServiceTest { @Test @DisplayName("curriculumId가 주어지면 해당 커리큘럼의 키워드들을 전부 조회할 수 있다.") - void findAllKeywords() throws Exception { + void findAllKeywords() { //given final Curriculum curriculum = new Curriculum(1L, "커리큘럼1"); final Session session = new Session(1L, curriculum.getId(), "세션1"); final List sessions = Arrays.asList(session); - final Keyword keyword = Keyword.createKeyword("자바1", "자바 설명1", 1, 5, session.getId(), - null); + final Keyword keyword = new Keyword(1L, "자바1", "자바 설명1", 1, 5, session.getId(), + null, Collections.emptySet()); when(curriculumRepository.findById(anyLong())) .thenReturn(Optional.of(curriculum)); @@ -53,7 +54,7 @@ void findAllKeywords() throws Exception { when(keywordRepository.findBySessionIdIn(any())) .thenReturn(Arrays.asList(keyword)); - final KeywordsResponse expected = KeywordsResponse.createResponse(Arrays.asList(keyword)); + final KeywordsResponse expected = KeywordsResponse.createResponseWithChildren(Arrays.asList(keyword)); //when final KeywordsResponse actual = From 8b0d8b3875b5ce485e0064a98a472d90d0c8708a Mon Sep 17 00:00:00 2001 From: java-saeng Date: Thu, 3 Aug 2023 17:01:43 +0900 Subject: [PATCH 3/4] =?UTF-8?q?refactor=20:=20=EC=B5=9C=EC=83=81=EC=9C=84?= =?UTF-8?q?=20keyword=EC=9D=B8=EC=A7=80=20=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=ED=8C=90=EB=8B=A8=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #1461 --- .../wooteco/prolog/roadmap/application/RoadMapService.java | 6 ++++-- .../prolog/roadmap/application/dto/KeywordsResponse.java | 2 +- .../main/java/wooteco/prolog/roadmap/domain/Keyword.java | 4 ++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/wooteco/prolog/roadmap/application/RoadMapService.java b/backend/src/main/java/wooteco/prolog/roadmap/application/RoadMapService.java index a8f1b30c1..d352c1dc2 100644 --- a/backend/src/main/java/wooteco/prolog/roadmap/application/RoadMapService.java +++ b/backend/src/main/java/wooteco/prolog/roadmap/application/RoadMapService.java @@ -1,11 +1,14 @@ package wooteco.prolog.roadmap.application; +import static wooteco.prolog.common.exception.BadRequestCode.CURRICULUM_NOT_FOUND_EXCEPTION; + import java.util.List; import java.util.Set; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import wooteco.prolog.common.exception.BadRequestException; import wooteco.prolog.roadmap.application.dto.KeywordsResponse; import wooteco.prolog.roadmap.domain.Curriculum; import wooteco.prolog.roadmap.domain.Keyword; @@ -26,8 +29,7 @@ public class RoadMapService { @Transactional(readOnly = true) public KeywordsResponse findAllKeywords(final Long curriculumId) { final Curriculum curriculum = curriculumRepository.findById(curriculumId) - .orElseThrow(() -> new IllegalArgumentException( - "해당 커리큘럼이 존재하지 않습니다. curriculumId = " + curriculumId)); + .orElseThrow(() -> new BadRequestException(CURRICULUM_NOT_FOUND_EXCEPTION)); final Set sessionIds = sessionRepository.findAllByCurriculumId(curriculum.getId()) .stream() diff --git a/backend/src/main/java/wooteco/prolog/roadmap/application/dto/KeywordsResponse.java b/backend/src/main/java/wooteco/prolog/roadmap/application/dto/KeywordsResponse.java index bbd9c3018..d904b67a3 100644 --- a/backend/src/main/java/wooteco/prolog/roadmap/application/dto/KeywordsResponse.java +++ b/backend/src/main/java/wooteco/prolog/roadmap/application/dto/KeywordsResponse.java @@ -26,7 +26,7 @@ public static KeywordsResponse createResponse(final List keywords) { public static KeywordsResponse createResponseWithChildren(final List keywords) { List keywordsResponse = keywords.stream() - .filter(it -> it.getParent() == null) + .filter(Keyword::isRoot) .map(KeywordResponse::createWithAllChildResponse) .collect(Collectors.toList()); return new KeywordsResponse(keywordsResponse); diff --git a/backend/src/main/java/wooteco/prolog/roadmap/domain/Keyword.java b/backend/src/main/java/wooteco/prolog/roadmap/domain/Keyword.java index 8518c806c..e4ffb3222 100644 --- a/backend/src/main/java/wooteco/prolog/roadmap/domain/Keyword.java +++ b/backend/src/main/java/wooteco/prolog/roadmap/domain/Keyword.java @@ -124,6 +124,10 @@ private void validateKeywordParent(final Keyword parentKeyword) { } } + public boolean isRoot() { + return parent == null; + } + public Long getParentIdOrNull() { if (parent == null) { return null; From 3877b7d1e1e383771d32dbe8015aa23784b6b3da Mon Sep 17 00:00:00 2001 From: java-saeng Date: Thu, 3 Aug 2023 17:02:00 +0900 Subject: [PATCH 4/4] =?UTF-8?q?refactor=20:=20=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EC=9D=98=EB=AF=B8=EA=B0=80=20=EB=B3=B4=EC=9D=B4=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #1461 --- .../main/java/wooteco/prolog/roadmap/ui/RoadmapController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/wooteco/prolog/roadmap/ui/RoadmapController.java b/backend/src/main/java/wooteco/prolog/roadmap/ui/RoadmapController.java index 16500a192..4fbfc82ee 100644 --- a/backend/src/main/java/wooteco/prolog/roadmap/ui/RoadmapController.java +++ b/backend/src/main/java/wooteco/prolog/roadmap/ui/RoadmapController.java @@ -14,7 +14,7 @@ public class RoadmapController { private final RoadMapService roadMapService; @GetMapping("/roadmaps") - public KeywordsResponse findKeywords(@RequestParam final Long curriculumId) { + public KeywordsResponse findRoadMapKeyword(@RequestParam final Long curriculumId) { return roadMapService.findAllKeywords(curriculumId); } }