From eefd843a10e9b2377cac0c000d8dc9605977da0c Mon Sep 17 00:00:00 2001 From: feel-coding Date: Sat, 21 Oct 2023 16:29:28 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feature:=20=ED=8A=B9=EC=A0=95=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=EC=9D=98=20=EB=A6=AC=EB=B7=B0=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20API=20=EC=B6=94=EA=B0=80=20#82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/cvsgo/config/WebConfig.java | 2 +- .../com/cvsgo/controller/UserController.java | 13 +++ .../dto/review/ReadReviewRequestDto.java | 2 - .../dto/review/ReadUserReviewQueryDto.java | 61 +++++++++++++ .../dto/review/ReadUserReviewResponseDto.java | 86 +++++++++++++++++++ .../repository/ReviewCustomRepository.java | 7 ++ .../ReviewCustomRepositoryImpl.java | 45 ++++++++++ .../cvsgo/repository/ReviewRepository.java | 2 +- .../java/com/cvsgo/service/ReviewService.java | 43 ++++++++++ 9 files changed, 257 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/cvsgo/dto/review/ReadUserReviewQueryDto.java create mode 100644 src/main/java/com/cvsgo/dto/review/ReadUserReviewResponseDto.java diff --git a/src/main/java/com/cvsgo/config/WebConfig.java b/src/main/java/com/cvsgo/config/WebConfig.java index 3c87ba8d..bde47a7a 100644 --- a/src/main/java/com/cvsgo/config/WebConfig.java +++ b/src/main/java/com/cvsgo/config/WebConfig.java @@ -30,6 +30,6 @@ public void addInterceptors(InterceptorRegistry registry) { .addPathPatterns("/**") .excludePathPatterns("/", "/docs/**", "/*.ico", "/api/auth/login", "/api/users", "/api/tags", "/api/users/emails/*/exists", "/api/users/nicknames/*/exists", - "/api/products", "/api/products/*", "/api/products/*/tags"); + "/api/products", "/api/products/*", "/api/products/*/tags", "/api/users/*/reviews"); } } diff --git a/src/main/java/com/cvsgo/controller/UserController.java b/src/main/java/com/cvsgo/controller/UserController.java index 71a98bc4..f36e2ae0 100644 --- a/src/main/java/com/cvsgo/controller/UserController.java +++ b/src/main/java/com/cvsgo/controller/UserController.java @@ -4,12 +4,15 @@ import com.cvsgo.dto.SuccessResponse; import com.cvsgo.dto.product.ReadProductResponseDto; import com.cvsgo.dto.product.ReadUserProductRequestDto; +import com.cvsgo.dto.review.ReadUserReviewResponseDto; +import com.cvsgo.dto.review.ReviewSortBy; import com.cvsgo.dto.user.SignUpRequestDto; import com.cvsgo.dto.user.SignUpResponseDto; import com.cvsgo.dto.user.UpdateUserRequestDto; import com.cvsgo.dto.user.UserResponseDto; import com.cvsgo.entity.User; import com.cvsgo.service.ProductService; +import com.cvsgo.service.ReviewService; import com.cvsgo.service.UserService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -33,8 +36,11 @@ public class UserController { private final UserService userService; + private final ProductService productService; + private final ReviewService reviewService; + @PostMapping("/users") @ResponseStatus(HttpStatus.CREATED) public SuccessResponse register( @@ -92,4 +98,11 @@ public SuccessResponse> readBookmarkedProductList( productService.readBookmarkedProductList(userId, request, pageable)); } + @GetMapping("/users/{userId}/reviews") + public SuccessResponse> readUserReviewList(@LoginUser User loginUser, + @PathVariable Long userId, ReviewSortBy sortBy, Pageable pageable) { + return SuccessResponse.from( + reviewService.readUserReviewList(loginUser, userId, sortBy, pageable)); + } + } diff --git a/src/main/java/com/cvsgo/dto/review/ReadReviewRequestDto.java b/src/main/java/com/cvsgo/dto/review/ReadReviewRequestDto.java index 342bf324..ad52d589 100644 --- a/src/main/java/com/cvsgo/dto/review/ReadReviewRequestDto.java +++ b/src/main/java/com/cvsgo/dto/review/ReadReviewRequestDto.java @@ -1,7 +1,5 @@ package com.cvsgo.dto.review; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; import java.util.List; import lombok.Getter; diff --git a/src/main/java/com/cvsgo/dto/review/ReadUserReviewQueryDto.java b/src/main/java/com/cvsgo/dto/review/ReadUserReviewQueryDto.java new file mode 100644 index 00000000..53a4cf14 --- /dev/null +++ b/src/main/java/com/cvsgo/dto/review/ReadUserReviewQueryDto.java @@ -0,0 +1,61 @@ +package com.cvsgo.dto.review; + +import com.cvsgo.entity.ProductBookmark; +import com.cvsgo.entity.ReviewLike; +import com.querydsl.core.annotations.QueryProjection; +import java.time.LocalDateTime; +import lombok.Getter; + +@Getter +public class ReadUserReviewQueryDto { + + private final Long reviewId; + + private final Long productId; + + private final String productName; + + private final String manufacturerName; + + private final String productImageUrl; + + private final Long reviewerId; + + private final String reviewerNickname; + + private final String reviewerProfileImageUrl; + + private final Long likeCount; + + private final Integer rating; + + private final String reviewContent; + + private final Boolean isReviewLiked; + + private final Boolean isProductBookmarked; + + private final LocalDateTime createdAt; + + @QueryProjection + public ReadUserReviewQueryDto(Long reviewId, Long productId, String productName, + String manufacturerName, String productImageUrl, Long reviewerId, String reviewerNickname, + String reviewerProfileImageUrl, Long likeCount, Integer rating, + String reviewContent, LocalDateTime createdAt, ReviewLike reviewLike, + ProductBookmark productBookmark) { + this.reviewId = reviewId; + this.productId = productId; + this.productName = productName; + this.manufacturerName = manufacturerName; + this.productImageUrl = productImageUrl; + this.reviewerId = reviewerId; + this.reviewerNickname = reviewerNickname; + this.reviewerProfileImageUrl = reviewerProfileImageUrl; + this.likeCount = likeCount; + this.rating = rating; + this.reviewContent = reviewContent; + this.createdAt = createdAt; + this.isReviewLiked = reviewLike != null; + this.isProductBookmarked = productBookmark != null; + } +} diff --git a/src/main/java/com/cvsgo/dto/review/ReadUserReviewResponseDto.java b/src/main/java/com/cvsgo/dto/review/ReadUserReviewResponseDto.java new file mode 100644 index 00000000..99207aaf --- /dev/null +++ b/src/main/java/com/cvsgo/dto/review/ReadUserReviewResponseDto.java @@ -0,0 +1,86 @@ +package com.cvsgo.dto.review; + +import com.fasterxml.jackson.annotation.JsonFormat; +import java.time.LocalDateTime; +import java.util.List; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class ReadUserReviewResponseDto { + + private final Long productId; + + private final String productName; + + private final String productManufacturer; + + private final String productImageUrl; + + private final Long reviewId; + + private final Long reviewerId; + + private final String reviewerNickname; + + private final String reviewerProfileImageUrl; + + private final Long reviewLikeCount; + + private final Integer reviewRating; + + private final String reviewContent; + + private final Boolean isReviewLiked; + + private final Boolean isProductBookmarked; + + private final List reviewImageUrls; + + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss", timezone = "Asia/Seoul") + private final LocalDateTime createdAt; + + @Builder + public ReadUserReviewResponseDto(Long productId, String productName, String productManufacturer, + String productImageUrl, Long reviewId, Long reviewerId, String reviewerNickname, + String reviewerProfileImageUrl, + Long reviewLikeCount, Integer reviewRating, String reviewContent, Boolean isReviewLiked, + Boolean isProductBookmarked, List reviewImageUrls, LocalDateTime createdAt) { + this.productId = productId; + this.productName = productName; + this.productManufacturer = productManufacturer; + this.productImageUrl = productImageUrl; + this.reviewId = reviewId; + this.reviewerId = reviewerId; + this.reviewerNickname = reviewerNickname; + this.reviewerProfileImageUrl = reviewerProfileImageUrl; + this.reviewLikeCount = reviewLikeCount; + this.reviewRating = reviewRating; + this.reviewContent = reviewContent; + this.isReviewLiked = isReviewLiked; + this.isProductBookmarked = isProductBookmarked; + this.reviewImageUrls = reviewImageUrls; + this.createdAt = createdAt; + } + + public static ReadUserReviewResponseDto of(ReadUserReviewQueryDto readUserReviewQueryDto, + List reviewImageUrls) { + return ReadUserReviewResponseDto.builder() + .productName(readUserReviewQueryDto.getProductName()) + .productId(readUserReviewQueryDto.getProductId()) + .reviewContent(readUserReviewQueryDto.getReviewContent()) + .productImageUrl(readUserReviewQueryDto.getProductImageUrl()) + .productManufacturer(readUserReviewQueryDto.getManufacturerName()) + .reviewId(readUserReviewQueryDto.getReviewId()) + .reviewerId(readUserReviewQueryDto.getReviewerId()) + .reviewerNickname(readUserReviewQueryDto.getReviewerNickname()) + .reviewerProfileImageUrl(readUserReviewQueryDto.getReviewerProfileImageUrl()) + .reviewRating(readUserReviewQueryDto.getRating()) + .reviewLikeCount(readUserReviewQueryDto.getLikeCount()) + .createdAt(readUserReviewQueryDto.getCreatedAt()) + .isProductBookmarked(readUserReviewQueryDto.getIsProductBookmarked()) + .isReviewLiked(readUserReviewQueryDto.getIsReviewLiked()) + .reviewImageUrls(reviewImageUrls) + .build(); + } +} diff --git a/src/main/java/com/cvsgo/repository/ReviewCustomRepository.java b/src/main/java/com/cvsgo/repository/ReviewCustomRepository.java index 98b06f2b..77653249 100644 --- a/src/main/java/com/cvsgo/repository/ReviewCustomRepository.java +++ b/src/main/java/com/cvsgo/repository/ReviewCustomRepository.java @@ -4,6 +4,8 @@ import com.cvsgo.dto.review.ReadProductReviewRequestDto; import com.cvsgo.dto.review.ReadReviewQueryDto; import com.cvsgo.dto.review.ReadReviewRequestDto; +import com.cvsgo.dto.review.ReadUserReviewQueryDto; +import com.cvsgo.dto.review.ReviewSortBy; import com.cvsgo.entity.User; import java.util.List; import org.springframework.data.domain.Pageable; @@ -18,6 +20,11 @@ List findAllByFilter(User loginUser, ReadReviewRequestDto fi List findAllByProductIdAndFilter(User loginUser, Long productId, ReadProductReviewRequestDto filter, Pageable pageable); + List findAllByUser(User loginUser, User reviewer, ReviewSortBy sortBy, + Pageable pageable); + Long countByProductIdAndFilter(Long productId, ReadProductReviewRequestDto filter); + Long countByUser(User user); + } diff --git a/src/main/java/com/cvsgo/repository/ReviewCustomRepositoryImpl.java b/src/main/java/com/cvsgo/repository/ReviewCustomRepositoryImpl.java index 192044fe..c383ad0a 100644 --- a/src/main/java/com/cvsgo/repository/ReviewCustomRepositoryImpl.java +++ b/src/main/java/com/cvsgo/repository/ReviewCustomRepositoryImpl.java @@ -11,9 +11,11 @@ import com.cvsgo.dto.review.QReadProductReviewQueryDto; import com.cvsgo.dto.review.QReadReviewQueryDto; +import com.cvsgo.dto.review.QReadUserReviewQueryDto; import com.cvsgo.dto.review.ReadProductReviewQueryDto; import com.cvsgo.dto.review.ReadProductReviewRequestDto; import com.cvsgo.dto.review.ReadReviewQueryDto; +import com.cvsgo.dto.review.ReadUserReviewQueryDto; import com.cvsgo.dto.review.ReviewSortBy; import com.cvsgo.dto.review.ReadReviewRequestDto; import com.cvsgo.entity.User; @@ -106,6 +108,38 @@ public List findAllByProductIdAndFilter(User loginUse .fetch(); } + public List findAllByUser(User loginUser, User reviewer, + ReviewSortBy sortBy, Pageable pageable) { + return queryFactory.select(new QReadUserReviewQueryDto( + review.id, + product.id, + product.name, + product.manufacturer.name, + product.imageUrl, + review.user.id, + review.user.nickname, + review.user.profileImageUrl, + review.likeCount, + review.rating, + review.content, + review.createdAt, + reviewLike, + productBookmark)) + .from(review) + .join(user).on(user.eq(review.user)) + .join(product).on(review.product.eq(product)) + .leftJoin(reviewLike).on(reviewLike.review.eq(review).and(reviewLikeUserEq(reviewer))) + .leftJoin(productBookmark) + .on(productBookmark.product.eq(review.product).and(productBookmarkUserEq(loginUser))) + .where( + review.user.eq(reviewer) + ) + .orderBy(sortBy(sortBy)) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + } + public Long countByProductIdAndFilter(Long productId, ReadProductReviewRequestDto filter) { return queryFactory.select(review.count()) .from(review) @@ -119,6 +153,17 @@ public Long countByProductIdAndFilter(Long productId, ReadProductReviewRequestDt .fetchOne(); } + public Long countByUser(User reviewer) { + return queryFactory.select(review.count()) + .from(review) + .join(user).on(user.eq(review.user)) + .join(product).on(review.product.eq(product)) + .where( + review.user.eq(reviewer) + ) + .fetchOne(); + } + private OrderSpecifier sortBy(ReviewSortBy sortBy) { return sortBy != null ? switch (sortBy) { diff --git a/src/main/java/com/cvsgo/repository/ReviewRepository.java b/src/main/java/com/cvsgo/repository/ReviewRepository.java index e308e09e..09a4fdce 100644 --- a/src/main/java/com/cvsgo/repository/ReviewRepository.java +++ b/src/main/java/com/cvsgo/repository/ReviewRepository.java @@ -15,7 +15,7 @@ public interface ReviewRepository extends JpaRepository, ReviewCus boolean existsByProductAndUser(Product product, User user); - long countByUser(User user); + Long countByUser(User user); @Lock(LockModeType.OPTIMISTIC) @Query(value = "select p from Review p where p.id = :id") diff --git a/src/main/java/com/cvsgo/service/ReviewService.java b/src/main/java/com/cvsgo/service/ReviewService.java index 551c9d69..82541326 100644 --- a/src/main/java/com/cvsgo/service/ReviewService.java +++ b/src/main/java/com/cvsgo/service/ReviewService.java @@ -6,6 +6,7 @@ import static com.cvsgo.exception.ExceptionConstants.NOT_FOUND_PRODUCT; import static com.cvsgo.exception.ExceptionConstants.NOT_FOUND_REVIEW; import static com.cvsgo.exception.ExceptionConstants.NOT_FOUND_REVIEW_LIKE; +import static com.cvsgo.exception.ExceptionConstants.NOT_FOUND_USER; import com.cvsgo.dto.review.CreateReviewRequestDto; import com.cvsgo.dto.review.ReadProductReviewQueryDto; @@ -14,7 +15,10 @@ import com.cvsgo.dto.review.ReadReviewQueryDto; import com.cvsgo.dto.review.ReadReviewRequestDto; import com.cvsgo.dto.review.ReadReviewResponseDto; +import com.cvsgo.dto.review.ReadUserReviewQueryDto; +import com.cvsgo.dto.review.ReadUserReviewResponseDto; import com.cvsgo.dto.review.ReviewDto; +import com.cvsgo.dto.review.ReviewSortBy; import com.cvsgo.dto.review.UpdateReviewRequestDto; import com.cvsgo.entity.Product; import com.cvsgo.entity.Review; @@ -31,6 +35,7 @@ import com.cvsgo.repository.ReviewImageRepository; import com.cvsgo.repository.ReviewLikeRepository; import com.cvsgo.repository.ReviewRepository; +import com.cvsgo.repository.UserRepository; import com.cvsgo.repository.UserTagRepository; import jakarta.persistence.EntityManager; import java.util.List; @@ -61,6 +66,8 @@ public class ReviewService { private final EntityManager entityManager; + private final UserRepository userRepository; + /** * 리뷰를 추가합니다. * @@ -183,6 +190,42 @@ public Page readProductReviewList(User user, Long return new PageImpl<>(results, pageable, totalCount); } + /** + * 특정 사용자가 작성한 리뷰를 조회합니다 + * + * @param loginUser 로그인한 사용자 + * @param userId 사용자 ID + * @param sortBy 정렬 정보 + * @param pageable 페이지 정보 + * @return 리뷰 목록 + */ + @Transactional(readOnly = true) + public Page readUserReviewList(User loginUser, Long userId, + ReviewSortBy sortBy, Pageable pageable) { + if (loginUser == null || loginUser.getRole() != Role.REGULAR) { + if (pageable.getPageNumber() > 0) { + throw FORBIDDEN_REVIEW; + } else { + pageable = PageRequest.of(pageable.getPageNumber(), 5); + } + } + + User user = userRepository.findById(userId).orElseThrow(() -> NOT_FOUND_USER); + List reviews = reviewRepository.findAllByUser(loginUser, user, + sortBy, pageable); + + long totalCount = reviewRepository.countByUser(user); + + List reviewImages = reviewImageRepository.findByReviewIdIn( + reviews.stream().map(ReadUserReviewQueryDto::getReviewId).distinct().toList()); + + List results = reviews.stream().map( + reviewDto -> ReadUserReviewResponseDto.of(reviewDto, + getReviewImages(reviewDto.getReviewId(), reviewImages))).toList(); + + return new PageImpl<>(results, pageable, totalCount); + } + /** * 리뷰 좋아요를 추가합니다. * From 7ce17b5715685c8d17cb599b079bdfd9c49e0c5c Mon Sep 17 00:00:00 2001 From: feel-coding Date: Sun, 12 Nov 2023 23:27:59 +0900 Subject: [PATCH 2/6] =?UTF-8?q?test:=20=ED=8A=B9=EC=A0=95=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=EC=9D=98=20=EB=A6=AC=EB=B7=B0=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80=20#82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cvsgo/controller/UserControllerTest.java | 79 ++++++++++++++++++- .../com/cvsgo/service/ReviewServiceTest.java | 26 ++++++ 2 files changed, 104 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/cvsgo/controller/UserControllerTest.java b/src/test/java/com/cvsgo/controller/UserControllerTest.java index 193a69b8..79bc7daa 100644 --- a/src/test/java/com/cvsgo/controller/UserControllerTest.java +++ b/src/test/java/com/cvsgo/controller/UserControllerTest.java @@ -23,6 +23,7 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; @@ -37,6 +38,9 @@ import com.cvsgo.dto.product.ReadUserProductRequestDto; import com.cvsgo.dto.product.ReadProductQueryDto; import com.cvsgo.dto.product.ReadProductResponseDto; +import com.cvsgo.dto.review.ReadUserReviewQueryDto; +import com.cvsgo.dto.review.ReadUserReviewResponseDto; +import com.cvsgo.dto.review.ReviewSortBy; import com.cvsgo.dto.user.SignUpRequestDto; import com.cvsgo.dto.user.SignUpResponseDto; import com.cvsgo.dto.user.UpdateUserRequestDto; @@ -51,13 +55,18 @@ import com.cvsgo.entity.Product; import com.cvsgo.entity.ProductBookmark; import com.cvsgo.entity.ProductLike; +import com.cvsgo.entity.Review; +import com.cvsgo.entity.ReviewImage; +import com.cvsgo.entity.Role; import com.cvsgo.entity.Tag; import com.cvsgo.entity.User; import com.cvsgo.exception.ExceptionConstants; import com.cvsgo.interceptor.AuthInterceptor; import com.cvsgo.service.ProductService; +import com.cvsgo.service.ReviewService; import com.cvsgo.service.UserService; import com.fasterxml.jackson.databind.ObjectMapper; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.BeforeEach; @@ -91,6 +100,9 @@ class UserControllerTest { @MockBean private ProductService productService; + @MockBean + private ReviewService reviewService; + private MockMvc mockMvc; private final ObjectMapper objectMapper = new ObjectMapper(); @@ -553,6 +565,46 @@ void respond_200_when_read_bookmarked_product_list_successfully() throws Excepti )); } + @Test + @DisplayName("특정 회원이 작성한 리뷰 목록을 정상적으로 조회하면 HTTP 200을 응답한다.") + public void respond_200_when_read_user_review_list_successfully() throws Exception { + Page responseDto = new PageImpl<>(getReviewsResponse()); + given(reviewService.readUserReviewList(any(), anyLong(), any(), any())) + .willReturn(responseDto); + + mockMvc.perform(RestDocumentationRequestBuilders.get("/api/users/{userId}/reviews", 1L) + .contentType(MediaType.APPLICATION_JSON) + .queryParam("sortBy", ReviewSortBy.LATEST.toString())) + .andExpect(status().isOk()) + .andDo(print()) + .andDo(document("{class-name}/{method-name}", + getDocumentRequest(), + getDocumentResponse(), + pathParameters( + parameterWithName("userId").description("리뷰 목록을 조회할 회원 ID") + ), + queryParameters( + parameterWithName("sortBy").description("정렬 기준").optional() + ), + relaxedResponseFields( + fieldWithPath("data.content[].productId").type(JsonFieldType.NUMBER).description("상품 ID"), + fieldWithPath("data.content[].productName").type(JsonFieldType.STRING).description("상품명"), + fieldWithPath("data.content[].productManufacturer").type(JsonFieldType.STRING).description("제조사"), + fieldWithPath("data.content[].productImageUrl").type(JsonFieldType.STRING).description("상품 이미지 URL"), + fieldWithPath("data.content[].reviewId").type(JsonFieldType.NUMBER).description("상품 카테고리 ID"), + fieldWithPath("data.content[].reviewerId").type(JsonFieldType.NUMBER).description("리뷰 작성자 ID"), + fieldWithPath("data.content[].reviewerNickname").type(JsonFieldType.STRING).description("리뷰 작성자 닉네임"), + fieldWithPath("data.content[].reviewerProfileImageUrl").type(JsonFieldType.STRING).description("리뷰 작성자"), + fieldWithPath("data.content[].reviewLikeCount").type(JsonFieldType.NUMBER).description("상품 리뷰 개수"), + fieldWithPath("data.content[].reviewRating").type(JsonFieldType.NUMBER).description("상품 리뷰 평점"), + fieldWithPath("data.content[].reviewContent").type(JsonFieldType.STRING).description("리뷰 내용").optional(), + fieldWithPath("data.content[].isReviewLiked").type(JsonFieldType.BOOLEAN).description("판매 편의점 이름"), + fieldWithPath("data.content[].isProductBookmarked").type(JsonFieldType.BOOLEAN).description("행사 정보").optional(), + fieldWithPath("data.content[].reviewImageUrls").type(JsonFieldType.ARRAY).description("할인 가격").optional() + ) + )); + } + private List getTags() { List tags = new ArrayList<>(); tags.add(Tag.builder().id(2L).name("맵부심").group(1).build()); @@ -614,7 +666,11 @@ private SignUpResponseDto createResponse() { .manufacturer(manufacturer2) .build(); - User user = User.builder().build(); + User user = User.builder() + .id(1L) + .role(Role.REGULAR) + .nickname("닉네임") + .build(); ProductLike productLike = ProductLike.builder() .user(user) @@ -658,6 +714,16 @@ private SignUpResponseDto createResponse() { .discountAmount(300) .build(); + Review review1 = Review.builder() + .id(1L) + .user(user) + .product(product1) + .rating(5) + .imageUrls(List.of("image1", "image2")) + .content("리뷰입니다") + .build(); + + private UserResponseDto getUser() { return UserResponseDto.of(createUser(), 4L); } @@ -682,4 +748,15 @@ private List getProductsResponse() { return List.of(productResponse1, productResponse2); } + private List getReviewsResponse() { + ReadUserReviewQueryDto queryDto1 = new ReadUserReviewQueryDto(review1.getId(), + review1.getProduct().getId(), product1.getName(), product1.getManufacturer().getName(), + product1.getImageUrl(), review1.getUser().getId(), review1.getUser().getNickname(), + "프로필 이미지", review1.getLikeCount(), review1.getRating(), + review1.getContent(), LocalDateTime.now(), null, null); + ReadUserReviewResponseDto dto1 = ReadUserReviewResponseDto.of(queryDto1, + review1.getReviewImages().stream().map(ReviewImage::getImageUrl).toList()); + return List.of(dto1); + } + } diff --git a/src/test/java/com/cvsgo/service/ReviewServiceTest.java b/src/test/java/com/cvsgo/service/ReviewServiceTest.java index b6252657..e2282e12 100644 --- a/src/test/java/com/cvsgo/service/ReviewServiceTest.java +++ b/src/test/java/com/cvsgo/service/ReviewServiceTest.java @@ -17,6 +17,8 @@ import com.cvsgo.dto.review.ReadProductReviewResponseDto; import com.cvsgo.dto.review.ReadReviewQueryDto; import com.cvsgo.dto.review.ReadReviewRequestDto; +import com.cvsgo.dto.review.ReadUserReviewQueryDto; +import com.cvsgo.dto.review.ReadUserReviewResponseDto; import com.cvsgo.dto.review.ReviewSortBy; import com.cvsgo.dto.review.UpdateReviewRequestDto; import com.cvsgo.entity.Product; @@ -35,6 +37,7 @@ import com.cvsgo.repository.ReviewImageRepository; import com.cvsgo.repository.ReviewLikeRepository; import com.cvsgo.repository.ReviewRepository; +import com.cvsgo.repository.UserRepository; import com.cvsgo.repository.UserTagRepository; import jakarta.persistence.EntityManager; import java.time.LocalDateTime; @@ -68,6 +71,9 @@ class ReviewServiceTest { @Mock private ReviewLikeRepository reviewLikeRepository; + @Mock + private UserRepository userRepository; + @InjectMocks ReviewService reviewService; @@ -411,6 +417,21 @@ void should_throw_NotFoundException_when_delete_review_like_but_review_like_does then(reviewLikeRepository).should(times(1)).findByReviewAndUser(any(), any()); } + @Test + @DisplayName("특정 회원이 작성한 리뷰 목록을 정상적으로 조회한다") + void succeed_to_read_user_review() { + given(reviewRepository.findAllByUser(any(), any(), any(), any())) + .willReturn(List.of(readUserReviewQueryDto)); + given(userRepository.findById(any())).willReturn(Optional.of(user1)); + + List reviews = reviewService.readUserReviewList(user1, 1L, + ReviewSortBy.LIKE, PageRequest.of(0, 20)).getContent(); + + then(reviewRepository).should(times(1)).findAllByUser(any(), any(), any(), any()); + then(reviewRepository).should(times(1)).countByUser(any()); + assertThat(reviews.size()).isEqualTo(1); + } + User user1 = User.builder() .id(1L) .userId("abc@naver.com") @@ -470,4 +491,9 @@ void should_throw_NotFoundException_when_delete_review_like_but_review_like_does ReviewLike reviewLike = ReviewLike.create(user1, review); + ReadUserReviewQueryDto readUserReviewQueryDto = new ReadUserReviewQueryDto(1L, + 1L, "상품", "제조사", "이미지 URL", 1L, + "닉네임", "프로필 이미지 URL", 3L, 5, + "내용", LocalDateTime.now(), null, null); + } From a90dbf0c61eccc45b2a486488b237392c388fbc2 Mon Sep 17 00:00:00 2001 From: feel-coding Date: Sun, 12 Nov 2023 23:32:16 +0900 Subject: [PATCH 3/6] =?UTF-8?q?docs:=20=ED=8A=B9=EC=A0=95=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=EC=9D=98=20=EB=A6=AC=EB=B7=B0=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EB=AC=B8=EC=84=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/api-doc.adoc | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/docs/asciidoc/api-doc.adoc b/src/docs/asciidoc/api-doc.adoc index 7af03a72..bb4eff67 100644 --- a/src/docs/asciidoc/api-doc.adoc +++ b/src/docs/asciidoc/api-doc.adoc @@ -362,7 +362,25 @@ include::{snippets}/review-controller-test/respond_200_when_success_to_read_prod ==== Sample Response include::{snippets}/review-controller-test/respond_200_when_success_to_read_product_reviews/http-response.adoc[] -=== 4-5. 리뷰 좋아요 추가 +=== 4-5. 특정 사용자의 리뷰 목록 조회 +==== Request Fields +include::{snippets}/user-controller-test/respond_200_when_read_user_review_list_successfully/request-fields.adoc[] +==== 정렬 기준 항목 +|=== +| sortBy 값 | 정렬 기준 | 비고 + +| LATEST | 최신순 | 정렬 기준 미지정시 기본값 +| LIKE | 좋아요순 | +| RATING | 별점순 | +|=== +==== Sample Request +include::{snippets}/user-controller-test/respond_200_when_read_user_review_list_successfully/http-request.adoc[] +==== Response Fields +include::{snippets}/user-controller-test/respond_200_when_read_user_review_list_successfully/response-fields.adoc[] +==== Sample Response +include::{snippets}/user-controller-test/respond_200_when_read_user_review_list_successfully/http-response.adoc[] + +=== 4-6. 리뷰 좋아요 추가 ==== Path Parameters include::{snippets}/review-controller-test/respond_201_when_succeed_to_create_review_like/path-parameters.adoc[] ==== Sample Request @@ -377,7 +395,7 @@ include::{snippets}/review-controller-test/respond_201_when_succeed_to_create_re | `409 CONFLICT` | `DUPLICATE_REVIEW_LIKE` | 해당하는 리뷰 좋아요가 이미 있는 경우 |=== -=== 4-6. 리뷰 좋아요 취소 +=== 4-7. 리뷰 좋아요 취소 ==== Path Parameters include::{snippets}/review-controller-test/respond_200_when_succeed_to_delete_review_like/path-parameters.adoc[] ==== Sample Request From ed2d46610a91f7a788489120954303494cb270d4 Mon Sep 17 00:00:00 2001 From: feel-coding Date: Fri, 5 Jan 2024 13:40:07 +0900 Subject: [PATCH 4/6] =?UTF-8?q?refactor:=20=ED=9A=8C=EC=9B=90=20=EB=93=B1?= =?UTF-8?q?=EA=B8=89=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=20=EA=B0=9C=EC=88=98=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EB=B6=80=EB=B6=84=20=EB=A9=94=EC=86=8C=EB=93=9C=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20#82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/cvsgo/service/ReviewService.java | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/cvsgo/service/ReviewService.java b/src/main/java/com/cvsgo/service/ReviewService.java index 82541326..5d0cbf82 100644 --- a/src/main/java/com/cvsgo/service/ReviewService.java +++ b/src/main/java/com/cvsgo/service/ReviewService.java @@ -163,13 +163,7 @@ public ReadReviewResponseDto readReviewList(User user, ReadReviewRequestDto requ public Page readProductReviewList(User user, Long productId, ReadProductReviewRequestDto request, Pageable pageable) { - if (user == null || user.getRole() != Role.REGULAR) { - if (pageable.getPageNumber() > 0) { - throw FORBIDDEN_REVIEW; - } else { - pageable = PageRequest.of(pageable.getPageNumber(), 5); - } - } + pageable = getPageableByRole(user, pageable); List reviews = reviewRepository.findAllByProductIdAndFilter(user, productId, request, pageable); @@ -202,13 +196,7 @@ public Page readProductReviewList(User user, Long @Transactional(readOnly = true) public Page readUserReviewList(User loginUser, Long userId, ReviewSortBy sortBy, Pageable pageable) { - if (loginUser == null || loginUser.getRole() != Role.REGULAR) { - if (pageable.getPageNumber() > 0) { - throw FORBIDDEN_REVIEW; - } else { - pageable = PageRequest.of(pageable.getPageNumber(), 5); - } - } + pageable = getPageableByRole(loginUser, pageable); User user = userRepository.findById(userId).orElseThrow(() -> NOT_FOUND_USER); List reviews = reviewRepository.findAllByUser(loginUser, user, @@ -226,6 +214,17 @@ public Page readUserReviewList(User loginUser, Long u return new PageImpl<>(results, pageable, totalCount); } + private Pageable getPageableByRole(User loginUser, Pageable pageable) { + if (loginUser == null || loginUser.getRole() != Role.REGULAR) { + if (pageable.getPageNumber() > 0) { + throw FORBIDDEN_REVIEW; + } else { + pageable = PageRequest.of(pageable.getPageNumber(), 5); + } + } + return pageable; + } + /** * 리뷰 좋아요를 추가합니다. * From 96be8b5dfb6959c9e85ae10bba925422c388440f Mon Sep 17 00:00:00 2001 From: feel-coding Date: Fri, 5 Jan 2024 13:50:13 +0900 Subject: [PATCH 5/6] =?UTF-8?q?refactor:=20=ED=95=B4=EB=8B=B9=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=EA=B0=80=20throw=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?#82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/cvsgo/service/ReviewService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/cvsgo/service/ReviewService.java b/src/main/java/com/cvsgo/service/ReviewService.java index 5d0cbf82..65302096 100644 --- a/src/main/java/com/cvsgo/service/ReviewService.java +++ b/src/main/java/com/cvsgo/service/ReviewService.java @@ -192,6 +192,8 @@ public Page readProductReviewList(User user, Long * @param sortBy 정렬 정보 * @param pageable 페이지 정보 * @return 리뷰 목록 + * @throws ForbiddenException 정회원이 아닌 회원이 0페이지가 아닌 다른 페이지를 조회하는 경우 + * @throws NotFoundException 해당 회원이 존재하지 않는 경우 */ @Transactional(readOnly = true) public Page readUserReviewList(User loginUser, Long userId, From 60dcd610a188c9cbeb75de6862a25410cdcf2dee Mon Sep 17 00:00:00 2001 From: feel-coding Date: Fri, 5 Jan 2024 14:08:12 +0900 Subject: [PATCH 6/6] =?UTF-8?q?docs:=20=EB=AC=B8=EC=84=9C=EC=97=90=20?= =?UTF-8?q?=ED=8A=B9=EC=A0=95=20=ED=9A=8C=EC=9B=90=EC=9D=98=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EC=A1=B0=ED=9A=8C=20API=20=EB=B6=84=EB=A5=98=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20#82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/api-doc.adoc | 40 +++++++++++++++++----------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/docs/asciidoc/api-doc.adoc b/src/docs/asciidoc/api-doc.adoc index bb4eff67..a14529a4 100644 --- a/src/docs/asciidoc/api-doc.adoc +++ b/src/docs/asciidoc/api-doc.adoc @@ -140,6 +140,24 @@ include::{snippets}/user-controller-test/respond_200_when_read_bookmarked_produc ==== Sample Response include::{snippets}/user-controller-test/respond_200_when_read_bookmarked_product_list_successfully/http-response.adoc[] +=== 1-11. 특정 사용자의 리뷰 목록 조회 +==== Request Fields +include::{snippets}/user-controller-test/respond_200_when_read_user_review_list_successfully/request-fields.adoc[] +==== 정렬 기준 항목 +|=== +| sortBy 값 | 정렬 기준 | 비고 + +| LATEST | 최신순 | 정렬 기준 미지정시 기본값 +| LIKE | 좋아요순 | +| RATING | 별점순 | +|=== +==== Sample Request +include::{snippets}/user-controller-test/respond_200_when_read_user_review_list_successfully/http-request.adoc[] +==== Response Fields +include::{snippets}/user-controller-test/respond_200_when_read_user_review_list_successfully/response-fields.adoc[] +==== Sample Response +include::{snippets}/user-controller-test/respond_200_when_read_user_review_list_successfully/http-response.adoc[] + == 2. 인증 === 2-1. 로그인 ==== Request Fields @@ -362,25 +380,7 @@ include::{snippets}/review-controller-test/respond_200_when_success_to_read_prod ==== Sample Response include::{snippets}/review-controller-test/respond_200_when_success_to_read_product_reviews/http-response.adoc[] -=== 4-5. 특정 사용자의 리뷰 목록 조회 -==== Request Fields -include::{snippets}/user-controller-test/respond_200_when_read_user_review_list_successfully/request-fields.adoc[] -==== 정렬 기준 항목 -|=== -| sortBy 값 | 정렬 기준 | 비고 - -| LATEST | 최신순 | 정렬 기준 미지정시 기본값 -| LIKE | 좋아요순 | -| RATING | 별점순 | -|=== -==== Sample Request -include::{snippets}/user-controller-test/respond_200_when_read_user_review_list_successfully/http-request.adoc[] -==== Response Fields -include::{snippets}/user-controller-test/respond_200_when_read_user_review_list_successfully/response-fields.adoc[] -==== Sample Response -include::{snippets}/user-controller-test/respond_200_when_read_user_review_list_successfully/http-response.adoc[] - -=== 4-6. 리뷰 좋아요 추가 +=== 4-5. 리뷰 좋아요 추가 ==== Path Parameters include::{snippets}/review-controller-test/respond_201_when_succeed_to_create_review_like/path-parameters.adoc[] ==== Sample Request @@ -395,7 +395,7 @@ include::{snippets}/review-controller-test/respond_201_when_succeed_to_create_re | `409 CONFLICT` | `DUPLICATE_REVIEW_LIKE` | 해당하는 리뷰 좋아요가 이미 있는 경우 |=== -=== 4-7. 리뷰 좋아요 취소 +=== 4-6. 리뷰 좋아요 취소 ==== Path Parameters include::{snippets}/review-controller-test/respond_200_when_succeed_to_delete_review_like/path-parameters.adoc[] ==== Sample Request