Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: 리뷰 랭킹 조회 API 수정 #75

Merged
merged 7 commits into from
Jun 7, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ public interface ReviewFavoriteRepository extends JpaRepository<ReviewFavorite,
void deleteByReview(final Review review);

List<ReviewFavorite> findByReview(final Review review);

boolean existsByMemberAndReviewAndFavoriteTrue(final Member member, final Review review);
}
19 changes: 15 additions & 4 deletions src/main/java/com/funeat/review/application/ReviewService.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public class ReviewService {
private static final int RANKING_SIZE = 2;
private static final long RANKING_MINIMUM_FAVORITE_COUNT = 1L;
private static final int REVIEW_PAGE_SIZE = 10;
private static final long GUEST_ID = -1L;

private final ReviewRepository reviewRepository;
private final TagRepository tagRepository;
Expand Down Expand Up @@ -212,17 +213,27 @@ private Boolean hasNextPage(final List<SortingReviewDto> sortingReviews) {
return sortingReviews.size() > REVIEW_PAGE_SIZE;
}

public RankingReviewsResponse getTopReviews() {
public RankingReviewsResponse getTopReviews(final Long memberId) {
final List<Review> reviews = reviewRepository.findReviewsByFavoriteCountGreaterThanEqual(RANKING_MINIMUM_FAVORITE_COUNT);
final List<RankingReviewDto> dtos = reviews.stream()
.sorted(Comparator.comparing(Review::calculateRankingScore).reversed())
.limit(RANKING_SIZE)
.map(RankingReviewDto::toDto)
.collect(Collectors.toList());

.map(review -> createRankingReviewDto(memberId, review))
.toList();
return RankingReviewsResponse.toResponse(dtos);
}

private RankingReviewDto createRankingReviewDto(final Long memberId, final Review review) {
if (memberId == GUEST_ID) {
return RankingReviewDto.toDto(review, false);
}

final Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new MemberNotFoundException(MEMBER_NOT_FOUND, memberId));
final Boolean favorite = reviewFavoriteRepository.existsByMemberAndReviewAndFavoriteTrue(member, review);
return RankingReviewDto.toDto(review, favorite);
}

public MemberReviewsResponse findReviewByMember(final Long memberId, final Pageable pageable) {
final Member findMember = memberRepository.findById(memberId)
.orElseThrow(() -> new MemberNotFoundException(MEMBER_NOT_FOUND, memberId));
Expand Down
74 changes: 22 additions & 52 deletions src/main/java/com/funeat/review/dto/RankingReviewDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,70 +5,40 @@
import com.funeat.tag.dto.TagDto;
import java.util.List;

public class RankingReviewDto {

private final Long reviewId;
private final Long productId;
private final String categoryType;
private final String productName;
private final String content;
private final String image;
private final List<TagDto> tags;

private RankingReviewDto(final Long reviewId, final Long productId, final String categoryType,
final String productName, final String content, final String image,
final List<TagDto> tags) {
this.reviewId = reviewId;
this.productId = productId;
this.categoryType = categoryType;
this.productName = productName;
this.content = content;
this.image = image;
this.tags = tags;
}

public static RankingReviewDto toDto(final Review review) {
public record RankingReviewDto(
Long id,
String userName,
String profileImage,
Long productId,
String productName,
String content,
String image,
Long rating,
Boolean rebuy,
Long favoriteCount,
Boolean favorite,
List<TagDto> tags
) {

public static RankingReviewDto toDto(final Review review, final Boolean favorite) {
final List<TagDto> tagDtos = review.getReviewTags().stream()
.map(ReviewTag::getTag)
.map(TagDto::toDto)
.toList();

return new RankingReviewDto(
review.getId(),
review.getMember().getNickname(),
review.getMember().getProfileImage(),
review.getProduct().getId(),
review.getProduct().getCategory().getType().getName(),
review.getProduct().getName(),
review.getContent(),
review.getImage(),
review.getRating(),
review.getReBuy(),
review.getFavoriteCount(),
favorite,
tagDtos
);
}

public Long getReviewId() {
return reviewId;
}

public Long getProductId() {
return productId;
}

public String getProductName() {
return productName;
}

public String getContent() {
return content;
}

public String getCategoryType() {
return categoryType;
}

public String getImage() {
return image;
}

public List<TagDto> getTags() {
return tags;
}
}
14 changes: 3 additions & 11 deletions src/main/java/com/funeat/review/dto/RankingReviewsResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,11 @@

import java.util.List;

public class RankingReviewsResponse {

private final List<RankingReviewDto> reviews;

public RankingReviewsResponse(final List<RankingReviewDto> reviews) {
this.reviews = reviews;
}
public record RankingReviewsResponse(
List<RankingReviewDto> reviews
) {

public static RankingReviewsResponse toResponse(final List<RankingReviewDto> reviews) {
return new RankingReviewsResponse(reviews);
}

public List<RankingReviewDto> getReviews() {
return reviews;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
import com.funeat.review.dto.ReviewFavoriteRequest;
import com.funeat.review.dto.SortingReviewRequest;
import com.funeat.review.dto.SortingReviewsResponse;
import jakarta.validation.Valid;
import java.net.URI;
import java.util.Optional;
import jakarta.validation.Valid;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
Expand Down Expand Up @@ -68,8 +68,8 @@ public ResponseEntity<SortingReviewsResponse> getSortingReviews(@AuthenticationP
}

@GetMapping("/api/ranks/reviews")
public ResponseEntity<RankingReviewsResponse> getRankingReviews() {
final RankingReviewsResponse response = reviewService.getTopReviews();
public ResponseEntity<RankingReviewsResponse> getRankingReviews(@AuthenticationPrincipal final LoginInfo loginInfo) {
final RankingReviewsResponse response = reviewService.getTopReviews(loginInfo.getId());

return ResponseEntity.ok(response);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ ResponseEntity<SortingReviewsResponse> getSortingReviews(@AuthenticationPrincipa
description = "리뷰 랭킹 Top3 조회 성공."
)
@GetMapping
ResponseEntity<RankingReviewsResponse> getRankingReviews();
ResponseEntity<RankingReviewsResponse> getRankingReviews(@AuthenticationPrincipal final LoginInfo loginInfo);

@Operation(summary = "좋아요를 제일 많은 받은 리뷰 조회", description = "특정 상품에 대해 좋아요를 제일 많이 받은 리뷰를 조회한다.")
@ApiResponse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import static com.funeat.acceptance.common.CommonSteps.정상_처리;
import static com.funeat.acceptance.common.CommonSteps.정상_처리_NO_CONTENT;
import static com.funeat.acceptance.common.CommonSteps.찾을수_없음;
import static com.funeat.acceptance.product.ProductSteps.상품_상세_조회_요청;
import static com.funeat.acceptance.review.ReviewSteps.리뷰_랭킹_조회_요청;
import static com.funeat.acceptance.review.ReviewSteps.리뷰_상세_조회_요청;
import static com.funeat.acceptance.review.ReviewSteps.리뷰_작성_요청;
Expand Down Expand Up @@ -715,7 +714,7 @@ class getSortingReviews_비로그인_사용자_실패_테스트 {
class getRankingReviews_성공_테스트 {

@Test
void 리뷰_랭킹을_조회하다() {
void 로그인을_하지_않고_리뷰_랭킹을_조회한다() {
// given
final var 카테고리 = 카테고리_즉석조리_생성();
단일_카테고리_저장(카테고리);
Expand All @@ -737,7 +736,33 @@ class getRankingReviews_성공_테스트 {

// then
STATUS_CODE를_검증한다(응답, 정상_처리);
리뷰_랭킹_조회_결과를_검증한다(응답, List.of(리뷰2, 리뷰3));
리뷰_랭킹_조회_결과를_검증한다(응답, List.of(리뷰2, 리뷰3), List.of(false, false));
}

@Test
void 로그인_후_리뷰_랭킹을_조회한다() {
// given
final var 카테고리 = 카테고리_즉석조리_생성();
단일_카테고리_저장(카테고리);
final var 상품1 = 단일_상품_저장(상품_삼각김밥_가격1000원_평점3점_생성(카테고리));
final var 상품2 = 단일_상품_저장(상품_삼각김밥_가격2000원_평점3점_생성(카테고리));
final var 태그 = 단일_태그_저장(태그_맛있어요_TASTE_생성());

리뷰_작성_요청(로그인_쿠키_획득(멤버1), 상품1, 사진_명세_요청(이미지1), 리뷰추가요청_재구매O_생성(점수_3점, List.of(태그)));
리뷰_작성_요청(로그인_쿠키_획득(멤버2), 상품1, 사진_명세_요청(이미지2), 리뷰추가요청_재구매O_생성(점수_4점, List.of(태그)));
리뷰_작성_요청(로그인_쿠키_획득(멤버3), 상품1, 사진_명세_요청(이미지3), 리뷰추가요청_재구매O_생성(점수_3점, List.of(태그)));
리뷰_작성_요청(로그인_쿠키_획득(멤버1), 상품2, 사진_명세_요청(이미지4), 리뷰추가요청_재구매O_생성(점수_5점, List.of(태그)));
리뷰_작성_요청(로그인_쿠키_획득(멤버2), 상품2, 사진_명세_요청(이미지5), 리뷰추가요청_재구매O_생성(점수_1점, List.of(태그)));
여러명이_리뷰_좋아요_요청(List.of(멤버1, 멤버2, 멤버3), 상품1, 리뷰2, 좋아요O);
여러명이_리뷰_좋아요_요청(List.of(멤버1, 멤버2), 상품1, 리뷰3, 좋아요O);
여러명이_리뷰_좋아요_요청(List.of(멤버1), 상품1, 리뷰4, 좋아요O);

// when
final var 응답 = 리뷰_랭킹_조회_요청(로그인_쿠키_획득(멤버2));

// then
STATUS_CODE를_검증한다(응답, 정상_처리);
리뷰_랭킹_조회_결과를_검증한다(응답, List.of(리뷰2, 리뷰3), List.of(true, true));
}
}

Expand Down Expand Up @@ -848,12 +873,15 @@ class getReviewDetail_실패_테스트 {
.containsExactlyElementsOf(reviewIds);
}

private void 리뷰_랭킹_조회_결과를_검증한다(final ExtractableResponse<Response> response, final List<Long> reviewIds) {
private void 리뷰_랭킹_조회_결과를_검증한다(final ExtractableResponse<Response> response, final List<Long> reviewIds,
final List<Boolean> favorites) {
final var actual = response.jsonPath()
.getList("reviews", RankingReviewDto.class);

assertThat(actual).extracting(RankingReviewDto::getReviewId)
assertThat(actual).extracting(RankingReviewDto::id)
.containsExactlyElementsOf(reviewIds);
assertThat(actual).extracting(RankingReviewDto::favorite)
.isEqualTo(favorites);
}

private void 좋아요를_제일_많이_받은_리뷰_결과를_검증한다(final ExtractableResponse<Response> response, final Long reviewId) {
Expand Down
9 changes: 9 additions & 0 deletions src/test/java/com/funeat/acceptance/review/ReviewSteps.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,15 @@ public class ReviewSteps {
.extract();
}

public static ExtractableResponse<Response> 리뷰_랭킹_조회_요청(final String loginCookie) {
return given()
.cookie("SESSION", loginCookie)
.when()
.get("/api/ranks/reviews")
.then()
.extract();
}

public static ExtractableResponse<Response> 좋아요를_제일_많이_받은_리뷰_조회_요청(final Long productId) {
return given()
.when()
Expand Down
5 changes: 5 additions & 0 deletions src/test/java/com/funeat/fixture/ReviewFixture.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import static com.funeat.fixture.PageFixture.평점_오름차순;

import com.funeat.member.domain.Member;
import com.funeat.member.domain.favorite.ReviewFavorite;
import com.funeat.product.domain.Product;
import com.funeat.review.domain.Review;
import com.funeat.review.dto.ReviewCreateRequest;
Expand Down Expand Up @@ -102,6 +103,10 @@ public class ReviewFixture {
return new ReviewFavoriteRequest(favorite);
}

public static ReviewFavorite 리뷰_좋아요_생성(final Member member, final Review review, final Boolean favorite) {
return ReviewFavorite.create(member, review, favorite);
}

public static SortingReviewRequest 리뷰정렬요청_좋아요수_내림차순_생성(final Long lastReviewId) {
return new SortingReviewRequest(좋아요수_내림차순, lastReviewId);
}
Expand Down
Loading