From bed93b1b6fd801a984934a17bff5cbb303747ed1 Mon Sep 17 00:00:00 2001 From: BGuga Date: Thu, 2 May 2024 00:23:33 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=B6=95=EC=A0=9C=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=20=EC=8B=9C=20~=EB=8C=80=20~=EB=8C=80=ED=95=99=EA=B5=90?= =?UTF-8?q?=EB=A5=BC=20=EB=B6=99=ED=9E=88=EC=A7=80=20=EC=95=8A=EB=8D=94?= =?UTF-8?q?=EB=9D=BC=EB=8F=84=20=EA=B2=80=EC=83=89=20=EA=B0=80=EB=8A=A5=20?= =?UTF-8?q?=EB=B0=8F=20=EC=A7=84=ED=96=89,=20=EC=98=88=EC=A0=95,=20?= =?UTF-8?q?=EC=A2=85=EB=A3=8C=20=EC=88=9C=20=EC=A0=95=EB=A0=AC=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FestivalSearchV1QueryService.java | 109 +++++++++- ...istFestivalSearchV1QueryDslRepository.java | 23 ++- .../FestivalSearchV1QueryServiceTest.java | 194 ++++++++++++++---- 3 files changed, 270 insertions(+), 56 deletions(-) diff --git a/backend/src/main/java/com/festago/festival/application/FestivalSearchV1QueryService.java b/backend/src/main/java/com/festago/festival/application/FestivalSearchV1QueryService.java index d7988e748..672e20cc9 100644 --- a/backend/src/main/java/com/festago/festival/application/FestivalSearchV1QueryService.java +++ b/backend/src/main/java/com/festago/festival/application/FestivalSearchV1QueryService.java @@ -1,11 +1,19 @@ package com.festago.festival.application; +import static com.festago.festival.repository.FestivalFilter.END; +import static com.festago.festival.repository.FestivalFilter.PLANNED; +import static com.festago.festival.repository.FestivalFilter.PROGRESS; + import com.festago.festival.dto.FestivalSearchV1Response; import com.festago.festival.repository.ArtistFestivalSearchV1QueryDslRepository; +import com.festago.festival.repository.FestivalFilter; import com.festago.festival.repository.SchoolFestivalSearchV1QueryDslRepository; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumMap; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -15,16 +23,101 @@ @RequiredArgsConstructor public class FestivalSearchV1QueryService { - private static final Pattern SCHOOL_PATTERN = Pattern.compile(".*대(학교)?$"); - private final ArtistFestivalSearchV1QueryDslRepository artistFestivalSearchV1QueryDslRepository; private final SchoolFestivalSearchV1QueryDslRepository schoolFestivalSearchV1QueryDslRepository; public List search(String keyword) { - Matcher schoolMatcher = SCHOOL_PATTERN.matcher(keyword); - if (schoolMatcher.matches()) { - return schoolFestivalSearchV1QueryDslRepository.executeSearch(keyword); + return sortFestival(findFestivals(keyword)); + } + + private List findFestivals(String keyword) { + if (artistFestivalSearchV1QueryDslRepository.existsByName(keyword)) { + return artistFestivalSearchV1QueryDslRepository.executeSearch(keyword); + } + return schoolFestivalSearchV1QueryDslRepository.executeSearch(keyword); + } + + private List sortFestival(List festivals) { + EnumMap> festivalByStatus = divideByStatus(festivals); + List result = new ArrayList<>(); + result.addAll(sortByStatus(PROGRESS, festivalByStatus.get(PROGRESS))); + result.addAll(sortByStatus(PLANNED, festivalByStatus.get(PLANNED))); + result.addAll(sortByStatus(END, festivalByStatus.get(END))); + return result; + } + + private EnumMap> divideByStatus( + List festivals) { + return festivals.stream() + .collect(Collectors.groupingBy( + this::determineStatus, + () -> new EnumMap<>(FestivalFilter.class), + Collectors.toList() + )); + } + + private FestivalFilter determineStatus(FestivalSearchV1Response festival) { + LocalDate now = LocalDate.now(); + if (now.isAfter(festival.endDate())) { + return END; + } + if (now.isBefore(festival.startDate())) { + return PLANNED; } - return artistFestivalSearchV1QueryDslRepository.executeSearch(keyword); + return PROGRESS; + } + + private List sortByStatus( + FestivalFilter status, + List festivals) { + if (festivals == null) { + return Collections.emptyList(); + } + if (status == END) { + sortEndFestival(festivals); + } + if (status == PROGRESS) { + sortProgressFestival(festivals); + } + if (status == PLANNED) { + sortPlannedFestival(festivals); + } + return festivals; + } + + private void sortEndFestival(List festivals) { + festivals.sort((festival1, festival2) -> { + if (festival1.endDate().isAfter(festival2.endDate())) { + return 1; + } + if (festival1.endDate().isEqual(festival2.endDate())) { + return 0; + } + return -1; + }); + } + + private void sortProgressFestival(List festivals) { + festivals.sort((festival1, festival2) -> { + if (festival1.startDate().isBefore(festival2.endDate())) { + return 1; + } + if (festival1.startDate().isEqual(festival2.startDate())) { + return 0; + } + return -1; + }); + } + + private void sortPlannedFestival(List festivals) { + festivals.sort((festival1, festival2) -> { + if (festival1.startDate().isBefore(festival2.endDate())) { + return 1; + } + if (festival1.startDate().isEqual(festival2.startDate())) { + return 0; + } + return -1; + }); } } diff --git a/backend/src/main/java/com/festago/festival/repository/ArtistFestivalSearchV1QueryDslRepository.java b/backend/src/main/java/com/festago/festival/repository/ArtistFestivalSearchV1QueryDslRepository.java index da4f277f6..a1c1dde48 100644 --- a/backend/src/main/java/com/festago/festival/repository/ArtistFestivalSearchV1QueryDslRepository.java +++ b/backend/src/main/java/com/festago/festival/repository/ArtistFestivalSearchV1QueryDslRepository.java @@ -24,18 +24,18 @@ public ArtistFestivalSearchV1QueryDslRepository() { } public List executeSearch(String keyword) { + return searchByExpression(getBooleanExpressionByKeyword(keyword)); + } + + private BooleanExpression getBooleanExpressionByKeyword(String keyword) { int keywordLength = keyword.length(); if (keywordLength == 0) { throw new BadRequestException(ErrorCode.INVALID_KEYWORD); } if (keywordLength == 1) { - return searchByEqual(keyword); + return artist.name.eq(keyword); } - return searchByLike(keyword); - } - - private List searchByEqual(String keyword) { - return searchByExpression(artist.name.eq(keyword)); + return artist.name.contains(keyword); } private List searchByExpression(BooleanExpression expression) { @@ -56,7 +56,14 @@ private List searchByExpression(BooleanExpression expr .fetch(); } - private List searchByLike(String keyword) { - return searchByExpression(artist.name.contains(keyword)); + public boolean existsByName(String keyword) { + return existsByExpression(getBooleanExpressionByKeyword(keyword)); + } + + private boolean existsByExpression(BooleanExpression expression) { + return !selectFrom(artist) + .where(expression) + .fetch() + .isEmpty(); } } diff --git a/backend/src/test/java/com/festago/festival/application/integration/query/FestivalSearchV1QueryServiceTest.java b/backend/src/test/java/com/festago/festival/application/integration/query/FestivalSearchV1QueryServiceTest.java index 266afc47e..4645baa23 100644 --- a/backend/src/test/java/com/festago/festival/application/integration/query/FestivalSearchV1QueryServiceTest.java +++ b/backend/src/test/java/com/festago/festival/application/integration/query/FestivalSearchV1QueryServiceTest.java @@ -58,68 +58,79 @@ class FestivalSearchV1QueryServiceTest extends ApplicationIntegrationTest { @Autowired FestivalSearchV1QueryService festivalSearchV1QueryService; - Stage 부산_공연; - Stage 서울_공연; - Stage 대구_공연; + School 부산_학교; + School 서울_학교; + School 대구_학교; + + Festival 부산대_종료_축제; + Festival 서울대_진행_축제; + Festival 대구대_예정_축제; + + Stage 부산대_종료_공연; + Stage 서울_진행_공연; + Stage 대구_예정_공연; + + LocalDate nowDate; + LocalDateTime nowDateTime; @BeforeEach void setting() { - LocalDate nowDate = LocalDate.now(); - LocalDateTime nowDateTime = LocalDateTime.now(); + nowDate = LocalDate.now(); + nowDateTime = LocalDateTime.now(); - School 부산_학교 = schoolRepository.save(SchoolFixture.builder() + 부산_학교 = schoolRepository.save(SchoolFixture.builder() .domain("domain1") .name("부산 학교") .region(SchoolRegion.부산) .build()); - School 서울_학교 = schoolRepository.save(SchoolFixture.builder() + 서울_학교 = schoolRepository.save(SchoolFixture.builder() .domain("domain2") .name("서울 학교") .region(SchoolRegion.서울) .build()); - School 대구_학교 = schoolRepository.save(SchoolFixture.builder() + 대구_학교 = schoolRepository.save(SchoolFixture.builder() .domain("domain3") .name("대구 학교") .region(SchoolRegion.대구) .build()); - Festival 부산_축제 = festivalRepository.save(FestivalFixture.builder() + 부산대_종료_축제 = festivalRepository.save(FestivalFixture.builder() .name("부산대학교 축제") .startDate(nowDate.minusDays(5)) .endDate(nowDate.minusDays(1)) .school(부산_학교) .build()); - Festival 서울_축제 = festivalRepository.save(FestivalFixture.builder() + 서울대_진행_축제 = festivalRepository.save(FestivalFixture.builder() .name("서울대학교 축제") .startDate(nowDate.minusDays(1)) .endDate(nowDate.plusDays(3)) .school(서울_학교) .build()); - Festival 대구_축제 = festivalRepository.save(FestivalFixture.builder() + 대구대_예정_축제 = festivalRepository.save(FestivalFixture.builder() .name("대구대학교 축제") .startDate(nowDate.plusDays(1)) .endDate(nowDate.plusDays(5)) .school(대구_학교) .build()); - festivalInfoRepository.save(FestivalQueryInfoFixture.builder().festivalId(부산_축제.getId()).build()); - festivalInfoRepository.save(FestivalQueryInfoFixture.builder().festivalId(서울_축제.getId()).build()); - festivalInfoRepository.save(FestivalQueryInfoFixture.builder().festivalId(대구_축제.getId()).build()); + festivalInfoRepository.save(FestivalQueryInfoFixture.builder().festivalId(부산대_종료_축제.getId()).build()); + festivalInfoRepository.save(FestivalQueryInfoFixture.builder().festivalId(서울대_진행_축제.getId()).build()); + festivalInfoRepository.save(FestivalQueryInfoFixture.builder().festivalId(대구대_예정_축제.getId()).build()); - 부산_공연 = stageRepository.save(StageFixture.builder() + 부산대_종료_공연 = stageRepository.save(StageFixture.builder() .startTime(nowDateTime.minusDays(5L)) .ticketOpenTime(nowDateTime.minusDays(6L)) - .festival(부산_축제) + .festival(부산대_종료_축제) .build()); - 서울_공연 = stageRepository.save(StageFixture.builder() + 서울_진행_공연 = stageRepository.save(StageFixture.builder() .startTime(nowDateTime.minusDays(1L)) .ticketOpenTime(nowDateTime.minusDays(2L)) - .festival(서울_축제) + .festival(서울대_진행_축제) .build()); - 대구_공연 = stageRepository.save(StageFixture.builder() + 대구_예정_공연 = stageRepository.save(StageFixture.builder() .startTime(nowDateTime.plusDays(1L)) .ticketOpenTime(nowDateTime) - .festival(대구_축제) + .festival(대구대_예정_축제) .build()); } @@ -155,6 +166,55 @@ class 학교_기반_축제_검색에서 { softly.assertThat(actual.get(0).name()).contains(keyword); }); } + + @Test + void 학교_이름만_으로_검색_가능하다() { + // given + String keyword = "부산"; + + // when + List actual = festivalSearchV1QueryService.search(keyword); + + // then + assertSoftly(softly -> { + softly.assertThat(actual).hasSize(1); + softly.assertThat(actual.get(0).name()).contains(keyword); + }); + } + + @Test + void 검색은_진행_예정_종료로_정렬된다() { + // given + String keyword = "부산"; + + Festival 부산대학교_예정_축제 = festivalRepository.save(FestivalFixture.builder() + .name("부산대학교 예정 축제") + .startDate(nowDate.plusDays(1)) + .endDate(nowDate.plusDays(3)) + .school(부산_학교) + .build()); + + Festival 부산대학교_진행_축제 = festivalRepository.save(FestivalFixture.builder() + .name("부산대학교 진행 축제") + .startDate(nowDate.minusDays(5)) + .endDate(nowDate.plusDays(1)) + .school(부산_학교) + .build()); + + festivalInfoRepository.save(FestivalQueryInfoFixture.builder().festivalId(부산대학교_예정_축제.getId()).build()); + festivalInfoRepository.save(FestivalQueryInfoFixture.builder().festivalId(부산대학교_진행_축제.getId()).build()); + + // when + List actual = festivalSearchV1QueryService.search(keyword); + + // then + assertSoftly(softly -> { + softly.assertThat(actual).hasSize(3); + softly.assertThat(actual) + .map(festivalSearchV1Response -> festivalSearchV1Response.name()) + .containsExactly(부산대학교_진행_축제.getName(), 부산대학교_예정_축제.getName(), 부산대_종료_축제.getName()); + }); + } } @Nested @@ -176,13 +236,13 @@ class 두_글자_이상_라이크_검색은 { .name("글렌") .build()); - stageArtistRepository.save(StageArtistFixture.builder(부산_공연.getId(), 오리.getId()).build()); - stageArtistRepository.save(StageArtistFixture.builder(부산_공연.getId(), 우푸우.getId()).build()); + stageArtistRepository.save(StageArtistFixture.builder(부산대_종료_공연.getId(), 오리.getId()).build()); + stageArtistRepository.save(StageArtistFixture.builder(부산대_종료_공연.getId(), 우푸우.getId()).build()); - stageArtistRepository.save(StageArtistFixture.builder(서울_공연.getId(), 오리.getId()).build()); - stageArtistRepository.save(StageArtistFixture.builder(서울_공연.getId(), 글렌.getId()).build()); + stageArtistRepository.save(StageArtistFixture.builder(서울_진행_공연.getId(), 오리.getId()).build()); + stageArtistRepository.save(StageArtistFixture.builder(서울_진행_공연.getId(), 글렌.getId()).build()); - stageArtistRepository.save(StageArtistFixture.builder(대구_공연.getId(), 우푸우.getId()).build()); + stageArtistRepository.save(StageArtistFixture.builder(대구_예정_공연.getId(), 우푸우.getId()).build()); // when List actual = festivalSearchV1QueryService.search("푸우"); @@ -190,8 +250,8 @@ class 두_글자_이상_라이크_검색은 { // then assertSoftly(softly -> { softly.assertThat(actual).hasSize(2); - softly.assertThat(actual.get(0).name()).isEqualTo("부산대학교 축제"); - softly.assertThat(actual.get(1).name()).isEqualTo("대구대학교 축제"); + softly.assertThat(actual.get(0).name()).isEqualTo("대구대학교 축제"); + softly.assertThat(actual.get(1).name()).isEqualTo("부산대학교 축제"); }); } @@ -208,13 +268,13 @@ class 두_글자_이상_라이크_검색은 { .name("글렌") .build()); - stageArtistRepository.save(StageArtistFixture.builder(부산_공연.getId(), 오리.getId()).build()); - stageArtistRepository.save(StageArtistFixture.builder(부산_공연.getId(), 우푸우.getId()).build()); + stageArtistRepository.save(StageArtistFixture.builder(부산대_종료_공연.getId(), 오리.getId()).build()); + stageArtistRepository.save(StageArtistFixture.builder(부산대_종료_공연.getId(), 우푸우.getId()).build()); - stageArtistRepository.save(StageArtistFixture.builder(서울_공연.getId(), 오리.getId()).build()); - stageArtistRepository.save(StageArtistFixture.builder(서울_공연.getId(), 글렌.getId()).build()); + stageArtistRepository.save(StageArtistFixture.builder(서울_진행_공연.getId(), 오리.getId()).build()); + stageArtistRepository.save(StageArtistFixture.builder(서울_진행_공연.getId(), 글렌.getId()).build()); - stageArtistRepository.save(StageArtistFixture.builder(대구_공연.getId(), 우푸우.getId()).build()); + stageArtistRepository.save(StageArtistFixture.builder(대구_예정_공연.getId(), 우푸우.getId()).build()); // when List actual = festivalSearchV1QueryService.search("렌글"); @@ -252,10 +312,10 @@ class 한_글자_동일_검색은 { .name("푸") .build()); - stageArtistRepository.save(StageArtistFixture.builder(부산_공연.getId(), 푸우.getId()).build()); - stageArtistRepository.save(StageArtistFixture.builder(부산_공연.getId(), 푸.getId()).build()); + stageArtistRepository.save(StageArtistFixture.builder(부산대_종료_공연.getId(), 푸우.getId()).build()); + stageArtistRepository.save(StageArtistFixture.builder(부산대_종료_공연.getId(), 푸.getId()).build()); - stageArtistRepository.save(StageArtistFixture.builder(서울_공연.getId(), 푸.getId()).build()); + stageArtistRepository.save(StageArtistFixture.builder(서울_진행_공연.getId(), 푸.getId()).build()); // when List actual = festivalSearchV1QueryService.search("푸"); @@ -263,8 +323,8 @@ class 한_글자_동일_검색은 { // then assertSoftly(softly -> { softly.assertThat(actual).hasSize(2); - softly.assertThat(actual.get(0).name()).isEqualTo("부산대학교 축제"); - softly.assertThat(actual.get(1).name()).isEqualTo("서울대학교 축제"); + softly.assertThat(actual.get(0).name()).isEqualTo("서울대학교 축제"); + softly.assertThat(actual.get(1).name()).isEqualTo("부산대학교 축제"); }); } @@ -281,9 +341,9 @@ class 한_글자_동일_검색은 { .name("글렌") .build()); - stageArtistRepository.save(StageArtistFixture.builder(부산_공연.getId(), 푸우.getId()).build()); - stageArtistRepository.save(StageArtistFixture.builder(부산_공연.getId(), 푸푸푸푸.getId()).build()); - stageArtistRepository.save(StageArtistFixture.builder(부산_공연.getId(), 글렌.getId()).build()); + stageArtistRepository.save(StageArtistFixture.builder(부산대_종료_공연.getId(), 푸우.getId()).build()); + stageArtistRepository.save(StageArtistFixture.builder(부산대_종료_공연.getId(), 푸푸푸푸.getId()).build()); + stageArtistRepository.save(StageArtistFixture.builder(부산대_종료_공연.getId(), 글렌.getId()).build()); // when List actual = festivalSearchV1QueryService.search("푸"); @@ -305,6 +365,60 @@ class 한_글자_동일_검색은 { // then assertThat(actual).isEmpty(); } + + @Test + void 검색은_진행_예정_종료로_정렬된다() { + // given + String keyword = "푸"; + + Festival 부산대학교_예정_축제 = festivalRepository.save(FestivalFixture.builder() + .name("부산대학교 예정 축제") + .startDate(nowDate.plusDays(1)) + .endDate(nowDate.plusDays(3)) + .school(부산_학교) + .build()); + + Festival 부산대학교_진행_축제 = festivalRepository.save(FestivalFixture.builder() + .name("부산대학교 진행 축제") + .startDate(nowDate.minusDays(5)) + .endDate(nowDate.plusDays(1)) + .school(부산_학교) + .build()); + + Artist 푸 = artistRepository.save(ArtistFixture.builder() + .name("푸") + .build()); + + Stage 부산대_예정_공연 = stageRepository.save(StageFixture.builder() + .startTime(nowDateTime.plusDays(2L)) + .ticketOpenTime(nowDateTime.plusMinutes(1L)) + .festival(부산대학교_예정_축제) + .build()); + + Stage 부산대_진행_공연 = stageRepository.save(StageFixture.builder() + .startTime(nowDateTime.minusDays(4L)) + .ticketOpenTime(nowDateTime.minusDays(6L)) + .festival(부산대학교_진행_축제) + .build()); + + stageArtistRepository.save(StageArtistFixture.builder(부산대_종료_공연.getId(), 푸.getId()).build()); + stageArtistRepository.save(StageArtistFixture.builder(부산대_예정_공연.getId(), 푸.getId()).build()); + stageArtistRepository.save(StageArtistFixture.builder(부산대_진행_공연.getId(), 푸.getId()).build()); + + festivalInfoRepository.save(FestivalQueryInfoFixture.builder().festivalId(부산대학교_예정_축제.getId()).build()); + festivalInfoRepository.save(FestivalQueryInfoFixture.builder().festivalId(부산대학교_진행_축제.getId()).build()); + + // when + List actual = festivalSearchV1QueryService.search(keyword); + + // then + assertSoftly(softly -> { + softly.assertThat(actual).hasSize(3); + softly.assertThat(actual) + .map(festivalSearchV1Response -> festivalSearchV1Response.name()) + .containsExactly(부산대학교_진행_축제.getName(), 부산대학교_예정_축제.getName(), 부산대_종료_축제.getName()); + }); + } } } }