diff --git a/src/main/java/umc/domain/controller/MissionController.java b/src/main/java/umc/domain/controller/MissionController.java deleted file mode 100644 index fe1f24c..0000000 --- a/src/main/java/umc/domain/controller/MissionController.java +++ /dev/null @@ -1,30 +0,0 @@ -package umc.domain.controller; - -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -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; -import umc.domain.mission.dto.req.MissionReqDTO; -import umc.domain.mission.dto.res.MissionResDTO; -import umc.domain.mission.service.command.MissionCommandService; -import umc.global.apiPayload.ApiResponse; -import umc.global.apiPayload.code.GeneralSuccessCode; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/missions") -public class MissionController { - private final MissionCommandService missionCommandService; - - @PostMapping("/") - public ApiResponse addMission( - @RequestBody @Valid MissionReqDTO.AddMissionReqDTO req - ){ - return ApiResponse.onSuccess( - GeneralSuccessCode.OK, - missionCommandService.addMission(req) - ); - } -} diff --git a/src/main/java/umc/domain/mission/controller/MissionController.java b/src/main/java/umc/domain/mission/controller/MissionController.java new file mode 100644 index 0000000..38dd738 --- /dev/null +++ b/src/main/java/umc/domain/mission/controller/MissionController.java @@ -0,0 +1,53 @@ +package umc.domain.mission.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import umc.domain.mission.dto.req.MissionReqDTO; +import umc.domain.mission.dto.res.MissionResDTO; +import umc.domain.mission.entity.Mission; +import umc.domain.mission.service.command.MissionCommandService; +import umc.domain.mission.service.query.MissionQueryService; +import umc.global.annotation.CheckPage; +import umc.global.apiPayload.ApiResponse; +import umc.global.apiPayload.code.GeneralSuccessCode; + +@RestController +@RequiredArgsConstructor +@Validated +@RequestMapping("/api/missions") +public class MissionController { + private final MissionCommandService missionCommandService; + private final MissionQueryService missionQueryService; + + @PostMapping("/") + public ApiResponse addMission( + @RequestBody @Valid MissionReqDTO.AddMissionReqDTO req + ){ + return ApiResponse.onSuccess( + GeneralSuccessCode.OK, + missionCommandService.addMission(req) + ); + } + + @GetMapping("/store/{storeId}") + @Operation(summary = "특정 가게의 미션 목록 조회 API", description = "특정 가게의 미션 목록을 조회합니다. page는 1부터 시작합니다.") + @Parameters({ + @Parameter(name = "storeId", description = "가게의 아이디, path variable 입니다!"), + @Parameter(name = "page", description = "페이지 번호 (1 이상)") + }) + public ApiResponse getMissionByStore( + @PathVariable Long storeId, + @CheckPage @RequestParam(name = "page") Integer page + ){ + return ApiResponse.onSuccess( + GeneralSuccessCode.OK, + missionQueryService.getMissionByStoreListResDTO(storeId, page) + ); + + } +} diff --git a/src/main/java/umc/domain/mission/converter/MissionConverter.java b/src/main/java/umc/domain/mission/converter/MissionConverter.java index d82e1a7..edec218 100644 --- a/src/main/java/umc/domain/mission/converter/MissionConverter.java +++ b/src/main/java/umc/domain/mission/converter/MissionConverter.java @@ -1,10 +1,14 @@ package umc.domain.mission.converter; +import org.springframework.data.domain.Page; import umc.domain.mission.dto.req.MissionReqDTO; import umc.domain.mission.dto.res.MissionResDTO; import umc.domain.mission.entity.Mission; import umc.domain.store.entity.Store; +import java.util.List; +import java.util.stream.Collectors; + public class MissionConverter { //DTO -> Entity @@ -26,4 +30,32 @@ public static MissionResDTO.AddMissionResDTO toAddMissionResDTO(Mission mission) .createdAt(mission.getCreatedAt()) .build(); } + + //Mission Entity -> MissionByStoreResDTO + public static MissionResDTO.MissionByStoreResDTO toMissionByStoreResDTO(Mission mission){ + return MissionResDTO.MissionByStoreResDTO.builder() + .missionId(mission.getId()) + .rewardPoint(mission.getRewardPoint()) + .baseAmount(mission.getBaseAmount()) + .deadline(mission.getDeadline()) + .isActive(mission.getIsActive()) + .build(); + } + + //Page -> MissionByStoreListResDTO + public static MissionResDTO.MissionByStoreListResDTO toMissionByStoreListResDTO(Page missionPage){ + List missionList = missionPage.getContent().stream() + .map(MissionConverter::toMissionByStoreResDTO) + .collect(Collectors.toList()); + + return MissionResDTO.MissionByStoreListResDTO.builder() + .missionList(missionList) + .listSize(missionPage.getSize()) + .totalPage(missionPage.getTotalPages()) + .totalElements(missionPage.getTotalElements()) + .isFirst(missionPage.isFirst()) + .isLast(missionPage.isLast()) + .build(); + + } } diff --git a/src/main/java/umc/domain/mission/dto/req/MissionReqDTO.java b/src/main/java/umc/domain/mission/dto/req/MissionReqDTO.java index d2b3520..679ae4d 100644 --- a/src/main/java/umc/domain/mission/dto/req/MissionReqDTO.java +++ b/src/main/java/umc/domain/mission/dto/req/MissionReqDTO.java @@ -25,4 +25,5 @@ public record AddMissionReqDTO( LocalDate deadline ){} + } diff --git a/src/main/java/umc/domain/mission/dto/res/MissionResDTO.java b/src/main/java/umc/domain/mission/dto/res/MissionResDTO.java index 215bfb4..2a31668 100644 --- a/src/main/java/umc/domain/mission/dto/res/MissionResDTO.java +++ b/src/main/java/umc/domain/mission/dto/res/MissionResDTO.java @@ -2,7 +2,9 @@ import lombok.Builder; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.List; public class MissionResDTO { @@ -11,4 +13,24 @@ public record AddMissionResDTO( Long missionId, LocalDateTime createdAt ){} + + @Builder + public record MissionByStoreListResDTO( + List missionList, + Integer listSize, //페이징 단위 + Integer totalPage, + Long totalElements, + Boolean isFirst, + Boolean isLast + + ){} + + @Builder + public record MissionByStoreResDTO( + Long missionId, + Integer rewardPoint, + Integer baseAmount, + LocalDate deadline, + Boolean isActive + ){} } diff --git a/src/main/java/umc/domain/mission/repository/MissionRepository.java b/src/main/java/umc/domain/mission/repository/MissionRepository.java index 68c56d5..5d3b50d 100644 --- a/src/main/java/umc/domain/mission/repository/MissionRepository.java +++ b/src/main/java/umc/domain/mission/repository/MissionRepository.java @@ -6,6 +6,7 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import umc.domain.mission.entity.Mission; +import umc.domain.store.entity.Store; import umc.domain.user.enums.MissionStatus; import java.util.List; @@ -40,4 +41,6 @@ Page findAvailableMissionsByRegion( @Param("statuses")List statuses, Pageable pageable ); + + Page findAllByStore(Store store, Pageable pageable); } diff --git a/src/main/java/umc/domain/mission/service/query/MissionQueryService.java b/src/main/java/umc/domain/mission/service/query/MissionQueryService.java new file mode 100644 index 0000000..3ec9ffd --- /dev/null +++ b/src/main/java/umc/domain/mission/service/query/MissionQueryService.java @@ -0,0 +1,7 @@ +package umc.domain.mission.service.query; + +import umc.domain.mission.dto.res.MissionResDTO; + +public interface MissionQueryService { + MissionResDTO.MissionByStoreListResDTO getMissionByStoreListResDTO(Long storeId, Integer page); +} diff --git a/src/main/java/umc/domain/mission/service/query/MissionQueryServiceImpl.java b/src/main/java/umc/domain/mission/service/query/MissionQueryServiceImpl.java new file mode 100644 index 0000000..26531a9 --- /dev/null +++ b/src/main/java/umc/domain/mission/service/query/MissionQueryServiceImpl.java @@ -0,0 +1,35 @@ +package umc.domain.mission.service.query; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; +import umc.domain.mission.converter.MissionConverter; +import umc.domain.mission.dto.res.MissionResDTO; +import umc.domain.mission.entity.Mission; +import umc.domain.mission.repository.MissionRepository; +import umc.domain.store.entity.Store; +import umc.domain.store.exception.StoreException; +import umc.domain.store.exception.code.StoreErrorCode; +import umc.domain.store.repository.StoreRepository; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class MissionQueryServiceImpl implements MissionQueryService{ + private final MissionRepository missionRepository; + private final StoreRepository storeRepository; + + @Override + public MissionResDTO.MissionByStoreListResDTO getMissionByStoreListResDTO(Long storeId, Integer page){ + + Store store = storeRepository.findById(storeId).orElseThrow(()-> new StoreException(StoreErrorCode.NOT_FOUND)); + PageRequest pageRequest = PageRequest.of(page-1,10); + Page missionPage = missionRepository.findAllByStore(store, pageRequest); + + return MissionConverter.toMissionByStoreListResDTO(missionPage); + + } +} diff --git a/src/main/java/umc/domain/review/controller/ReviewController.java b/src/main/java/umc/domain/review/controller/ReviewController.java index 28bce18..42b20a1 100644 --- a/src/main/java/umc/domain/review/controller/ReviewController.java +++ b/src/main/java/umc/domain/review/controller/ReviewController.java @@ -1,12 +1,17 @@ package umc.domain.review.controller; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import umc.domain.review.dto.req.ReviewReqDTO; import umc.domain.review.dto.res.ReviewResDTO; +import umc.domain.review.exception.code.ReviewSuccessCode; import umc.domain.review.service.command.ReviewCommandService; import umc.domain.review.service.query.ReviewQueryService; +import umc.global.annotation.CheckPage; import umc.global.apiPayload.ApiResponse; import umc.global.apiPayload.code.GeneralSuccessCode; @@ -14,13 +19,14 @@ @RestController @RequiredArgsConstructor +@Validated @RequestMapping("/api/reviews") -public class ReviewController { +public class ReviewController implements ReviewControllerDocs{ private final ReviewQueryService reviewQueryService; private final ReviewCommandService reviewCommandService; @GetMapping("/search") //워크북 실습용 API - public ApiResponse> searchReview( + public ApiResponse> searchReview( @RequestParam String query, @RequestParam String type ){ @@ -30,13 +36,24 @@ public ApiResponse> searchReview( ); } + //가게의 리뷰 목록 조회 + @GetMapping("/reviews") + @Override + public ApiResponse getReviews( + @RequestParam String storeName, + @RequestParam(defaultValue = "1") Integer page + ){ + ReviewSuccessCode code = ReviewSuccessCode.FOUND; + return ApiResponse.onSuccess(code,reviewQueryService.findReview(storeName,page)); + } + //과제(미션)용 API @GetMapping("/my") -public ApiResponse>getMyReviews( +public ApiResponse>getMyReviews( @RequestParam(name = "userId")Long userId, @RequestParam(name = "storeId", required = false) Long storeId, - @RequestParam(name = "starFloor", required = false) Integer starFloor + @RequestParam(name = "starFloor", required = false) Float starFloor ) { return ApiResponse.onSuccess( GeneralSuccessCode.OK, @@ -55,6 +72,17 @@ public ApiResponse> searchReview( ); } + @GetMapping("/my/list") + @Override + public ApiResponse getMyReviewList( + @CheckPage @RequestParam(name="page") Integer page + ){ + return ApiResponse.onSuccess( + GeneralSuccessCode.OK, + reviewQueryService.getMyReviewList(page) + ); + } + } diff --git a/src/main/java/umc/domain/review/controller/ReviewControllerDocs.java b/src/main/java/umc/domain/review/controller/ReviewControllerDocs.java new file mode 100644 index 0000000..3b6aaee --- /dev/null +++ b/src/main/java/umc/domain/review/controller/ReviewControllerDocs.java @@ -0,0 +1,40 @@ +package umc.domain.review.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.web.bind.annotation.RequestParam; +import umc.domain.review.dto.res.ReviewResDTO; +import umc.global.annotation.CheckPage; +import umc.global.apiPayload.ApiResponse; +public interface ReviewControllerDocs { + @Operation( + summary = "가게의 리뷰 목록 조회 API By 박콩(개발 중)", + description = "특정 가게의 리뷰를 모두 조회합니다. 페이지네이션으로 제공합니다." + ) + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200",description = "성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "실패") + }) + @Parameters({ + @Parameter(name="storeName", description = "가게 이름"), + @Parameter(name="page",description = "페이지 번호") + }) + ApiResponse getReviews( + @RequestParam(name="storeName") String storeName, + @RequestParam(name="page") Integer page + ); + + @Operation(summary = "내가 작성한 리뷰 목록 조회 API", description = "나의 리뷰 목록을 조회합니다. page는 1부터 시작") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "페이지 번호가 1보다 작을 때 발생") + }) + @Parameters({ + @Parameter(name = "page", description = "페이지 번호 (1 이상)") + }) + public ApiResponse getMyReviewList( + @CheckPage @RequestParam(name = "page") Integer page // 커스텀 어노테이션 적용! + ); +} diff --git a/src/main/java/umc/domain/review/converter/ReviewConverter.java b/src/main/java/umc/domain/review/converter/ReviewConverter.java index 969a104..efe60b7 100644 --- a/src/main/java/umc/domain/review/converter/ReviewConverter.java +++ b/src/main/java/umc/domain/review/converter/ReviewConverter.java @@ -1,21 +1,24 @@ package umc.domain.review.converter; +import org.springframework.data.domain.Page; import umc.domain.review.dto.req.ReviewReqDTO; import umc.domain.review.dto.res.ReviewResDTO; import umc.domain.review.entity.Review; import umc.domain.store.entity.Store; -import umc.domain.test.dto.res.TestResDTO; import umc.domain.user.entity.User; import java.time.LocalDate; +import java.util.List; +import java.util.stream.Collectors; public class ReviewConverter { - public static ReviewResDTO.ReviewPreviewDTO toReviewPreviewDTO(Review review){ - return ReviewResDTO.ReviewPreviewDTO.builder() + //단건 변환 + public static ReviewResDTO.ReviewPreviewWorkbookDTO toReviewPreviewBookDTO(Review review){ + return ReviewResDTO.ReviewPreviewWorkbookDTO.builder() .reviewId(review.getId()) .storeName(review.getStore().getName()) - .rating(review.getRating()) + .rating(review.getRating().floatValue()) .content(review.getContent()) .createdAt(review.getCreatedAt().toLocalDate()) .build(); @@ -42,4 +45,42 @@ public static ReviewResDTO.AddReviewResDTO toAddReviewResDTO(Review review){ .build(); } + //result -> DTO + public static ReviewResDTO.ReviewPreViewListDTO toReviewPreviewListDTO( + Page result + ){ + return ReviewResDTO.ReviewPreViewListDTO.builder() + .reviewList(result.getContent().stream().map(ReviewConverter::toReviewPreviewDTO).toList()) //실제 리뷰 내용물 + .listSize(result.getSize()) //한 페이지에 몇 개씩 보여주는지 + .totalPage(result.getTotalPages()) // 전체 페이지가 몇 쪽인지 + .totalElements(result.getTotalElements()) //전체 리뷰 개수가 몇 개인지 + .isFirst(result.isFirst()) //지금 보고 있는 게 첫 페이지인지 + .isLast(result.isLast()) + .build(); + } + + public static ReviewResDTO.ReviewPreviewDTO toReviewPreviewDTO(Review review){ + return ReviewResDTO.ReviewPreviewDTO.builder() + .ownerNickname(review.getUser().getName()) + .rating(review.getRating()) + .content(review.getContent()) + .createdAt(LocalDate.from(review.getCreatedAt())) + .build(); + } + + //Page -> 내가 작성한 리뷰 목록 DTO로 변환 + public static ReviewResDTO.MyReviewPreviewListDTO toMyReviewPreviewListDTO(Page reviewPage){ + List myReviewDTOList = reviewPage.getContent().stream() + .map(ReviewConverter::toReviewPreviewBookDTO) + .collect(Collectors.toList()); + + return ReviewResDTO.MyReviewPreviewListDTO.builder() + .reviewList(myReviewDTOList) + .listSize(reviewPage.getSize()) + .totalPage(reviewPage.getTotalPages()) + .totalElements(reviewPage.getTotalElements()) + .isFirst(reviewPage.isFirst()) + .isLast(reviewPage.isLast()) + .build(); + } } diff --git a/src/main/java/umc/domain/review/dto/req/ReviewReqDTO.java b/src/main/java/umc/domain/review/dto/req/ReviewReqDTO.java index 1fd5586..4d9b8cb 100644 --- a/src/main/java/umc/domain/review/dto/req/ReviewReqDTO.java +++ b/src/main/java/umc/domain/review/dto/req/ReviewReqDTO.java @@ -14,7 +14,7 @@ public record AddReviewDTO( @NotNull @Min(value = 0, message = "별점은 0점 이상이어야 합니다.") @Max(value = 5, message = "별점은 5점 이하여야 합니다.") - Integer rating, + Float rating, String content ){} diff --git a/src/main/java/umc/domain/review/dto/res/ReviewResDTO.java b/src/main/java/umc/domain/review/dto/res/ReviewResDTO.java index 884237f..d49ace0 100644 --- a/src/main/java/umc/domain/review/dto/res/ReviewResDTO.java +++ b/src/main/java/umc/domain/review/dto/res/ReviewResDTO.java @@ -5,6 +5,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.List; public class ReviewResDTO { @@ -13,10 +14,10 @@ public class ReviewResDTO { //그래서 Response와 관련된 Review DTO들을 하나의 클래스로 묶어버리는 방식 @Builder @Getter - public static class ReviewPreviewDTO{ + public static class ReviewPreviewWorkbookDTO { private Long reviewId; private String storeName; - private Integer rating; + private Float rating; private String content; private LocalDate createdAt; } @@ -26,9 +27,40 @@ public record AddReviewResDTO( Long reviewId, Long storeId, String content, - Integer rating, + Float rating, LocalDateTime createdAt ){} + @Builder + public record ReviewPreViewListDTO( + List reviewList, + Integer listSize, + Integer totalPage, + Long totalElements, + Boolean isFirst, + Boolean isLast + ){} + + @Builder + public record ReviewPreviewDTO( + String ownerNickname, + Float rating, + String content, + LocalDate createdAt + ){} + + //내 리뷰 목록(페이징 정보 포함) + @Builder + public record MyReviewPreviewListDTO( + List reviewList, + Integer listSize, + Integer totalPage, + Long totalElements, + Boolean isFirst, + Boolean isLast + ){} + + + } diff --git a/src/main/java/umc/domain/review/entity/Review.java b/src/main/java/umc/domain/review/entity/Review.java index 56f6920..a5b6024 100644 --- a/src/main/java/umc/domain/review/entity/Review.java +++ b/src/main/java/umc/domain/review/entity/Review.java @@ -27,7 +27,7 @@ public class Review extends BaseEntity { private Store store; @Column(nullable = false) - private Integer rating; + private Float rating; @Column(nullable = false) private String content; diff --git a/src/main/java/umc/domain/review/exception/code/ReviewErrorCode.java b/src/main/java/umc/domain/review/exception/code/ReviewErrorCode.java index e010b79..13baf60 100644 --- a/src/main/java/umc/domain/review/exception/code/ReviewErrorCode.java +++ b/src/main/java/umc/domain/review/exception/code/ReviewErrorCode.java @@ -8,9 +8,7 @@ @Getter @AllArgsConstructor public enum ReviewErrorCode implements BaseErrorCode { - INVALID_STAR_VALUE(HttpStatus.BAD_REQUEST, "REVIEW-4002", "별점은 숫자여야 합니다."), - REVIEW_NOT_FOUND(HttpStatus.NOT_FOUND, "REVIEW-404", "리뷰를 찾을 수 없습니다."); - //워크북때 배운거 쓰려고 ErrorCode 만들었는데 생각해보니 안쓰는게 맞는거같아서 Service에서 사용 안했습니다 + NOT_FOUND(HttpStatus.NOT_FOUND, "REVIEW404-1", "리뷰를 찾을 수 없습니다."); private final HttpStatus status; private final String code; diff --git a/src/main/java/umc/domain/review/exception/code/ReviewSuccessCode.java b/src/main/java/umc/domain/review/exception/code/ReviewSuccessCode.java new file mode 100644 index 0000000..6419e14 --- /dev/null +++ b/src/main/java/umc/domain/review/exception/code/ReviewSuccessCode.java @@ -0,0 +1,18 @@ +package umc.domain.review.exception.code; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; +import umc.global.apiPayload.code.BaseErrorCode; +import umc.global.apiPayload.code.BaseSuccessCode; + +@Getter +@AllArgsConstructor +public enum ReviewSuccessCode implements BaseSuccessCode { + + FOUND(HttpStatus.FOUND, "REVIEW200_1","리뷰를 찾는데 성공했습니다."), + ; + private final HttpStatus status; + private final String code; + private final String message; +} diff --git a/src/main/java/umc/domain/review/repository/ReviewQueryDsl.java b/src/main/java/umc/domain/review/repository/ReviewQueryDsl.java index e7375b9..f011f6a 100644 --- a/src/main/java/umc/domain/review/repository/ReviewQueryDsl.java +++ b/src/main/java/umc/domain/review/repository/ReviewQueryDsl.java @@ -13,5 +13,5 @@ List searchReview( ); //워크북 코드 //과제 코드 - List findMyReviewsFiltered(Long userId, Long storeId, Integer starFloor); + List findMyReviewsFiltered(Long userId, Long storeId, Float starFloor); } diff --git a/src/main/java/umc/domain/review/repository/ReviewQueryDslImpl.java b/src/main/java/umc/domain/review/repository/ReviewQueryDslImpl.java index b58f09e..06842d9 100644 --- a/src/main/java/umc/domain/review/repository/ReviewQueryDslImpl.java +++ b/src/main/java/umc/domain/review/repository/ReviewQueryDslImpl.java @@ -36,7 +36,7 @@ public List searchReview(Predicate predicate){ //과제(미션 코드) @Override - public List findMyReviewsFiltered(Long userId, Long storeId, Integer starFloor){ + public List findMyReviewsFiltered(Long userId, Long storeId, Float starFloor){ QReview review = QReview.review; QStore store = QStore.store; @@ -67,7 +67,7 @@ private BooleanExpression storeIdEq(Long storeId) { return storeId != null ? QStore.store.id.eq(storeId) : null; } - private BooleanExpression ratingEq(Integer starFloor) { + private BooleanExpression ratingEq(Float starFloor) { // starFloor가 null이면 where절에 추가되지 않음 (null 반환) return starFloor != null ? QReview.review.rating.eq(starFloor) : null; } diff --git a/src/main/java/umc/domain/review/repository/ReviewRepository.java b/src/main/java/umc/domain/review/repository/ReviewRepository.java index a7875d3..23f369b 100644 --- a/src/main/java/umc/domain/review/repository/ReviewRepository.java +++ b/src/main/java/umc/domain/review/repository/ReviewRepository.java @@ -2,15 +2,20 @@ import com.querydsl.core.types.Predicate; import com.querydsl.jpa.impl.JPAQueryFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import umc.domain.review.entity.QReview; import umc.domain.review.entity.Review; +import umc.domain.store.entity.Store; +import umc.domain.user.entity.User; import java.util.List; public interface ReviewRepository extends JpaRepository , ReviewQueryDsl{ + Page findAllByStore(Store store, PageRequest pageRequest); - //save 구현 필요 X 이미 있는 함수. 추후 생길 Service Layer에서 reviewRepository.save(newReview) 형태로 리뷰 insert - + Page findAllByUser(User user, Pageable pageable); } diff --git a/src/main/java/umc/domain/review/service/query/ReviewQueryService.java b/src/main/java/umc/domain/review/service/query/ReviewQueryService.java index 05e986c..6732c40 100644 --- a/src/main/java/umc/domain/review/service/query/ReviewQueryService.java +++ b/src/main/java/umc/domain/review/service/query/ReviewQueryService.java @@ -5,6 +5,13 @@ import java.util.List; public interface ReviewQueryService { - List searchReview(String query, String type); - List getMyReviews(Long userId, Long storeId, Integer starFloor); + List searchReview(String query, String type); + List getMyReviews(Long userId, Long storeId, Float starFloor); + + + ReviewResDTO.ReviewPreViewListDTO findReview(String storeName, Integer page + ); + + ReviewResDTO.MyReviewPreviewListDTO getMyReviewList(Integer page); + } diff --git a/src/main/java/umc/domain/review/service/query/ReviewQueryServiceImpl.java b/src/main/java/umc/domain/review/service/query/ReviewQueryServiceImpl.java index 956e184..85210fb 100644 --- a/src/main/java/umc/domain/review/service/query/ReviewQueryServiceImpl.java +++ b/src/main/java/umc/domain/review/service/query/ReviewQueryServiceImpl.java @@ -2,26 +2,40 @@ import com.querydsl.core.BooleanBuilder; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import umc.domain.region.entity.QRegion; import umc.domain.review.converter.ReviewConverter; import umc.domain.review.dto.res.ReviewResDTO; import umc.domain.review.entity.QReview; import umc.domain.review.entity.Review; import umc.domain.review.repository.ReviewRepository; +import umc.domain.store.entity.Store; +import umc.domain.store.exception.StoreException; +import umc.domain.store.exception.code.StoreErrorCode; +import umc.domain.store.repository.StoreRepository; +import umc.domain.user.entity.User; +import umc.domain.user.exception.UserException; +import umc.domain.user.exception.code.UserErrorCode; +import umc.domain.user.repository.UserRepository; import java.util.List; import java.util.stream.Collectors; +@Transactional(readOnly = true) @Service @RequiredArgsConstructor public class ReviewQueryServiceImpl implements ReviewQueryService { private final ReviewRepository reviewRepository; + private final StoreRepository storeRepository; + private final UserRepository userRepository; - //워크북 실습 코드 + //워크북 실습 코드 @Override - public List searchReview(String query, String type) { + public List searchReview(String query, String type) { //Q클래스 정의 QReview review = QReview.review; @@ -53,22 +67,49 @@ public List searchReview(String query, String typ List reviews = reviewRepository.searchReview(builder); return reviews.stream() - .map(ReviewConverter::toReviewPreviewDTO) + .map(ReviewConverter::toReviewPreviewBookDTO) .collect(Collectors.toList()); } //과제(미션) 코드 - public List getMyReviews(Long userId, Long storeId, Integer starFloor) { + public List getMyReviews(Long userId, Long storeId, Float starFloor) { //리포지토리에 파라미터 그대루 넘기기 Listreviews = reviewRepository.findMyReviewsFiltered(userId, storeId, starFloor); //Entity List를 Dto List로 변환 return reviews.stream() - .map(ReviewConverter::toReviewPreviewDTO) + .map(ReviewConverter::toReviewPreviewBookDTO) .collect(Collectors.toList()); } + @Override + public ReviewResDTO.ReviewPreViewListDTO findReview(String storeName, Integer page + + ){ + //가게를 가져온다 (가게 존재 여부 검증) + Store store = storeRepository.findByName(storeName).orElseThrow(()-> new StoreException(StoreErrorCode.NOT_FOUND)); + + //가게에 맞는 리뷰를 가져온다 (Offset 페이징) + PageRequest pageRequest = PageRequest.of(page,5); + Page result = reviewRepository.findAllByStore(store,pageRequest); + + return ReviewConverter.toReviewPreviewListDTO(result); + } + + @Override + public ReviewResDTO.MyReviewPreviewListDTO getMyReviewList(Integer page){ + + User user = userRepository.findById(1L).orElseThrow(()-> new UserException(UserErrorCode.NOT_FOUND)); + + //프론트는 반드시 1부터 시작하는 조건이 있기에 -1해줌. + PageRequest pageRequest = PageRequest.of(page-1,10); + + Page reviewPage = reviewRepository.findAllByUser(user,pageRequest); + + return ReviewConverter.toMyReviewPreviewListDTO(reviewPage); + + } } diff --git a/src/main/java/umc/domain/store/converter/StoreConverter.java b/src/main/java/umc/domain/store/converter/StoreConverter.java index c1e3f4d..054f146 100644 --- a/src/main/java/umc/domain/store/converter/StoreConverter.java +++ b/src/main/java/umc/domain/store/converter/StoreConverter.java @@ -66,6 +66,6 @@ public static List toBusinessTimeList(List { + Optional findByName(String name); } diff --git a/src/main/java/umc/domain/user/controller/UserMissionController.java b/src/main/java/umc/domain/user/controller/UserMissionController.java index ec274a1..49e4652 100644 --- a/src/main/java/umc/domain/user/controller/UserMissionController.java +++ b/src/main/java/umc/domain/user/controller/UserMissionController.java @@ -1,22 +1,27 @@ package umc.domain.user.controller; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -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; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; import umc.domain.user.dto.req.UserMissionReqDTO; import umc.domain.user.dto.res.UserMissionResDTO; import umc.domain.user.service.command.UserMissionCommandService; +import umc.domain.user.service.query.UserMissionQueryService; +import umc.global.annotation.CheckPage; import umc.global.apiPayload.ApiResponse; import umc.global.apiPayload.code.GeneralSuccessCode; @RestController @RequiredArgsConstructor +@Validated @RequestMapping("/user-mission") public class UserMissionController { private final UserMissionCommandService userMissionCommandService; + private final UserMissionQueryService userMissionQueryService; @PostMapping("/") public ApiResponse joinMission( @@ -27,4 +32,35 @@ public ApiResponse joinMission( userMissionCommandService.joinMission(req) ); } + + @GetMapping("/my/in-progress") + @Operation(summary = "내가 진행 중인 미션 목록 조회 API", description = "내가 진행 중인 미션 목록을 조회합니다. page는 1부터 시작합니다") + @Parameters({ + @Parameter(name = "page", description = "페이지 번호 (1 이상)") + }) + public ApiResponse getMyInProgressMissionList( + @CheckPage @RequestParam(name="page") Integer page + ){ + return ApiResponse.onSuccess( + GeneralSuccessCode.OK, + userMissionQueryService.getMyInProgressMissionList(page) + ); + } + + @PatchMapping("/{userMissionId}/complete") + @Operation(summary = "미션 완료 API", description = "진행 중인 미션을 완료 상태로 변경합니다.") + @Parameters({ + @Parameter(name = "userMissionId", description = "유저 미션 ID (진행 중인 미션의 ID)") + }) + public ApiResponse completeMission( + @PathVariable(name="userMissionId") Long userMissionId, + @RequestBody @Valid UserMissionReqDTO.CompleteMissionDTO req + ){ + return ApiResponse.onSuccess( + GeneralSuccessCode.OK, + userMissionCommandService.completeMission(userMissionId, req) + ); + } + + } diff --git a/src/main/java/umc/domain/user/converter/UserMissionConverter.java b/src/main/java/umc/domain/user/converter/UserMissionConverter.java index 38abf51..ebcceba 100644 --- a/src/main/java/umc/domain/user/converter/UserMissionConverter.java +++ b/src/main/java/umc/domain/user/converter/UserMissionConverter.java @@ -1,5 +1,6 @@ package umc.domain.user.converter; +import org.springframework.data.domain.Page; import umc.domain.mission.entity.Mission; import umc.domain.user.dto.req.UserMissionReqDTO; import umc.domain.user.dto.res.UserMissionResDTO; @@ -7,6 +8,10 @@ import umc.domain.user.enums.MissionStatus; import umc.domain.user.mapping.UserMission; +import java.awt.print.Pageable; +import java.util.List; +import java.util.stream.Collectors; + public class UserMissionConverter { //DTO -> Entity @@ -26,4 +31,43 @@ public static UserMissionResDTO.JoinMissionResDTO toJoinMissionResDTO(UserMissio .createdAt(userMission.getCreatedAt()) .build(); } + + //UserMission 엔티티 -> MyMissionByStatusResDTO + public static UserMissionResDTO.MyMissionByStatusResDTO toMyMissionByStatusResDTO(UserMission userMission){ + return UserMissionResDTO.MyMissionByStatusResDTO.builder() + .userMissionId(userMission.getId()) + .storeName(userMission.getMission().getStore().getName()) + .rewardPoint(userMission.getMission().getRewardPoint()) + .baseAmount(userMission.getMission().getBaseAmount()) + .deadLine(userMission.getMission().getDeadline()) + .status(userMission.getStatus()) + .build(); + } + + //Page -> MyMissionByStatusListDTO + public static UserMissionResDTO.MyMissionByStatusListResDTO toMyMissionByStatusListResDTO(Page missionPage){ + List missionList = missionPage.getContent().stream() + .map(UserMissionConverter::toMyMissionByStatusResDTO) + .collect(Collectors.toList()); + + return UserMissionResDTO.MyMissionByStatusListResDTO.builder() + .missionList(missionList) + .listSize(missionPage.getSize()) + .totalPage(missionPage.getTotalPages()) + .totalElements(missionPage.getTotalElements()) + .isFirst(missionPage.isFirst()) + .isLast(missionPage.isLast()) + .build(); + } + + //UserMission 엔티티 -> CompleteMissionResDTO + public static UserMissionResDTO.CompleteMissionResDTO toCompleteMissionResDTO(UserMission mission){ + return UserMissionResDTO.CompleteMissionResDTO.builder() + .userMissionId(mission.getId()) + .status(mission.getStatus()) + .missionId(mission.getMission().getId()) + .rewardPoint(mission.getMission().getRewardPoint()) + .updatedAt(mission.getUpdatedAt()) + .build(); + } } diff --git a/src/main/java/umc/domain/user/dto/req/UserMissionReqDTO.java b/src/main/java/umc/domain/user/dto/req/UserMissionReqDTO.java index f33dee7..4e2b6c0 100644 --- a/src/main/java/umc/domain/user/dto/req/UserMissionReqDTO.java +++ b/src/main/java/umc/domain/user/dto/req/UserMissionReqDTO.java @@ -1,6 +1,7 @@ package umc.domain.user.dto.req; import jakarta.validation.constraints.NotNull; +import umc.domain.user.enums.MissionStatus; public class UserMissionReqDTO { @@ -8,4 +9,9 @@ public record JoinMissionReqDTO( @NotNull Long missionId ){} + + public record CompleteMissionDTO( + @NotNull + MissionStatus status + ){} } diff --git a/src/main/java/umc/domain/user/dto/req/UserReqDTO.java b/src/main/java/umc/domain/user/dto/req/UserReqDTO.java index 19a3dde..f0a6764 100644 --- a/src/main/java/umc/domain/user/dto/req/UserReqDTO.java +++ b/src/main/java/umc/domain/user/dto/req/UserReqDTO.java @@ -17,9 +17,9 @@ public record JoinDto( Gender gender, @NotNull LocalDate birth, - @NotNull + @NotBlank String address, - @NotNull + @NotBlank String specAddress, @ExistFoods List preferCategory diff --git a/src/main/java/umc/domain/user/dto/res/UserMissionResDTO.java b/src/main/java/umc/domain/user/dto/res/UserMissionResDTO.java index 3e7a08c..d1c99c6 100644 --- a/src/main/java/umc/domain/user/dto/res/UserMissionResDTO.java +++ b/src/main/java/umc/domain/user/dto/res/UserMissionResDTO.java @@ -3,7 +3,9 @@ import lombok.Builder; import umc.domain.user.enums.MissionStatus; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.List; public class UserMissionResDTO { @@ -14,4 +16,33 @@ public record JoinMissionResDTO( MissionStatus status, LocalDateTime createdAt ){} + + @Builder + public record MyMissionByStatusListResDTO( + List missionList, + Integer listSize, + Integer totalPage, + Long totalElements, + Boolean isFirst, + Boolean isLast + ){} + + @Builder + public record MyMissionByStatusResDTO( + Long userMissionId, + String storeName, + Integer rewardPoint, + Integer baseAmount, + LocalDate deadLine, + MissionStatus status + ){} + + @Builder + public record CompleteMissionResDTO( + Long userMissionId, + MissionStatus status, + Long missionId, + Integer rewardPoint, + LocalDateTime updatedAt + ){} } diff --git a/src/main/java/umc/domain/user/exception/UserMissionException.java b/src/main/java/umc/domain/user/exception/UserMissionException.java new file mode 100644 index 0000000..22f9fa6 --- /dev/null +++ b/src/main/java/umc/domain/user/exception/UserMissionException.java @@ -0,0 +1,10 @@ +package umc.domain.user.exception; + +import umc.global.apiPayload.code.BaseErrorCode; +import umc.global.apiPayload.exception.GeneralException; + +public class UserMissionException extends GeneralException { + public UserMissionException(BaseErrorCode code) { + super(code); + } +} diff --git a/src/main/java/umc/domain/user/exception/code/UserMissionErrorCode.java b/src/main/java/umc/domain/user/exception/code/UserMissionErrorCode.java new file mode 100644 index 0000000..96549fb --- /dev/null +++ b/src/main/java/umc/domain/user/exception/code/UserMissionErrorCode.java @@ -0,0 +1,27 @@ +package umc.domain.user.exception.code; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; +import umc.global.apiPayload.code.BaseErrorCode; + +@Getter +@AllArgsConstructor +public enum UserMissionErrorCode implements BaseErrorCode { + + NOT_FOUND(HttpStatus.NOT_FOUND, + "USERMISSION404_1", + "해당 유저미션을 찾지 못했습니다."), + + INVALID_STATUS(HttpStatus.BAD_REQUEST, + "USERMISSION400_1", + "SUCCESS만 올 수 있습니다."), + + ALREADY_COMPLETED(HttpStatus.BAD_REQUEST, + "USERMISSION400_2", + "이미 완료된 미션입니다."); + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git a/src/main/java/umc/domain/user/mapping/UserMission.java b/src/main/java/umc/domain/user/mapping/UserMission.java index cef9f5b..588f23e 100644 --- a/src/main/java/umc/domain/user/mapping/UserMission.java +++ b/src/main/java/umc/domain/user/mapping/UserMission.java @@ -29,4 +29,8 @@ public class UserMission extends BaseEntity { @Column(name="status", nullable = false) @Enumerated(EnumType.STRING) private MissionStatus status; + + public void complete(){ + this.status = MissionStatus.SUCCESS; + } } diff --git a/src/main/java/umc/domain/user/service/command/UserCommandService.java b/src/main/java/umc/domain/user/service/command/UserCommandService.java index a3b2b64..eb96c82 100644 --- a/src/main/java/umc/domain/user/service/command/UserCommandService.java +++ b/src/main/java/umc/domain/user/service/command/UserCommandService.java @@ -1,11 +1,14 @@ package umc.domain.user.service.command; +import umc.domain.user.dto.req.UserMissionReqDTO; import umc.domain.user.dto.req.UserReqDTO; +import umc.domain.user.dto.res.UserMissionResDTO; import umc.domain.user.dto.res.UserResDTO; public interface UserCommandService { //회원가입 로직 UserResDTO.JoinDTO signup(UserReqDTO.JoinDto joinDto); + } diff --git a/src/main/java/umc/domain/user/service/command/UserCommandServiceImpl.java b/src/main/java/umc/domain/user/service/command/UserCommandServiceImpl.java index 70da7de..1f92b70 100644 --- a/src/main/java/umc/domain/user/service/command/UserCommandServiceImpl.java +++ b/src/main/java/umc/domain/user/service/command/UserCommandServiceImpl.java @@ -3,29 +3,36 @@ import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import umc.domain.food.entity.Food; import umc.domain.food.exception.FoodException; import umc.domain.food.exception.code.FoodErrorCode; import umc.domain.food.repository.FoodRepository; import umc.domain.user.converter.UserConverter; +import umc.domain.user.converter.UserMissionConverter; +import umc.domain.user.dto.req.UserMissionReqDTO; import umc.domain.user.dto.req.UserReqDTO; +import umc.domain.user.dto.res.UserMissionResDTO; import umc.domain.user.dto.res.UserResDTO; import umc.domain.user.entity.User; +import umc.domain.user.exception.UserMissionException; +import umc.domain.user.exception.code.UserMissionErrorCode; import umc.domain.user.mapping.UserFood; +import umc.domain.user.mapping.UserMission; import umc.domain.user.repository.UserFoodRepository; +import umc.domain.user.repository.UserMissionRepository; import umc.domain.user.repository.UserRepository; -import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @Service @RequiredArgsConstructor +@Transactional public class UserCommandServiceImpl implements UserCommandService { private final UserRepository userRepository; private final FoodRepository foodRepository; private final UserFoodRepository userFoodRepository; + private final UserMissionRepository userMissionRepository; //회원가입 로직 @Override @@ -72,4 +79,5 @@ public UserResDTO.JoinDTO signup(UserReqDTO.JoinDto joinDto){ return UserConverter.toJoinDTO(user); } + } diff --git a/src/main/java/umc/domain/user/service/command/UserMissionCommandService.java b/src/main/java/umc/domain/user/service/command/UserMissionCommandService.java index b2efaee..ca081a5 100644 --- a/src/main/java/umc/domain/user/service/command/UserMissionCommandService.java +++ b/src/main/java/umc/domain/user/service/command/UserMissionCommandService.java @@ -5,4 +5,6 @@ public interface UserMissionCommandService { UserMissionResDTO.JoinMissionResDTO joinMission(UserMissionReqDTO.JoinMissionReqDTO req); + UserMissionResDTO.CompleteMissionResDTO completeMission(Long userMissionId, UserMissionReqDTO.CompleteMissionDTO req); + } diff --git a/src/main/java/umc/domain/user/service/command/UserMissionCommandServiceImpl.java b/src/main/java/umc/domain/user/service/command/UserMissionCommandServiceImpl.java index e67447b..09def98 100644 --- a/src/main/java/umc/domain/user/service/command/UserMissionCommandServiceImpl.java +++ b/src/main/java/umc/domain/user/service/command/UserMissionCommandServiceImpl.java @@ -2,6 +2,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import umc.domain.mission.entity.Mission; import umc.domain.mission.exception.MissionException; import umc.domain.mission.exception.code.MissionErrorCode; @@ -12,13 +13,16 @@ import umc.domain.user.entity.User; import umc.domain.user.enums.MissionStatus; import umc.domain.user.exception.UserException; +import umc.domain.user.exception.UserMissionException; import umc.domain.user.exception.code.UserErrorCode; +import umc.domain.user.exception.code.UserMissionErrorCode; import umc.domain.user.mapping.UserMission; import umc.domain.user.repository.UserMissionRepository; import umc.domain.user.repository.UserRepository; @Service @RequiredArgsConstructor +@Transactional public class UserMissionCommandServiceImpl implements UserMissionCommandService{ private final UserMissionRepository userMissionRepository; private final UserRepository userRepository; @@ -38,4 +42,19 @@ public UserMissionResDTO.JoinMissionResDTO joinMission(UserMissionReqDTO.JoinMis return UserMissionConverter.toJoinMissionResDTO(userMission); } + + @Override + public UserMissionResDTO.CompleteMissionResDTO completeMission(Long userMissionId, UserMissionReqDTO.CompleteMissionDTO req){ + UserMission userMission = userMissionRepository.findById(userMissionId).orElseThrow(()->new UserMissionException(UserMissionErrorCode.NOT_FOUND)); + + if(userMission.getStatus()==MissionStatus.SUCCESS){ + throw new UserMissionException(UserMissionErrorCode.ALREADY_COMPLETED); + } + if(req.status()!=MissionStatus.SUCCESS){ + throw new UserMissionException(UserMissionErrorCode.INVALID_STATUS); + } + + userMission.complete(); + return UserMissionConverter.toCompleteMissionResDTO(userMission); + } } diff --git a/src/main/java/umc/domain/user/service/query/UserMissionQueryService.java b/src/main/java/umc/domain/user/service/query/UserMissionQueryService.java new file mode 100644 index 0000000..a634356 --- /dev/null +++ b/src/main/java/umc/domain/user/service/query/UserMissionQueryService.java @@ -0,0 +1,7 @@ +package umc.domain.user.service.query; + +import umc.domain.user.dto.res.UserMissionResDTO; + +public interface UserMissionQueryService { + UserMissionResDTO.MyMissionByStatusListResDTO getMyInProgressMissionList(Integer page); +} diff --git a/src/main/java/umc/domain/user/service/query/UserMissionQueryServiceImpl.java b/src/main/java/umc/domain/user/service/query/UserMissionQueryServiceImpl.java new file mode 100644 index 0000000..633fa20 --- /dev/null +++ b/src/main/java/umc/domain/user/service/query/UserMissionQueryServiceImpl.java @@ -0,0 +1,40 @@ +package umc.domain.user.service.query; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import umc.domain.user.converter.UserMissionConverter; +import umc.domain.user.dto.res.UserMissionResDTO; +import umc.domain.user.entity.User; +import umc.domain.user.enums.MissionStatus; +import umc.domain.user.exception.UserException; +import umc.domain.user.exception.code.UserErrorCode; +import umc.domain.user.mapping.UserMission; +import umc.domain.user.repository.UserMissionRepository; +import umc.domain.user.repository.UserRepository; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class UserMissionQueryServiceImpl implements UserMissionQueryService{ + private final UserMissionRepository userMissionRepository; + private final UserRepository userRepository; + + @Override + public UserMissionResDTO.MyMissionByStatusListResDTO getMyInProgressMissionList(Integer page){ + //유저 조회(하드코딩) + User user = userRepository.findById(1L).orElseThrow(()-> new UserException(UserErrorCode.NOT_FOUND)); + + PageRequest pageRequest = PageRequest.of(page-1,10); + + Page missionPage = userMissionRepository.findUserMissionsByStatus( + user.getId(), + MissionStatus.IN_PROGRESS, + pageRequest + ); + + return UserMissionConverter.toMyMissionByStatusListResDTO(missionPage); + } +} diff --git a/src/main/java/umc/global/annotation/CheckPage.java b/src/main/java/umc/global/annotation/CheckPage.java new file mode 100644 index 0000000..6b97d55 --- /dev/null +++ b/src/main/java/umc/global/annotation/CheckPage.java @@ -0,0 +1,19 @@ +package umc.global.annotation; + + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import umc.global.validator.CheckPageValidator; + +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = CheckPageValidator.class) //검증기 연결 +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface CheckPage { + + String message() default "페이지 번호는 1 이상이어야 합니다."; //기본 에러 메세지 + Class[] groups() default {}; + Class[] payload() default {}; +} diff --git a/src/main/java/umc/global/apiPayload/handler/GeneralExceptionAdvice.java b/src/main/java/umc/global/apiPayload/handler/GeneralExceptionAdvice.java index 7032396..e3c89d1 100644 --- a/src/main/java/umc/global/apiPayload/handler/GeneralExceptionAdvice.java +++ b/src/main/java/umc/global/apiPayload/handler/GeneralExceptionAdvice.java @@ -1,5 +1,6 @@ package umc.global.apiPayload.handler; +import jakarta.validation.ConstraintViolationException; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -18,6 +19,7 @@ public class GeneralExceptionAdvice { //애플리케이션에서 발생하는 커스텀 예외를 처리 + @ExceptionHandler(GeneralException.class) public ResponseEntity> handleException( GeneralException ex ){ @@ -67,4 +69,24 @@ protected ResponseEntity>> handleMethodArgumentN return ResponseEntity.status(code.getStatus()).body(errorResponse); } + //@Validated 검증 실패 시 발생하는 예외 처리(ConstraintViolationException) + @ExceptionHandler(ConstraintViolationException.class) + public ResponseEntity>> handleConstraintViolationException(ConstraintViolationException ex){ + + Map errors = new HashMap<>(); //어떤 필드가 어떤 메시지때문에 틀렸는지 Map형태로 담음 + ex.getConstraintViolations().forEach(violation -> { + + String path = violation.getPropertyPath().toString(); + String fieldName = path.substring(path.lastIndexOf('.') + 1); // 결과: page + + String errorMessage = violation.getMessage(); + errors.put(fieldName, errorMessage); + }); + GeneralErrorCode code = GeneralErrorCode.BAD_REQUEST; + ApiResponse> errorResponse = ApiResponse.onFailure(code, errors); + return ResponseEntity.status(code.getStatus()).body(errorResponse); + } + + + } diff --git a/src/main/java/umc/global/validator/CheckPageValidator.java b/src/main/java/umc/global/validator/CheckPageValidator.java new file mode 100644 index 0000000..1b2602b --- /dev/null +++ b/src/main/java/umc/global/validator/CheckPageValidator.java @@ -0,0 +1,26 @@ +package umc.global.validator; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import umc.global.annotation.CheckPage; +import umc.global.apiPayload.code.GeneralErrorCode; + +public class CheckPageValidator implements ConstraintValidator { + + @Override + public void initialize(CheckPage constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(Integer value, ConstraintValidatorContext context) { + if(value == null || value < 1){ + context.disableDefaultConstraintViolation();//기본 메시지 끄기 + //커스텀 에러 메세지 설정 + context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()) + .addConstraintViolation(); + return false; + } + return true; + } +}