From 9834a0e1d5c07af500697bfa06ac2979898f55b7 Mon Sep 17 00:00:00 2001 From: HongGit Date: Sun, 9 Feb 2025 23:49:47 +0900 Subject: [PATCH 1/6] =?UTF-8?q?[feat/#42]=20=EB=B0=9C=ED=96=89=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1,=20=EC=97=B0=EC=9B=94=EB=B3=84=20=EC=A1=B0=ED=9A=8C,?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C=20api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ProblemSetRepository.java | 8 ++ .../publish/controller/PublishController.java | 54 ++++++++ .../dto/request/PublishPostRequest.java | 29 ++++ .../dto/response/PublishMonthGetResponse.java | 17 +++ .../response/PublishProblemSetResponse.java | 18 +++ .../publish/repository/PublishRepository.java | 16 +++ .../publish/service/PublishDeleteService.java | 20 +++ .../publish/service/PublishGetService.java | 49 +++++++ .../publish/service/PublishSaveService.java | 24 ++++ .../global/error/exception/ErrorCode.java | 5 + .../publish/service/PublishServiceTest.java | 129 ++++++++++++++++++ src/test/resources/insert-problem-set2.sql | 11 ++ 12 files changed, 380 insertions(+) create mode 100644 src/main/java/com/moplus/moplus_server/domain/publish/controller/PublishController.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/publish/dto/request/PublishPostRequest.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/publish/dto/response/PublishMonthGetResponse.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/publish/dto/response/PublishProblemSetResponse.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/publish/repository/PublishRepository.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/publish/service/PublishDeleteService.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/publish/service/PublishGetService.java create mode 100644 src/main/java/com/moplus/moplus_server/domain/publish/service/PublishSaveService.java create mode 100644 src/test/java/com/moplus/moplus_server/domain/publish/service/PublishServiceTest.java create mode 100644 src/test/resources/insert-problem-set2.sql diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetRepository.java b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetRepository.java index fff031f..b145fad 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetRepository.java @@ -1,6 +1,7 @@ package com.moplus.moplus_server.domain.problemset.repository; import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; +import com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; import org.springframework.data.jpa.repository.JpaRepository; @@ -11,4 +12,11 @@ default ProblemSet findByIdElseThrow(Long problemSetId) { return findById(problemSetId).orElseThrow(() -> new NotFoundException(ErrorCode.PROBLEM_SET_NOT_FOUND)); } + default void existsConfirmedActiveByIdElseThrow(Long problemSetId) { + if (!existsByIdAndIsDeletedFalseAndConfirmStatus(problemSetId, ProblemSetConfirmStatus.CONFIRMED)) { + throw new NotFoundException(ErrorCode.PROBLEM_SET_NOT_FOUND); + } + } + + boolean existsByIdAndIsDeletedFalseAndConfirmStatus(Long problemSetId, ProblemSetConfirmStatus confirmStatus); } diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/controller/PublishController.java b/src/main/java/com/moplus/moplus_server/domain/publish/controller/PublishController.java new file mode 100644 index 0000000..a703911 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/publish/controller/PublishController.java @@ -0,0 +1,54 @@ +package com.moplus.moplus_server.domain.publish.controller; + +import com.moplus.moplus_server.domain.publish.dto.request.PublishPostRequest; +import com.moplus.moplus_server.domain.publish.dto.response.PublishMonthGetResponse; +import com.moplus.moplus_server.domain.publish.service.PublishDeleteService; +import com.moplus.moplus_server.domain.publish.service.PublishGetService; +import com.moplus.moplus_server.domain.publish.service.PublishSaveService; +import io.swagger.v3.oas.annotations.Operation; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/publish") +@RequiredArgsConstructor +public class PublishController { + + private final PublishGetService publishGetService; + private final PublishSaveService publishSaveService; + private final PublishDeleteService publishDeleteService; + + @GetMapping("/{year}/{month}") + @Operation(summary = "연월별 발행 조회", description = "연월별로 발행된 세트들을 조회합니다.") + public ResponseEntity> getPublishMonth( + @PathVariable int year, + @PathVariable int month + ) { + return ResponseEntity.ok(publishGetService.getPublishMonth(year, month)); + } + + @PostMapping("") + @Operation(summary = "발행 생성하기", description = "특정 날짜에 문항세트를 발행합니다.") + public ResponseEntity postPublish( + @RequestBody PublishPostRequest request + ) { + return ResponseEntity.ok(publishSaveService.createPublish(request)); + } + + @DeleteMapping("/{publishId}") + @Operation(summary = "발행 삭제", description = "발행을 삭제합니다.") + public ResponseEntity deleteProblemSet( + @PathVariable Long publishId + ) { + publishDeleteService.deletePublish(publishId); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/dto/request/PublishPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/publish/dto/request/PublishPostRequest.java new file mode 100644 index 0000000..10c74ae --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/publish/dto/request/PublishPostRequest.java @@ -0,0 +1,29 @@ +package com.moplus.moplus_server.domain.publish.dto.request; + +import com.moplus.moplus_server.domain.publish.domain.Publish; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.InvalidValueException; +import java.time.LocalDate; + +public record PublishPostRequest( + LocalDate publishedDate, + Long problemSetId +) { + public PublishPostRequest { + validatePublishedDate(publishedDate); + } + + public Publish toEntity() { + return Publish.builder() + .publishedDate(this.publishedDate) + .problemSetId(this.problemSetId) + .build(); + } + + private static void validatePublishedDate(LocalDate publishedDate) { + // 발행 시점 다음날부터 발행 가능 + if (publishedDate.isBefore(LocalDate.now().plusDays(1))) { + throw new InvalidValueException(ErrorCode.INVALID_DATE_ERROR); + } + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/dto/response/PublishMonthGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/publish/dto/response/PublishMonthGetResponse.java new file mode 100644 index 0000000..8b399a0 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/publish/dto/response/PublishMonthGetResponse.java @@ -0,0 +1,17 @@ +package com.moplus.moplus_server.domain.publish.dto.response; + +import lombok.Builder; + +@Builder +public record PublishMonthGetResponse( + int day, + PublishProblemSetResponse problemSetInfo +) { + public static PublishMonthGetResponse of(int day, PublishProblemSetResponse problemSetInfos) { + + return PublishMonthGetResponse.builder() + .day(day) + .problemSetInfo(problemSetInfos) + .build(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/dto/response/PublishProblemSetResponse.java b/src/main/java/com/moplus/moplus_server/domain/publish/dto/response/PublishProblemSetResponse.java new file mode 100644 index 0000000..1afc053 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/publish/dto/response/PublishProblemSetResponse.java @@ -0,0 +1,18 @@ +package com.moplus.moplus_server.domain.publish.dto.response; + +import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; +import lombok.Builder; + +@Builder +public record PublishProblemSetResponse( + Long id, + String title +) { + public static PublishProblemSetResponse of(ProblemSet problemSet) { + + return PublishProblemSetResponse.builder() + .id(problemSet.getId()) + .title(problemSet.getTitle().getValue()) + .build(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/repository/PublishRepository.java b/src/main/java/com/moplus/moplus_server/domain/publish/repository/PublishRepository.java new file mode 100644 index 0000000..0f7433d --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/publish/repository/PublishRepository.java @@ -0,0 +1,16 @@ +package com.moplus.moplus_server.domain.publish.repository; + +import com.moplus.moplus_server.domain.publish.domain.Publish; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.NotFoundException; +import java.time.LocalDate; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PublishRepository extends JpaRepository { + List findByPublishedDateBetween(LocalDate startDate, LocalDate endDate); + + default Publish findByIdElseThrow(Long publishId) { + return findById(publishId).orElseThrow(() -> new NotFoundException(ErrorCode.PUBLISH_NOT_FOUND)); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishDeleteService.java b/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishDeleteService.java new file mode 100644 index 0000000..75d9e9c --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishDeleteService.java @@ -0,0 +1,20 @@ +package com.moplus.moplus_server.domain.publish.service; + +import com.moplus.moplus_server.domain.publish.domain.Publish; +import com.moplus.moplus_server.domain.publish.repository.PublishRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class PublishDeleteService { + + private final PublishRepository publishRepository; + + @Transactional + public void deletePublish(Long publishId) { + Publish publish = publishRepository.findByIdElseThrow(publishId); + publishRepository.delete(publish); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishGetService.java b/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishGetService.java new file mode 100644 index 0000000..e299e6b --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishGetService.java @@ -0,0 +1,49 @@ +package com.moplus.moplus_server.domain.publish.service; + +import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; +import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; +import com.moplus.moplus_server.domain.publish.domain.Publish; +import com.moplus.moplus_server.domain.publish.dto.response.PublishMonthGetResponse; +import com.moplus.moplus_server.domain.publish.dto.response.PublishProblemSetResponse; +import com.moplus.moplus_server.domain.publish.repository.PublishRepository; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.InvalidValueException; +import java.time.LocalDate; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class PublishGetService { + + private final PublishRepository publishRepository; + private final ProblemSetRepository problemSetRepository; + + @Transactional(readOnly = true) + public List getPublishMonth(int year, int month) { + if (month < 1 || month > 12) { + throw new InvalidValueException(ErrorCode.INVALID_MONTH_ERROR); + } + LocalDate startDate = LocalDate.of(year, month, 1); + LocalDate endDate = startDate.withDayOfMonth(startDate.lengthOfMonth()); + + // 주어진 월에 해당하는 모든 Publish 조회 + List publishes = publishRepository.findByPublishedDateBetween(startDate, endDate); + + // 데이터를 day 기준으로 매핑 + return publishes.stream() + .map(publish -> { + ProblemSet problemSet = problemSetRepository.findByIdElseThrow(publish.getProblemSetId()); + PublishProblemSetResponse problemSetResponse = PublishProblemSetResponse.of(problemSet); + + return PublishMonthGetResponse.of( + publish.getPublishedDate().getDayOfMonth(), + problemSetResponse + ); + }) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishSaveService.java b/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishSaveService.java new file mode 100644 index 0000000..9da928f --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishSaveService.java @@ -0,0 +1,24 @@ +package com.moplus.moplus_server.domain.publish.service; + +import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; +import com.moplus.moplus_server.domain.publish.domain.Publish; +import com.moplus.moplus_server.domain.publish.dto.request.PublishPostRequest; +import com.moplus.moplus_server.domain.publish.repository.PublishRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class PublishSaveService { + + private final ProblemSetRepository problemSetRepository; + private final PublishRepository publishRepository; + + @Transactional + public Long createPublish(PublishPostRequest request) { + problemSetRepository.existsConfirmedActiveByIdElseThrow(request.problemSetId()); + Publish publish = request.toEntity(); + return publishRepository.save(publish).getId(); + } +} diff --git a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java index 747ef27..8395524 100644 --- a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java +++ b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java @@ -57,6 +57,11 @@ public enum ErrorCode { //문항세트 PROBLEM_SET_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 문항세트를 찾을 수 없습니다"), EMPTY_PROBLEMS_ERROR(HttpStatus.BAD_REQUEST, "적어도 1개의 문항을 등록해주세요"), + + // 발행 + INVALID_MONTH_ERROR(HttpStatus.BAD_REQUEST, "유효하지 않은 월입니다."), + INVALID_DATE_ERROR(HttpStatus.BAD_REQUEST, "오늘 이후 날짜에만 발행이 가능합니다."), + PUBLISH_NOT_FOUND(HttpStatus.NOT_FOUND, "발행 정보를 찾을 수 없습니다"), ; diff --git a/src/test/java/com/moplus/moplus_server/domain/publish/service/PublishServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/publish/service/PublishServiceTest.java new file mode 100644 index 0000000..66f6cbe --- /dev/null +++ b/src/test/java/com/moplus/moplus_server/domain/publish/service/PublishServiceTest.java @@ -0,0 +1,129 @@ +package com.moplus.moplus_server.domain.publish.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +import com.moplus.moplus_server.domain.publish.domain.Publish; +import com.moplus.moplus_server.domain.publish.dto.request.PublishPostRequest; +import com.moplus.moplus_server.domain.publish.dto.response.PublishMonthGetResponse; +import com.moplus.moplus_server.domain.publish.repository.PublishRepository; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.InvalidValueException; +import com.moplus.moplus_server.global.error.exception.NotFoundException; +import java.time.LocalDate; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +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.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +@ActiveProfiles("h2test") +@Sql({"/insert-problem.sql", "/insert-problem-set2.sql"}) +@SpringBootTest +public class PublishServiceTest { + + @Autowired + private PublishSaveService publishSaveService; + + @Autowired + private PublishDeleteService publishDeleteService; + + @Autowired + private PublishGetService publishGetService; + + @Autowired + private PublishRepository publishRepository; + + private PublishPostRequest publishPostRequest; + + @BeforeEach + void setUp() { + // 발행 요청 데이터 생성 + publishPostRequest = new PublishPostRequest( + LocalDate.now().plusDays(1), // 내일부터 발행 가능 + 1L + ); + } + + @Test + void 발행_생성_테스트() { + // when + Long publishId = publishSaveService.createPublish(publishPostRequest); + + // then + Publish savedPublish = publishRepository.findByIdElseThrow(publishId); + + assertThat(savedPublish).isNotNull(); + assertThat(savedPublish.getPublishedDate()).isEqualTo(publishPostRequest.publishedDate()); + assertThat(savedPublish.getProblemSetId()).isEqualTo(1L); + } + + @Test + void 발행_삭제_테스트() { + // given + Long publishId = publishSaveService.createPublish(publishPostRequest); + + // when + publishDeleteService.deletePublish(publishId); + + // then + assertThatThrownBy(() -> publishRepository.findByIdElseThrow(publishId)) + .isInstanceOf(NotFoundException.class) + .hasMessageContaining(ErrorCode.PUBLISH_NOT_FOUND.getMessage()); + } + + @Test + void 월별_발행_조회_테스트() { + // given + publishSaveService.createPublish(new PublishPostRequest( + LocalDate.of(2025, 2, 10), + 1L + )); + + publishSaveService.createPublish(new PublishPostRequest( + LocalDate.of(2025, 2, 15), + 1L + )); + + // when + List publishList = publishGetService.getPublishMonth(2025, 2); + + // then + assertThat(publishList).hasSize(2); + assertThat(publishList.get(0).day()).isEqualTo(10); + assertThat(publishList.get(0).problemSetInfo().title()).isEqualTo("2025년 5월 고2 모의고사 문제 세트"); + assertThat(publishList.get(1).day()).isEqualTo(15); + } + + @Test + void 유효하지_않은_월_입력시_예외_테스트() { + // when & then + assertThatThrownBy(() -> publishGetService.getPublishMonth(2025, 13)) + .isInstanceOf(InvalidValueException.class) + .hasMessageContaining(ErrorCode.INVALID_MONTH_ERROR.getMessage()); + + assertThatThrownBy(() -> publishGetService.getPublishMonth(2025, 0)) + .isInstanceOf(InvalidValueException.class) + .hasMessageContaining(ErrorCode.INVALID_MONTH_ERROR.getMessage()); + } + + @Test + void 오늘날짜_또는_과거날짜로_발행_시_예외_테스트() { + // given + LocalDate today = LocalDate.now(); + LocalDate pastDate = today.minusDays(1); + + // when & then (createPublish에서 예외 발생하도록) + assertThatThrownBy(() -> publishSaveService.createPublish(new PublishPostRequest(today, 1L))) + .isInstanceOf(InvalidValueException.class) + .hasMessageContaining(ErrorCode.INVALID_DATE_ERROR.getMessage()); + + assertThatThrownBy(() -> publishSaveService.createPublish(new PublishPostRequest(pastDate, 1L))) + .isInstanceOf(InvalidValueException.class) + .hasMessageContaining(ErrorCode.INVALID_DATE_ERROR.getMessage()); + } +} \ No newline at end of file diff --git a/src/test/resources/insert-problem-set2.sql b/src/test/resources/insert-problem-set2.sql new file mode 100644 index 0000000..ee87ce3 --- /dev/null +++ b/src/test/resources/insert-problem-set2.sql @@ -0,0 +1,11 @@ +DELETE FROM problem_set_problems; +DELETE FROM problem_set; + +-- 문제 세트 추가 +INSERT INTO problem_set (problem_set_id, title, is_deleted, confirm_status) +VALUES (1, '2025년 5월 고2 모의고사 문제 세트', false, 'CONFIRMED'); + +-- 문제 세트에 포함된 문제 추가 +INSERT INTO problem_set_problems (problem_set_id, problem_id, sequence) +VALUES (1, '240520012001', 0), + (1, '240520012002', 1); \ No newline at end of file From d990fcd265a54be82e78d5846e20e29675d86e5d Mon Sep 17 00:00:00 2001 From: HongGit Date: Mon, 10 Feb 2025 00:51:44 +0900 Subject: [PATCH 2/6] =?UTF-8?q?[feat/#42]=20=EB=AC=B8=ED=95=AD=EC=84=B8?= =?UTF-8?q?=ED=8A=B8=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=EB=B0=9C=ED=96=89?= =?UTF-8?q?=EB=82=A0=EC=A7=9C=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProblemSetSearchController.java | 4 +- .../dto/response/ProblemSetGetResponse.java | 5 ++- .../response/ProblemSetSearchGetResponse.java | 8 +++- .../ProblemSetSearchRepositoryCustom.java | 4 ++ .../service/ProblemSetGetService.java | 9 +++- .../publish/repository/PublishRepository.java | 3 ++ .../ProblemSetSearchRepositoryCustomTest.java | 44 +++++++++++++++++-- .../publish/service/PublishServiceTest.java | 6 +-- src/test/resources/insert-problem-set.sql | 7 ++- 9 files changed, 79 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java index 405a66f..9a318c1 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java @@ -3,6 +3,7 @@ import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetSearchGetResponse; import com.moplus.moplus_server.domain.problemset.repository.ProblemSetSearchRepositoryCustom; +import com.moplus.moplus_server.domain.publish.repository.PublishRepository; import io.swagger.v3.oas.annotations.Operation; import java.util.List; import lombok.RequiredArgsConstructor; @@ -18,11 +19,12 @@ public class ProblemSetSearchController { private final ProblemSetSearchRepositoryCustom problemSetSearchRepository; + private final PublishRepository publishRepository; @GetMapping("/search") @Operation( summary = "문항세트 검색", - description = "문항세트 타이틀, 문항세트 내 포함된 개념태그, 문항세트 내 포함된 문항 타이틀로 검색합니다." + description = "문항세트 타이틀, 문항세트 내 포함된 개념태그, 문항세트 내 포함된 문항 타이틀로 검색합니다. 발행상태는 발행이면 CONFIRMED, 아니면 NOT_CONFIRMED 입니다." ) public ResponseEntity> search( @RequestParam(value = "problemSetTitle", required = false) String problemSetTitle, diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java index 704d9af..7c32ba7 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetGetResponse.java @@ -2,6 +2,7 @@ import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; import com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus; +import java.time.LocalDate; import java.util.List; import lombok.Builder; @@ -10,14 +11,16 @@ public record ProblemSetGetResponse( Long id, String title, ProblemSetConfirmStatus confirmStatus, + LocalDate publishedDate, List problemSummaries ) { - public static ProblemSetGetResponse of(ProblemSet problemSet, List problemSummaries) { + public static ProblemSetGetResponse of(ProblemSet problemSet, LocalDate publishedDate, List problemSummaries) { return ProblemSetGetResponse.builder() .id(problemSet.getId()) .title(problemSet.getTitle().getValue()) .confirmStatus(problemSet.getConfirmStatus()) + .publishedDate(publishedDate) .problemSummaries(problemSummaries) .build(); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java index 140e1c3..789b310 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSetSearchGetResponse.java @@ -1,5 +1,7 @@ package com.moplus.moplus_server.domain.problemset.dto.response; +import com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus; +import java.time.LocalDate; import java.util.List; import lombok.Getter; import lombok.NoArgsConstructor; @@ -8,12 +10,16 @@ @NoArgsConstructor public class ProblemSetSearchGetResponse { private String problemSetTitle; + private ProblemSetConfirmStatus confirmStatus; + private LocalDate publishedDate; private List problemThumbnailResponses; public ProblemSetSearchGetResponse( - String problemSetTitle, List problemThumbnailResponses + String problemSetTitle, ProblemSetConfirmStatus confirmStatus, LocalDate publishedDate, List problemThumbnailResponses ) { this.problemSetTitle = problemSetTitle; + this.confirmStatus = confirmStatus; + this.publishedDate = publishedDate; this.problemThumbnailResponses = problemThumbnailResponses; } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java index 52474a1..665da6a 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java @@ -3,6 +3,7 @@ import static com.moplus.moplus_server.domain.concept.domain.QConceptTag.conceptTag; import static com.moplus.moplus_server.domain.problem.domain.problem.QProblem.problem; import static com.moplus.moplus_server.domain.problemset.domain.QProblemSet.problemSet; +import static com.moplus.moplus_server.domain.publish.domain.QPublish.publish; import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetSearchGetResponse; import com.moplus.moplus_server.domain.problemset.dto.response.ProblemThumbnailResponse; @@ -25,6 +26,7 @@ public List search(String problemSetTitle, String p .from(problemSet) .leftJoin(problem).on(problem.id.in(problemSet.problemIds)) // 문제 세트 내 포함된 문항과 조인 .leftJoin(conceptTag).on(conceptTag.id.in(problem.conceptTagIds)) // 문제의 개념 태그 조인 + .leftJoin(publish).on(publish.problemSetId.eq(problemSet.id)) // 문제 세트와 발행 데이터 조인 .where( containsProblemSetTitle(problemSetTitle), containsProblemTitle(problemTitle), @@ -34,6 +36,8 @@ public List search(String problemSetTitle, String p .transform(GroupBy.groupBy(problemSet.id).list( Projections.constructor(ProblemSetSearchGetResponse.class, problemSet.title.value, + problemSet.confirmStatus, + publish.publishedDate, // 발행되지 않은 경우 null 반환 GroupBy.list( Projections.constructor(ProblemThumbnailResponse.class, problem.mainProblemImageUrl diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java index 20aae25..3392a8d 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java @@ -11,6 +11,9 @@ import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetGetResponse; import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSummaryResponse; import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; +import com.moplus.moplus_server.domain.publish.domain.Publish; +import com.moplus.moplus_server.domain.publish.repository.PublishRepository; +import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import lombok.RequiredArgsConstructor; @@ -25,11 +28,15 @@ public class ProblemSetGetService { private final ProblemRepository problemRepository; private final PracticeTestTagRepository practiceTestTagRepository; private final ConceptTagRepository conceptTagRepository; + private final PublishRepository publishRepository; @Transactional(readOnly = true) public ProblemSetGetResponse getProblemSet(Long problemSetId) { ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); + LocalDate publishedDate = publishRepository.findByProblemSetId(problemSetId) + .map(Publish::getPublishedDate) + .orElse(null); List problemSummaries = new ArrayList<>(); for (ProblemId problemId : problemSet.getProblemIds()) { @@ -41,6 +48,6 @@ public ProblemSetGetResponse getProblemSet(Long problemSetId) { .toList(); problemSummaries.add(ProblemSummaryResponse.of(problem, practiceTestTag.getName(), tagNames)); } - return ProblemSetGetResponse.of(problemSet, problemSummaries); + return ProblemSetGetResponse.of(problemSet, publishedDate, problemSummaries); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/repository/PublishRepository.java b/src/main/java/com/moplus/moplus_server/domain/publish/repository/PublishRepository.java index 0f7433d..0fb157d 100644 --- a/src/main/java/com/moplus/moplus_server/domain/publish/repository/PublishRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/publish/repository/PublishRepository.java @@ -5,6 +5,7 @@ import com.moplus.moplus_server.global.error.exception.NotFoundException; import java.time.LocalDate; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; public interface PublishRepository extends JpaRepository { @@ -13,4 +14,6 @@ public interface PublishRepository extends JpaRepository { default Publish findByIdElseThrow(Long publishId) { return findById(publishId).orElseThrow(() -> new NotFoundException(ErrorCode.PUBLISH_NOT_FOUND)); } + + Optional findByProblemSetId(Long problemSetId); } diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java index 9e05157..042dfa9 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java @@ -2,8 +2,12 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus; import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetSearchGetResponse; import com.moplus.moplus_server.domain.problemset.dto.response.ProblemThumbnailResponse; +import com.moplus.moplus_server.domain.publish.dto.request.PublishPostRequest; +import com.moplus.moplus_server.domain.publish.service.PublishSaveService; +import java.time.LocalDate; import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -21,6 +25,9 @@ public class ProblemSetSearchRepositoryCustomTest { @Autowired private ProblemSetSearchRepositoryCustom problemSetSearchRepository; + @Autowired + private PublishSaveService publishSaveService; + @Test void 문항세트_타이틀_일부_포함_검색() { // when @@ -37,8 +44,9 @@ public class ProblemSetSearchRepositoryCustomTest { List result = problemSetSearchRepository.search(null, "설명 1", null); // then - assertThat(result).hasSize(1); + assertThat(result).hasSize(2); assertThat(result.get(0).getProblemSetTitle()).isEqualTo("2025년 5월 고2 모의고사 문제 세트"); + assertThat(result.get(1).getProblemSetTitle()).isEqualTo("2025년 5월 고3 모의고사 문제 세트"); } @Test @@ -47,8 +55,9 @@ public class ProblemSetSearchRepositoryCustomTest { List result = problemSetSearchRepository.search(null, null, List.of("미분 개념")); // then - assertThat(result).hasSize(1); + assertThat(result).hasSize(2); assertThat(result.get(0).getProblemSetTitle()).isEqualTo("2025년 5월 고2 모의고사 문제 세트"); + assertThat(result.get(1).getProblemSetTitle()).isEqualTo("2025년 5월 고3 모의고사 문제 세트"); } @Test @@ -67,7 +76,7 @@ public class ProblemSetSearchRepositoryCustomTest { List result = problemSetSearchRepository.search(null, null, null); // then - assertThat(result).hasSize(1); + assertThat(result).hasSize(2); } @Test @@ -88,4 +97,33 @@ public class ProblemSetSearchRepositoryCustomTest { assertThat(problems.get(0).getMainProblemImageUrl()).isEqualTo("mainProblem.png1"); assertThat(problems.get(1).getMainProblemImageUrl()).isEqualTo("mainProblem.png2"); } + + @Test + void 발행되지_않은_문항세트는_NOT_CONFIRMED_테스트() { + // when + List result = problemSetSearchRepository.search("고2 모의고사", null, null); + + // then + assertThat(result).hasSize(1); + ProblemSetSearchGetResponse response = result.get(0); + + assertThat(response.getConfirmStatus()).isEqualTo(ProblemSetConfirmStatus.NOT_CONFIRMED); + assertThat(response.getPublishedDate()).isNull(); + } + + @Test + void 발행된_문항세트_발행날짜_테스트() { + // given + LocalDate publishDate = LocalDate.now().plusDays(1); + publishSaveService.createPublish(new PublishPostRequest(publishDate, 2L)); + + // when + List result = problemSetSearchRepository.search("고3 모의고사", null, null); + + // then + assertThat(result).hasSize(1); + ProblemSetSearchGetResponse response = result.get(0); + + assertThat(response.getPublishedDate()).isEqualTo(publishDate); + } } \ No newline at end of file diff --git a/src/test/java/com/moplus/moplus_server/domain/publish/service/PublishServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/publish/service/PublishServiceTest.java index 66f6cbe..463bf7d 100644 --- a/src/test/java/com/moplus/moplus_server/domain/publish/service/PublishServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/publish/service/PublishServiceTest.java @@ -80,17 +80,17 @@ void setUp() { void 월별_발행_조회_테스트() { // given publishSaveService.createPublish(new PublishPostRequest( - LocalDate.of(2025, 2, 10), + LocalDate.of(2025, 3, 10), 1L )); publishSaveService.createPublish(new PublishPostRequest( - LocalDate.of(2025, 2, 15), + LocalDate.of(2025, 3, 15), 1L )); // when - List publishList = publishGetService.getPublishMonth(2025, 2); + List publishList = publishGetService.getPublishMonth(2025, 3); // then assertThat(publishList).hasSize(2); diff --git a/src/test/resources/insert-problem-set.sql b/src/test/resources/insert-problem-set.sql index e9c8af1..051949c 100644 --- a/src/test/resources/insert-problem-set.sql +++ b/src/test/resources/insert-problem-set.sql @@ -4,8 +4,13 @@ DELETE FROM problem_set; -- 문제 세트 추가 INSERT INTO problem_set (problem_set_id, title, is_deleted, confirm_status) VALUES (1, '2025년 5월 고2 모의고사 문제 세트', false, 'NOT_CONFIRMED'); +INSERT INTO problem_set (problem_set_id, title, is_deleted, confirm_status) +VALUES (2, '2025년 5월 고3 모의고사 문제 세트', false, 'CONFIRMED'); -- 문제 세트에 포함된 문제 추가 INSERT INTO problem_set_problems (problem_set_id, problem_id, sequence) VALUES (1, '240520012001', 0), - (1, '240520012002', 1); \ No newline at end of file + (1, '240520012002', 1); +INSERT INTO problem_set_problems (problem_set_id, problem_id, sequence) +VALUES (2, '240520012001', 0), + (2, '240520012002', 1); \ No newline at end of file From 5e1e097183f816e3711fdb453c966347466f1e0f Mon Sep 17 00:00:00 2001 From: HongGit Date: Mon, 10 Feb 2025 01:13:14 +0900 Subject: [PATCH 3/6] =?UTF-8?q?[feat/#42]=20=EB=AC=B8=ED=95=AD=EC=84=B8?= =?UTF-8?q?=ED=8A=B8=20=EC=BB=A8=ED=8E=8C=20=EC=8B=9C=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EB=AC=B8=ED=95=ADid?= =?UTF-8?q?=20=EC=97=90=EB=9F=AC=EB=A9=94=EC=8B=9C=EC=A7=80=EC=97=90=20?= =?UTF-8?q?=ED=8F=AC=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/problemset/domain/ProblemSet.java | 13 ++++++++----- .../global/error/exception/ErrorCode.java | 2 +- .../domain/problemset/ProblemSetServiceTest.java | 1 + 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java index cfa041e..bcd4d16 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java @@ -65,11 +65,14 @@ public void deleteProblemSet() { public void toggleConfirm(List problems) { if(this.confirmStatus == ProblemSetConfirmStatus.NOT_CONFIRMED){ - // 문항 유효성 검사 - for (Problem problem : problems) { - if (!problem.isValid()) { - throw new InvalidValueException(ErrorCode.INVALID_CONFIRM_PROBLEM); - } + List invalidProblemIds = problems.stream() + .filter(problem -> !problem.isValid()) + .map(problem -> problem.getId().getId()) + .toList(); + if (!invalidProblemIds.isEmpty()) { + String message = ErrorCode.INVALID_CONFIRM_PROBLEM.getMessage() + + String.join("번 ", invalidProblemIds) + "번"; + throw new InvalidValueException(message, ErrorCode.INVALID_CONFIRM_PROBLEM); } } this.confirmStatus = this.confirmStatus.toggle(); diff --git a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java index 8395524..6b645b8 100644 --- a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java +++ b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java @@ -32,7 +32,7 @@ public enum ErrorCode { PROBLEM_ALREADY_EXIST(HttpStatus.CONFLICT, "해당 문제는 이미 존재합니다"), INVALID_MULTIPLE_CHOICE_ANSWER(HttpStatus.BAD_REQUEST, "객관식 문제의 정답은 1~5 사이의 숫자여야 합니다"), INVALID_SHORT_NUMBER_ANSWER(HttpStatus.BAD_REQUEST, "주관식 문제의 정답은 0~999 사이의 숫자여야 합니다"), - INVALID_CONFIRM_PROBLEM(HttpStatus.BAD_REQUEST, "문항의 모든 요소를 등록해야 컨펌을 완료할 수 있습니다."), + INVALID_CONFIRM_PROBLEM(HttpStatus.BAD_REQUEST, "유효하지 않은 문항들 : "), //새끼 문항 CHILD_PROBLEM_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 새끼 문제를 찾을 수 없습니다"), diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java index 50c5d4e..d682201 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java @@ -135,6 +135,7 @@ void setUp() { // when & then assertThatThrownBy(() -> problemSetUpdateService.toggleConfirmProblemSet(problemSetId)) .isInstanceOf(InvalidValueException.class) + .hasMessageContaining("24052001004번") // 메시지에 포함된 ID 확인 .hasMessageContaining(ErrorCode.INVALID_CONFIRM_PROBLEM.getMessage()); } From 2ba6044e08c3bfc01e4d560ef00a2d0eb6a4a0c0 Mon Sep 17 00:00:00 2001 From: HongGit Date: Tue, 11 Feb 2025 14:40:39 +0900 Subject: [PATCH 4/6] =?UTF-8?q?[fix/#42]=20=EB=B0=9C=ED=96=89=EB=82=A0?= =?UTF-8?q?=EC=A7=9C=20=EC=9C=A0=ED=9A=A8=EC=84=B1=EA=B2=80=EC=82=AC=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=EC=9C=BC=EB=A1=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/publish/domain/Publish.java | 8 ++++++++ .../publish/dto/request/PublishPostRequest.java | 13 ------------- .../domain/publish/service/PublishSaveService.java | 2 ++ 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/domain/Publish.java b/src/main/java/com/moplus/moplus_server/domain/publish/domain/Publish.java index 55a66ab..52aca88 100644 --- a/src/main/java/com/moplus/moplus_server/domain/publish/domain/Publish.java +++ b/src/main/java/com/moplus/moplus_server/domain/publish/domain/Publish.java @@ -1,6 +1,8 @@ package com.moplus.moplus_server.domain.publish.domain; import com.moplus.moplus_server.global.common.BaseEntity; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.InvalidValueException; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; @@ -34,4 +36,10 @@ public Publish(LocalDate publishedDate, Long problemSetId) { this.problemSetId = problemSetId; } + public void validatePublishedDate() { + // 발행 시점 다음날부터 발행 가능 + if (this.publishedDate.isBefore(LocalDate.now().plusDays(1))) { + throw new InvalidValueException(ErrorCode.INVALID_DATE_ERROR); + } + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/dto/request/PublishPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/publish/dto/request/PublishPostRequest.java index 10c74ae..a020f43 100644 --- a/src/main/java/com/moplus/moplus_server/domain/publish/dto/request/PublishPostRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/publish/dto/request/PublishPostRequest.java @@ -1,29 +1,16 @@ package com.moplus.moplus_server.domain.publish.dto.request; import com.moplus.moplus_server.domain.publish.domain.Publish; -import com.moplus.moplus_server.global.error.exception.ErrorCode; -import com.moplus.moplus_server.global.error.exception.InvalidValueException; import java.time.LocalDate; public record PublishPostRequest( LocalDate publishedDate, Long problemSetId ) { - public PublishPostRequest { - validatePublishedDate(publishedDate); - } - public Publish toEntity() { return Publish.builder() .publishedDate(this.publishedDate) .problemSetId(this.problemSetId) .build(); } - - private static void validatePublishedDate(LocalDate publishedDate) { - // 발행 시점 다음날부터 발행 가능 - if (publishedDate.isBefore(LocalDate.now().plusDays(1))) { - throw new InvalidValueException(ErrorCode.INVALID_DATE_ERROR); - } - } } diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishSaveService.java b/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishSaveService.java index 9da928f..d134080 100644 --- a/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishSaveService.java +++ b/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishSaveService.java @@ -19,6 +19,8 @@ public class PublishSaveService { public Long createPublish(PublishPostRequest request) { problemSetRepository.existsConfirmedActiveByIdElseThrow(request.problemSetId()); Publish publish = request.toEntity(); + // 발행날짜 유효성 검사 + publish.validatePublishedDate(); return publishRepository.save(publish).getId(); } } From 909bf4f845aacf62b4f40fba13d063b9901fefdc Mon Sep 17 00:00:00 2001 From: HongGit Date: Tue, 11 Feb 2025 14:48:30 +0900 Subject: [PATCH 5/6] =?UTF-8?q?[fix/42]=20=EB=B0=9C=ED=96=89=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=BF=BC=EB=A6=AC=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../publish/service/PublishGetService.java | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishGetService.java b/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishGetService.java index e299e6b..6cc6c52 100644 --- a/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishGetService.java +++ b/src/main/java/com/moplus/moplus_server/domain/publish/service/PublishGetService.java @@ -10,6 +10,7 @@ import com.moplus.moplus_server.global.error.exception.InvalidValueException; import java.time.LocalDate; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -33,17 +34,32 @@ public List getPublishMonth(int year, int month) { // 주어진 월에 해당하는 모든 Publish 조회 List publishes = publishRepository.findByPublishedDateBetween(startDate, endDate); - // 데이터를 day 기준으로 매핑 + // 한 번의 쿼리로 모든 ProblemSet 조회 + Map problemSetMap = getProblemSetMap(publishes); + return publishes.stream() - .map(publish -> { - ProblemSet problemSet = problemSetRepository.findByIdElseThrow(publish.getProblemSetId()); - PublishProblemSetResponse problemSetResponse = PublishProblemSetResponse.of(problemSet); - - return PublishMonthGetResponse.of( - publish.getPublishedDate().getDayOfMonth(), - problemSetResponse - ); - }) + .map(publish -> convertToResponse(publish, problemSetMap)) + .collect(Collectors.toList()); + } + + private Map getProblemSetMap(List publishes) { + List problemSetIds = publishes.stream() + .map(Publish::getProblemSetId) + .distinct() .collect(Collectors.toList()); + + return problemSetRepository.findAllById(problemSetIds).stream() + .collect(Collectors.toMap(ProblemSet::getId, problemSet -> problemSet)); + } + + private PublishMonthGetResponse convertToResponse(Publish publish, Map problemSetMap) { + ProblemSet problemSet = problemSetMap.get(publish.getProblemSetId()); + if (problemSet == null) { + throw new InvalidValueException(ErrorCode.PROBLEM_SET_NOT_FOUND); + } + return PublishMonthGetResponse.of( + publish.getPublishedDate().getDayOfMonth(), + PublishProblemSetResponse.of(problemSet) + ); } } From 757b6a0d3733431a0fe3b211924e9e790182c41b Mon Sep 17 00:00:00 2001 From: HongGit Date: Tue, 11 Feb 2025 15:09:52 +0900 Subject: [PATCH 6/6] =?UTF-8?q?[feat/#42]=20=EB=B0=9C=ED=96=89=EC=9A=A9=20?= =?UTF-8?q?=EC=BB=A8=ED=8E=8C=EB=90=9C=20=EB=AC=B8=ED=95=AD=EC=84=B8?= =?UTF-8?q?=ED=8A=B8=20=EA=B2=80=EC=83=89=20api=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProblemSetSearchController.java | 16 ++++- .../ProblemSetSearchRepositoryCustom.java | 28 +++++++++ .../ProblemSetSearchRepositoryCustomTest.java | 61 +++++++++++++++++++ 3 files changed, 103 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java index 9a318c1..09c0b11 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/controller/ProblemSetSearchController.java @@ -3,7 +3,6 @@ import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetSearchGetResponse; import com.moplus.moplus_server.domain.problemset.repository.ProblemSetSearchRepositoryCustom; -import com.moplus.moplus_server.domain.publish.repository.PublishRepository; import io.swagger.v3.oas.annotations.Operation; import java.util.List; import lombok.RequiredArgsConstructor; @@ -19,7 +18,6 @@ public class ProblemSetSearchController { private final ProblemSetSearchRepositoryCustom problemSetSearchRepository; - private final PublishRepository publishRepository; @GetMapping("/search") @Operation( @@ -34,4 +32,18 @@ public ResponseEntity> search( List problemSets = problemSetSearchRepository.search(problemSetTitle, problemTitle, conceptTagNames); return ResponseEntity.ok(problemSets); } + + @GetMapping("/confirm/search") + @Operation( + summary = "발행용 문항세트 검색", + description = "문항세트 타이틀, 문항세트 내 포함된 개념태그, 문항세트 내 포함된 문항 타이틀로 검색합니다. 발행상태가 CONFIRMED 문항세트만 조회됩니다.." + ) + public ResponseEntity> confirmSearch( + @RequestParam(value = "problemSetTitle", required = false) String problemSetTitle, + @RequestParam(value = "problemTitle", required = false) String problemTitle, + @RequestParam(value = "conceptTagNames", required = false) List conceptTagNames + ) { + List problemSets = problemSetSearchRepository.confirmSearch(problemSetTitle, problemTitle, conceptTagNames); + return ResponseEntity.ok(problemSets); + } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java index 665da6a..9bf6d22 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java @@ -2,6 +2,7 @@ import static com.moplus.moplus_server.domain.concept.domain.QConceptTag.conceptTag; import static com.moplus.moplus_server.domain.problem.domain.problem.QProblem.problem; +import static com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus.CONFIRMED; import static com.moplus.moplus_server.domain.problemset.domain.QProblemSet.problemSet; import static com.moplus.moplus_server.domain.publish.domain.QPublish.publish; @@ -47,6 +48,33 @@ public List search(String problemSetTitle, String p )); } + public List confirmSearch(String problemSetTitle, String problemTitle, List conceptTagNames) { + return queryFactory + .from(problemSet) + .leftJoin(problem).on(problem.id.in(problemSet.problemIds)) // 문제 세트 내 포함된 문항과 조인 + .leftJoin(conceptTag).on(conceptTag.id.in(problem.conceptTagIds)) // 문제의 개념 태그 조인 + .leftJoin(publish).on(publish.problemSetId.eq(problemSet.id)) // 문제 세트와 발행 데이터 조인 + .where( + problemSet.confirmStatus.eq(CONFIRMED), + containsProblemSetTitle(problemSetTitle), + containsProblemTitle(problemTitle), + containsConceptTagNames(conceptTagNames) + ) + .distinct() + .transform(GroupBy.groupBy(problemSet.id).list( + Projections.constructor(ProblemSetSearchGetResponse.class, + problemSet.title.value, + problemSet.confirmStatus, + publish.publishedDate, // 발행되지 않은 경우 null 반환 + GroupBy.list( + Projections.constructor(ProblemThumbnailResponse.class, + problem.mainProblemImageUrl + ) + ) + ) + )); + } + private BooleanExpression containsProblemSetTitle(String problemSetTitle) { return (problemSetTitle == null || problemSetTitle.isEmpty()) ? null : problemSet.title.value.containsIgnoreCase(problemSetTitle); } diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java index 042dfa9..5085caf 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java @@ -5,6 +5,7 @@ import com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus; import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetSearchGetResponse; import com.moplus.moplus_server.domain.problemset.dto.response.ProblemThumbnailResponse; +import com.moplus.moplus_server.domain.problemset.service.ProblemSetUpdateService; import com.moplus.moplus_server.domain.publish.dto.request.PublishPostRequest; import com.moplus.moplus_server.domain.publish.service.PublishSaveService; import java.time.LocalDate; @@ -28,6 +29,10 @@ public class ProblemSetSearchRepositoryCustomTest { @Autowired private PublishSaveService publishSaveService; + @Autowired + private ProblemSetUpdateService problemSetUpdateService; + + @Test void 문항세트_타이틀_일부_포함_검색() { // when @@ -126,4 +131,60 @@ public class ProblemSetSearchRepositoryCustomTest { assertThat(response.getPublishedDate()).isEqualTo(publishDate); } + + @Test + void 컴펌된_문항세트_검색_테스트() { + // given: CONFIRMED 상태의 문제 세트만 발행 + LocalDate publishDate = LocalDate.now(); + publishSaveService.createPublish(new PublishPostRequest(publishDate.plusDays(5), 2L)); // CONFIRMED 상태 + + // when: publishSearch 실행 (CONFIRMED 상태만 검색되어야 함) + List result = problemSetSearchRepository.confirmSearch( + "고", + "설명", + List.of("미분 개념") + ); + + // then + assertThat(result).isNotEmpty(); + assertThat(result).allSatisfy(response -> + assertThat(response.getConfirmStatus()).isEqualTo(ProblemSetConfirmStatus.CONFIRMED) + ); + } + + @Test + void 컴펌되지_않은_문항세트_검색_결과없음_테스트() { + // given: 발행되지 않은 문제 세트 존재 + problemSetUpdateService.toggleConfirmProblemSet(2L); + + // when: 발행된 문제 세트만 조회하는 publishSearch 실행 + List result = problemSetSearchRepository.confirmSearch( + null, + null, + null + ); + + // then: CONFIRMED 상태가 없는 경우, 결과가 비어 있어야 함 + assertThat(result).isEmpty(); + } + + @Test + void 컨펌된_문항세트_정확한_발행날짜_검증() { + // given + LocalDate publishDate = LocalDate.now().plusDays(1); + publishSaveService.createPublish(new PublishPostRequest(publishDate, 2L)); // 발행 처리 + + // when + List result = problemSetSearchRepository.confirmSearch( + "고3 모의고사", + null, + null + ); + + // then + assertThat(result).hasSize(1); + ProblemSetSearchGetResponse response = result.get(0); + + assertThat(response.getPublishedDate()).isEqualTo(publishDate); + } } \ No newline at end of file