From 428d0696550e05666406391d4df4418cdda083b0 Mon Sep 17 00:00:00 2001 From: Jangwon0319 Date: Wed, 27 Nov 2024 18:07:00 +0900 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Practice(#9)=20:=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20API=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apiPayload/code/status/ErrorStatus.java | 3 ++ .../umc/spring/converter/StoreConverter.java | 35 +++++++++++++++ .../ReviewRepository/ReviewRepository.java | 5 +++ .../StoreService/StoreQueryService.java | 5 +++ .../StoreService/StoreQueryServiceImpl.java | 16 +++++++ .../validation/annotation/ExistStore.java | 17 ++++++++ .../validator/StoreExistValidator.java | 36 ++++++++++++++++ .../web/controller/StoreRestController.java | 43 +++++++++++++++++++ .../umc/spring/web/dto/StoreRequestDTO.java | 4 ++ .../umc/spring/web/dto/StoreResponseDTO.java | 36 ++++++++++++++++ 10 files changed, 200 insertions(+) create mode 100644 src/main/java/umc/spring/converter/StoreConverter.java create mode 100644 src/main/java/umc/spring/validation/annotation/ExistStore.java create mode 100644 src/main/java/umc/spring/validation/validator/StoreExistValidator.java create mode 100644 src/main/java/umc/spring/web/controller/StoreRestController.java create mode 100644 src/main/java/umc/spring/web/dto/StoreRequestDTO.java create mode 100644 src/main/java/umc/spring/web/dto/StoreResponseDTO.java diff --git a/src/main/java/umc/spring/apiPayload/code/status/ErrorStatus.java b/src/main/java/umc/spring/apiPayload/code/status/ErrorStatus.java index 6d82b71..d5a0581 100644 --- a/src/main/java/umc/spring/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/umc/spring/apiPayload/code/status/ErrorStatus.java @@ -24,6 +24,9 @@ public enum ErrorStatus implements BaseErrorCode { // FoodCategory Error FOOD_CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, "FOOD_CATEGORY4001", "음식 카테고리가 없습니다."), + // Store Error + STORE_NOT_FOUND(HttpStatus.NOT_FOUND, "STORE_4001","가게가 없습니다."), + // 예시,,, ARTICLE_NOT_FOUND(HttpStatus.NOT_FOUND, "ARTICLE4001", "게시글이 없습니다."), diff --git a/src/main/java/umc/spring/converter/StoreConverter.java b/src/main/java/umc/spring/converter/StoreConverter.java new file mode 100644 index 0000000..a3a8eb6 --- /dev/null +++ b/src/main/java/umc/spring/converter/StoreConverter.java @@ -0,0 +1,35 @@ +package umc.spring.converter; + +import org.springframework.data.domain.Page; +import umc.spring.domain.Review; +import umc.spring.web.dto.StoreResponseDTO; + +import java.util.List; +import java.util.stream.Collectors; + +public class StoreConverter { + + public static StoreResponseDTO.ReviewPreViewDTO reviewPreViewDTO(Review review){ + return StoreResponseDTO.ReviewPreViewDTO.builder() + .ownerNickname(review.getMember().getName()) + .score(review.getScore()) + .body(review.getBody()) + .createdAt(review.getCreatedAt().toLocalDate()) + .build(); + } + + + public static StoreResponseDTO.ReviewPreViewListDTO reviewPreViewListDTO(Page reviewList){ + List reviewPreViewDTOList = reviewList.stream() + .map(StoreConverter::reviewPreViewDTO).collect(Collectors.toList()); + + return StoreResponseDTO.ReviewPreViewListDTO.builder() + .isLast(reviewList.isLast()) + .isFirst(reviewList.isFirst()) + .totalPage(reviewList.getTotalPages()) + .totalElements(reviewList.getTotalElements()) + .listSize(reviewPreViewDTOList.size()) + .reviewList(reviewPreViewDTOList) + .build(); + } +} diff --git a/src/main/java/umc/spring/repository/ReviewRepository/ReviewRepository.java b/src/main/java/umc/spring/repository/ReviewRepository/ReviewRepository.java index d8810f5..965773d 100644 --- a/src/main/java/umc/spring/repository/ReviewRepository/ReviewRepository.java +++ b/src/main/java/umc/spring/repository/ReviewRepository/ReviewRepository.java @@ -1,7 +1,12 @@ package umc.spring.repository.ReviewRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; import umc.spring.domain.Review; +import umc.spring.domain.Store; public interface ReviewRepository extends JpaRepository, ReviewRepositoryCustom { + + Page findAllByStore(Store store, PageRequest pageRequest); } diff --git a/src/main/java/umc/spring/service/StoreService/StoreQueryService.java b/src/main/java/umc/spring/service/StoreService/StoreQueryService.java index f033ba9..227c4c3 100644 --- a/src/main/java/umc/spring/service/StoreService/StoreQueryService.java +++ b/src/main/java/umc/spring/service/StoreService/StoreQueryService.java @@ -1,5 +1,8 @@ package umc.spring.service.StoreService; +import jakarta.persistence.criteria.CriteriaBuilder; +import org.springframework.data.domain.Page; +import umc.spring.domain.Review; import umc.spring.domain.Store; import java.util.List; @@ -9,4 +12,6 @@ public interface StoreQueryService { Optional findStore(Long id); List findStoresByNameAndScore(String name, Float score); + + Page getReviewList(Long storeId, Integer page); } \ No newline at end of file diff --git a/src/main/java/umc/spring/service/StoreService/StoreQueryServiceImpl.java b/src/main/java/umc/spring/service/StoreService/StoreQueryServiceImpl.java index 955bde8..b4a440a 100644 --- a/src/main/java/umc/spring/service/StoreService/StoreQueryServiceImpl.java +++ b/src/main/java/umc/spring/service/StoreService/StoreQueryServiceImpl.java @@ -1,9 +1,13 @@ package umc.spring.service.StoreService; 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.spring.domain.Review; import umc.spring.domain.Store; +import umc.spring.repository.ReviewRepository.ReviewRepository; import umc.spring.repository.StoreRepository.StoreRepository; import java.util.List; @@ -16,6 +20,8 @@ public class StoreQueryServiceImpl implements StoreQueryService{ private final StoreRepository storeRepository; + private final ReviewRepository reviewRepository; + @Override public Optional findStore(Long id) { return storeRepository.findById(id); @@ -29,4 +35,14 @@ public List findStoresByNameAndScore(String name, Float score) { return filteredStores; } + + @Override + public Page getReviewList(Long storeId, Integer page) { + + Store store = storeRepository.findById(storeId).get(); + + Page storePage = reviewRepository.findAllByStore(store, PageRequest.of(page, 10)); + + return storePage; + } } \ No newline at end of file diff --git a/src/main/java/umc/spring/validation/annotation/ExistStore.java b/src/main/java/umc/spring/validation/annotation/ExistStore.java new file mode 100644 index 0000000..39223fc --- /dev/null +++ b/src/main/java/umc/spring/validation/annotation/ExistStore.java @@ -0,0 +1,17 @@ +package umc.spring.validation.annotation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import umc.spring.validation.validator.StoreExistValidator; + +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = StoreExistValidator.class) +@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExistStore { + String message() default "해당하는 가게가 존재하지 않습니다."; + Class[] groups() default {}; + Class[] payload() default {}; +} \ No newline at end of file diff --git a/src/main/java/umc/spring/validation/validator/StoreExistValidator.java b/src/main/java/umc/spring/validation/validator/StoreExistValidator.java new file mode 100644 index 0000000..105176b --- /dev/null +++ b/src/main/java/umc/spring/validation/validator/StoreExistValidator.java @@ -0,0 +1,36 @@ +package umc.spring.validation.validator; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import umc.spring.apiPayload.code.status.ErrorStatus; +import umc.spring.domain.Store; +import umc.spring.service.StoreService.StoreQueryService; +import umc.spring.validation.annotation.ExistStore; + +import java.util.Optional; + +@Component +@RequiredArgsConstructor +public class StoreExistValidator implements ConstraintValidator { + + private final StoreQueryService storeQueryService; + + @Override + public void initialize(ExistStore constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(Long value, ConstraintValidatorContext context) { + Optional target = storeQueryService.findStore(value); + + if (target.isEmpty()){ + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(ErrorStatus.STORE_NOT_FOUND.toString()).addConstraintViolation(); + return false; + } + return true; + } +} \ No newline at end of file diff --git a/src/main/java/umc/spring/web/controller/StoreRestController.java b/src/main/java/umc/spring/web/controller/StoreRestController.java new file mode 100644 index 0000000..d5227c1 --- /dev/null +++ b/src/main/java/umc/spring/web/controller/StoreRestController.java @@ -0,0 +1,43 @@ +package umc.spring.web.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.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import umc.spring.apiPayload.ApiResponse; +import umc.spring.converter.StoreConverter; +import umc.spring.domain.Review; +import umc.spring.service.StoreService.StoreQueryService; +import umc.spring.validation.annotation.ExistStore; +import umc.spring.web.dto.StoreResponseDTO; + +@RestController +@RequiredArgsConstructor +@Validated +@RequestMapping("/stores") +public class StoreRestController { + + private final StoreQueryService storeQueryService; + + @GetMapping("/{storeId}/reviews") + @Operation(summary = "특정 가게의 리뷰 목록 조회 API",description = "특정 가게의 리뷰들의 목록을 조회하는 API이며, 페이징을 포함합니다. query String 으로 page 번호를 주세요") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200",description = "OK, 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "AUTH003", description = "access 토큰을 주세요!",content = @Content(schema = @Schema(implementation = ApiResponse.class))), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "AUTH004", description = "acess 토큰 만료",content = @Content(schema = @Schema(implementation = ApiResponse.class))), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "AUTH006", description = "acess 토큰 모양이 이상함",content = @Content(schema = @Schema(implementation = ApiResponse.class))), + }) + @Parameters({ + @Parameter(name = "storeId", description = "가게의 아이디, path variable 입니다!") + }) + public ApiResponse getReviewList(@ExistStore @PathVariable(name = "storeId") Long storeId, @RequestParam(name = "page") Integer page){ + Page reviewList = storeQueryService.getReviewList(storeId, page); + return ApiResponse.onSuccess(StoreConverter.reviewPreViewListDTO(reviewList)); + } +} diff --git a/src/main/java/umc/spring/web/dto/StoreRequestDTO.java b/src/main/java/umc/spring/web/dto/StoreRequestDTO.java new file mode 100644 index 0000000..31ca29f --- /dev/null +++ b/src/main/java/umc/spring/web/dto/StoreRequestDTO.java @@ -0,0 +1,4 @@ +package umc.spring.web.dto; + +public class StoreRequestDTO { +} diff --git a/src/main/java/umc/spring/web/dto/StoreResponseDTO.java b/src/main/java/umc/spring/web/dto/StoreResponseDTO.java new file mode 100644 index 0000000..0586fc4 --- /dev/null +++ b/src/main/java/umc/spring/web/dto/StoreResponseDTO.java @@ -0,0 +1,36 @@ +package umc.spring.web.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.util.List; + +public class StoreResponseDTO { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class ReviewPreViewListDTO { + List reviewList; + Integer listSize; + Integer totalPage; + Long totalElements; + Boolean isFirst; + Boolean isLast; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class ReviewPreViewDTO { + String ownerNickname; + Float score; + String body; + LocalDate createdAt; + } +}