Skip to content

Commit a553462

Browse files
authored
Merge pull request #57 from IT-Cotato/feature/48
[Feat] 리뷰 조회 기능 구현
2 parents 1e79272 + cd86eaf commit a553462

File tree

20 files changed

+1460
-2
lines changed

20 files changed

+1460
-2
lines changed
Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
11
package com.ongil.backend.domain.order.repository;
22

3+
import java.util.List;
4+
5+
import org.springframework.data.jpa.repository.EntityGraph;
36
import org.springframework.data.jpa.repository.JpaRepository;
7+
import org.springframework.data.jpa.repository.Query;
8+
import org.springframework.data.repository.query.Param;
49

510
import com.ongil.backend.domain.order.entity.OrderItem;
611

712
public interface OrderItemRepository extends JpaRepository<OrderItem, Long> {
8-
}
13+
14+
// 사용자의 주문 상품 목록 조회 (리뷰 작성 가능 목록용)
15+
@EntityGraph(attributePaths = {"product", "product.brand", "order"})
16+
@Query("SELECT oi FROM OrderItem oi " +
17+
"WHERE oi.order.user.id = :userId " +
18+
"ORDER BY oi.order.createdAt DESC")
19+
List<OrderItem> findByOrderUserIdWithProduct(@Param("userId") Long userId);
20+
}

src/main/java/com/ongil/backend/domain/product/converter/ProductConverter.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ public ProductDetailResponse toDetailResponse(Product product, List<ProductOptio
4040
.categoryName(product.getCategory().getName())
4141
.onSale(product.getOnSale())
4242
.productType(product.getProductType())
43+
.reviewCount(product.getReviewCount())
44+
.reviewRating(product.getReviewRating())
4345
.build();
4446
}
4547

@@ -62,6 +64,7 @@ public ProductSimpleResponse toSimpleResponse(Product product) {
6264
.viewCount(product.getViewCount())
6365
.purchaseCount(product.getPurchaseCount())
6466
.reviewCount(product.getReviewCount())
67+
.reviewRating(product.getReviewRating())
6568
.build();
6669
}
6770

src/main/java/com/ongil/backend/domain/product/dto/response/ProductDetailResponse.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ public class ProductDetailResponse {
6767
@Schema(description = "상품 타입 (NORMAL: 일반 상품, SPECIAL_SALE: 특가 상품)")
6868
private ProductType productType;
6969

70+
// 리뷰 정보
71+
@Schema(description = "리뷰 개수")
72+
private Integer reviewCount;
73+
74+
@Schema(description = "리뷰 평균 평점")
75+
private Double reviewRating;
76+
7077
// 소재 설명 내부 클래스
7178
@Getter
7279
@Builder

src/main/java/com/ongil/backend/domain/product/dto/response/ProductSimpleResponse.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,7 @@ public class ProductSimpleResponse {
4343

4444
@Schema(description = "리뷰 개수")
4545
private Integer reviewCount;
46+
47+
@Schema(description = "리뷰 평균 평점")
48+
private Double reviewRating;
4649
}

src/main/java/com/ongil/backend/domain/product/entity/Product.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ public class Product extends BaseEntity {
7070
@Column(name = "review_count")
7171
private Integer reviewCount = 0;
7272

73+
@Column(name = "review_rating", nullable = false)
74+
private Double reviewRating = 0.0;
75+
7376
@Formula("coalesce(view_count, 0) + coalesce(purchase_count, 0)")
7477
private Integer popularity;
7578

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.ongil.backend.domain.review.controller;
2+
3+
import java.util.List;
4+
5+
import org.springframework.data.domain.Page;
6+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
7+
import org.springframework.web.bind.annotation.*;
8+
9+
import com.ongil.backend.domain.review.dto.request.ReviewListRequest;
10+
import com.ongil.backend.domain.review.dto.response.*;
11+
import com.ongil.backend.domain.review.enums.ReviewType;
12+
import com.ongil.backend.domain.review.service.ReviewService;
13+
import com.ongil.backend.global.common.dto.DataResponse;
14+
15+
import io.swagger.v3.oas.annotations.Operation;
16+
import io.swagger.v3.oas.annotations.Parameter;
17+
import io.swagger.v3.oas.annotations.tags.Tag;
18+
import lombok.RequiredArgsConstructor;
19+
20+
@Tag(name = "Review", description = "리뷰 API")
21+
@RestController
22+
@RequiredArgsConstructor
23+
public class ReviewController {
24+
25+
private final ReviewService reviewService;
26+
27+
@Operation(summary = "상품별 리뷰 목록 조회", description = "상품의 리뷰 목록을 조회합니다. 유사 체형 필터, 사이즈/색상 필터, 정렬을 지원합니다.")
28+
@GetMapping("/api/products/{productId}/reviews")
29+
public DataResponse<Page<ReviewListResponse>> getProductReviews(
30+
@PathVariable Long productId,
31+
@AuthenticationPrincipal Long userId,
32+
@ModelAttribute ReviewListRequest request
33+
) {
34+
Page<ReviewListResponse> reviews = reviewService.getProductReviews(productId, userId, request);
35+
return DataResponse.from(reviews);
36+
}
37+
38+
@Operation(summary = "리뷰 통계 요약 조회", description = "상품의 리뷰 통계 요약을 조회합니다. 사이즈는 유사 체형 기준, 색감/소재는 전체 유저 기준입니다.")
39+
@GetMapping("/api/products/{productId}/reviews/summary")
40+
public DataResponse<ReviewSummaryResponse> getReviewSummary(
41+
@PathVariable Long productId,
42+
@AuthenticationPrincipal Long userId
43+
) {
44+
ReviewSummaryResponse summary = reviewService.getReviewSummary(productId, userId);
45+
return DataResponse.from(summary);
46+
}
47+
48+
@Operation(summary = "리뷰 상세 조회", description = "리뷰의 상세 정보를 조회합니다. 모든 선택지 답변을 부문별로 반환합니다.")
49+
@GetMapping("/api/reviews/{reviewId}/details")
50+
public DataResponse<ReviewDetailResponse> getReviewDetail(
51+
@PathVariable Long reviewId,
52+
@AuthenticationPrincipal Long userId
53+
) {
54+
ReviewDetailResponse detail = reviewService.getReviewDetail(reviewId, userId);
55+
return DataResponse.from(detail);
56+
}
57+
58+
@Operation(summary = "내가 작성한 리뷰 조회", description = "로그인한 사용자가 작성한 리뷰 목록을 조회합니다.")
59+
@GetMapping("/api/users/me/reviews")
60+
public DataResponse<Page<MyReviewResponse>> getMyReviews(
61+
@AuthenticationPrincipal Long userId,
62+
@Parameter(description = "리뷰 타입 필터") @RequestParam(required = false) ReviewType reviewType,
63+
@Parameter(description = "페이지 번호") @RequestParam(defaultValue = "0") int page,
64+
@Parameter(description = "페이지 크기") @RequestParam(defaultValue = "10") int pageSize
65+
) {
66+
Page<MyReviewResponse> reviews = reviewService.getMyReviews(userId, reviewType, page, pageSize);
67+
return DataResponse.from(reviews);
68+
}
69+
70+
@Operation(summary = "작성 가능한 리뷰 목록 조회", description = "로그인한 사용자가 작성 가능한 리뷰 목록을 조회합니다.")
71+
@GetMapping("/api/users/me/reviews/pending")
72+
public DataResponse<List<PendingReviewResponse>> getPendingReviews(
73+
@AuthenticationPrincipal Long userId
74+
) {
75+
List<PendingReviewResponse> pendingReviews = reviewService.getPendingReviews(userId);
76+
return DataResponse.from(pendingReviews);
77+
}
78+
79+
@Operation(summary = "리뷰 도움돼요 토글", description = "리뷰의 도움돼요를 토글합니다. 이미 눌렀으면 취소, 안 눌렀으면 추가됩니다.")
80+
@PostMapping("/api/reviews/{reviewId}/helpful")
81+
public DataResponse<ReviewHelpfulResponse> toggleHelpful(
82+
@PathVariable Long reviewId,
83+
@AuthenticationPrincipal Long userId
84+
) {
85+
ReviewHelpfulResponse response = reviewService.toggleHelpful(reviewId, userId);
86+
return DataResponse.from(response);
87+
}
88+
}

0 commit comments

Comments
 (0)