From 4d23cffee6f0849f6235bd1b7a90539cd5a0aa58 Mon Sep 17 00:00:00 2001 From: 70825 Date: Tue, 19 Sep 2023 16:27:46 +0900 Subject: [PATCH 01/35] =?UTF-8?q?feat:=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=EA=B8=B0=EC=A4=80=20=EB=82=B4=EB=A6=BC=EC=B0=A8=EC=88=9C=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=BF=BC=EB=A6=AC=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../funeat/review/dto/SortingReviewDto.java | 37 +++++++++++++------ .../review/persistence/ReviewRepository.java | 24 ++++++++++++ 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java b/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java index 7254dd6c1..ffeb7f892 100644 --- a/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java +++ b/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java @@ -11,17 +11,17 @@ public class SortingReviewDto { - private final Long id; - private final String userName; - private final String profileImage; - private final String image; - private final Long rating; - private final List tags; - private final String content; - private final boolean rebuy; - private final Long favoriteCount; - private final boolean favorite; - private final LocalDateTime createdAt; + private Long id; + private String userName; + private String profileImage; + private String image; + private Long rating; + private List tags; + private String content; + private boolean rebuy; + private Long favoriteCount; + private boolean favorite; + private LocalDateTime createdAt; public SortingReviewDto(final Long id, final String userName, final String profileImage, final String image, final Long rating, final List tags, @@ -40,6 +40,21 @@ public SortingReviewDto(final Long id, final String userName, final String profi this.createdAt = createdAt; } + public SortingReviewDto(final Long id, final String userName, final String profileImage, final String image, + final Long rating, final String content, final boolean rebuy, final Long favoriteCount, + final boolean favorite, final LocalDateTime createdAt) { + this.id = id; + this.userName = userName; + this.profileImage = profileImage; + this.image = image; + this.rating = rating; + this.content = content; + this.rebuy = rebuy; + this.favoriteCount = favoriteCount; + this.favorite = favorite; + this.createdAt = createdAt; + } + public static SortingReviewDto toDto(final Review review, final Member member) { return new SortingReviewDto( review.getId(), diff --git a/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java b/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java index f5ed0058f..edb324f37 100644 --- a/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java +++ b/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java @@ -5,6 +5,7 @@ import com.funeat.member.domain.Member; import com.funeat.product.domain.Product; import com.funeat.review.domain.Review; +import com.funeat.review.dto.SortingReviewDto; import java.util.List; import java.util.Optional; import org.springframework.data.domain.Page; @@ -18,6 +19,29 @@ public interface ReviewRepository extends JpaRepository { Page findReviewsByProduct(final Pageable pageable, final Product product); + @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + + "FROM Review r " + + "JOIN r.member m " + + "LEFT JOIN r.reviewFavorites rf " + + "WHERE r.product = :product " + + "AND (" + + "(r.favoriteCount = " + + "(SELECT r2.favoriteCount " + + "FROM Review r2 " + + "WHERE r2.id = :lastReviewId) " + + "AND r.id > :lastReviewId " + + ") " + + "OR " + + "(r.favoriteCount < " + + "(SELECT r2.favoriteCount " + + "FROM Review r2 " + + "WHERE r2.id = :lastReviewId)" + + ")" + + ")") + List findSortingReviewsByFavoriteCountDesc(@Param("product") final Product product, + @Param("lastReviewId") final Long lastReviewId, + final Pageable pageable); + List findTop3ByOrderByFavoriteCountDesc(); Long countByProduct(final Product product); From 2dab49878d3aca31fb9d528dbf2e71e146f69ad4 Mon Sep 17 00:00:00 2001 From: 70825 Date: Wed, 20 Sep 2023 06:52:59 +0900 Subject: [PATCH 02/35] =?UTF-8?q?test:=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=EA=B8=B0=EC=A4=80=20=EB=82=B4=EB=A6=BC=EC=B0=A8=EC=88=9C=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=AC=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/persistence/ReviewRepository.java | 8 +++ .../persistence/ReviewRepositoryTest.java | 58 +++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java b/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java index edb324f37..75cd264c7 100644 --- a/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java +++ b/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java @@ -19,6 +19,14 @@ public interface ReviewRepository extends JpaRepository { Page findReviewsByProduct(final Pageable pageable, final Product product); + @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + + "FROM Review r " + + "JOIN r.member m " + + "LEFT JOIN r.reviewFavorites rf " + + "WHERE r.product = :product") + List findSortingReviewsFirstPageByFavoriteCountDesc(@Param("product") final Product product, + final Pageable pageable); + @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + "FROM Review r " + "JOIN r.member m " diff --git a/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java b/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java index d0198db1a..111487c4d 100644 --- a/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java +++ b/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java @@ -20,6 +20,7 @@ import static org.assertj.core.api.SoftAssertions.assertSoftly; import com.funeat.common.RepositoryTest; +import com.funeat.review.dto.SortingReviewDto; import java.util.List; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -101,6 +102,63 @@ class findReviewsByProduct_성공_테스트 { } } + @Nested + class findSortingReviewsByFavoriteCountDesc_관련_성공_테스트 { + + @Test + void 좋아요_기준_내림차순_리뷰_목록의_첫_페이지를_보여준다() { + // given + final var category = 카테고리_간편식사_생성(); + 단일_카테고리_저장(category); + final var product = 상품_삼각김밥_가격1000원_평점2점_생성(category); + 단일_상품_저장(product); + final var member = 멤버_멤버1_생성(); + 단일_멤버_저장(member); + + final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member, product, 130L); + final var review2 = 리뷰_이미지test4_평점4점_재구매O_생성(member, product, 24L); + final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 351L); + 복수_리뷰_저장(review1, review2, review3); + + final var lastReviewId = 0L; + final var page = 페이지요청_좋아요_내림차순_생성(0, 2); + + // when + final var actual = reviewRepository.findSortingReviewsFirstPageByFavoriteCountDesc(product, page); + + // then + assertThat(actual).extracting(SortingReviewDto::getId) + .containsExactly(3L, 1L); + } + + @Test + void 좋아요_기준_내림차순_리뷰_목록의_2페이지부터_보여준다() { + // given + final var category = 카테고리_간편식사_생성(); + 단일_카테고리_저장(category); + final var product = 상품_삼각김밥_가격1000원_평점2점_생성(category); + 단일_상품_저장(product); + final var member = 멤버_멤버1_생성(); + 단일_멤버_저장(member); + + final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member, product, 130L); + final var review2 = 리뷰_이미지test4_평점4점_재구매O_생성(member, product, 24L); + final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 351L); + 복수_리뷰_저장(review1, review2, review3); + + final var lastReviewId = 1L; + final var page = 페이지요청_좋아요_내림차순_생성(0, 2); + + // when + final var actual = reviewRepository.findSortingReviewsByFavoriteCountDesc(product, lastReviewId, page); + + // then + assertThat(actual).extracting(SortingReviewDto::getId) + .containsExactly(2L); + } + + } + @Nested class findTop3ByOrderByFavoriteCountDesc_성공_테스트 { From dfc6dc204748e80203fcee70a91591127ff78abe Mon Sep 17 00:00:00 2001 From: 70825 Date: Wed, 20 Sep 2023 06:59:39 +0900 Subject: [PATCH 03/35] =?UTF-8?q?feat:=20=EC=B5=9C=EC=8B=A0=EC=88=9C?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=A6=AC=EB=B7=B0=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=BF=BC=EB=A6=AC=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/persistence/ReviewRepository.java | 34 ++++++++++++++++++- .../persistence/ReviewRepositoryTest.java | 2 +- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java b/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java index 75cd264c7..b0232acec 100644 --- a/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java +++ b/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java @@ -24,7 +24,7 @@ public interface ReviewRepository extends JpaRepository { + "JOIN r.member m " + "LEFT JOIN r.reviewFavorites rf " + "WHERE r.product = :product") - List findSortingReviewsFirstPageByFavoriteCountDesc(@Param("product") final Product product, + List findSortingReviewsByFavoriteCountDescFirstPage(@Param("product") final Product product, final Pageable pageable); @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " @@ -50,6 +50,38 @@ List findSortingReviewsByFavoriteCountDesc(@Param("product") f @Param("lastReviewId") final Long lastReviewId, final Pageable pageable); + @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + + "FROM Review r " + + "JOIN r.member m " + + "LEFT JOIN r.reviewFavorites rf " + + "WHERE r.product = :product") + List findSortingReviewsByCreatedAtDescFirstPage(@Param("product") final Product product, + @Param("lastReviewId") final Long lastReviewId, + final Pageable pageable); + + @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + + "FROM Review r " + + "JOIN r.member m " + + "LEFT JOIN r.reviewFavorites rf " + + "WHERE r.product = :product " + + "AND (" + + "(r.createdAt = " + + "(SELECT r2.createdAt " + + "FROM Review r2 " + + "WHERE r2.id = :lastReviewId) " + + "AND r.id > :lastReviewId " + + ") " + + "OR " + + "(r.createdAt < " + + "(SELECT r2.createdAt " + + "FROM Review r2 " + + "WHERE r2.id = :lastReviewId)" + + ")" + + ")") + List findSortingReviewsByCreatedAtDesc(@Param("product") final Product product, + @Param("lastReviewId") final Long lastReviewId, + final Pageable pageable); + List findTop3ByOrderByFavoriteCountDesc(); Long countByProduct(final Product product); diff --git a/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java b/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java index 111487c4d..5cb02177e 100644 --- a/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java +++ b/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java @@ -124,7 +124,7 @@ class findSortingReviewsByFavoriteCountDesc_관련_성공_테스트 { final var page = 페이지요청_좋아요_내림차순_생성(0, 2); // when - final var actual = reviewRepository.findSortingReviewsFirstPageByFavoriteCountDesc(product, page); + final var actual = reviewRepository.findSortingReviewsByFavoriteCountDescFirstPage(product, page); // then assertThat(actual).extracting(SortingReviewDto::getId) From 15febd73133914ef5a3a102df2260b502e995546 Mon Sep 17 00:00:00 2001 From: 70825 Date: Wed, 20 Sep 2023 07:08:08 +0900 Subject: [PATCH 04/35] =?UTF-8?q?test:=20=EC=B5=9C=EC=8B=A0=EC=88=9C=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=AA=A9=EB=A1=9D=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=9E=AC=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/persistence/ReviewRepository.java | 1 - .../persistence/ReviewRepositoryTest.java | 61 ++++++++++++++++++- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java b/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java index b0232acec..3c72e9390 100644 --- a/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java +++ b/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java @@ -56,7 +56,6 @@ List findSortingReviewsByFavoriteCountDesc(@Param("product") f + "LEFT JOIN r.reviewFavorites rf " + "WHERE r.product = :product") List findSortingReviewsByCreatedAtDescFirstPage(@Param("product") final Product product, - @Param("lastReviewId") final Long lastReviewId, final Pageable pageable); @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " diff --git a/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java b/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java index 5cb02177e..be1af3e0e 100644 --- a/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java +++ b/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java @@ -5,8 +5,6 @@ import static com.funeat.fixture.MemberFixture.멤버_멤버1_생성; import static com.funeat.fixture.MemberFixture.멤버_멤버2_생성; import static com.funeat.fixture.MemberFixture.멤버_멤버3_생성; -import static com.funeat.fixture.PageFixture.좋아요수_내림차순; -import static com.funeat.fixture.PageFixture.페이지요청_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격1000원_평점1점_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격1000원_평점2점_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격2000원_평점3점_생성; @@ -159,6 +157,65 @@ class findSortingReviewsByFavoriteCountDesc_관련_성공_테스트 { } + @Nested + class findSortingReviewsByCreatedAtDesc_관련_성공_테스트 { + + @Test + void 최신순으로_리뷰_목록의_첫_페이지를_보여준다() { + // given + final var category = 카테고리_간편식사_생성(); + 단일_카테고리_저장(category); + final var product = 상품_삼각김밥_가격1000원_평점2점_생성(category); + 단일_상품_저장(product); + final var member = 멤버_멤버1_생성(); + 단일_멤버_저장(member); + + final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member, product, 130L); + 단일_리뷰_저장(review1); + final var review2 = 리뷰_이미지test4_평점4점_재구매O_생성(member, product, 24L); + 단일_리뷰_저장(review2); + final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 351L); + 단일_리뷰_저장(review3); + + final var page = 페이지요청_생성_시간_내림차순_생성(0, 2); + + // when + final var actual = reviewRepository.findSortingReviewsByCreatedAtDescFirstPage(product, page); + + // then + assertThat(actual).extracting(SortingReviewDto::getId) + .containsExactly(3L, 2L); + } + + @Test + void 최신순으로_리뷰_목록의_2페이지부터_보여준다() { + // given + final var category = 카테고리_간편식사_생성(); + 단일_카테고리_저장(category); + final var product = 상품_삼각김밥_가격1000원_평점2점_생성(category); + 단일_상품_저장(product); + final var member = 멤버_멤버1_생성(); + 단일_멤버_저장(member); + + final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member, product, 130L); + 단일_리뷰_저장(review1); + final var review2 = 리뷰_이미지test4_평점4점_재구매O_생성(member, product, 24L); + 단일_리뷰_저장(review2); + final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 351L); + 단일_리뷰_저장(review3); + + final var lastReviewId = 2L; + final var page = 페이지요청_생성_시간_내림차순_생성(0, 2); + + // when + final var actual = reviewRepository.findSortingReviewsByCreatedAtDesc(product, lastReviewId, page); + + // then + assertThat(actual).extracting(SortingReviewDto::getId) + .containsExactly(1L); + } + } + @Nested class findTop3ByOrderByFavoriteCountDesc_성공_테스트 { From 80e83c2ad4c559c528a8b58b8763e3ef94492297 Mon Sep 17 00:00:00 2001 From: 70825 Date: Wed, 20 Sep 2023 07:12:57 +0900 Subject: [PATCH 05/35] =?UTF-8?q?feat:=20=ED=8F=89=EC=A0=90=EC=88=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC=20=EB=A6=AC=EB=B7=B0=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=BF=BC=EB=A6=AC=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/persistence/ReviewRepository.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java b/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java index 3c72e9390..ccec1d81a 100644 --- a/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java +++ b/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java @@ -81,6 +81,60 @@ List findSortingReviewsByCreatedAtDesc(@Param("product") final @Param("lastReviewId") final Long lastReviewId, final Pageable pageable); + @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + + "FROM Review r " + + "JOIN r.member m " + + "LEFT JOIN r.reviewFavorites rf " + + "WHERE r.product = :product") + List findSortingReviewsByRatingFirstPage(@Param("product") final Product product, + final Pageable pageable); + + @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + + "FROM Review r " + + "JOIN r.member m " + + "LEFT JOIN r.reviewFavorites rf " + + "WHERE r.product = :product " + + "AND (" + + "(r.rating = " + + "(SELECT r2.rating " + + "FROM Review r2 " + + "WHERE r2.id = :lastReviewId) " + + "AND r.id > :lastReviewId " + + ") " + + "OR " + + "(r.rating > " + + "(SELECT r2.rating " + + "FROM Review r2 " + + "WHERE r2.id = :lastReviewId)" + + ")" + + ")") + List findSortingRatingByRatingAsc(@Param("product") final Product product, + @Param("lastReviewId") final Long lastReviewId, + final Pageable pageable); + + @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + + "FROM Review r " + + "JOIN r.member m " + + "LEFT JOIN r.reviewFavorites rf " + + "WHERE r.product = :product " + + "AND (" + + "(r.rating = " + + "(SELECT r2.rating " + + "FROM Review r2 " + + "WHERE r2.id = :lastReviewId) " + + "AND r.id > :lastReviewId " + + ") " + + "OR " + + "(r.rating < " + + "(SELECT r2.rating " + + "FROM Review r2 " + + "WHERE r2.id = :lastReviewId)" + + ")" + + ")") + List findSortingRatingByRatingDesc(@Param("product") final Product product, + @Param("lastReviewId") final Long lastReviewId, + final Pageable pageable); + List findTop3ByOrderByFavoriteCountDesc(); Long countByProduct(final Product product); From c440c8c9eb687fa1ffb2de3f73553d4324096605 Mon Sep 17 00:00:00 2001 From: 70825 Date: Wed, 20 Sep 2023 07:21:36 +0900 Subject: [PATCH 06/35] =?UTF-8?q?test:=20=ED=8F=89=EC=A0=90=EC=88=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC=20=EB=A6=AC=EB=B7=B0=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=AC=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/ReviewRepositoryTest.java | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java b/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java index be1af3e0e..07f45c069 100644 --- a/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java +++ b/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java @@ -5,11 +5,16 @@ import static com.funeat.fixture.MemberFixture.멤버_멤버1_생성; import static com.funeat.fixture.MemberFixture.멤버_멤버2_생성; import static com.funeat.fixture.MemberFixture.멤버_멤버3_생성; +import static com.funeat.fixture.PageFixture.페이지요청_생성_시간_내림차순_생성; +import static com.funeat.fixture.PageFixture.페이지요청_좋아요_내림차순_생성; +import static com.funeat.fixture.PageFixture.페이지요청_평점_내림차순_생성; +import static com.funeat.fixture.PageFixture.페이지요청_평점_오름차순_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격1000원_평점1점_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격1000원_평점2점_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격2000원_평점3점_생성; import static com.funeat.fixture.ReviewFixture.리뷰_이미지test1_평점1점_재구매O_생성; import static com.funeat.fixture.ReviewFixture.리뷰_이미지test1_평점1점_재구매X_생성; +import static com.funeat.fixture.ReviewFixture.리뷰_이미지test2_평점2점_재구매X_생성; import static com.funeat.fixture.ReviewFixture.리뷰_이미지test3_평점3점_재구매O_생성; import static com.funeat.fixture.ReviewFixture.리뷰_이미지test3_평점3점_재구매X_생성; import static com.funeat.fixture.ReviewFixture.리뷰_이미지test4_평점4점_재구매O_생성; @@ -216,6 +221,120 @@ class findSortingReviewsByCreatedAtDesc_관련_성공_테스트 { } } + @Nested + class findSortingReviewsByRating_관련_성공_테스트 { + + @Test + void 평점_기준_오름차순_리뷰_목록의_첫_페이지를_보여준다() { + // given + final var category = 카테고리_간편식사_생성(); + 단일_카테고리_저장(category); + final var product = 상품_삼각김밥_가격1000원_평점2점_생성(category); + 단일_상품_저장(product); + final var member = 멤버_멤버1_생성(); + 단일_멤버_저장(member); + + final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member, product, 130L); + 단일_리뷰_저장(review1); + final var review2 = 리뷰_이미지test5_평점5점_재구매O_생성(member, product, 24L); + 단일_리뷰_저장(review2); + final var review3 = 리뷰_이미지test2_평점2점_재구매X_생성(member, product, 351L); + 단일_리뷰_저장(review3); + + final var page = 페이지요청_평점_오름차순_생성(0, 2); + + // when + final var actual = reviewRepository.findSortingReviewsByRatingFirstPage(product, page); + + // then + assertThat(actual).extracting(SortingReviewDto::getId) + .containsExactly(3L, 1L); + } + + @Test + void 평점_기준_내림차순_리뷰_목록의_첫_페이지를_보여준다() { + // given + final var category = 카테고리_간편식사_생성(); + 단일_카테고리_저장(category); + final var product = 상품_삼각김밥_가격1000원_평점2점_생성(category); + 단일_상품_저장(product); + final var member = 멤버_멤버1_생성(); + 단일_멤버_저장(member); + + final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member, product, 130L); + 단일_리뷰_저장(review1); + final var review2 = 리뷰_이미지test5_평점5점_재구매O_생성(member, product, 24L); + 단일_리뷰_저장(review2); + final var review3 = 리뷰_이미지test2_평점2점_재구매X_생성(member, product, 351L); + 단일_리뷰_저장(review3); + + final var page = 페이지요청_평점_내림차순_생성(0, 2); + + // when + final var actual = reviewRepository.findSortingReviewsByRatingFirstPage(product, page); + + // then + assertThat(actual).extracting(SortingReviewDto::getId) + .containsExactly(2L, 1L); + } + + @Test + void 평점_기준_오름차순_리뷰_목록의_2페이지부터_보여준다() { + // given + final var category = 카테고리_간편식사_생성(); + 단일_카테고리_저장(category); + final var product = 상품_삼각김밥_가격1000원_평점2점_생성(category); + 단일_상품_저장(product); + final var member = 멤버_멤버1_생성(); + 단일_멤버_저장(member); + + final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member, product, 130L); + 단일_리뷰_저장(review1); + final var review2 = 리뷰_이미지test5_평점5점_재구매O_생성(member, product, 24L); + 단일_리뷰_저장(review2); + final var review3 = 리뷰_이미지test2_평점2점_재구매X_생성(member, product, 351L); + 단일_리뷰_저장(review3); + + final var lastReviewId = 1L; + final var page = 페이지요청_평점_오름차순_생성(0, 2); + + // when + final var actual = reviewRepository.findSortingRatingByRatingAsc(product, lastReviewId, page); + + // then + assertThat(actual).extracting(SortingReviewDto::getId) + .containsExactly(2L); + } + + @Test + void 평점_기준_내림차순_리뷰_목록의_2페이지부터_보여준다() { + // given + final var category = 카테고리_간편식사_생성(); + 단일_카테고리_저장(category); + final var product = 상품_삼각김밥_가격1000원_평점2점_생성(category); + 단일_상품_저장(product); + final var member = 멤버_멤버1_생성(); + 단일_멤버_저장(member); + + final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member, product, 130L); + 단일_리뷰_저장(review1); + final var review2 = 리뷰_이미지test5_평점5점_재구매O_생성(member, product, 24L); + 단일_리뷰_저장(review2); + final var review3 = 리뷰_이미지test2_평점2점_재구매X_생성(member, product, 351L); + 단일_리뷰_저장(review3); + + final var lastReviewId = 1L; + final var page = 페이지요청_평점_내림차순_생성(0, 2); + + // when + final var actual = reviewRepository.findSortingRatingByRatingDesc(product, lastReviewId, page); + + // then + assertThat(actual).extracting(SortingReviewDto::getId) + .containsExactly(3L); + } + } + @Nested class findTop3ByOrderByFavoriteCountDesc_성공_테스트 { From 5df31ebe1e5215737290a4c97275126a65313e5c Mon Sep 17 00:00:00 2001 From: 70825 Date: Wed, 20 Sep 2023 07:47:40 +0900 Subject: [PATCH 07/35] =?UTF-8?q?feat:=20=EC=A0=95=EB=A0=AC=20=EC=A1=B0?= =?UTF-8?q?=EA=B1=B4=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=EC=9D=84=20=EB=B0=98=ED=99=98=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/application/ReviewService.java | 37 ++++++++++++++- .../review/dto/SortingReviewRequest.java | 46 +++++++++++++++++++ .../review/exception/ReviewErrorCode.java | 1 + .../review/exception/ReviewException.java | 6 +++ 4 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 backend/src/main/java/com/funeat/review/dto/SortingReviewRequest.java diff --git a/backend/src/main/java/com/funeat/review/application/ReviewService.java b/backend/src/main/java/com/funeat/review/application/ReviewService.java index ee482a8c5..3b846c6ce 100644 --- a/backend/src/main/java/com/funeat/review/application/ReviewService.java +++ b/backend/src/main/java/com/funeat/review/application/ReviewService.java @@ -4,6 +4,7 @@ import static com.funeat.member.exception.MemberErrorCode.MEMBER_NOT_FOUND; import static com.funeat.product.exception.ProductErrorCode.PRODUCT_NOT_FOUND; import static com.funeat.review.exception.ReviewErrorCode.REVIEW_NOT_FOUND; +import static com.funeat.review.exception.ReviewErrorCode.REVIEW_SORTING_OPTION_NOT_FOUND; import com.funeat.common.ImageUploader; import com.funeat.common.dto.PageDto; @@ -26,8 +27,10 @@ import com.funeat.review.dto.ReviewCreateRequest; import com.funeat.review.dto.ReviewFavoriteRequest; import com.funeat.review.dto.SortingReviewDto; +import com.funeat.review.dto.SortingReviewRequest; import com.funeat.review.dto.SortingReviewsResponse; import com.funeat.review.exception.ReviewException.ReviewNotFoundException; +import com.funeat.review.exception.ReviewException.ReviewSortingOptionNotFoundException; import com.funeat.review.persistence.ReviewRepository; import com.funeat.review.persistence.ReviewTagRepository; import com.funeat.tag.domain.Tag; @@ -39,6 +42,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort.Direction; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -142,7 +146,6 @@ public void updateProductImage(final Long reviewId) { public SortingReviewsResponse sortingReviews(final Long productId, final Pageable pageable, final Long memberId) { final Member member = memberRepository.findById(memberId) .orElseThrow(() -> new MemberNotFoundException(MEMBER_NOT_FOUND, memberId)); - final Product product = productRepository.findById(productId) .orElseThrow(() -> new ProductNotFoundException(PRODUCT_NOT_FOUND, productId)); @@ -156,6 +159,38 @@ public SortingReviewsResponse sortingReviews(final Long productId, final Pageabl return SortingReviewsResponse.toResponse(pageDto, reviewDtos); } + private List executeSortingReviews(final Product product, final SortingReviewRequest request) { + final Long lastReviewId = request.getLastReviewId(); + final Pageable pageable = request.getPageable(); + + final String sort = request.getSort(); + + if (sort.equals("favoriteCount,desc")) { + if (lastReviewId == 0L) { + return reviewRepository.findSortingReviewsByFavoriteCountDescFirstPage(product, pageable); + } + return reviewRepository.findSortingReviewsByFavoriteCountDesc(product, lastReviewId, pageable); + } + if (sort.equals("createdAt,desc")) { + if (lastReviewId == 0L) { + return reviewRepository.findSortingReviewsByCreatedAtDescFirstPage(product, pageable); + } + return reviewRepository.findSortingReviewsByCreatedAtDesc(product, lastReviewId, pageable); + } + if (sort.equals("rating,asc") || sort.equals("rating,desc")) { + if (lastReviewId == 0L) { + return reviewRepository.findSortingReviewsByRatingFirstPage(product, pageable); + } + if (sort.equals("rating,asc")) { + return reviewRepository.findSortingRatingByRatingAsc(product, lastReviewId, pageable); + } + if (sort.equals("rating,desc")) { + return reviewRepository.findSortingRatingByRatingDesc(product, lastReviewId, pageable); + } + } + throw new ReviewSortingOptionNotFoundException(REVIEW_SORTING_OPTION_NOT_FOUND, product.getId()); + } + public RankingReviewsResponse getTopReviews() { final List rankingReviews = reviewRepository.findTop3ByOrderByFavoriteCountDesc(); diff --git a/backend/src/main/java/com/funeat/review/dto/SortingReviewRequest.java b/backend/src/main/java/com/funeat/review/dto/SortingReviewRequest.java new file mode 100644 index 000000000..bcb00da3a --- /dev/null +++ b/backend/src/main/java/com/funeat/review/dto/SortingReviewRequest.java @@ -0,0 +1,46 @@ +package com.funeat.review.dto; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.PositiveOrZero; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; + +public class SortingReviewRequest { + + private static final int START_PAGE = 0; + private static final int ELEVEN = 11; + private static final String DELIMITER = ","; + private static final int ORDER_INDEX = 0; + private static final int DIRECTION_INDEX = 1; + private static final String ID = "id"; + + @NotNull(message = "정렬 조건을 확인해주세요") + private String sort; + + @NotNull(message = "마지막으로 조회한 리뷰 ID를 확인해주세요") + @PositiveOrZero(message = "마지막으로 조회한 ID는 0 이상이어야 합니다. (처음 조회하면 0)") + private Long lastReviewId; + + public SortingReviewRequest(final String sort, final Long lastReviewId) { + this.sort = sort; + this.lastReviewId = lastReviewId; + } + + public Pageable getPageable() { + final String[] splitSort = sort.split(DELIMITER); + final String order = splitSort[ORDER_INDEX]; + final Direction direction = Direction.fromString(splitSort[DIRECTION_INDEX]); + + return PageRequest.of(START_PAGE, ELEVEN, Sort.by(direction, order).and(Sort.by(Direction.ASC, ID))); + } + + public String getSort() { + return sort; + } + + public Long getLastReviewId() { + return lastReviewId; + } +} diff --git a/backend/src/main/java/com/funeat/review/exception/ReviewErrorCode.java b/backend/src/main/java/com/funeat/review/exception/ReviewErrorCode.java index d91c0c8c3..46cd5ca7f 100644 --- a/backend/src/main/java/com/funeat/review/exception/ReviewErrorCode.java +++ b/backend/src/main/java/com/funeat/review/exception/ReviewErrorCode.java @@ -5,6 +5,7 @@ public enum ReviewErrorCode { REVIEW_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 리뷰입니다. 리뷰 id를 확인하세요.", "3001"), + REVIEW_SORTING_OPTION_NOT_FOUND(HttpStatus.BAD_REQUEST, "존재하지 않는 정렬 옵션입니다. 정렬 옵션과 정렬하려는 상품 id를 확인하세요.", "3002"), ; private final HttpStatus status; diff --git a/backend/src/main/java/com/funeat/review/exception/ReviewException.java b/backend/src/main/java/com/funeat/review/exception/ReviewException.java index 4699f3af6..c1ac968e3 100644 --- a/backend/src/main/java/com/funeat/review/exception/ReviewException.java +++ b/backend/src/main/java/com/funeat/review/exception/ReviewException.java @@ -15,4 +15,10 @@ public ReviewNotFoundException(final ReviewErrorCode errorCode, final Long revie super(errorCode.getStatus(), new ErrorCode<>(errorCode.getCode(), errorCode.getMessage(), reviewId)); } } + + public static class ReviewSortingOptionNotFoundException extends ReviewException { + public ReviewSortingOptionNotFoundException(final ReviewErrorCode errorCode, final Long productId) { + super(errorCode.getStatus(), new ErrorCode<>(errorCode.getCode(), errorCode.getMessage(), productId)); + } + } } From 2adbb63d7f0e04a4487fd799ae9b2911e9bd5935 Mon Sep 17 00:00:00 2001 From: 70825 Date: Wed, 20 Sep 2023 08:21:35 +0900 Subject: [PATCH 08/35] =?UTF-8?q?feat:=20=EC=A0=95=EB=A0=AC=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/application/ReviewService.java | 35 +++++++++++++------ .../funeat/review/dto/SortingReviewDto.java | 12 +++++++ .../review/dto/SortingReviewsResponse.java | 18 +++++----- .../presentation/ReviewApiController.java | 6 ++-- .../review/presentation/ReviewController.java | 4 ++- .../funeat/tag/persistence/TagRepository.java | 8 +++++ 6 files changed, 60 insertions(+), 23 deletions(-) diff --git a/backend/src/main/java/com/funeat/review/application/ReviewService.java b/backend/src/main/java/com/funeat/review/application/ReviewService.java index 3b846c6ce..8eb3b6bfc 100644 --- a/backend/src/main/java/com/funeat/review/application/ReviewService.java +++ b/backend/src/main/java/com/funeat/review/application/ReviewService.java @@ -36,8 +36,10 @@ import com.funeat.tag.domain.Tag; import com.funeat.tag.persistence.TagRepository; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; +import javax.swing.text.StyledEditorKit.BoldAction; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -53,6 +55,7 @@ public class ReviewService { private static final int TOP = 0; private static final int ONE = 1; + private static final Long FIRST = 0L; private static final String EMPTY_URL = ""; private final ReviewRepository reviewRepository; @@ -143,20 +146,18 @@ public void updateProductImage(final Long reviewId) { } } - public SortingReviewsResponse sortingReviews(final Long productId, final Pageable pageable, final Long memberId) { + public SortingReviewsResponse sortingReviews(final Long productId, final Long memberId, + final SortingReviewRequest request) { final Member member = memberRepository.findById(memberId) .orElseThrow(() -> new MemberNotFoundException(MEMBER_NOT_FOUND, memberId)); final Product product = productRepository.findById(productId) .orElseThrow(() -> new ProductNotFoundException(PRODUCT_NOT_FOUND, productId)); - final Page reviewPage = reviewRepository.findReviewsByProduct(pageable, product); + final List sortingReviewsWithoutTags = executeSortingReviews(product, request); + final List sortingReviews = addTagsToSortingReviews(sortingReviewsWithoutTags); + final Boolean hasNextReview = hasMoreReview(request.getPageable(), sortingReviews.size()); - final PageDto pageDto = PageDto.toDto(reviewPage); - final List reviewDtos = reviewPage.stream() - .map(review -> SortingReviewDto.toDto(review, member)) - .collect(Collectors.toList()); - - return SortingReviewsResponse.toResponse(pageDto, reviewDtos); + return SortingReviewsResponse.toResponse(sortingReviews, hasNextReview); } private List executeSortingReviews(final Product product, final SortingReviewRequest request) { @@ -166,19 +167,19 @@ private List executeSortingReviews(final Product product, fina final String sort = request.getSort(); if (sort.equals("favoriteCount,desc")) { - if (lastReviewId == 0L) { + if (Objects.equals(lastReviewId, FIRST)) { return reviewRepository.findSortingReviewsByFavoriteCountDescFirstPage(product, pageable); } return reviewRepository.findSortingReviewsByFavoriteCountDesc(product, lastReviewId, pageable); } if (sort.equals("createdAt,desc")) { - if (lastReviewId == 0L) { + if (Objects.equals(lastReviewId, FIRST)) { return reviewRepository.findSortingReviewsByCreatedAtDescFirstPage(product, pageable); } return reviewRepository.findSortingReviewsByCreatedAtDesc(product, lastReviewId, pageable); } if (sort.equals("rating,asc") || sort.equals("rating,desc")) { - if (lastReviewId == 0L) { + if (Objects.equals(lastReviewId, FIRST)) { return reviewRepository.findSortingReviewsByRatingFirstPage(product, pageable); } if (sort.equals("rating,asc")) { @@ -191,6 +192,18 @@ private List executeSortingReviews(final Product product, fina throw new ReviewSortingOptionNotFoundException(REVIEW_SORTING_OPTION_NOT_FOUND, product.getId()); } + private List addTagsToSortingReviews(final List sortingReviews) { + return sortingReviews.stream() + .map(review -> SortingReviewDto.toDto(review, tagRepository.findTagsByReviewId(review.getId()))) + .collect(Collectors.toList()); + } + + private Boolean hasMoreReview(final Pageable pageable, final int reviewSize) { + final int pageSize = pageable.getPageSize(); + + return reviewSize == pageSize + ONE; + } + public RankingReviewsResponse getTopReviews() { final List rankingReviews = reviewRepository.findTop3ByOrderByFavoriteCountDesc(); diff --git a/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java b/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java index ffeb7f892..8ecb52c19 100644 --- a/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java +++ b/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java @@ -4,6 +4,7 @@ import com.funeat.member.domain.favorite.ReviewFavorite; import com.funeat.review.domain.Review; import com.funeat.review.domain.ReviewTag; +import com.funeat.tag.domain.Tag; import com.funeat.tag.dto.TagDto; import java.time.LocalDateTime; import java.util.List; @@ -71,6 +72,17 @@ public static SortingReviewDto toDto(final Review review, final Member member) { ); } + public static SortingReviewDto toDto(final SortingReviewDto sortingReviewDto, final List tags) { + final List tagDtos = tags.stream() + .map(TagDto::toDto) + .collect(Collectors.toList()); + + return new SortingReviewDto(sortingReviewDto.getId(), sortingReviewDto.getUserName(), + sortingReviewDto.getProfileImage(), sortingReviewDto.getImage(), sortingReviewDto.getRating(), tagDtos, + sortingReviewDto.getContent(), sortingReviewDto.isRebuy(), sortingReviewDto.getFavoriteCount(), + sortingReviewDto.isFavorite(), sortingReviewDto.getCreatedAt()); + } + private static List findTagDtos(final Review review) { return review.getReviewTags().stream() .map(ReviewTag::getTag) diff --git a/backend/src/main/java/com/funeat/review/dto/SortingReviewsResponse.java b/backend/src/main/java/com/funeat/review/dto/SortingReviewsResponse.java index caf1ea155..4c7fe41d0 100644 --- a/backend/src/main/java/com/funeat/review/dto/SortingReviewsResponse.java +++ b/backend/src/main/java/com/funeat/review/dto/SortingReviewsResponse.java @@ -5,23 +5,23 @@ public class SortingReviewsResponse { - private final PageDto page; private final List reviews; + private final Boolean hasNextReview; - public SortingReviewsResponse(final PageDto page, final List reviews) { - this.page = page; + public SortingReviewsResponse(final List reviews, final Boolean hasNextReview) { this.reviews = reviews; + this.hasNextReview = hasNextReview; } - public static SortingReviewsResponse toResponse(final PageDto page, final List reviews) { - return new SortingReviewsResponse(page, reviews); - } - - public PageDto getPage() { - return page; + public static SortingReviewsResponse toResponse(final List reviews, final Boolean hasNextReview) { + return new SortingReviewsResponse(reviews, hasNextReview); } public List getReviews() { return reviews; } + + public Boolean getHasNextReview() { + return hasNextReview; + } } diff --git a/backend/src/main/java/com/funeat/review/presentation/ReviewApiController.java b/backend/src/main/java/com/funeat/review/presentation/ReviewApiController.java index 57bf20359..60d8b3941 100644 --- a/backend/src/main/java/com/funeat/review/presentation/ReviewApiController.java +++ b/backend/src/main/java/com/funeat/review/presentation/ReviewApiController.java @@ -8,6 +8,7 @@ import com.funeat.review.dto.RankingReviewsResponse; import com.funeat.review.dto.ReviewCreateRequest; import com.funeat.review.dto.ReviewFavoriteRequest; +import com.funeat.review.dto.SortingReviewRequest; import com.funeat.review.dto.SortingReviewsResponse; import java.net.URI; import java.util.Objects; @@ -18,6 +19,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -61,8 +63,8 @@ public ResponseEntity toggleLikeReview(@PathVariable final Long reviewId, @GetMapping("/api/products/{productId}/reviews") public ResponseEntity getSortingReviews(@AuthenticationPrincipal final LoginInfo loginInfo, @PathVariable final Long productId, - @PageableDefault final Pageable pageable) { - final SortingReviewsResponse response = reviewService.sortingReviews(productId, pageable, loginInfo.getId()); + @ModelAttribute final SortingReviewRequest request) { + final SortingReviewsResponse response = reviewService.sortingReviews(productId, loginInfo.getId(), request); return ResponseEntity.ok(response); } diff --git a/backend/src/main/java/com/funeat/review/presentation/ReviewController.java b/backend/src/main/java/com/funeat/review/presentation/ReviewController.java index 886ee5a15..04cfef773 100644 --- a/backend/src/main/java/com/funeat/review/presentation/ReviewController.java +++ b/backend/src/main/java/com/funeat/review/presentation/ReviewController.java @@ -6,6 +6,7 @@ import com.funeat.review.dto.RankingReviewsResponse; import com.funeat.review.dto.ReviewCreateRequest; import com.funeat.review.dto.ReviewFavoriteRequest; +import com.funeat.review.dto.SortingReviewRequest; import com.funeat.review.dto.SortingReviewsResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -15,6 +16,7 @@ import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -54,7 +56,7 @@ ResponseEntity toggleLikeReview(@PathVariable final Long reviewId, @GetMapping ResponseEntity getSortingReviews(@AuthenticationPrincipal final LoginInfo loginInfo, @PathVariable final Long productId, - @PageableDefault final Pageable pageable); + @ModelAttribute final SortingReviewRequest request); @Operation(summary = "리뷰 랭킹 Top3 조회", description = "리뷰 랭킹 Top3 조회한다.") @ApiResponse( diff --git a/backend/src/main/java/com/funeat/tag/persistence/TagRepository.java b/backend/src/main/java/com/funeat/tag/persistence/TagRepository.java index b74e0197c..9ad319f7a 100644 --- a/backend/src/main/java/com/funeat/tag/persistence/TagRepository.java +++ b/backend/src/main/java/com/funeat/tag/persistence/TagRepository.java @@ -4,10 +4,18 @@ import com.funeat.tag.domain.TagType; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface TagRepository extends JpaRepository { List findTagsByIdIn(final List tagIds); List findTagsByTagType(final TagType tagType); + + @Query("SELECT t " + + "FROM ReviewTag rt " + + "JOIN rt.tag t " + + "WHERE rt.review.id = :reviewId") + List findTagsByReviewId(@Param("reviewId") final Long reviewId); } From 305a61d46d1812fe8a877dbaa2fda68cdba35574 Mon Sep 17 00:00:00 2001 From: 70825 Date: Wed, 20 Sep 2023 08:59:31 +0900 Subject: [PATCH 09/35] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20conflict=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/application/ReviewService.java | 1 + .../review/dto/SortingReviewRequest.java | 3 + .../java/com/funeat/fixture/PageFixture.java | 26 ++++ .../com/funeat/fixture/ReviewFixture.java | 26 ++++ .../review/application/ReviewServiceTest.java | 146 ++++++++---------- .../persistence/ReviewRepositoryTest.java | 50 ++---- 6 files changed, 127 insertions(+), 125 deletions(-) diff --git a/backend/src/main/java/com/funeat/review/application/ReviewService.java b/backend/src/main/java/com/funeat/review/application/ReviewService.java index 8eb3b6bfc..c3c9f8ad5 100644 --- a/backend/src/main/java/com/funeat/review/application/ReviewService.java +++ b/backend/src/main/java/com/funeat/review/application/ReviewService.java @@ -44,6 +44,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; diff --git a/backend/src/main/java/com/funeat/review/dto/SortingReviewRequest.java b/backend/src/main/java/com/funeat/review/dto/SortingReviewRequest.java index bcb00da3a..afd5953e8 100644 --- a/backend/src/main/java/com/funeat/review/dto/SortingReviewRequest.java +++ b/backend/src/main/java/com/funeat/review/dto/SortingReviewRequest.java @@ -1,6 +1,7 @@ package com.funeat.review.dto; import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; import javax.validation.constraints.PositiveOrZero; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -17,6 +18,8 @@ public class SortingReviewRequest { private static final String ID = "id"; @NotNull(message = "정렬 조건을 확인해주세요") + @Pattern(regexp = "^(createdAt,desc|favoriteCount,desc|rating,desc|rating,asc)$", + message = "정렬 조건은 'createdAt,desc', 'favoriteCount,desc', 'rating,desc', 'rating,asc' 중 하나만 가능합니다.") private String sort; @NotNull(message = "마지막으로 조회한 리뷰 ID를 확인해주세요") diff --git a/backend/src/test/java/com/funeat/fixture/PageFixture.java b/backend/src/test/java/com/funeat/fixture/PageFixture.java index afae2d14a..773658f48 100644 --- a/backend/src/test/java/com/funeat/fixture/PageFixture.java +++ b/backend/src/test/java/com/funeat/fixture/PageFixture.java @@ -2,6 +2,7 @@ import com.funeat.common.dto.PageDto; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; @@ -18,6 +19,7 @@ public class PageFixture { public static final String 평점_내림차순 = "rating,desc"; public static final String 과거순 = "createdAt,asc"; public static final String 최신순 = "createdAt,desc"; + public static final String 아이디_내림차순 = "id,desc"; public static final Long PAGE_SIZE = 10L; public static final Long FIRST_PAGE = 0L; @@ -45,6 +47,30 @@ public class PageFixture { return new PageDto(totalDataCount, totalPages, firstPage, lastPage, requestPage, requestSize); } + public static Pageable 페이지요청_좋아요_내림차순_생성(final int page, final int size) { + final var sort = Sort.by(Direction.DESC, "favoriteCount"); + + return PageRequest.of(page, size, sort); + } + + public static Pageable 페이지요청_최신순_생성(final int page, final int size) { + final var sort = Sort.by(Direction.DESC, "createdAt"); + + return PageRequest.of(page, size, sort); + } + + public static Pageable 페이지요청_평점_오름차순_생성(final int page, final int size) { + final var sort = Sort.by(Direction.ASC, "rating"); + + return PageRequest.of(page, size, sort); + } + + public static Pageable 페이지요청_평점_내림차순_생성(final int page, final int size) { + final var sort = Sort.by(Direction.DESC, "rating"); + + return PageRequest.of(page, size, sort); + } + public static Long 총_데이터_개수(final Long count) { return count; } diff --git a/backend/src/test/java/com/funeat/fixture/ReviewFixture.java b/backend/src/test/java/com/funeat/fixture/ReviewFixture.java index 3ae53a3c7..e27227d0c 100644 --- a/backend/src/test/java/com/funeat/fixture/ReviewFixture.java +++ b/backend/src/test/java/com/funeat/fixture/ReviewFixture.java @@ -1,10 +1,16 @@ package com.funeat.fixture; +import static com.funeat.fixture.PageFixture.좋아요수_내림차순; +import static com.funeat.fixture.PageFixture.최신순; +import static com.funeat.fixture.PageFixture.평점_내림차순; +import static com.funeat.fixture.PageFixture.평점_오름차순; + import com.funeat.member.domain.Member; import com.funeat.product.domain.Product; import com.funeat.review.domain.Review; import com.funeat.review.dto.ReviewCreateRequest; import com.funeat.review.dto.ReviewFavoriteRequest; +import com.funeat.review.dto.SortingReviewRequest; import java.util.List; @SuppressWarnings("NonAsciiCharacters") @@ -81,4 +87,24 @@ public class ReviewFixture { public static ReviewFavoriteRequest 리뷰좋아요요청_생성(final Boolean favorite) { return new ReviewFavoriteRequest(favorite); } + + public static SortingReviewRequest 리뷰정렬요청_좋아요수_내림차순_생성(final Long lastReviewId) { + return new SortingReviewRequest(좋아요수_내림차순, lastReviewId); + } + + public static SortingReviewRequest 리뷰정렬요청_최신순_생성(final Long lastReviewId) { + return new SortingReviewRequest(최신순, lastReviewId); + } + + public static SortingReviewRequest 리뷰정렬요청_평점_오름차순_생성(final Long lastReviewId) { + return new SortingReviewRequest(평점_오름차순, lastReviewId); + } + + public static SortingReviewRequest 리뷰정렬요청_평점_내림차순_생성(final Long lastReviewId) { + return new SortingReviewRequest(평점_내림차순, lastReviewId); + } + + public static SortingReviewRequest 리뷰정렬요청_존재하지않는정렬_생성() { + return new SortingReviewRequest("test,test", 1L); + } } diff --git a/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java b/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java index 00c3ed691..3cb3b027a 100644 --- a/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java +++ b/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java @@ -24,6 +24,11 @@ import static com.funeat.fixture.ReviewFixture.리뷰_이미지test3_평점3점_재구매X_생성; import static com.funeat.fixture.ReviewFixture.리뷰_이미지test4_평점4점_재구매O_생성; import static com.funeat.fixture.ReviewFixture.리뷰_이미지없음_평점1점_재구매O_생성; +import static com.funeat.fixture.ReviewFixture.리뷰정렬요청_존재하지않는정렬_생성; +import static com.funeat.fixture.ReviewFixture.리뷰정렬요청_좋아요수_내림차순_생성; +import static com.funeat.fixture.ReviewFixture.리뷰정렬요청_최신순_생성; +import static com.funeat.fixture.ReviewFixture.리뷰정렬요청_평점_내림차순_생성; +import static com.funeat.fixture.ReviewFixture.리뷰정렬요청_평점_오름차순_생성; import static com.funeat.fixture.ReviewFixture.리뷰좋아요요청_생성; import static com.funeat.fixture.ReviewFixture.리뷰추가요청_재구매O_생성; import static com.funeat.fixture.TagFixture.태그_맛있어요_TASTE_생성; @@ -340,44 +345,36 @@ class sortingReviews_성공_테스트 { @Test void 좋아요_기준으로_내림차순_정렬을_할_수_있다() { // given - final var member1 = 멤버_멤버1_생성(); - final var member2 = 멤버_멤버2_생성(); - final var member3 = 멤버_멤버3_생성(); - 복수_멤버_저장(member1, member2, member3); + final var member = 멤버_멤버1_생성(); + final var memberId = 단일_멤버_저장(member); final var category = 카테고리_즉석조리_생성(); 단일_카테고리_저장(category); - final var product = 상품_삼각김밥_가격1000원_평점3점_생성(category); final var productId = 단일_상품_저장(product); - final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member1, product, 351L); - final var review2 = 리뷰_이미지test4_평점4점_재구매O_생성(member2, product, 24L); - final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member3, product, 130L); + final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member, product, 351L); + final var review2 = 리뷰_이미지test4_평점4점_재구매O_생성(member, product, 24L); + final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 130L); 복수_리뷰_저장(review1, review2, review3); - final var page = 페이지요청_생성(0, 2, 좋아요수_내림차순); - final var member1Id = member1.getId(); + final var request = 리뷰정렬요청_좋아요수_내림차순_생성(1L); - final var expected = Stream.of(review1, review3) - .map(review -> SortingReviewDto.toDto(review, member1)) - .collect(Collectors.toList()); + final var expected = List.of(review3.getId(), review2.getId()); // when - final var actual = reviewService.sortingReviews(productId, page, member1Id).getReviews(); + final var actual = reviewService.sortingReviews(productId, memberId, request).getReviews(); // then - assertThat(actual).usingRecursiveComparison() - .isEqualTo(expected); + assertThat(actual).extracting(SortingReviewDto::getId) + .containsExactlyElementsOf(expected); } @Test - void 평점_기준으로_오름차순_정렬을_할_수_있다() { + void 최신순으로_정렬을_할_수_있다() { // given - final var member1 = 멤버_멤버1_생성(); - final var member2 = 멤버_멤버2_생성(); - final var member3 = 멤버_멤버3_생성(); - 복수_멤버_저장(member1, member2, member3); + final var member = 멤버_멤버1_생성(); + final var memberId = 단일_멤버_저장(member); final var category = 카테고리_즉석조리_생성(); 단일_카테고리_저장(category); @@ -385,33 +382,28 @@ class sortingReviews_성공_테스트 { final var product = 상품_삼각김밥_가격1000원_평점3점_생성(category); final var productId = 단일_상품_저장(product); - final var review1 = 리뷰_이미지test2_평점2점_재구매O_생성(member1, product, 351L); - final var review2 = 리뷰_이미지test4_평점4점_재구매O_생성(member2, product, 24L); - final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member3, product, 130L); + final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member, product, 351L); + final var review2 = 리뷰_이미지test4_평점4점_재구매O_생성(member, product, 24L); + final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 130L); 복수_리뷰_저장(review1, review2, review3); - final var page = 페이지요청_생성(0, 2, 평점_오름차순); - final var member1Id = member1.getId(); + final var request = 리뷰정렬요청_최신순_생성(3L); - final var expected = Stream.of(review1, review3) - .map(review -> SortingReviewDto.toDto(review, member1)) - .collect(Collectors.toList()); + final var expected = List.of(review2.getId(), review1.getId()); // when - final var actual = reviewService.sortingReviews(productId, page, member1Id).getReviews(); + final var actual = reviewService.sortingReviews(productId, memberId, request).getReviews(); // then - assertThat(actual).usingRecursiveComparison() - .isEqualTo(expected); + assertThat(actual).extracting(SortingReviewDto::getId) + .containsExactlyElementsOf(expected); } @Test - void 평점_기준으로_내림차순_정렬을_할_수_있다() { + void 평점_기준으로_오름차순_정렬을_할_수_있다() { // given - final var member1 = 멤버_멤버1_생성(); - final var member2 = 멤버_멤버2_생성(); - final var member3 = 멤버_멤버3_생성(); - 복수_멤버_저장(member1, member2, member3); + final var member = 멤버_멤버1_생성(); + final var memberId = 단일_멤버_저장(member); final var category = 카테고리_즉석조리_생성(); 단일_카테고리_저장(category); @@ -419,33 +411,28 @@ class sortingReviews_성공_테스트 { final var product = 상품_삼각김밥_가격1000원_평점3점_생성(category); final var productId = 단일_상품_저장(product); - final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member1, product, 351L); - final var review2 = 리뷰_이미지test4_평점4점_재구매O_생성(member2, product, 24L); - final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member3, product, 130L); + final var review1 = 리뷰_이미지test2_평점2점_재구매O_생성(member, product, 351L); + final var review2 = 리뷰_이미지test4_평점4점_재구매O_생성(member, product, 24L); + final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 130L); 복수_리뷰_저장(review1, review2, review3); - final var page = 페이지요청_생성(0, 2, 평점_내림차순); - final var member1Id = member1.getId(); + final var request = 리뷰정렬요청_평점_오름차순_생성(1L); - final var expected = Stream.of(review2, review3) - .map(review -> SortingReviewDto.toDto(review, member1)) - .collect(Collectors.toList()); + final var expected = List.of(review3.getId(), review2.getId()); // when - final var actual = reviewService.sortingReviews(productId, page, member1Id).getReviews(); + final var actual = reviewService.sortingReviews(productId, memberId, request).getReviews(); // then - assertThat(actual).usingRecursiveComparison() - .isEqualTo(expected); + assertThat(actual).extracting(SortingReviewDto::getId) + .containsExactlyElementsOf(expected); } @Test - void 최신순으로_정렬을_할_수_있다() { + void 평점_기준으로_내림차순_정렬을_할_수_있다() { // given - final var member1 = 멤버_멤버1_생성(); - final var member2 = 멤버_멤버2_생성(); - final var member3 = 멤버_멤버3_생성(); - 복수_멤버_저장(member1, member2, member3); + final var member = 멤버_멤버1_생성(); + final var memberId = 단일_멤버_저장(member); final var category = 카테고리_즉석조리_생성(); 단일_카테고리_저장(category); @@ -453,24 +440,21 @@ class sortingReviews_성공_테스트 { final var product = 상품_삼각김밥_가격1000원_평점3점_생성(category); final var productId = 단일_상품_저장(product); - final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member1, product, 351L); - final var review2 = 리뷰_이미지test4_평점4점_재구매O_생성(member2, product, 24L); - final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member3, product, 130L); + final var review1 = 리뷰_이미지test4_평점4점_재구매O_생성(member, product, 351L); + final var review2 = 리뷰_이미지test2_평점2점_재구매O_생성(member, product, 24L); + final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 130L); 복수_리뷰_저장(review1, review2, review3); - final var page = 페이지요청_생성(0, 2, 최신순); - final var member1Id = member1.getId(); + final var request = 리뷰정렬요청_평점_내림차순_생성(1L); - final var expected = Stream.of(review3, review2) - .map(review -> SortingReviewDto.toDto(review, member1)) - .collect(Collectors.toList()); + final var expected = List.of(review3.getId(), review2.getId()); // when - final var actual = reviewService.sortingReviews(productId, page, member1Id).getReviews(); + final var actual = reviewService.sortingReviews(productId, memberId, request).getReviews(); // then - assertThat(actual).usingRecursiveComparison() - .isEqualTo(expected); + assertThat(actual).extracting(SortingReviewDto::getId) + .containsExactlyElementsOf(expected); } } @@ -480,10 +464,8 @@ class sortingReviews_실패_테스트 { @Test void 존재하지_않는_멤버가_상품에_있는_리뷰들을_정렬하면_예외가_발생한다() { // given - final var member1 = 멤버_멤버1_생성(); - final var member2 = 멤버_멤버2_생성(); - final var member3 = 멤버_멤버3_생성(); - 복수_멤버_저장(member1, member2, member3); + final var member = 멤버_멤버1_생성(); + final var wrongMemberId = 단일_멤버_저장(member) + 3L; final var category = 카테고리_즉석조리_생성(); 단일_카테고리_저장(category); @@ -491,26 +473,23 @@ class sortingReviews_실패_테스트 { final var product = 상품_삼각김밥_가격1000원_평점3점_생성(category); final var productId = 단일_상품_저장(product); - final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member1, product, 351L); - final var review2 = 리뷰_이미지test4_평점4점_재구매O_생성(member2, product, 24L); - final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member3, product, 130L); + final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member, product, 351L); + final var review2 = 리뷰_이미지test4_평점4점_재구매O_생성(member, product, 24L); + final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 130L); 복수_리뷰_저장(review1, review2, review3); - final var page = 페이지요청_기본_생성(0, 2); - final var wrongMemberId = member1.getId() + 3L; + final var request = 리뷰정렬요청_평점_내림차순_생성(1L); // when & then - assertThatThrownBy(() -> reviewService.sortingReviews(productId, page, wrongMemberId)) + assertThatThrownBy(() -> reviewService.sortingReviews(productId, wrongMemberId, request)) .isInstanceOf(MemberNotFoundException.class); } @Test void 멤버가_존재하지_않는_상품에_있는_리뷰들을_정렬하면_예외가_발생한다() { // given - final var member1 = 멤버_멤버1_생성(); - final var member2 = 멤버_멤버2_생성(); - final var member3 = 멤버_멤버3_생성(); - 복수_멤버_저장(member1, member2, member3); + final var member = 멤버_멤버1_생성(); + final var memberId = 단일_멤버_저장(member); final var category = 카테고리_즉석조리_생성(); 단일_카테고리_저장(category); @@ -518,16 +497,15 @@ class sortingReviews_실패_테스트 { final var product = 상품_삼각김밥_가격1000원_평점3점_생성(category); final var wrongProductId = 단일_상품_저장(product) + 1L; - final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member1, product, 351L); - final var review2 = 리뷰_이미지test4_평점4점_재구매O_생성(member2, product, 24L); - final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member3, product, 130L); + final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member, product, 351L); + final var review2 = 리뷰_이미지test4_평점4점_재구매O_생성(member, product, 24L); + final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 130L); 복수_리뷰_저장(review1, review2, review3); - final var page = 페이지요청_기본_생성(0, 2); - final var member1Id = member1.getId(); + final var request = 리뷰정렬요청_평점_내림차순_생성(1L); // when & then - assertThatThrownBy(() -> reviewService.sortingReviews(wrongProductId, page, member1Id)) + assertThatThrownBy(() -> reviewService.sortingReviews(wrongProductId, memberId, request)) .isInstanceOf(ProductNotFoundException.class); } } diff --git a/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java b/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java index 07f45c069..4a5c98fd2 100644 --- a/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java +++ b/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java @@ -5,8 +5,8 @@ import static com.funeat.fixture.MemberFixture.멤버_멤버1_생성; import static com.funeat.fixture.MemberFixture.멤버_멤버2_생성; import static com.funeat.fixture.MemberFixture.멤버_멤버3_생성; -import static com.funeat.fixture.PageFixture.페이지요청_생성_시간_내림차순_생성; import static com.funeat.fixture.PageFixture.페이지요청_좋아요_내림차순_생성; +import static com.funeat.fixture.PageFixture.페이지요청_최신순_생성; import static com.funeat.fixture.PageFixture.페이지요청_평점_내림차순_생성; import static com.funeat.fixture.PageFixture.페이지요청_평점_오름차순_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격1000원_평점1점_생성; @@ -70,41 +70,6 @@ class countByProduct_성공_테스트 { } } - @Nested - class findReviewsByProduct_성공_테스트 { - - @Test - void 특정_상품에_대한_좋아요_기준_내림차순으로_정렬한다() { - // given - final var member1 = 멤버_멤버1_생성(); - final var member2 = 멤버_멤버2_생성(); - final var member3 = 멤버_멤버3_생성(); - 복수_멤버_저장(member1, member2, member3); - - final var category = 카테고리_간편식사_생성(); - 단일_카테고리_저장(category); - - final var product = 상품_삼각김밥_가격1000원_평점2점_생성(category); - 단일_상품_저장(product); - - final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member1, product, 351L); - final var review2 = 리뷰_이미지test4_평점4점_재구매O_생성(member2, product, 24L); - final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member3, product, 130L); - 복수_리뷰_저장(review1, review2, review3); - - final var page = 페이지요청_생성(0, 2, 좋아요수_내림차순); - - final var expected = List.of(review1, review3); - - // when - final var actual = reviewRepository.findReviewsByProduct(page, product).getContent(); - - // then - assertThat(actual).usingRecursiveComparison() - .isEqualTo(expected); - } - } - @Nested class findSortingReviewsByFavoriteCountDesc_관련_성공_테스트 { @@ -119,11 +84,12 @@ class findSortingReviewsByFavoriteCountDesc_관련_성공_테스트 { 단일_멤버_저장(member); final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member, product, 130L); + 단일_리뷰_저장(review1); final var review2 = 리뷰_이미지test4_평점4점_재구매O_생성(member, product, 24L); + 단일_리뷰_저장(review2); final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 351L); - 복수_리뷰_저장(review1, review2, review3); + 단일_리뷰_저장(review3); - final var lastReviewId = 0L; final var page = 페이지요청_좋아요_내림차순_생성(0, 2); // when @@ -145,9 +111,11 @@ class findSortingReviewsByFavoriteCountDesc_관련_성공_테스트 { 단일_멤버_저장(member); final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member, product, 130L); + 단일_리뷰_저장(review1); final var review2 = 리뷰_이미지test4_평점4점_재구매O_생성(member, product, 24L); + 단일_리뷰_저장(review2); final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 351L); - 복수_리뷰_저장(review1, review2, review3); + 단일_리뷰_저장(review3); final var lastReviewId = 1L; final var page = 페이지요청_좋아요_내림차순_생성(0, 2); @@ -182,7 +150,7 @@ class findSortingReviewsByCreatedAtDesc_관련_성공_테스트 { final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 351L); 단일_리뷰_저장(review3); - final var page = 페이지요청_생성_시간_내림차순_생성(0, 2); + final var page = 페이지요청_최신순_생성(0, 2); // when final var actual = reviewRepository.findSortingReviewsByCreatedAtDescFirstPage(product, page); @@ -210,7 +178,7 @@ class findSortingReviewsByCreatedAtDesc_관련_성공_테스트 { 단일_리뷰_저장(review3); final var lastReviewId = 2L; - final var page = 페이지요청_생성_시간_내림차순_생성(0, 2); + final var page = 페이지요청_최신순_생성(0, 2); // when final var actual = reviewRepository.findSortingReviewsByCreatedAtDesc(product, lastReviewId, page); From 6301f071425972a55639ab10944c3c4a7006c7c7 Mon Sep 17 00:00:00 2001 From: 70825 Date: Wed, 20 Sep 2023 10:23:45 +0900 Subject: [PATCH 10/35] =?UTF-8?q?fix:=20=EC=83=9D=EC=84=B1=EC=9E=90?= =?UTF-8?q?=EA=B0=80=20=EC=97=AC=EB=9F=AC=EA=B0=9C=EB=9D=BC=20jackson?= =?UTF-8?q?=EC=9D=B4=20json=EC=9C=BC=EB=A1=9C=20=EB=B3=80=ED=99=98?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EB=AA=BB=ED=95=98=EB=8A=94=20=ED=98=84?= =?UTF-8?q?=EC=83=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/funeat/review/dto/SortingReviewDto.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java b/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java index 8ecb52c19..42b7fe609 100644 --- a/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java +++ b/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java @@ -1,5 +1,6 @@ package com.funeat.review.dto; +import com.fasterxml.jackson.annotation.JsonCreator; import com.funeat.member.domain.Member; import com.funeat.member.domain.favorite.ReviewFavorite; import com.funeat.review.domain.Review; @@ -24,6 +25,7 @@ public class SortingReviewDto { private boolean favorite; private LocalDateTime createdAt; + @JsonCreator public SortingReviewDto(final Long id, final String userName, final String profileImage, final String image, final Long rating, final List tags, final String content, final boolean rebuy, final Long favoriteCount, final boolean favorite, From be9c68f66b60a73d63b9c39f8c8ec75a1084b7be Mon Sep 17 00:00:00 2001 From: 70825 Date: Wed, 20 Sep 2023 10:32:19 +0900 Subject: [PATCH 11/35] =?UTF-8?q?fix:=202=EC=B0=A8=20=EC=A0=95=EB=A0=AC=20?= =?UTF-8?q?=EA=B8=B0=EC=A4=80=EC=9D=B4=20ID=20=EA=B8=B0=EC=A4=80=20?= =?UTF-8?q?=EB=82=B4=EB=A6=BC=EC=B0=A8=EC=88=9C=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/funeat/review/dto/SortingReviewRequest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/com/funeat/review/dto/SortingReviewRequest.java b/backend/src/main/java/com/funeat/review/dto/SortingReviewRequest.java index afd5953e8..ac5e8996a 100644 --- a/backend/src/main/java/com/funeat/review/dto/SortingReviewRequest.java +++ b/backend/src/main/java/com/funeat/review/dto/SortingReviewRequest.java @@ -36,7 +36,7 @@ public Pageable getPageable() { final String order = splitSort[ORDER_INDEX]; final Direction direction = Direction.fromString(splitSort[DIRECTION_INDEX]); - return PageRequest.of(START_PAGE, ELEVEN, Sort.by(direction, order).and(Sort.by(Direction.ASC, ID))); + return PageRequest.of(START_PAGE, ELEVEN, Sort.by(direction, order).and(Sort.by(Direction.DESC, ID))); } public String getSort() { From e7c86de8ff7f6c3c7d96b02227417a1e8c92c0b8 Mon Sep 17 00:00:00 2001 From: 70825 Date: Wed, 20 Sep 2023 13:25:49 +0900 Subject: [PATCH 12/35] =?UTF-8?q?fix:=20=EC=A2=8B=EC=95=84=EC=9A=94?= =?UTF-8?q?=EB=A5=BC=20=EB=88=84=EB=A5=B8=20=EC=82=AC=EB=9E=8C=EC=9D=B4=20?= =?UTF-8?q?=EC=97=AC=EB=9F=AC=EB=AA=85=EC=9D=B4=EB=A9=B4=20=EA=B7=B8=20?= =?UTF-8?q?=EA=B0=9C=EC=88=98=EB=A7=8C=ED=81=BC=20=EA=B0=99=EC=9D=80=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=EB=A5=BC=20=EB=B0=98=ED=99=98=ED=95=98?= =?UTF-8?q?=EB=8D=98=20=EC=BF=BC=EB=A6=AC=EB=AC=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/application/ReviewService.java | 19 ++++++------ .../review/persistence/ReviewRepository.java | 31 ++++++++++++------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/backend/src/main/java/com/funeat/review/application/ReviewService.java b/backend/src/main/java/com/funeat/review/application/ReviewService.java index c3c9f8ad5..f2e9b085e 100644 --- a/backend/src/main/java/com/funeat/review/application/ReviewService.java +++ b/backend/src/main/java/com/funeat/review/application/ReviewService.java @@ -154,14 +154,15 @@ public SortingReviewsResponse sortingReviews(final Long productId, final Long me final Product product = productRepository.findById(productId) .orElseThrow(() -> new ProductNotFoundException(PRODUCT_NOT_FOUND, productId)); - final List sortingReviewsWithoutTags = executeSortingReviews(product, request); + final List sortingReviewsWithoutTags = executeSortingReviews(product, member, request); final List sortingReviews = addTagsToSortingReviews(sortingReviewsWithoutTags); final Boolean hasNextReview = hasMoreReview(request.getPageable(), sortingReviews.size()); return SortingReviewsResponse.toResponse(sortingReviews, hasNextReview); } - private List executeSortingReviews(final Product product, final SortingReviewRequest request) { + private List executeSortingReviews(final Product product, final Member member, + final SortingReviewRequest request) { final Long lastReviewId = request.getLastReviewId(); final Pageable pageable = request.getPageable(); @@ -169,25 +170,25 @@ private List executeSortingReviews(final Product product, fina if (sort.equals("favoriteCount,desc")) { if (Objects.equals(lastReviewId, FIRST)) { - return reviewRepository.findSortingReviewsByFavoriteCountDescFirstPage(product, pageable); + return reviewRepository.findSortingReviewsByFavoriteCountDescFirstPage(product, member, pageable); } - return reviewRepository.findSortingReviewsByFavoriteCountDesc(product, lastReviewId, pageable); + return reviewRepository.findSortingReviewsByFavoriteCountDesc(product, member, lastReviewId, pageable); } if (sort.equals("createdAt,desc")) { if (Objects.equals(lastReviewId, FIRST)) { - return reviewRepository.findSortingReviewsByCreatedAtDescFirstPage(product, pageable); + return reviewRepository.findSortingReviewsByCreatedAtDescFirstPage(product, member, pageable); } - return reviewRepository.findSortingReviewsByCreatedAtDesc(product, lastReviewId, pageable); + return reviewRepository.findSortingReviewsByCreatedAtDesc(product, member, lastReviewId, pageable); } if (sort.equals("rating,asc") || sort.equals("rating,desc")) { if (Objects.equals(lastReviewId, FIRST)) { - return reviewRepository.findSortingReviewsByRatingFirstPage(product, pageable); + return reviewRepository.findSortingReviewsByRatingFirstPage(product, member, pageable); } if (sort.equals("rating,asc")) { - return reviewRepository.findSortingRatingByRatingAsc(product, lastReviewId, pageable); + return reviewRepository.findSortingRatingByRatingAsc(product, member, lastReviewId, pageable); } if (sort.equals("rating,desc")) { - return reviewRepository.findSortingRatingByRatingDesc(product, lastReviewId, pageable); + return reviewRepository.findSortingRatingByRatingDesc(product, member, lastReviewId, pageable); } } throw new ReviewSortingOptionNotFoundException(REVIEW_SORTING_OPTION_NOT_FOUND, product.getId()); diff --git a/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java b/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java index ccec1d81a..fd05965af 100644 --- a/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java +++ b/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java @@ -22,15 +22,16 @@ public interface ReviewRepository extends JpaRepository { @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + "FROM Review r " + "JOIN r.member m " - + "LEFT JOIN r.reviewFavorites rf " + + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " + "WHERE r.product = :product") List findSortingReviewsByFavoriteCountDescFirstPage(@Param("product") final Product product, + @Param("loginMember") final Member member, final Pageable pageable); @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + "FROM Review r " + "JOIN r.member m " - + "LEFT JOIN r.reviewFavorites rf " + + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " + "WHERE r.product = :product " + "AND (" + "(r.favoriteCount = " @@ -47,21 +48,23 @@ List findSortingReviewsByFavoriteCountDescFirstPage(@Param("pr + ")" + ")") List findSortingReviewsByFavoriteCountDesc(@Param("product") final Product product, + @Param("loginMember") final Member member, @Param("lastReviewId") final Long lastReviewId, final Pageable pageable); @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + "FROM Review r " + "JOIN r.member m " - + "LEFT JOIN r.reviewFavorites rf " + + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " + "WHERE r.product = :product") List findSortingReviewsByCreatedAtDescFirstPage(@Param("product") final Product product, + @Param("loginMember") final Member member, final Pageable pageable); @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + "FROM Review r " + "JOIN r.member m " - + "LEFT JOIN r.reviewFavorites rf " + + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " + "WHERE r.product = :product " + "AND (" + "(r.createdAt = " @@ -78,21 +81,23 @@ List findSortingReviewsByCreatedAtDescFirstPage(@Param("produc + ")" + ")") List findSortingReviewsByCreatedAtDesc(@Param("product") final Product product, + @Param("loginMember") final Member member, @Param("lastReviewId") final Long lastReviewId, final Pageable pageable); @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + "FROM Review r " + "JOIN r.member m " - + "LEFT JOIN r.reviewFavorites rf " + + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " + "WHERE r.product = :product") List findSortingReviewsByRatingFirstPage(@Param("product") final Product product, - final Pageable pageable); + @Param("loginMember") final Member member, + final Pageable pageable); @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + "FROM Review r " + "JOIN r.member m " - + "LEFT JOIN r.reviewFavorites rf " + + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " + "WHERE r.product = :product " + "AND (" + "(r.rating = " @@ -109,13 +114,14 @@ List findSortingReviewsByRatingFirstPage(@Param("product") fin + ")" + ")") List findSortingRatingByRatingAsc(@Param("product") final Product product, - @Param("lastReviewId") final Long lastReviewId, - final Pageable pageable); + @Param("loginMember") final Member member, + @Param("lastReviewId") final Long lastReviewId, + final Pageable pageable); @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + "FROM Review r " + "JOIN r.member m " - + "LEFT JOIN r.reviewFavorites rf " + + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " + "WHERE r.product = :product " + "AND (" + "(r.rating = " @@ -132,8 +138,9 @@ List findSortingRatingByRatingAsc(@Param("product") final Prod + ")" + ")") List findSortingRatingByRatingDesc(@Param("product") final Product product, - @Param("lastReviewId") final Long lastReviewId, - final Pageable pageable); + @Param("loginMember") final Member member, + @Param("lastReviewId") final Long lastReviewId, + final Pageable pageable); List findTop3ByOrderByFavoriteCountDesc(); From 7595878dfa9004b1014fd033144d1f174c78b5bc Mon Sep 17 00:00:00 2001 From: 70825 Date: Wed, 20 Sep 2023 13:26:04 +0900 Subject: [PATCH 13/35] =?UTF-8?q?test:=20=ED=94=84=EB=A1=9C=EB=8D=95?= =?UTF-8?q?=EC=85=98=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../funeat/acceptance/common/CommonSteps.java | 6 ++ .../review/ReviewAcceptanceTest.java | 58 +++++++------------ .../funeat/acceptance/review/ReviewSteps.java | 5 +- .../com/funeat/fixture/ReviewFixture.java | 4 ++ .../persistence/ReviewRepositoryTest.java | 16 ++--- 5 files changed, 41 insertions(+), 48 deletions(-) diff --git a/backend/src/test/java/com/funeat/acceptance/common/CommonSteps.java b/backend/src/test/java/com/funeat/acceptance/common/CommonSteps.java index 32dcb85e2..932115005 100644 --- a/backend/src/test/java/com/funeat/acceptance/common/CommonSteps.java +++ b/backend/src/test/java/com/funeat/acceptance/common/CommonSteps.java @@ -74,4 +74,10 @@ public class CommonSteps { assertThat(actual).usingRecursiveComparison() .isEqualTo(expected); } + + public static void 다음_데이터가_있는지_검증한다(final ExtractableResponse response, final boolean expected) { + final var actual = response.jsonPath().getBoolean("hasNextReview"); + + assertThat(actual).isEqualTo(expected); + } } diff --git a/backend/src/test/java/com/funeat/acceptance/review/ReviewAcceptanceTest.java b/backend/src/test/java/com/funeat/acceptance/review/ReviewAcceptanceTest.java index b1c7218db..76e4f48a5 100644 --- a/backend/src/test/java/com/funeat/acceptance/review/ReviewAcceptanceTest.java +++ b/backend/src/test/java/com/funeat/acceptance/review/ReviewAcceptanceTest.java @@ -2,6 +2,7 @@ import static com.funeat.acceptance.auth.LoginSteps.로그인_쿠키_획득; import static com.funeat.acceptance.common.CommonSteps.STATUS_CODE를_검증한다; +import static com.funeat.acceptance.common.CommonSteps.다음_데이터가_있는지_검증한다; import static com.funeat.acceptance.common.CommonSteps.사진_명세_요청; import static com.funeat.acceptance.common.CommonSteps.인증되지_않음; import static com.funeat.acceptance.common.CommonSteps.잘못된_요청; @@ -9,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.common.CommonSteps.페이지를_검증한다; import static com.funeat.acceptance.product.ProductSteps.상품_상세_조회_요청; import static com.funeat.acceptance.review.ReviewSteps.리뷰_랭킹_조회_요청; import static com.funeat.acceptance.review.ReviewSteps.리뷰_작성_요청; @@ -29,13 +29,7 @@ import static com.funeat.fixture.MemberFixture.멤버2; import static com.funeat.fixture.MemberFixture.멤버3; import static com.funeat.fixture.PageFixture.FIRST_PAGE; -import static com.funeat.fixture.PageFixture.PAGE_SIZE; -import static com.funeat.fixture.PageFixture.마지막페이지O; -import static com.funeat.fixture.PageFixture.응답_페이지_생성; import static com.funeat.fixture.PageFixture.좋아요수_내림차순; -import static com.funeat.fixture.PageFixture.첫페이지O; -import static com.funeat.fixture.PageFixture.총_데이터_개수; -import static com.funeat.fixture.PageFixture.총_페이지; import static com.funeat.fixture.PageFixture.최신순; import static com.funeat.fixture.PageFixture.평점_내림차순; import static com.funeat.fixture.PageFixture.평점_오름차순; @@ -43,6 +37,7 @@ import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격1000원_평점3점_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격2000원_평점3점_생성; import static com.funeat.fixture.ProductFixture.존재하지_않는_상품; +import static com.funeat.fixture.ReviewFixture.다음_데이터_존재X; import static com.funeat.fixture.ReviewFixture.리뷰; import static com.funeat.fixture.ReviewFixture.리뷰1; import static com.funeat.fixture.ReviewFixture.리뷰2; @@ -56,6 +51,7 @@ import static com.funeat.fixture.ReviewFixture.존재하지_않는_리뷰; import static com.funeat.fixture.ReviewFixture.좋아요O; import static com.funeat.fixture.ReviewFixture.좋아요X; +import static com.funeat.fixture.ReviewFixture.첫_목록을_가져옴; import static com.funeat.fixture.ScoreFixture.점수_1점; import static com.funeat.fixture.ScoreFixture.점수_2점; import static com.funeat.fixture.ScoreFixture.점수_3점; @@ -386,14 +382,12 @@ class 좋아요_기준_내림차순으로_리뷰_목록_조회 { 여러명이_리뷰_좋아요_요청(List.of(멤버1), 상품, 리뷰3, 좋아요O); 여러명이_리뷰_좋아요_요청(List.of(멤버2, 멤버3), 상품, 리뷰2, 좋아요O); - final var 예상_응답_페이지 = 응답_페이지_생성(총_데이터_개수(3L), 총_페이지(1L), 첫페이지O, 마지막페이지O, FIRST_PAGE, PAGE_SIZE); - // when - final var 응답 = 정렬된_리뷰_목록_조회_요청(로그인_쿠키_획득(멤버1), 상품, 좋아요수_내림차순, FIRST_PAGE); + final var 응답 = 정렬된_리뷰_목록_조회_요청(로그인_쿠키_획득(멤버1), 상품, 첫_목록을_가져옴, 좋아요수_내림차순, FIRST_PAGE); // then STATUS_CODE를_검증한다(응답, 정상_처리); - 페이지를_검증한다(응답, 예상_응답_페이지); + 다음_데이터가_있는지_검증한다(응답, 다음_데이터_존재X); 정렬된_리뷰_목록_조회_결과를_검증한다(응답, List.of(리뷰2, 리뷰3, 리뷰1)); } @@ -409,14 +403,12 @@ class 좋아요_기준_내림차순으로_리뷰_목록_조회 { 리뷰_작성_요청(로그인_쿠키_획득(멤버2), 상품, 사진_명세_요청(이미지2), 리뷰추가요청_재구매O_생성(점수_4점, List.of(태그))); 리뷰_작성_요청(로그인_쿠키_획득(멤버3), 상품, 사진_명세_요청(이미지3), 리뷰추가요청_재구매X_생성(점수_3점, List.of(태그))); - final var 예상_응답_페이지 = 응답_페이지_생성(총_데이터_개수(3L), 총_페이지(1L), 첫페이지O, 마지막페이지O, FIRST_PAGE, PAGE_SIZE); - // when - final var 응답 = 정렬된_리뷰_목록_조회_요청(로그인_쿠키_획득(멤버1), 상품, 좋아요수_내림차순, FIRST_PAGE); + final var 응답 = 정렬된_리뷰_목록_조회_요청(로그인_쿠키_획득(멤버1), 상품, 첫_목록을_가져옴, 좋아요수_내림차순, FIRST_PAGE); // then STATUS_CODE를_검증한다(응답, 정상_처리); - 페이지를_검증한다(응답, 예상_응답_페이지); + 다음_데이터가_있는지_검증한다(응답, 다음_데이터_존재X); 정렬된_리뷰_목록_조회_결과를_검증한다(응답, List.of(리뷰3, 리뷰2, 리뷰1)); } } @@ -436,14 +428,12 @@ class 평점_기준_오름차순으로_리뷰_목록을_조회 { 리뷰_작성_요청(로그인_쿠키_획득(멤버2), 상품, 사진_명세_요청(이미지2), 리뷰추가요청_재구매O_생성(점수_4점, List.of(태그))); 리뷰_작성_요청(로그인_쿠키_획득(멤버3), 상품, 사진_명세_요청(이미지3), 리뷰추가요청_재구매X_생성(점수_3점, List.of(태그))); - final var 예상_응답_페이지 = 응답_페이지_생성(총_데이터_개수(3L), 총_페이지(1L), 첫페이지O, 마지막페이지O, FIRST_PAGE, PAGE_SIZE); - // when - final var 응답 = 정렬된_리뷰_목록_조회_요청(로그인_쿠키_획득(멤버1), 상품, 평점_오름차순, FIRST_PAGE); + final var 응답 = 정렬된_리뷰_목록_조회_요청(로그인_쿠키_획득(멤버1), 상품, 첫_목록을_가져옴, 평점_오름차순, FIRST_PAGE); // then STATUS_CODE를_검증한다(응답, 정상_처리); - 페이지를_검증한다(응답, 예상_응답_페이지); + 다음_데이터가_있는지_검증한다(응답, 다음_데이터_존재X); 정렬된_리뷰_목록_조회_결과를_검증한다(응답, List.of(리뷰1, 리뷰3, 리뷰2)); } @@ -459,14 +449,12 @@ class 평점_기준_오름차순으로_리뷰_목록을_조회 { 리뷰_작성_요청(로그인_쿠키_획득(멤버2), 상품, 사진_명세_요청(이미지2), 리뷰추가요청_재구매O_생성(점수_3점, List.of(태그))); 리뷰_작성_요청(로그인_쿠키_획득(멤버3), 상품, 사진_명세_요청(이미지3), 리뷰추가요청_재구매X_생성(점수_3점, List.of(태그))); - final var 예상_응답_페이지 = 응답_페이지_생성(총_데이터_개수(3L), 총_페이지(1L), 첫페이지O, 마지막페이지O, FIRST_PAGE, PAGE_SIZE); - // when - final var 응답 = 정렬된_리뷰_목록_조회_요청(로그인_쿠키_획득(멤버1), 상품, 평점_오름차순, FIRST_PAGE); + final var 응답 = 정렬된_리뷰_목록_조회_요청(로그인_쿠키_획득(멤버1), 상품, 첫_목록을_가져옴, 평점_오름차순, FIRST_PAGE); // then STATUS_CODE를_검증한다(응답, 정상_처리); - 페이지를_검증한다(응답, 예상_응답_페이지); + 다음_데이터가_있는지_검증한다(응답, 다음_데이터_존재X); 정렬된_리뷰_목록_조회_결과를_검증한다(응답, List.of(리뷰3, 리뷰2, 리뷰1)); } } @@ -486,14 +474,12 @@ class 평점_기준_내림차순으로_리뷰_목록_조회 { 리뷰_작성_요청(로그인_쿠키_획득(멤버2), 상품, 사진_명세_요청(이미지2), 리뷰추가요청_재구매O_생성(점수_4점, List.of(태그))); 리뷰_작성_요청(로그인_쿠키_획득(멤버3), 상품, 사진_명세_요청(이미지3), 리뷰추가요청_재구매X_생성(점수_3점, List.of(태그))); - final var 예상_응답_페이지 = 응답_페이지_생성(총_데이터_개수(3L), 총_페이지(1L), 첫페이지O, 마지막페이지O, FIRST_PAGE, PAGE_SIZE); - // when - final var 응답 = 정렬된_리뷰_목록_조회_요청(로그인_쿠키_획득(멤버1), 상품, 평점_내림차순, FIRST_PAGE); + final var 응답 = 정렬된_리뷰_목록_조회_요청(로그인_쿠키_획득(멤버1), 상품, 첫_목록을_가져옴, 평점_내림차순, FIRST_PAGE); // then STATUS_CODE를_검증한다(응답, 정상_처리); - 페이지를_검증한다(응답, 예상_응답_페이지); + 다음_데이터가_있는지_검증한다(응답, 다음_데이터_존재X); 정렬된_리뷰_목록_조회_결과를_검증한다(응답, List.of(리뷰2, 리뷰3, 리뷰1)); } @@ -509,14 +495,12 @@ class 평점_기준_내림차순으로_리뷰_목록_조회 { 리뷰_작성_요청(로그인_쿠키_획득(멤버2), 상품, 사진_명세_요청(이미지2), 리뷰추가요청_재구매O_생성(점수_3점, List.of(태그))); 리뷰_작성_요청(로그인_쿠키_획득(멤버3), 상품, 사진_명세_요청(이미지3), 리뷰추가요청_재구매X_생성(점수_3점, List.of(태그))); - final var 예상_응답_페이지 = 응답_페이지_생성(총_데이터_개수(3L), 총_페이지(1L), 첫페이지O, 마지막페이지O, FIRST_PAGE, PAGE_SIZE); - // when - final var 응답 = 정렬된_리뷰_목록_조회_요청(로그인_쿠키_획득(멤버1), 상품, 평점_내림차순, FIRST_PAGE); + final var 응답 = 정렬된_리뷰_목록_조회_요청(로그인_쿠키_획득(멤버1), 상품, 첫_목록을_가져옴, 평점_내림차순, FIRST_PAGE); // then STATUS_CODE를_검증한다(응답, 정상_처리); - 페이지를_검증한다(응답, 예상_응답_페이지); + 다음_데이터가_있는지_검증한다(응답, 다음_데이터_존재X); 정렬된_리뷰_목록_조회_결과를_검증한다(응답, List.of(리뷰3, 리뷰2, 리뷰1)); } } @@ -536,14 +520,12 @@ class 최신순으로_리뷰_목록을_조회 { 리뷰_작성_요청(로그인_쿠키_획득(멤버2), 상품, 사진_명세_요청(이미지2), 리뷰추가요청_재구매O_생성(점수_4점, List.of(태그))); 리뷰_작성_요청(로그인_쿠키_획득(멤버3), 상품, 사진_명세_요청(이미지3), 리뷰추가요청_재구매X_생성(점수_3점, List.of(태그))); - final var 예상_응답_페이지 = 응답_페이지_생성(총_데이터_개수(3L), 총_페이지(1L), 첫페이지O, 마지막페이지O, FIRST_PAGE, PAGE_SIZE); - // when - final var 응답 = 정렬된_리뷰_목록_조회_요청(로그인_쿠키_획득(멤버1), 상품, 최신순, FIRST_PAGE); + final var 응답 = 정렬된_리뷰_목록_조회_요청(로그인_쿠키_획득(멤버1), 상품, 첫_목록을_가져옴, 최신순, FIRST_PAGE); // then STATUS_CODE를_검증한다(응답, 정상_처리); - 페이지를_검증한다(응답, 예상_응답_페이지); + 다음_데이터가_있는지_검증한다(응답, 다음_데이터_존재X); 정렬된_리뷰_목록_조회_결과를_검증한다(응답, List.of(리뷰3, 리뷰2, 리뷰1)); } } @@ -564,7 +546,7 @@ class getSortingReviews_실패_테스트 { 리뷰_작성_요청(로그인_쿠키_획득(멤버1), 상품, 사진_명세_요청(이미지1), 리뷰추가요청_재구매O_생성(점수_2점, List.of(태그))); // when - final var 응답 = 정렬된_리뷰_목록_조회_요청(cookie, 상품, 좋아요수_내림차순, FIRST_PAGE); + final var 응답 = 정렬된_리뷰_목록_조회_요청(cookie, 상품, 첫_목록을_가져옴, 좋아요수_내림차순, FIRST_PAGE); // then STATUS_CODE를_검증한다(응답, 인증되지_않음); @@ -574,8 +556,8 @@ class getSortingReviews_실패_테스트 { @Test void 존재하지_않는_상품의_리뷰_목록을_조회시_예외가_발생한다() { - // given & when - final var 응답 = 정렬된_리뷰_목록_조회_요청(로그인_쿠키_획득(멤버1), 존재하지_않는_상품, 좋아요수_내림차순, FIRST_PAGE); + // given && when + final var 응답 = 정렬된_리뷰_목록_조회_요청(로그인_쿠키_획득(멤버1), 존재하지_않는_상품, 첫_목록을_가져옴, 좋아요수_내림차순, FIRST_PAGE); // then STATUS_CODE를_검증한다(응답, 찾을수_없음); diff --git a/backend/src/test/java/com/funeat/acceptance/review/ReviewSteps.java b/backend/src/test/java/com/funeat/acceptance/review/ReviewSteps.java index 0d8ce8fd7..ae4f81c16 100644 --- a/backend/src/test/java/com/funeat/acceptance/review/ReviewSteps.java +++ b/backend/src/test/java/com/funeat/acceptance/review/ReviewSteps.java @@ -1,7 +1,6 @@ package com.funeat.acceptance.review; import static com.funeat.acceptance.auth.LoginSteps.로그인_쿠키_획득; -import static com.funeat.acceptance.common.CommonSteps.LOCATION_헤더에서_ID_추출; import static com.funeat.fixture.ReviewFixture.리뷰좋아요요청_생성; import static io.restassured.RestAssured.given; @@ -56,14 +55,16 @@ public class ReviewSteps { } public static ExtractableResponse 정렬된_리뷰_목록_조회_요청(final String loginCookie, final Long productId, + final Long lastReviewId, final String sort, final Long page) { return given() .cookie("JSESSIONID", loginCookie) .queryParam("sort", sort) .queryParam("page", page) + .queryParam("lastReviewId", lastReviewId).log().all() .when() .get("/api/products/{product_id}/reviews", productId) - .then() + .then().log().all() .extract(); } diff --git a/backend/src/test/java/com/funeat/fixture/ReviewFixture.java b/backend/src/test/java/com/funeat/fixture/ReviewFixture.java index e27227d0c..d68a09b6b 100644 --- a/backend/src/test/java/com/funeat/fixture/ReviewFixture.java +++ b/backend/src/test/java/com/funeat/fixture/ReviewFixture.java @@ -27,6 +27,10 @@ public class ReviewFixture { public static final boolean 재구매O = true; public static final boolean 재구매X = false; + public static final Long 첫_목록을_가져옴 = 0L; + public static final boolean 다음_데이터_존재O = true; + public static final boolean 다음_데이터_존재X = false; + public static Review 리뷰_이미지test1_평점1점_재구매O_생성(final Member member, final Product product, final Long count) { return new Review(member, product, "test1", 1L, "test", true, count); } diff --git a/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java b/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java index 4a5c98fd2..7027154a1 100644 --- a/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java +++ b/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java @@ -93,7 +93,7 @@ class findSortingReviewsByFavoriteCountDesc_관련_성공_테스트 { final var page = 페이지요청_좋아요_내림차순_생성(0, 2); // when - final var actual = reviewRepository.findSortingReviewsByFavoriteCountDescFirstPage(product, page); + final var actual = reviewRepository.findSortingReviewsByFavoriteCountDescFirstPage(product, member, page); // then assertThat(actual).extracting(SortingReviewDto::getId) @@ -121,7 +121,7 @@ class findSortingReviewsByFavoriteCountDesc_관련_성공_테스트 { final var page = 페이지요청_좋아요_내림차순_생성(0, 2); // when - final var actual = reviewRepository.findSortingReviewsByFavoriteCountDesc(product, lastReviewId, page); + final var actual = reviewRepository.findSortingReviewsByFavoriteCountDesc(product, member, lastReviewId, page); // then assertThat(actual).extracting(SortingReviewDto::getId) @@ -153,7 +153,7 @@ class findSortingReviewsByCreatedAtDesc_관련_성공_테스트 { final var page = 페이지요청_최신순_생성(0, 2); // when - final var actual = reviewRepository.findSortingReviewsByCreatedAtDescFirstPage(product, page); + final var actual = reviewRepository.findSortingReviewsByCreatedAtDescFirstPage(product, member, page); // then assertThat(actual).extracting(SortingReviewDto::getId) @@ -181,7 +181,7 @@ class findSortingReviewsByCreatedAtDesc_관련_성공_테스트 { final var page = 페이지요청_최신순_생성(0, 2); // when - final var actual = reviewRepository.findSortingReviewsByCreatedAtDesc(product, lastReviewId, page); + final var actual = reviewRepository.findSortingReviewsByCreatedAtDesc(product, member, lastReviewId, page); // then assertThat(actual).extracting(SortingReviewDto::getId) @@ -212,7 +212,7 @@ class findSortingReviewsByRating_관련_성공_테스트 { final var page = 페이지요청_평점_오름차순_생성(0, 2); // when - final var actual = reviewRepository.findSortingReviewsByRatingFirstPage(product, page); + final var actual = reviewRepository.findSortingReviewsByRatingFirstPage(product, member, page); // then assertThat(actual).extracting(SortingReviewDto::getId) @@ -239,7 +239,7 @@ class findSortingReviewsByRating_관련_성공_테스트 { final var page = 페이지요청_평점_내림차순_생성(0, 2); // when - final var actual = reviewRepository.findSortingReviewsByRatingFirstPage(product, page); + final var actual = reviewRepository.findSortingReviewsByRatingFirstPage(product, member, page); // then assertThat(actual).extracting(SortingReviewDto::getId) @@ -267,7 +267,7 @@ class findSortingReviewsByRating_관련_성공_테스트 { final var page = 페이지요청_평점_오름차순_생성(0, 2); // when - final var actual = reviewRepository.findSortingRatingByRatingAsc(product, lastReviewId, page); + final var actual = reviewRepository.findSortingRatingByRatingAsc(product, member, lastReviewId, page); // then assertThat(actual).extracting(SortingReviewDto::getId) @@ -295,7 +295,7 @@ class findSortingReviewsByRating_관련_성공_테스트 { final var page = 페이지요청_평점_내림차순_생성(0, 2); // when - final var actual = reviewRepository.findSortingRatingByRatingDesc(product, lastReviewId, page); + final var actual = reviewRepository.findSortingRatingByRatingDesc(product, member, lastReviewId, page); // then assertThat(actual).extracting(SortingReviewDto::getId) From f15a48ae60cd0954cb6eb747ba6432a33ac21d85 Mon Sep 17 00:00:00 2001 From: 70825 Date: Thu, 21 Sep 2023 10:47:57 +0900 Subject: [PATCH 14/35] =?UTF-8?q?refactor:=20=EC=A0=95=EB=A0=AC=20?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=AA=A9=EB=A1=9D=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/application/ReviewService.java | 64 +---- .../review/application/SortReviewService.java | 157 ++++++++++++ .../review/dto/SortingReviewRequest.java | 22 -- .../review/exception/ReviewException.java | 4 +- .../review/persistence/ReviewRepository.java | 168 ++++++------ .../review/application/ReviewServiceTest.java | 14 +- .../persistence/ReviewRepositoryTest.java | 239 ------------------ 7 files changed, 263 insertions(+), 405 deletions(-) create mode 100644 backend/src/main/java/com/funeat/review/application/SortReviewService.java diff --git a/backend/src/main/java/com/funeat/review/application/ReviewService.java b/backend/src/main/java/com/funeat/review/application/ReviewService.java index f2e9b085e..02d25e847 100644 --- a/backend/src/main/java/com/funeat/review/application/ReviewService.java +++ b/backend/src/main/java/com/funeat/review/application/ReviewService.java @@ -4,7 +4,6 @@ import static com.funeat.member.exception.MemberErrorCode.MEMBER_NOT_FOUND; import static com.funeat.product.exception.ProductErrorCode.PRODUCT_NOT_FOUND; import static com.funeat.review.exception.ReviewErrorCode.REVIEW_NOT_FOUND; -import static com.funeat.review.exception.ReviewErrorCode.REVIEW_SORTING_OPTION_NOT_FOUND; import com.funeat.common.ImageUploader; import com.funeat.common.dto.PageDto; @@ -30,22 +29,17 @@ import com.funeat.review.dto.SortingReviewRequest; import com.funeat.review.dto.SortingReviewsResponse; import com.funeat.review.exception.ReviewException.ReviewNotFoundException; -import com.funeat.review.exception.ReviewException.ReviewSortingOptionNotFoundException; import com.funeat.review.persistence.ReviewRepository; import com.funeat.review.persistence.ReviewTagRepository; import com.funeat.tag.domain.Tag; import com.funeat.tag.persistence.TagRepository; import java.util.List; -import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; -import javax.swing.text.StyledEditorKit.BoldAction; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.domain.Sort.Direction; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -56,8 +50,8 @@ public class ReviewService { private static final int TOP = 0; private static final int ONE = 1; - private static final Long FIRST = 0L; private static final String EMPTY_URL = ""; + private static final int ELEVEN = 11; private final ReviewRepository reviewRepository; private final TagRepository tagRepository; @@ -66,11 +60,13 @@ public class ReviewService { private final ProductRepository productRepository; private final ReviewFavoriteRepository reviewFavoriteRepository; private final ImageUploader imageUploader; + private final SortReviewService sortReviewService; public ReviewService(final ReviewRepository reviewRepository, final TagRepository tagRepository, final ReviewTagRepository reviewTagRepository, final MemberRepository memberRepository, final ProductRepository productRepository, - final ReviewFavoriteRepository reviewFavoriteRepository, final ImageUploader imageUploader) { + final ReviewFavoriteRepository reviewFavoriteRepository, + final ImageUploader imageUploader, final SortReviewService sortReviewService) { this.reviewRepository = reviewRepository; this.tagRepository = tagRepository; this.reviewTagRepository = reviewTagRepository; @@ -78,6 +74,7 @@ public ReviewService(final ReviewRepository reviewRepository, final TagRepositor this.productRepository = productRepository; this.reviewFavoriteRepository = reviewFavoriteRepository; this.imageUploader = imageUploader; + this.sortReviewService = sortReviewService; } @Transactional @@ -148,62 +145,23 @@ public void updateProductImage(final Long reviewId) { } public SortingReviewsResponse sortingReviews(final Long productId, final Long memberId, - final SortingReviewRequest request) { + final SortingReviewRequest request) { final Member member = memberRepository.findById(memberId) .orElseThrow(() -> new MemberNotFoundException(MEMBER_NOT_FOUND, memberId)); final Product product = productRepository.findById(productId) .orElseThrow(() -> new ProductNotFoundException(PRODUCT_NOT_FOUND, productId)); - final List sortingReviewsWithoutTags = executeSortingReviews(product, member, request); - final List sortingReviews = addTagsToSortingReviews(sortingReviewsWithoutTags); - final Boolean hasNextReview = hasMoreReview(request.getPageable(), sortingReviews.size()); - - return SortingReviewsResponse.toResponse(sortingReviews, hasNextReview); - } - - private List executeSortingReviews(final Product product, final Member member, - final SortingReviewRequest request) { final Long lastReviewId = request.getLastReviewId(); - final Pageable pageable = request.getPageable(); - final String sort = request.getSort(); - if (sort.equals("favoriteCount,desc")) { - if (Objects.equals(lastReviewId, FIRST)) { - return reviewRepository.findSortingReviewsByFavoriteCountDescFirstPage(product, member, pageable); - } - return reviewRepository.findSortingReviewsByFavoriteCountDesc(product, member, lastReviewId, pageable); - } - if (sort.equals("createdAt,desc")) { - if (Objects.equals(lastReviewId, FIRST)) { - return reviewRepository.findSortingReviewsByCreatedAtDescFirstPage(product, member, pageable); - } - return reviewRepository.findSortingReviewsByCreatedAtDesc(product, member, lastReviewId, pageable); - } - if (sort.equals("rating,asc") || sort.equals("rating,desc")) { - if (Objects.equals(lastReviewId, FIRST)) { - return reviewRepository.findSortingReviewsByRatingFirstPage(product, member, pageable); - } - if (sort.equals("rating,asc")) { - return reviewRepository.findSortingRatingByRatingAsc(product, member, lastReviewId, pageable); - } - if (sort.equals("rating,desc")) { - return reviewRepository.findSortingRatingByRatingDesc(product, member, lastReviewId, pageable); - } - } - throw new ReviewSortingOptionNotFoundException(REVIEW_SORTING_OPTION_NOT_FOUND, product.getId()); - } + final List sortingReviews = sortReviewService.execute(product, member, lastReviewId, sort); + final Boolean hasNextReview = hasMoreReview(sortingReviews); - private List addTagsToSortingReviews(final List sortingReviews) { - return sortingReviews.stream() - .map(review -> SortingReviewDto.toDto(review, tagRepository.findTagsByReviewId(review.getId()))) - .collect(Collectors.toList()); + return SortingReviewsResponse.toResponse(sortingReviews, hasNextReview); } - private Boolean hasMoreReview(final Pageable pageable, final int reviewSize) { - final int pageSize = pageable.getPageSize(); - - return reviewSize == pageSize + ONE; + private Boolean hasMoreReview(final List reviews) { + return reviews.size() == ELEVEN; } public RankingReviewsResponse getTopReviews() { diff --git a/backend/src/main/java/com/funeat/review/application/SortReviewService.java b/backend/src/main/java/com/funeat/review/application/SortReviewService.java new file mode 100644 index 000000000..17e7a9e92 --- /dev/null +++ b/backend/src/main/java/com/funeat/review/application/SortReviewService.java @@ -0,0 +1,157 @@ +package com.funeat.review.application; + +import static com.funeat.review.exception.ReviewErrorCode.REVIEW_NOT_FOUND; +import static com.funeat.review.exception.ReviewErrorCode.REVIEW_SORTING_OPTION_NOT_FOUND; + +import com.funeat.member.domain.Member; +import com.funeat.product.domain.Product; +import com.funeat.review.domain.Review; +import com.funeat.review.dto.SortingReviewDto; +import com.funeat.review.exception.ReviewException.ReviewNotFoundException; +import com.funeat.review.exception.ReviewException.ReviewSortingOptionNotFoundException; +import com.funeat.review.persistence.ReviewRepository; +import com.funeat.tag.persistence.TagRepository; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; + +@Service +public class SortReviewService { + + private static final Long FIRST = 0L; + private static final int TEN = 10; + private static final int ELEVEN = 11; + private static final PageRequest PAGE = PageRequest.ofSize(ELEVEN); + private static final String FAVORITE_COUNT_DESC = "favoriteCount,desc"; + private static final String CREATED_AT_DESC = "createdAt,desc"; + private static final String RATING_ASC = "rating,asc"; + private static final String RATING_DESC = "rating,desc"; + + private final ReviewRepository reviewRepository; + private final TagRepository tagRepository; + + + public SortReviewService(final ReviewRepository reviewRepository, final TagRepository tagRepository) { + this.reviewRepository = reviewRepository; + this.tagRepository = tagRepository; + } + + public List execute(final Product product, final Member member, + final Long lastReviewId, final String sort) { + final List sortingReviewsWithoutTags = getSortingReviews(product, member, lastReviewId, sort); + final List sortingReviews = addTagsToSortingReviews(sortingReviewsWithoutTags); + + return sortingReviews; + } + + private List getSortingReviews(final Product product, final Member member, + final Long lastReviewId, + final String sort) { + // 첫페이지 - 데이터 11개 + if (Objects.equals(lastReviewId, FIRST)) { + return getSortingReviewsFirstPage(product, member, sort); + } + + // 첫페이지 이후 - 같은값, 이후값 + final Review lastReview = reviewRepository.findById(lastReviewId) + .orElseThrow(() -> new ReviewNotFoundException(REVIEW_NOT_FOUND, lastReviewId)); + + final List sortingReviewByEquals = getSortingReviewsEquals(product, member, lastReview, sort); + if (sortingReviewByEquals.size() >= TEN) { + return sortingReviewByEquals; + } + + final List sortingReviewsByCondition = getSortingReviewsConditions(product, member, + lastReview, sort); + + return completeSortingReviews(sortingReviewByEquals, sortingReviewsByCondition); + } + + private List getSortingReviewsFirstPage(final Product product, final Member member, + final String sort) { + if (FAVORITE_COUNT_DESC.equals(sort)) { + return reviewRepository.sortingReviewsByFavoriteCountDescFirstPage(product, member, PAGE); + } + if (CREATED_AT_DESC.equals(sort)) { + return reviewRepository.sortingReviewsByCreatedAtDescFirstPage(product, member, PAGE); + } + if (RATING_ASC.equals(sort)) { + return reviewRepository.sortingReviewsByRatingAscFirstPage(product, member, PAGE); + } + if (RATING_DESC.equals(sort)) { + return reviewRepository.sortingReviewsByRatingDescFirstPage(product, member, PAGE); + } + throw new ReviewSortingOptionNotFoundException(REVIEW_SORTING_OPTION_NOT_FOUND); + } + + private List getSortingReviewsEquals(final Product product, final Member member, + final Review lastReview, final String sort) { + final Long lastReviewId = lastReview.getId(); + if (FAVORITE_COUNT_DESC.equals(sort)) { + final Long lastReviewFavoriteCount = lastReview.getFavoriteCount(); + return reviewRepository.sortingReviewsByFavoriteCountDescEquals(product, member, lastReviewFavoriteCount, + lastReviewId); + } + if (CREATED_AT_DESC.equals(sort)) { + final LocalDateTime lastReviewCreatedAt = lastReview.getCreatedAt(); + return reviewRepository.sortingReviewsByCreatedAtDescEquals(product, member, lastReviewCreatedAt, + lastReviewId); + } + if (RATING_ASC.equals(sort)) { + final Long lastReviewRating = lastReview.getRating(); + return reviewRepository.sortingReviewsByRatingAscEquals(product, member, lastReviewRating, lastReviewId); + } + if (RATING_DESC.equals(sort)) { + final Long lastReviewRating = lastReview.getRating(); + return reviewRepository.sortingReviewsByRatingDescEquals(product, member, lastReviewRating, lastReviewId); + } + throw new ReviewSortingOptionNotFoundException(REVIEW_SORTING_OPTION_NOT_FOUND); + } + + private List getSortingReviewsConditions(final Product product, final Member member, + final Review lastReview, final String sort) { + if (FAVORITE_COUNT_DESC.equals(sort)) { + final Long lastReviewFavoriteCount = lastReview.getFavoriteCount(); + return reviewRepository + .sortingReviewByFavoriteCountDescCondition(product, member, lastReviewFavoriteCount, PAGE); + } + if (CREATED_AT_DESC.equals(sort)) { + final LocalDateTime lastReviewCreatedAt = lastReview.getCreatedAt(); + return reviewRepository + .sortingReviewByCreatedAtDescCondition(product, member, lastReviewCreatedAt, PAGE); + } + if (RATING_ASC.equals(sort)) { + final Long lastReviewRating = lastReview.getRating(); + return reviewRepository + .sortingReviewByRatingAscCondition(product, member, lastReviewRating, PAGE); + } + if (RATING_DESC.equals(sort)) { + final Long lastReviewRating = lastReview.getRating(); + return reviewRepository + .sortingReviewByRatingDescCondition(product, member, lastReviewRating, PAGE); + } + throw new ReviewSortingOptionNotFoundException(REVIEW_SORTING_OPTION_NOT_FOUND); + } + + private List completeSortingReviews(final List equals, + final List condition) { + final List sortingReviews = new ArrayList<>(); + sortingReviews.addAll(equals); + sortingReviews.addAll(condition); + + if (sortingReviews.size() <= ELEVEN) { + return sortingReviews; + } + return sortingReviews.subList(0, ELEVEN); + } + + private List addTagsToSortingReviews(final List sortingReviews) { + return sortingReviews.stream() + .map(review -> SortingReviewDto.toDto(review, tagRepository.findTagsByReviewId(review.getId()))) + .collect(Collectors.toList()); + } +} diff --git a/backend/src/main/java/com/funeat/review/dto/SortingReviewRequest.java b/backend/src/main/java/com/funeat/review/dto/SortingReviewRequest.java index ac5e8996a..b6bdeb1eb 100644 --- a/backend/src/main/java/com/funeat/review/dto/SortingReviewRequest.java +++ b/backend/src/main/java/com/funeat/review/dto/SortingReviewRequest.java @@ -1,25 +1,11 @@ package com.funeat.review.dto; import javax.validation.constraints.NotNull; -import javax.validation.constraints.Pattern; import javax.validation.constraints.PositiveOrZero; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.domain.Sort.Direction; public class SortingReviewRequest { - private static final int START_PAGE = 0; - private static final int ELEVEN = 11; - private static final String DELIMITER = ","; - private static final int ORDER_INDEX = 0; - private static final int DIRECTION_INDEX = 1; - private static final String ID = "id"; - @NotNull(message = "정렬 조건을 확인해주세요") - @Pattern(regexp = "^(createdAt,desc|favoriteCount,desc|rating,desc|rating,asc)$", - message = "정렬 조건은 'createdAt,desc', 'favoriteCount,desc', 'rating,desc', 'rating,asc' 중 하나만 가능합니다.") private String sort; @NotNull(message = "마지막으로 조회한 리뷰 ID를 확인해주세요") @@ -31,14 +17,6 @@ public SortingReviewRequest(final String sort, final Long lastReviewId) { this.lastReviewId = lastReviewId; } - public Pageable getPageable() { - final String[] splitSort = sort.split(DELIMITER); - final String order = splitSort[ORDER_INDEX]; - final Direction direction = Direction.fromString(splitSort[DIRECTION_INDEX]); - - return PageRequest.of(START_PAGE, ELEVEN, Sort.by(direction, order).and(Sort.by(Direction.DESC, ID))); - } - public String getSort() { return sort; } diff --git a/backend/src/main/java/com/funeat/review/exception/ReviewException.java b/backend/src/main/java/com/funeat/review/exception/ReviewException.java index c1ac968e3..3fa43266b 100644 --- a/backend/src/main/java/com/funeat/review/exception/ReviewException.java +++ b/backend/src/main/java/com/funeat/review/exception/ReviewException.java @@ -17,8 +17,8 @@ public ReviewNotFoundException(final ReviewErrorCode errorCode, final Long revie } public static class ReviewSortingOptionNotFoundException extends ReviewException { - public ReviewSortingOptionNotFoundException(final ReviewErrorCode errorCode, final Long productId) { - super(errorCode.getStatus(), new ErrorCode<>(errorCode.getCode(), errorCode.getMessage(), productId)); + public ReviewSortingOptionNotFoundException(final ReviewErrorCode errorCode) { + super(errorCode.getStatus(), new ErrorCode<>(errorCode.getCode(), errorCode.getMessage())); } } } diff --git a/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java b/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java index fd05965af..edc2236af 100644 --- a/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java +++ b/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java @@ -6,6 +6,7 @@ import com.funeat.product.domain.Product; import com.funeat.review.domain.Review; import com.funeat.review.dto.SortingReviewDto; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; import org.springframework.data.domain.Page; @@ -17,80 +18,109 @@ public interface ReviewRepository extends JpaRepository { - Page findReviewsByProduct(final Pageable pageable, final Product product); + @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + + "FROM Review r " + + "JOIN r.member m " + + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " + + "WHERE r.product = :product " + + "ORDER BY r.favoriteCount DESC, r.id DESC") + List sortingReviewsByFavoriteCountDescFirstPage(@Param("product") final Product product, + @Param("loginMember") final Member member, + final Pageable pageable); @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + "FROM Review r " + "JOIN r.member m " + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " - + "WHERE r.product = :product") - List findSortingReviewsByFavoriteCountDescFirstPage(@Param("product") final Product product, - @Param("loginMember") final Member member, - final Pageable pageable); + + "WHERE r.product = :product AND r.favoriteCount = :lastFavoriteCount AND r.id < :lastReviewId " + + "ORDER BY r.favoriteCount DESC, r.id DESC") + List sortingReviewsByFavoriteCountDescEquals(@Param("product") final Product product, + @Param("loginMember") final Member member, + @Param("lastFavoriteCount") final Long lastReviewFavoriteCount, + @Param("lastReviewId") final Long lastReviewId); + + @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + + "FROM Review r " + + "JOIN r.member m " + + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " + + "WHERE r.product = :product AND r.id > :lastReviewId " + + "ORDER BY r.favoriteCount DESC, r.id DESC") + List sortingReviewByFavoriteCountDescCondition(@Param("product") final Product product, + @Param("loginMember") final Member member, + @Param("lastReviewId") final Long lastReviewId, + final Pageable pageable); @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + "FROM Review r " + "JOIN r.member m " + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " + "WHERE r.product = :product " - + "AND (" - + "(r.favoriteCount = " - + "(SELECT r2.favoriteCount " - + "FROM Review r2 " - + "WHERE r2.id = :lastReviewId) " - + "AND r.id > :lastReviewId " - + ") " - + "OR " - + "(r.favoriteCount < " - + "(SELECT r2.favoriteCount " - + "FROM Review r2 " - + "WHERE r2.id = :lastReviewId)" - + ")" - + ")") - List findSortingReviewsByFavoriteCountDesc(@Param("product") final Product product, + + "ORDER BY r.createdAt DESC, r.id DESC") + List sortingReviewsByCreatedAtDescFirstPage(@Param("product") final Product product, + @Param("loginMember") final Member member, + final Pageable pageable); + + @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + + "FROM Review r " + + "JOIN r.member m " + + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " + + "WHERE r.product = :product AND r.createdAt = :lastReviewCreatedAt AND r.id < :lastReviewId " + + "ORDER BY r.createdAt DESC, r.id DESC") + List sortingReviewsByCreatedAtDescEquals(@Param("product") final Product product, + @Param("loginMember") final Member member, + @Param("lastReviewCreatedAt") final LocalDateTime createdAt, + @Param("lastReviewId") final Long lastReviewId); + + @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + + "FROM Review r " + + "JOIN r.member m " + + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " + + "WHERE r.product = :product AND r.createdAt < :lastReviewCreatedAt " + + "ORDER BY r.createdAt DESC, r.id DESC") + List sortingReviewByCreatedAtDescCondition(@Param("product") final Product product, @Param("loginMember") final Member member, - @Param("lastReviewId") final Long lastReviewId, + @Param("lastReviewCreatedAt") final LocalDateTime createdAt, final Pageable pageable); @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + "FROM Review r " + "JOIN r.member m " + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " - + "WHERE r.product = :product") - List findSortingReviewsByCreatedAtDescFirstPage(@Param("product") final Product product, - @Param("loginMember") final Member member, - final Pageable pageable); + + "WHERE r.product = :product " + + "ORDER BY r.rating ASC, r.id DESC") + List sortingReviewsByRatingAscFirstPage(@Param("product") final Product product, + @Param("loginMember") final Member member, + final Pageable pageable); @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + "FROM Review r " + "JOIN r.member m " + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " - + "WHERE r.product = :product " - + "AND (" - + "(r.createdAt = " - + "(SELECT r2.createdAt " - + "FROM Review r2 " - + "WHERE r2.id = :lastReviewId) " - + "AND r.id > :lastReviewId " - + ") " - + "OR " - + "(r.createdAt < " - + "(SELECT r2.createdAt " - + "FROM Review r2 " - + "WHERE r2.id = :lastReviewId)" - + ")" - + ")") - List findSortingReviewsByCreatedAtDesc(@Param("product") final Product product, + + "WHERE r.product = :product AND r.rating = :lastReviewRating AND r.id = :lastReviewId " + + "ORDER BY r.rating ASC, r.id DESC") + List sortingReviewsByRatingAscEquals(@Param("product") final Product product, + @Param("loginMember") final Member member, + @Param("lastReviewRating") final Long lastReviewRating, + @Param("lastReviewId") final Long lastReviewId); + + @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + + "FROM Review r " + + "JOIN r.member m " + + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " + + "WHERE r.product = :product AND r.rating > :lastReviewRating " + + "ORDER BY r.rating ASC, r.id DESC") + List sortingReviewByRatingAscCondition(@Param("product") final Product product, @Param("loginMember") final Member member, - @Param("lastReviewId") final Long lastReviewId, + @Param("lastReviewRating") final Long lastReviewRating, final Pageable pageable); @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + "FROM Review r " + "JOIN r.member m " + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " - + "WHERE r.product = :product") - List findSortingReviewsByRatingFirstPage(@Param("product") final Product product, + + "WHERE r.product = :product " + + "ORDER BY r.rating DESC, r.id DESC") + List sortingReviewsByRatingDescFirstPage(@Param("product") final Product product, @Param("loginMember") final Member member, final Pageable pageable); @@ -98,49 +128,23 @@ List findSortingReviewsByRatingFirstPage(@Param("product") fin + "FROM Review r " + "JOIN r.member m " + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " - + "WHERE r.product = :product " - + "AND (" - + "(r.rating = " - + "(SELECT r2.rating " - + "FROM Review r2 " - + "WHERE r2.id = :lastReviewId) " - + "AND r.id > :lastReviewId " - + ") " - + "OR " - + "(r.rating > " - + "(SELECT r2.rating " - + "FROM Review r2 " - + "WHERE r2.id = :lastReviewId)" - + ")" - + ")") - List findSortingRatingByRatingAsc(@Param("product") final Product product, - @Param("loginMember") final Member member, - @Param("lastReviewId") final Long lastReviewId, - final Pageable pageable); + + "WHERE r.product = :product AND r.rating = :lastReviewRating AND r.id = :lastReviewId " + + "ORDER BY r.rating DESC, r.id DESC") + List sortingReviewsByRatingDescEquals(@Param("product") final Product product, + @Param("loginMember") final Member member, + @Param("lastReviewRating") final Long lastReviewRating, + @Param("lastReviewId") final Long lastReviewId); @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + "FROM Review r " + "JOIN r.member m " + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " - + "WHERE r.product = :product " - + "AND (" - + "(r.rating = " - + "(SELECT r2.rating " - + "FROM Review r2 " - + "WHERE r2.id = :lastReviewId) " - + "AND r.id > :lastReviewId " - + ") " - + "OR " - + "(r.rating < " - + "(SELECT r2.rating " - + "FROM Review r2 " - + "WHERE r2.id = :lastReviewId)" - + ")" - + ")") - List findSortingRatingByRatingDesc(@Param("product") final Product product, - @Param("loginMember") final Member member, - @Param("lastReviewId") final Long lastReviewId, - final Pageable pageable); + + "WHERE r.product = :product AND r.rating < :lastReviewRating " + + "ORDER BY r.rating DESC, r.id DESC") + List sortingReviewByRatingDescCondition(@Param("product") final Product product, + @Param("loginMember") final Member member, + @Param("lastReviewRating") final Long lastReviewRating, + final Pageable pageable); List findTop3ByOrderByFavoriteCountDesc(); diff --git a/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java b/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java index 3cb3b027a..e5ccfc36b 100644 --- a/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java +++ b/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java @@ -358,9 +358,9 @@ class sortingReviews_성공_테스트 { final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 130L); 복수_리뷰_저장(review1, review2, review3); - final var request = 리뷰정렬요청_좋아요수_내림차순_생성(1L); + final var request = 리뷰정렬요청_좋아요수_내림차순_생성(0L); - final var expected = List.of(review3.getId(), review2.getId()); + final var expected = List.of(review1.getId(), review3.getId(), review2.getId()); // when final var actual = reviewService.sortingReviews(productId, memberId, request).getReviews(); @@ -416,9 +416,9 @@ class sortingReviews_성공_테스트 { final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 130L); 복수_리뷰_저장(review1, review2, review3); - final var request = 리뷰정렬요청_평점_오름차순_생성(1L); + final var request = 리뷰정렬요청_평점_오름차순_생성(0L); - final var expected = List.of(review3.getId(), review2.getId()); + final var expected = List.of(review1.getId(), review3.getId(), review2.getId()); // when final var actual = reviewService.sortingReviews(productId, memberId, request).getReviews(); @@ -440,14 +440,14 @@ class sortingReviews_성공_테스트 { final var product = 상품_삼각김밥_가격1000원_평점3점_생성(category); final var productId = 단일_상품_저장(product); - final var review1 = 리뷰_이미지test4_평점4점_재구매O_생성(member, product, 351L); + final var review1 = 리뷰_이미지test4_평점4점_재구매O_생성(member, product, 5L); final var review2 = 리뷰_이미지test2_평점2점_재구매O_생성(member, product, 24L); - final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 130L); + final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 13L); 복수_리뷰_저장(review1, review2, review3); final var request = 리뷰정렬요청_평점_내림차순_생성(1L); - final var expected = List.of(review3.getId(), review2.getId()); + final var expected = List.of(review1.getId(), review3.getId(), review2.getId()); // when final var actual = reviewService.sortingReviews(productId, memberId, request).getReviews(); diff --git a/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java b/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java index 7027154a1..9e9e0d8d8 100644 --- a/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java +++ b/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java @@ -5,16 +5,11 @@ import static com.funeat.fixture.MemberFixture.멤버_멤버1_생성; import static com.funeat.fixture.MemberFixture.멤버_멤버2_생성; import static com.funeat.fixture.MemberFixture.멤버_멤버3_생성; -import static com.funeat.fixture.PageFixture.페이지요청_좋아요_내림차순_생성; -import static com.funeat.fixture.PageFixture.페이지요청_최신순_생성; -import static com.funeat.fixture.PageFixture.페이지요청_평점_내림차순_생성; -import static com.funeat.fixture.PageFixture.페이지요청_평점_오름차순_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격1000원_평점1점_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격1000원_평점2점_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격2000원_평점3점_생성; import static com.funeat.fixture.ReviewFixture.리뷰_이미지test1_평점1점_재구매O_생성; import static com.funeat.fixture.ReviewFixture.리뷰_이미지test1_평점1점_재구매X_생성; -import static com.funeat.fixture.ReviewFixture.리뷰_이미지test2_평점2점_재구매X_생성; import static com.funeat.fixture.ReviewFixture.리뷰_이미지test3_평점3점_재구매O_생성; import static com.funeat.fixture.ReviewFixture.리뷰_이미지test3_평점3점_재구매X_생성; import static com.funeat.fixture.ReviewFixture.리뷰_이미지test4_평점4점_재구매O_생성; @@ -23,7 +18,6 @@ import static org.assertj.core.api.SoftAssertions.assertSoftly; import com.funeat.common.RepositoryTest; -import com.funeat.review.dto.SortingReviewDto; import java.util.List; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -70,239 +64,6 @@ class countByProduct_성공_테스트 { } } - @Nested - class findSortingReviewsByFavoriteCountDesc_관련_성공_테스트 { - - @Test - void 좋아요_기준_내림차순_리뷰_목록의_첫_페이지를_보여준다() { - // given - final var category = 카테고리_간편식사_생성(); - 단일_카테고리_저장(category); - final var product = 상품_삼각김밥_가격1000원_평점2점_생성(category); - 단일_상품_저장(product); - final var member = 멤버_멤버1_생성(); - 단일_멤버_저장(member); - - final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member, product, 130L); - 단일_리뷰_저장(review1); - final var review2 = 리뷰_이미지test4_평점4점_재구매O_생성(member, product, 24L); - 단일_리뷰_저장(review2); - final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 351L); - 단일_리뷰_저장(review3); - - final var page = 페이지요청_좋아요_내림차순_생성(0, 2); - - // when - final var actual = reviewRepository.findSortingReviewsByFavoriteCountDescFirstPage(product, member, page); - - // then - assertThat(actual).extracting(SortingReviewDto::getId) - .containsExactly(3L, 1L); - } - - @Test - void 좋아요_기준_내림차순_리뷰_목록의_2페이지부터_보여준다() { - // given - final var category = 카테고리_간편식사_생성(); - 단일_카테고리_저장(category); - final var product = 상품_삼각김밥_가격1000원_평점2점_생성(category); - 단일_상품_저장(product); - final var member = 멤버_멤버1_생성(); - 단일_멤버_저장(member); - - final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member, product, 130L); - 단일_리뷰_저장(review1); - final var review2 = 리뷰_이미지test4_평점4점_재구매O_생성(member, product, 24L); - 단일_리뷰_저장(review2); - final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 351L); - 단일_리뷰_저장(review3); - - final var lastReviewId = 1L; - final var page = 페이지요청_좋아요_내림차순_생성(0, 2); - - // when - final var actual = reviewRepository.findSortingReviewsByFavoriteCountDesc(product, member, lastReviewId, page); - - // then - assertThat(actual).extracting(SortingReviewDto::getId) - .containsExactly(2L); - } - - } - - @Nested - class findSortingReviewsByCreatedAtDesc_관련_성공_테스트 { - - @Test - void 최신순으로_리뷰_목록의_첫_페이지를_보여준다() { - // given - final var category = 카테고리_간편식사_생성(); - 단일_카테고리_저장(category); - final var product = 상품_삼각김밥_가격1000원_평점2점_생성(category); - 단일_상품_저장(product); - final var member = 멤버_멤버1_생성(); - 단일_멤버_저장(member); - - final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member, product, 130L); - 단일_리뷰_저장(review1); - final var review2 = 리뷰_이미지test4_평점4점_재구매O_생성(member, product, 24L); - 단일_리뷰_저장(review2); - final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 351L); - 단일_리뷰_저장(review3); - - final var page = 페이지요청_최신순_생성(0, 2); - - // when - final var actual = reviewRepository.findSortingReviewsByCreatedAtDescFirstPage(product, member, page); - - // then - assertThat(actual).extracting(SortingReviewDto::getId) - .containsExactly(3L, 2L); - } - - @Test - void 최신순으로_리뷰_목록의_2페이지부터_보여준다() { - // given - final var category = 카테고리_간편식사_생성(); - 단일_카테고리_저장(category); - final var product = 상품_삼각김밥_가격1000원_평점2점_생성(category); - 단일_상품_저장(product); - final var member = 멤버_멤버1_생성(); - 단일_멤버_저장(member); - - final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member, product, 130L); - 단일_리뷰_저장(review1); - final var review2 = 리뷰_이미지test4_평점4점_재구매O_생성(member, product, 24L); - 단일_리뷰_저장(review2); - final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 351L); - 단일_리뷰_저장(review3); - - final var lastReviewId = 2L; - final var page = 페이지요청_최신순_생성(0, 2); - - // when - final var actual = reviewRepository.findSortingReviewsByCreatedAtDesc(product, member, lastReviewId, page); - - // then - assertThat(actual).extracting(SortingReviewDto::getId) - .containsExactly(1L); - } - } - - @Nested - class findSortingReviewsByRating_관련_성공_테스트 { - - @Test - void 평점_기준_오름차순_리뷰_목록의_첫_페이지를_보여준다() { - // given - final var category = 카테고리_간편식사_생성(); - 단일_카테고리_저장(category); - final var product = 상품_삼각김밥_가격1000원_평점2점_생성(category); - 단일_상품_저장(product); - final var member = 멤버_멤버1_생성(); - 단일_멤버_저장(member); - - final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member, product, 130L); - 단일_리뷰_저장(review1); - final var review2 = 리뷰_이미지test5_평점5점_재구매O_생성(member, product, 24L); - 단일_리뷰_저장(review2); - final var review3 = 리뷰_이미지test2_평점2점_재구매X_생성(member, product, 351L); - 단일_리뷰_저장(review3); - - final var page = 페이지요청_평점_오름차순_생성(0, 2); - - // when - final var actual = reviewRepository.findSortingReviewsByRatingFirstPage(product, member, page); - - // then - assertThat(actual).extracting(SortingReviewDto::getId) - .containsExactly(3L, 1L); - } - - @Test - void 평점_기준_내림차순_리뷰_목록의_첫_페이지를_보여준다() { - // given - final var category = 카테고리_간편식사_생성(); - 단일_카테고리_저장(category); - final var product = 상품_삼각김밥_가격1000원_평점2점_생성(category); - 단일_상품_저장(product); - final var member = 멤버_멤버1_생성(); - 단일_멤버_저장(member); - - final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member, product, 130L); - 단일_리뷰_저장(review1); - final var review2 = 리뷰_이미지test5_평점5점_재구매O_생성(member, product, 24L); - 단일_리뷰_저장(review2); - final var review3 = 리뷰_이미지test2_평점2점_재구매X_생성(member, product, 351L); - 단일_리뷰_저장(review3); - - final var page = 페이지요청_평점_내림차순_생성(0, 2); - - // when - final var actual = reviewRepository.findSortingReviewsByRatingFirstPage(product, member, page); - - // then - assertThat(actual).extracting(SortingReviewDto::getId) - .containsExactly(2L, 1L); - } - - @Test - void 평점_기준_오름차순_리뷰_목록의_2페이지부터_보여준다() { - // given - final var category = 카테고리_간편식사_생성(); - 단일_카테고리_저장(category); - final var product = 상품_삼각김밥_가격1000원_평점2점_생성(category); - 단일_상품_저장(product); - final var member = 멤버_멤버1_생성(); - 단일_멤버_저장(member); - - final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member, product, 130L); - 단일_리뷰_저장(review1); - final var review2 = 리뷰_이미지test5_평점5점_재구매O_생성(member, product, 24L); - 단일_리뷰_저장(review2); - final var review3 = 리뷰_이미지test2_평점2점_재구매X_생성(member, product, 351L); - 단일_리뷰_저장(review3); - - final var lastReviewId = 1L; - final var page = 페이지요청_평점_오름차순_생성(0, 2); - - // when - final var actual = reviewRepository.findSortingRatingByRatingAsc(product, member, lastReviewId, page); - - // then - assertThat(actual).extracting(SortingReviewDto::getId) - .containsExactly(2L); - } - - @Test - void 평점_기준_내림차순_리뷰_목록의_2페이지부터_보여준다() { - // given - final var category = 카테고리_간편식사_생성(); - 단일_카테고리_저장(category); - final var product = 상품_삼각김밥_가격1000원_평점2점_생성(category); - 단일_상품_저장(product); - final var member = 멤버_멤버1_생성(); - 단일_멤버_저장(member); - - final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member, product, 130L); - 단일_리뷰_저장(review1); - final var review2 = 리뷰_이미지test5_평점5점_재구매O_생성(member, product, 24L); - 단일_리뷰_저장(review2); - final var review3 = 리뷰_이미지test2_평점2점_재구매X_생성(member, product, 351L); - 단일_리뷰_저장(review3); - - final var lastReviewId = 1L; - final var page = 페이지요청_평점_내림차순_생성(0, 2); - - // when - final var actual = reviewRepository.findSortingRatingByRatingDesc(product, member, lastReviewId, page); - - // then - assertThat(actual).extracting(SortingReviewDto::getId) - .containsExactly(3L); - } - } - @Nested class findTop3ByOrderByFavoriteCountDesc_성공_테스트 { From 6230eaeb4a684a4cc7f9e5e921517d9ecaf41067 Mon Sep 17 00:00:00 2001 From: 70825 Date: Thu, 21 Sep 2023 10:49:50 +0900 Subject: [PATCH 15/35] =?UTF-8?q?refactor:=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/funeat/review/application/SortReviewService.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/src/main/java/com/funeat/review/application/SortReviewService.java b/backend/src/main/java/com/funeat/review/application/SortReviewService.java index 17e7a9e92..231d1ae51 100644 --- a/backend/src/main/java/com/funeat/review/application/SortReviewService.java +++ b/backend/src/main/java/com/funeat/review/application/SortReviewService.java @@ -51,12 +51,10 @@ public List execute(final Product product, final Member member private List getSortingReviews(final Product product, final Member member, final Long lastReviewId, final String sort) { - // 첫페이지 - 데이터 11개 if (Objects.equals(lastReviewId, FIRST)) { return getSortingReviewsFirstPage(product, member, sort); } - // 첫페이지 이후 - 같은값, 이후값 final Review lastReview = reviewRepository.findById(lastReviewId) .orElseThrow(() -> new ReviewNotFoundException(REVIEW_NOT_FOUND, lastReviewId)); From 73a6891e83436f6fc7c8618604c8b4ce103f6084 Mon Sep 17 00:00:00 2001 From: 70825 Date: Thu, 21 Sep 2023 11:29:00 +0900 Subject: [PATCH 16/35] =?UTF-8?q?fix:=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=EB=A5=BC=2011=EA=B0=9C=EA=B0=80=20=EC=95=84=EB=8B=88=EB=9D=BC?= =?UTF-8?q?=2010=EA=B0=9C=EB=A5=BC=20=EB=B0=98=ED=99=98=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/application/ReviewService.java | 17 ++++++++++------- .../review/application/SortReviewService.java | 14 ++++++++------ .../review/persistence/ReviewRepository.java | 16 ++++++++++------ 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/backend/src/main/java/com/funeat/review/application/ReviewService.java b/backend/src/main/java/com/funeat/review/application/ReviewService.java index 02d25e847..a2254146a 100644 --- a/backend/src/main/java/com/funeat/review/application/ReviewService.java +++ b/backend/src/main/java/com/funeat/review/application/ReviewService.java @@ -52,6 +52,10 @@ public class ReviewService { private static final int ONE = 1; private static final String EMPTY_URL = ""; private static final int ELEVEN = 11; + private static final int START = 0; + private static final int END = 10; + private static final boolean EXIST_NEXT_DATA = true; + private static final boolean NOT_EXIST_NEXT_DATA = false; private final ReviewRepository reviewRepository; private final TagRepository tagRepository; @@ -154,14 +158,13 @@ public SortingReviewsResponse sortingReviews(final Long productId, final Long me final Long lastReviewId = request.getLastReviewId(); final String sort = request.getSort(); - final List sortingReviews = sortReviewService.execute(product, member, lastReviewId, sort); - final Boolean hasNextReview = hasMoreReview(sortingReviews); + final List sortingReviewsAll = sortReviewService.execute(product, member, lastReviewId, sort); - return SortingReviewsResponse.toResponse(sortingReviews, hasNextReview); - } - - private Boolean hasMoreReview(final List reviews) { - return reviews.size() == ELEVEN; + if (sortingReviewsAll.size() == ELEVEN) { + final List sortingReviews = sortingReviewsAll.subList(START, END); + return SortingReviewsResponse.toResponse(sortingReviews, EXIST_NEXT_DATA); + } + return SortingReviewsResponse.toResponse(sortingReviewsAll, NOT_EXIST_NEXT_DATA); } public RankingReviewsResponse getTopReviews() { diff --git a/backend/src/main/java/com/funeat/review/application/SortReviewService.java b/backend/src/main/java/com/funeat/review/application/SortReviewService.java index 231d1ae51..22b8b7a84 100644 --- a/backend/src/main/java/com/funeat/review/application/SortReviewService.java +++ b/backend/src/main/java/com/funeat/review/application/SortReviewService.java @@ -91,21 +91,23 @@ private List getSortingReviewsEquals(final Product product, fi final Long lastReviewId = lastReview.getId(); if (FAVORITE_COUNT_DESC.equals(sort)) { final Long lastReviewFavoriteCount = lastReview.getFavoriteCount(); - return reviewRepository.sortingReviewsByFavoriteCountDescEquals(product, member, lastReviewFavoriteCount, - lastReviewId); + return reviewRepository + .sortingReviewsByFavoriteCountDescEquals(product, member, lastReviewFavoriteCount, lastReviewId, PAGE); } if (CREATED_AT_DESC.equals(sort)) { final LocalDateTime lastReviewCreatedAt = lastReview.getCreatedAt(); - return reviewRepository.sortingReviewsByCreatedAtDescEquals(product, member, lastReviewCreatedAt, - lastReviewId); + return reviewRepository + .sortingReviewsByCreatedAtDescEquals(product, member, lastReviewCreatedAt, lastReviewId, PAGE); } if (RATING_ASC.equals(sort)) { final Long lastReviewRating = lastReview.getRating(); - return reviewRepository.sortingReviewsByRatingAscEquals(product, member, lastReviewRating, lastReviewId); + return reviewRepository + .sortingReviewsByRatingAscEquals(product, member, lastReviewRating, lastReviewId, PAGE); } if (RATING_DESC.equals(sort)) { final Long lastReviewRating = lastReview.getRating(); - return reviewRepository.sortingReviewsByRatingDescEquals(product, member, lastReviewRating, lastReviewId); + return reviewRepository + .sortingReviewsByRatingDescEquals(product, member, lastReviewRating, lastReviewId, PAGE); } throw new ReviewSortingOptionNotFoundException(REVIEW_SORTING_OPTION_NOT_FOUND); } diff --git a/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java b/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java index edc2236af..ecff3c0fe 100644 --- a/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java +++ b/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java @@ -37,17 +37,18 @@ List sortingReviewsByFavoriteCountDescFirstPage(@Param("produc List sortingReviewsByFavoriteCountDescEquals(@Param("product") final Product product, @Param("loginMember") final Member member, @Param("lastFavoriteCount") final Long lastReviewFavoriteCount, - @Param("lastReviewId") final Long lastReviewId); + @Param("lastReviewId") final Long lastReviewId, + final Pageable pageable); @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + "FROM Review r " + "JOIN r.member m " + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " - + "WHERE r.product = :product AND r.id > :lastReviewId " + + "WHERE r.product = :product AND r.favoriteCount < :lastFavoriteCount " + "ORDER BY r.favoriteCount DESC, r.id DESC") List sortingReviewByFavoriteCountDescCondition(@Param("product") final Product product, @Param("loginMember") final Member member, - @Param("lastReviewId") final Long lastReviewId, + @Param("lastFavoriteCount") final Long lastFavoriteCount, final Pageable pageable); @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " @@ -69,7 +70,8 @@ List sortingReviewsByCreatedAtDescFirstPage(@Param("product") List sortingReviewsByCreatedAtDescEquals(@Param("product") final Product product, @Param("loginMember") final Member member, @Param("lastReviewCreatedAt") final LocalDateTime createdAt, - @Param("lastReviewId") final Long lastReviewId); + @Param("lastReviewId") final Long lastReviewId, + final Pageable pageable); @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + "FROM Review r " @@ -101,7 +103,8 @@ List sortingReviewsByRatingAscFirstPage(@Param("product") fina List sortingReviewsByRatingAscEquals(@Param("product") final Product product, @Param("loginMember") final Member member, @Param("lastReviewRating") final Long lastReviewRating, - @Param("lastReviewId") final Long lastReviewId); + @Param("lastReviewId") final Long lastReviewId, + final Pageable pageable); @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + "FROM Review r " @@ -133,7 +136,8 @@ List sortingReviewsByRatingDescFirstPage(@Param("product") fin List sortingReviewsByRatingDescEquals(@Param("product") final Product product, @Param("loginMember") final Member member, @Param("lastReviewRating") final Long lastReviewRating, - @Param("lastReviewId") final Long lastReviewId); + @Param("lastReviewId") final Long lastReviewId, + final Pageable pageable); @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + "FROM Review r " From b0746b0dabd271b7529b8a699dee4bbe2146feb4 Mon Sep 17 00:00:00 2001 From: 70825 Date: Thu, 21 Sep 2023 14:00:10 +0900 Subject: [PATCH 17/35] =?UTF-8?q?refactor:=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=9E=AD=ED=82=B9=EC=97=90=EC=84=9C=20=EC=A2=8B=EC=95=84?= =?UTF-8?q?=EC=9A=94=EA=B0=80=20=EA=B0=99=EC=9C=BC=EB=A9=B4=20=EC=B5=9C?= =?UTF-8?q?=EC=8B=A0=20=EB=A6=AC=EB=B7=B0=EC=88=9C=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC=ED=95=98=EA=B8=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/funeat/review/application/ReviewService.java | 2 +- .../java/com/funeat/review/persistence/ReviewRepository.java | 2 +- .../com/funeat/review/persistence/ReviewRepositoryTest.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/com/funeat/review/application/ReviewService.java b/backend/src/main/java/com/funeat/review/application/ReviewService.java index a2254146a..343076f74 100644 --- a/backend/src/main/java/com/funeat/review/application/ReviewService.java +++ b/backend/src/main/java/com/funeat/review/application/ReviewService.java @@ -168,7 +168,7 @@ public SortingReviewsResponse sortingReviews(final Long productId, final Long me } public RankingReviewsResponse getTopReviews() { - final List rankingReviews = reviewRepository.findTop3ByOrderByFavoriteCountDesc(); + final List rankingReviews = reviewRepository.findTop3ByOrderByFavoriteCountDescIdDesc(); final List dtos = rankingReviews.stream() .map(RankingReviewDto::toDto) diff --git a/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java b/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java index ecff3c0fe..074e60a19 100644 --- a/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java +++ b/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java @@ -150,7 +150,7 @@ List sortingReviewByRatingDescCondition(@Param("product") fina @Param("lastReviewRating") final Long lastReviewRating, final Pageable pageable); - List findTop3ByOrderByFavoriteCountDesc(); + List findTop3ByOrderByFavoriteCountDescIdDesc(); Long countByProduct(final Product product); diff --git a/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java b/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java index 9e9e0d8d8..ebac6adac 100644 --- a/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java +++ b/backend/src/test/java/com/funeat/review/persistence/ReviewRepositoryTest.java @@ -92,7 +92,7 @@ class findTop3ByOrderByFavoriteCountDesc_성공_테스트 { final var expected = List.of(review1_2, review2_2, review1_3); // when - final var actual = reviewRepository.findTop3ByOrderByFavoriteCountDesc(); + final var actual = reviewRepository.findTop3ByOrderByFavoriteCountDescIdDesc(); // then assertThat(actual).usingRecursiveComparison() From 37eeb3a854f06624268c6791da4c20bd16cc3a39 Mon Sep 17 00:00:00 2001 From: 70825 Date: Thu, 12 Oct 2023 12:34:17 +0900 Subject: [PATCH 18/35] =?UTF-8?q?temp:=20Criteria=20API=20+=20Specificatio?= =?UTF-8?q?n=EC=9C=BC=EB=A1=9C=20=EB=8F=99=EC=A0=81=20=EC=BF=BC=EB=A6=AC?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=84=B0=EB=A7=81=20=EC=A7=84=ED=96=89=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/application/ReviewService.java | 46 ++++-- .../funeat/review/dto/SortingReviewDto.java | 36 +++-- .../review/dto/TestSortingReviewDto.java | 73 +++++++++ .../persistence/ReviewCustomRepository.java | 14 ++ .../review/persistence/ReviewRepository.java | 2 +- .../persistence/ReviewRepositoryImpl.java | 91 ++++++++++++ .../funeat/review/specification/SortSpec.java | 27 ++++ .../SortingReviewSpecification.java | 138 ++++++++++++++++++ .../review/application/ReviewServiceTest.java | 55 ++++++- 9 files changed, 456 insertions(+), 26 deletions(-) create mode 100644 backend/src/main/java/com/funeat/review/dto/TestSortingReviewDto.java create mode 100644 backend/src/main/java/com/funeat/review/persistence/ReviewCustomRepository.java create mode 100644 backend/src/main/java/com/funeat/review/persistence/ReviewRepositoryImpl.java create mode 100644 backend/src/main/java/com/funeat/review/specification/SortSpec.java create mode 100644 backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java diff --git a/backend/src/main/java/com/funeat/review/application/ReviewService.java b/backend/src/main/java/com/funeat/review/application/ReviewService.java index 343076f74..33fb6a17f 100644 --- a/backend/src/main/java/com/funeat/review/application/ReviewService.java +++ b/backend/src/main/java/com/funeat/review/application/ReviewService.java @@ -28,9 +28,11 @@ import com.funeat.review.dto.SortingReviewDto; import com.funeat.review.dto.SortingReviewRequest; import com.funeat.review.dto.SortingReviewsResponse; +import com.funeat.review.dto.TestSortingReviewDto; import com.funeat.review.exception.ReviewException.ReviewNotFoundException; import com.funeat.review.persistence.ReviewRepository; import com.funeat.review.persistence.ReviewTagRepository; +import com.funeat.review.specification.SortingReviewSpecification; import com.funeat.tag.domain.Tag; import com.funeat.tag.persistence.TagRepository; import java.util.List; @@ -40,6 +42,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -51,9 +54,8 @@ public class ReviewService { private static final int TOP = 0; private static final int ONE = 1; private static final String EMPTY_URL = ""; - private static final int ELEVEN = 11; + private static final int PAGE_SIZE = 10; private static final int START = 0; - private static final int END = 10; private static final boolean EXIST_NEXT_DATA = true; private static final boolean NOT_EXIST_NEXT_DATA = false; @@ -124,8 +126,7 @@ public void likeReview(final Long reviewId, final Long memberId, final ReviewFav private ReviewFavorite saveReviewFavorite(final Member member, final Review review, final Boolean favorite) { try { - final ReviewFavorite reviewFavorite = ReviewFavorite.create(member, review, - favorite); + final ReviewFavorite reviewFavorite = ReviewFavorite.create(member, review, favorite); return reviewFavoriteRepository.save(reviewFavorite); } catch (final DataIntegrityViolationException e) { throw new MemberDuplicateFavoriteException(MEMBER_DUPLICATE_FAVORITE, member.getId()); @@ -155,16 +156,37 @@ public SortingReviewsResponse sortingReviews(final Long productId, final Long me final Product product = productRepository.findById(productId) .orElseThrow(() -> new ProductNotFoundException(PRODUCT_NOT_FOUND, productId)); - final Long lastReviewId = request.getLastReviewId(); - final String sort = request.getSort(); - - final List sortingReviewsAll = sortReviewService.execute(product, member, lastReviewId, sort); + final List sortingReviews = getSortingReviews(member, product, request); + if (sortingReviews.size() > PAGE_SIZE) { + final List resizeSortingReviews = sortingReviews.subList(START, PAGE_SIZE); + return SortingReviewsResponse.toResponse(resizeSortingReviews, EXIST_NEXT_DATA); + } + return SortingReviewsResponse.toResponse(sortingReviews, NOT_EXIST_NEXT_DATA); + } - if (sortingReviewsAll.size() == ELEVEN) { - final List sortingReviews = sortingReviewsAll.subList(START, END); - return SortingReviewsResponse.toResponse(sortingReviews, EXIST_NEXT_DATA); + private List getSortingReviews(final Member member, final Product product, + final SortingReviewRequest request) { + final Long lastReviewId = request.getLastReviewId(); + final String sortOption = request.getSort(); + + if (lastReviewId == TOP) { + final Specification specification = SortingReviewSpecification.sortingFirstPageBy(product); + final List sortingReviewsTest = reviewRepository.getSortingReview(member, + specification, sortOption); + return sortingReviewsTest.stream() + .map(SortingReviewDto::toDto) + .collect(Collectors.toList()); } - return SortingReviewsResponse.toResponse(sortingReviewsAll, NOT_EXIST_NEXT_DATA); + + final Review lastReview = reviewRepository.findById(lastReviewId) + .orElseThrow(() -> new ReviewNotFoundException(REVIEW_NOT_FOUND, lastReviewId)); + final Specification specification = SortingReviewSpecification.sortingBy(product, sortOption, + lastReview); + final List sortingReviewsTest = reviewRepository.getSortingReview(member, specification, + sortOption); + return sortingReviewsTest.stream() + .map(SortingReviewDto::toDto) + .collect(Collectors.toList()); } public RankingReviewsResponse getTopReviews() { diff --git a/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java b/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java index 42b7fe609..5ee84592b 100644 --- a/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java +++ b/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java @@ -8,7 +8,9 @@ import com.funeat.tag.domain.Tag; import com.funeat.tag.dto.TagDto; import java.time.LocalDateTime; +import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; public class SortingReviewDto { @@ -58,22 +60,32 @@ public SortingReviewDto(final Long id, final String userName, final String profi this.createdAt = createdAt; } - public static SortingReviewDto toDto(final Review review, final Member member) { + public static SortingReviewDto toDto(final TestSortingReviewDto dto) { + final Boolean isFavorite = checkingFavorite(dto.getFavorite()); + return new SortingReviewDto( - review.getId(), - review.getMember().getNickname(), - review.getMember().getProfileImage(), - review.getImage(), - review.getRating(), - findTagDtos(review), - review.getContent(), - review.getReBuy(), - review.getFavoriteCount(), - findReviewFavoriteChecked(review, member), - review.getCreatedAt() + dto.getId(), + dto.getUserName(), + dto.getProfileImage(), + dto.getImage(), + dto.getRating(), + Collections.emptyList(), + dto.getContent(), + dto.getRebuy(), + dto.getFavoriteCount(), + isFavorite, + dto.getCreatedAt() ); } + private static Boolean checkingFavorite(final Boolean favorite) { + final Optional isFavorite = Optional.ofNullable(favorite); + if (isFavorite.isEmpty()) { + return Boolean.FALSE; + } + return Boolean.TRUE; + } + public static SortingReviewDto toDto(final SortingReviewDto sortingReviewDto, final List tags) { final List tagDtos = tags.stream() .map(TagDto::toDto) diff --git a/backend/src/main/java/com/funeat/review/dto/TestSortingReviewDto.java b/backend/src/main/java/com/funeat/review/dto/TestSortingReviewDto.java new file mode 100644 index 000000000..5a0b1f998 --- /dev/null +++ b/backend/src/main/java/com/funeat/review/dto/TestSortingReviewDto.java @@ -0,0 +1,73 @@ +package com.funeat.review.dto; + +import java.time.LocalDateTime; + +public class TestSortingReviewDto { + + private Long id; + private String userName; + private String profileImage; + private String image; + private Long rating; + private String content; + private Boolean rebuy; + private Long favoriteCount; + private Boolean favorite; + private LocalDateTime createdAt; + + public TestSortingReviewDto(final Long id, final String userName, final String profileImage, final String image, + final Long rating, final String content, + final Boolean rebuy, final Long favoriteCount, final Boolean favorite, + final LocalDateTime createdAt) { + this.id = id; + this.userName = userName; + this.profileImage = profileImage; + this.image = image; + this.rating = rating; + this.content = content; + this.rebuy = rebuy; + this.favoriteCount = favoriteCount; + this.favorite = favorite; + this.createdAt = createdAt; + } + + public Long getId() { + return id; + } + + public String getUserName() { + return userName; + } + + public String getProfileImage() { + return profileImage; + } + + public String getImage() { + return image; + } + + public Long getRating() { + return rating; + } + + public String getContent() { + return content; + } + + public Boolean getRebuy() { + return rebuy; + } + + public Long getFavoriteCount() { + return favoriteCount; + } + + public Boolean getFavorite() { + return favorite; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } +} diff --git a/backend/src/main/java/com/funeat/review/persistence/ReviewCustomRepository.java b/backend/src/main/java/com/funeat/review/persistence/ReviewCustomRepository.java new file mode 100644 index 000000000..cd9d9a908 --- /dev/null +++ b/backend/src/main/java/com/funeat/review/persistence/ReviewCustomRepository.java @@ -0,0 +1,14 @@ +package com.funeat.review.persistence; + +import com.funeat.member.domain.Member; +import com.funeat.review.domain.Review; +import com.funeat.review.dto.TestSortingReviewDto; +import java.util.List; +import org.springframework.data.jpa.domain.Specification; + +public interface ReviewCustomRepository { + + List getSortingReview(final Member loginMember, + final Specification specification, + final String sortField); +} diff --git a/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java b/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java index 074e60a19..8b6fe0c94 100644 --- a/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java +++ b/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java @@ -16,7 +16,7 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -public interface ReviewRepository extends JpaRepository { +public interface ReviewRepository extends JpaRepository, ReviewCustomRepository { @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " + "FROM Review r " diff --git a/backend/src/main/java/com/funeat/review/persistence/ReviewRepositoryImpl.java b/backend/src/main/java/com/funeat/review/persistence/ReviewRepositoryImpl.java new file mode 100644 index 000000000..946043de0 --- /dev/null +++ b/backend/src/main/java/com/funeat/review/persistence/ReviewRepositoryImpl.java @@ -0,0 +1,91 @@ +package com.funeat.review.persistence; + +import com.funeat.member.domain.Member; +import com.funeat.member.domain.favorite.ReviewFavorite; +import com.funeat.review.domain.Review; +import com.funeat.review.dto.TestSortingReviewDto; +import java.util.List; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CompoundSelection; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Join; +import javax.persistence.criteria.JoinType; +import javax.persistence.criteria.Order; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Repository; + +@Repository +public class ReviewRepositoryImpl implements ReviewCustomRepository { + + @PersistenceContext + private EntityManager em; + + @Override + public List getSortingReview(final Member loginMember, + final Specification specification, + final String sortOption) { + final CriteriaBuilder cb = em.getCriteriaBuilder(); + final CriteriaQuery cq = cb.createQuery(TestSortingReviewDto.class); + final Root root = cq.from(Review.class); + + // sortField, sortOrder + final String[] sortOptionSplit = sortOption.split(","); + final String sortField = sortOptionSplit[0]; + final String sortOrder = sortOptionSplit[1]; + + // join + final Join joinMember = root.join("member", JoinType.INNER); + + // left join + final Join leftJoinReviewFavorite = root.join("reviewFavorites", JoinType.LEFT); + final Predicate condition = cb.equal(leftJoinReviewFavorite.get("member"), loginMember); + leftJoinReviewFavorite.on(condition); + + // select - from - where - order by + cq.select(getConstruct(root, cb, joinMember, leftJoinReviewFavorite)) + .where(specification.toPredicate(root, cq, cb)) + .orderBy(getOrderBy(root, cb, sortField, sortOrder)); + + // limit + final TypedQuery query = em.createQuery(cq); + query.setMaxResults(11); + + // result + return query.getResultList(); + } + + private static CompoundSelection getConstruct(final Root root, + final CriteriaBuilder cb, + final Join joinMember, + final Join leftJoinReviewFavorite) { + + return cb.construct(TestSortingReviewDto.class, + root.get("id"), + joinMember.get("nickname"), + joinMember.get("profileImage"), + root.get("image"), + root.get("rating"), + root.get("content"), + root.get("reBuy"), + root.get("favoriteCount"), + leftJoinReviewFavorite.get("favorite"), + root.get("createdAt")); + } + + private static List getOrderBy(final Root root, + final CriteriaBuilder cb, + final String fieldName, + final String sortOption) { + if ("ASC".equalsIgnoreCase(sortOption)) { + final Order order = cb.asc(root.get(fieldName)); + return List.of(order, cb.desc(root.get("id"))); + } + final Order order = cb.desc(root.get(fieldName)); + return List.of(order, cb.desc(root.get("id"))); + } +} diff --git a/backend/src/main/java/com/funeat/review/specification/SortSpec.java b/backend/src/main/java/com/funeat/review/specification/SortSpec.java new file mode 100644 index 000000000..3821a00ba --- /dev/null +++ b/backend/src/main/java/com/funeat/review/specification/SortSpec.java @@ -0,0 +1,27 @@ +package com.funeat.review.specification; + +import com.funeat.review.domain.Review; +import java.util.Arrays; +import java.util.function.Function; + +public enum SortSpec { + + FAVORITE_COUNT("favoriteCount", Review::getFavoriteCount), + RATING("rating", Review::getRating); + + private final String fieldName; + private final Function function; + + SortSpec(final String fieldName, final Function function) { + this.fieldName = fieldName; + this.function = function; + } + + public static Long find(final String fieldName, final Review lastReview) { + return Arrays.stream(SortSpec.values()) + .filter(sortSpec -> sortSpec.fieldName.equals(fieldName)) + .findFirst() + .orElseThrow(IllegalArgumentException::new) + .function.apply(lastReview); + } +} diff --git a/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java b/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java new file mode 100644 index 000000000..cab6818d4 --- /dev/null +++ b/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java @@ -0,0 +1,138 @@ +package com.funeat.review.specification; + +import com.funeat.product.domain.Product; +import com.funeat.review.domain.Review; +import java.time.LocalDateTime; +import java.util.Objects; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.Path; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; +import org.springframework.data.jpa.domain.Specification; + +public class SortingReviewSpecification { + + public static Specification sortingFirstPageBy(final Product product) { + return (root, query, criteriaBuilder) -> Specification + .where(equalsProduct(product)) + .toPredicate(root, query, criteriaBuilder); + } + + public static Specification sortingBy(final Product product, final String sortOption, + final Review lastReview) { + return (root, query, criteriaBuilder) -> { + final String[] sortFieldSplit = sortOption.split(","); + final String field = sortFieldSplit[0]; + final String sort = sortFieldSplit[1]; + + return Specification + .where((equalsProduct(product).and(equals(field, lastReview)).and(lessThanLastReviewId(lastReview))) + .or(equalsProduct(product).and(lessOrGreaterThan(field, sort, lastReview)))) + .toPredicate(root, query, criteriaBuilder); + }; + } + + private static Specification equalsProduct(final Product product) { + return (root, query, criteriaBuilder) -> { + if (Objects.isNull(product)) { + return null; + } + + final Path productPath = root.get("product"); + + return criteriaBuilder.equal(productPath, product); + }; + } + + private static Specification lessThanLastReviewId(final Review lastReview) { + return (root, query, criteriaBuilder) -> { + if (Objects.isNull(lastReview)) { + return null; + } + + final Path reviewPath = root.get("id"); + + return criteriaBuilder.lessThan(reviewPath, lastReview.getId()); + }; + } + + private static Specification equals(final String fieldName, final Review lastReview) { + return (root, query, criteriaBuilder) -> { + if (validateNull(fieldName, lastReview)) { + return null; + } + + return checkEquals(fieldName, lastReview, root, criteriaBuilder); + }; + } + + private static Predicate checkEquals(final String fieldName, + final Review lastReview, + final Root root, + final CriteriaBuilder criteriaBuilder) { + if ("createdAt".equalsIgnoreCase(fieldName)) { + final Path createdAtPath = root.get(fieldName); + final LocalDateTime lastReviewCreatedAt = lastReview.getCreatedAt(); + return criteriaBuilder.equal(createdAtPath, lastReviewCreatedAt); + } + final Path reviewPath = root.get(fieldName); + final Long lastReviewField = SortSpec.find(fieldName, lastReview); + return criteriaBuilder.equal(reviewPath, lastReviewField); + } + + private static Specification lessOrGreaterThan(final String field, final String sort, + final Review lastReview) { + if ("ASC".equalsIgnoreCase(sort)) { + return greaterThan(field, lastReview); + } + return lessThan(field, lastReview); + } + + private static Specification greaterThan(final String fieldName, final Review lastReview) { + return (root, query, criteriaBuilder) -> { + if (validateNull(fieldName, lastReview)) { + return null; + } + + return checkGreaterThan(fieldName, lastReview, root, criteriaBuilder); + }; + } + + private static Predicate checkGreaterThan(final String fieldName, final Review lastReview, final Root root, + final CriteriaBuilder criteriaBuilder) { + if ("createdAt".equalsIgnoreCase(fieldName)) { + final Path createdAtPath = root.get(fieldName); + final LocalDateTime lastReviewCreatedAt = lastReview.getCreatedAt(); + return criteriaBuilder.greaterThan(createdAtPath, lastReviewCreatedAt); + } + final Path reviewPath = root.get(fieldName); + final Long lastReviewField = SortSpec.find(fieldName, lastReview); + return criteriaBuilder.greaterThan(reviewPath, lastReviewField); + } + + private static Specification lessThan(final String fieldName, final Review lastReview) { + return (root, query, criteriaBuilder) -> { + if (validateNull(fieldName, lastReview)) { + return null; + } + + return checkLessThan(fieldName, lastReview, root, criteriaBuilder); + }; + } + + private static boolean validateNull(final String fieldName, final Review lastReview) { + return Objects.isNull(fieldName) || Objects.isNull(lastReview); + } + + private static Predicate checkLessThan(final String fieldName, final Review lastReview, final Root root, + final CriteriaBuilder criteriaBuilder) { + if ("createdAt".equalsIgnoreCase(fieldName)) { + final Path createdAtPath = root.get(fieldName); + final LocalDateTime lastReviewCreatedAt = lastReview.getCreatedAt(); + return criteriaBuilder.lessThan(createdAtPath, lastReviewCreatedAt); + } + final Path reviewPath = root.get(fieldName); + final Long lastReviewField = SortSpec.find(fieldName, lastReview); + return criteriaBuilder.lessThan(reviewPath, lastReviewField); + } +} diff --git a/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java b/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java index e5ccfc36b..a1be29e23 100644 --- a/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java +++ b/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java @@ -342,6 +342,59 @@ class likeReview_실패_테스트 { @Nested class sortingReviews_성공_테스트 { + @Test + void TEST_좋아요_기준으로_내림차순_정렬을_할_수_있다() { + // given + final var member = 멤버_멤버1_생성(); + final var memberId = 단일_멤버_저장(member); + + final var category = 카테고리_즉석조리_생성(); + 단일_카테고리_저장(category); + final var product = 상품_삼각김밥_가격1000원_평점3점_생성(category); + final var productId = 단일_상품_저장(product); + + final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member, product, 1L); + final var review2 = 리뷰_이미지test4_평점4점_재구매O_생성(member, product, 2L); + final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 3L); + final var review4 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 4L); + final var review5 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 5L); + final var review6 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 6L); + final var review7 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 7L); + final var review8 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 8L); + final var review9 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 9L); + final var review10 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 10L); + final var review11 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 11L); + final var review12 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 12L); + final var review13 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 13L); + 복수_리뷰_저장(review1, review2, review3, review4, review5, review6, review7, review8, review9, review10, review11, review12, review13); + + reviewService.likeReview(1L, memberId, 리뷰좋아요요청_생성(true)); + reviewService.likeReview(2L, memberId, 리뷰좋아요요청_생성(true)); + reviewService.likeReview(3L, memberId, 리뷰좋아요요청_생성(true)); + reviewService.likeReview(4L, memberId, 리뷰좋아요요청_생성(true)); + reviewService.likeReview(5L, memberId, 리뷰좋아요요청_생성(true)); + reviewService.likeReview(6L, memberId, 리뷰좋아요요청_생성(true)); + reviewService.likeReview(7L, memberId, 리뷰좋아요요청_생성(true)); + reviewService.likeReview(8L, memberId, 리뷰좋아요요청_생성(true)); + reviewService.likeReview(9L, memberId, 리뷰좋아요요청_생성(true)); + reviewService.likeReview(10L, memberId, 리뷰좋아요요청_생성(true)); + reviewService.likeReview(11L, memberId, 리뷰좋아요요청_생성(true)); + reviewService.likeReview(12L, memberId, 리뷰좋아요요청_생성(true)); + reviewService.likeReview(13L, memberId, 리뷰좋아요요청_생성(true)); + + + final var request = 리뷰정렬요청_좋아요수_내림차순_생성(4L); + + final var expected = List.of(review3.getId(), review2.getId(), review1.getId()); + + // when + final var actual = reviewService.sortingReviews(productId, memberId, request).getReviews(); + + // then + assertThat(actual).extracting(SortingReviewDto::getId) + .containsExactlyElementsOf(expected); + } + @Test void 좋아요_기준으로_내림차순_정렬을_할_수_있다() { // given @@ -447,7 +500,7 @@ class sortingReviews_성공_테스트 { final var request = 리뷰정렬요청_평점_내림차순_생성(1L); - final var expected = List.of(review1.getId(), review3.getId(), review2.getId()); + final var expected = List.of(review3.getId(), review2.getId()); // when final var actual = reviewService.sortingReviews(productId, memberId, request).getReviews(); From 3c709c5fbc5cb785c0820ff800ef05bdb5b047be Mon Sep 17 00:00:00 2001 From: 70825 Date: Mon, 16 Oct 2023 10:47:39 +0900 Subject: [PATCH 19/35] =?UTF-8?q?refactor:=20SortSpec=20->=20ReviewSortSpe?= =?UTF-8?q?c=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../specification/{SortSpec.java => ReviewSortSpec.java} | 8 ++++---- .../review/specification/SortingReviewSpecification.java | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) rename backend/src/main/java/com/funeat/review/specification/{SortSpec.java => ReviewSortSpec.java} (71%) diff --git a/backend/src/main/java/com/funeat/review/specification/SortSpec.java b/backend/src/main/java/com/funeat/review/specification/ReviewSortSpec.java similarity index 71% rename from backend/src/main/java/com/funeat/review/specification/SortSpec.java rename to backend/src/main/java/com/funeat/review/specification/ReviewSortSpec.java index 3821a00ba..a7aa0f66e 100644 --- a/backend/src/main/java/com/funeat/review/specification/SortSpec.java +++ b/backend/src/main/java/com/funeat/review/specification/ReviewSortSpec.java @@ -4,7 +4,7 @@ import java.util.Arrays; import java.util.function.Function; -public enum SortSpec { +public enum ReviewSortSpec { FAVORITE_COUNT("favoriteCount", Review::getFavoriteCount), RATING("rating", Review::getRating); @@ -12,14 +12,14 @@ public enum SortSpec { private final String fieldName; private final Function function; - SortSpec(final String fieldName, final Function function) { + ReviewSortSpec(final String fieldName, final Function function) { this.fieldName = fieldName; this.function = function; } public static Long find(final String fieldName, final Review lastReview) { - return Arrays.stream(SortSpec.values()) - .filter(sortSpec -> sortSpec.fieldName.equals(fieldName)) + return Arrays.stream(ReviewSortSpec.values()) + .filter(reviewSortSpec -> reviewSortSpec.fieldName.equals(fieldName)) .findFirst() .orElseThrow(IllegalArgumentException::new) .function.apply(lastReview); diff --git a/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java b/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java index cab6818d4..20ec13a7f 100644 --- a/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java +++ b/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java @@ -76,7 +76,7 @@ private static Predicate checkEquals(final String fieldName, return criteriaBuilder.equal(createdAtPath, lastReviewCreatedAt); } final Path reviewPath = root.get(fieldName); - final Long lastReviewField = SortSpec.find(fieldName, lastReview); + final Long lastReviewField = ReviewSortSpec.find(fieldName, lastReview); return criteriaBuilder.equal(reviewPath, lastReviewField); } @@ -106,7 +106,7 @@ private static Predicate checkGreaterThan(final String fieldName, final Review l return criteriaBuilder.greaterThan(createdAtPath, lastReviewCreatedAt); } final Path reviewPath = root.get(fieldName); - final Long lastReviewField = SortSpec.find(fieldName, lastReview); + final Long lastReviewField = ReviewSortSpec.find(fieldName, lastReview); return criteriaBuilder.greaterThan(reviewPath, lastReviewField); } @@ -132,7 +132,7 @@ private static Predicate checkLessThan(final String fieldName, final Review last return criteriaBuilder.lessThan(createdAtPath, lastReviewCreatedAt); } final Path reviewPath = root.get(fieldName); - final Long lastReviewField = SortSpec.find(fieldName, lastReview); + final Long lastReviewField = ReviewSortSpec.find(fieldName, lastReview); return criteriaBuilder.lessThan(reviewPath, lastReviewField); } } From e6074ae6f464a4c8cb078fc7f202e9c75f5c221b Mon Sep 17 00:00:00 2001 From: 70825 Date: Mon, 16 Oct 2023 10:48:29 +0900 Subject: [PATCH 20/35] =?UTF-8?q?refactor:=20=EB=8B=A4=EB=A5=B8=20?= =?UTF-8?q?=EA=B3=B3=EC=97=90=EC=84=9C=20=EA=B0=9D=EC=B2=B4=EB=A5=BC=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=ED=95=A0=20=EC=88=98=20=EC=97=86=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/specification/SortingReviewSpecification.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java b/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java index 20ec13a7f..6c04029d5 100644 --- a/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java +++ b/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java @@ -10,7 +10,10 @@ import javax.persistence.criteria.Root; import org.springframework.data.jpa.domain.Specification; -public class SortingReviewSpecification { +public final class SortingReviewSpecification { + + private SortingReviewSpecification() { + } public static Specification sortingFirstPageBy(final Product product) { return (root, query, criteriaBuilder) -> Specification From a5d8ccde6b5bce6c1ed53ebd68d7a92754bc879a Mon Sep 17 00:00:00 2001 From: 70825 Date: Mon, 16 Oct 2023 10:54:00 +0900 Subject: [PATCH 21/35] =?UTF-8?q?refactor:=20SortingReviewDto=20Wrapper=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD,=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EC=A2=8B=EC=95=84=EC=9A=94=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EA=B8=B0=EB=B3=B8=EA=B0=92=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../funeat/review/dto/SortingReviewDto.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java b/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java index 5ee84592b..b3be9bb76 100644 --- a/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java +++ b/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java @@ -10,6 +10,7 @@ import java.time.LocalDateTime; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; @@ -22,15 +23,15 @@ public class SortingReviewDto { private Long rating; private List tags; private String content; - private boolean rebuy; + private Boolean rebuy; private Long favoriteCount; - private boolean favorite; + private Boolean favorite; private LocalDateTime createdAt; @JsonCreator public SortingReviewDto(final Long id, final String userName, final String profileImage, final String image, final Long rating, final List tags, - final String content, final boolean rebuy, final Long favoriteCount, final boolean favorite, + final String content, final Boolean rebuy, final Long favoriteCount, final Boolean favorite, final LocalDateTime createdAt) { this.id = id; this.userName = userName; @@ -46,8 +47,8 @@ public SortingReviewDto(final Long id, final String userName, final String profi } public SortingReviewDto(final Long id, final String userName, final String profileImage, final String image, - final Long rating, final String content, final boolean rebuy, final Long favoriteCount, - final boolean favorite, final LocalDateTime createdAt) { + final Long rating, final String content, final Boolean rebuy, final Long favoriteCount, + final Boolean favorite, final LocalDateTime createdAt) { this.id = id; this.userName = userName; this.profileImage = profileImage; @@ -79,8 +80,7 @@ public static SortingReviewDto toDto(final TestSortingReviewDto dto) { } private static Boolean checkingFavorite(final Boolean favorite) { - final Optional isFavorite = Optional.ofNullable(favorite); - if (isFavorite.isEmpty()) { + if (Objects.isNull(favorite)) { return Boolean.FALSE; } return Boolean.TRUE; @@ -142,7 +142,7 @@ public String getContent() { return content; } - public boolean isRebuy() { + public Boolean isRebuy() { return rebuy; } @@ -150,7 +150,7 @@ public Long getFavoriteCount() { return favoriteCount; } - public boolean isFavorite() { + public Boolean isFavorite() { return favorite; } From 0bf7ce133803ad1b875416feb2a675a700e9b6b7 Mon Sep 17 00:00:00 2001 From: 70825 Date: Mon, 16 Oct 2023 10:54:42 +0900 Subject: [PATCH 22/35] =?UTF-8?q?refactor:=20static=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/persistence/ReviewRepositoryImpl.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/src/main/java/com/funeat/review/persistence/ReviewRepositoryImpl.java b/backend/src/main/java/com/funeat/review/persistence/ReviewRepositoryImpl.java index 946043de0..ad408b0d0 100644 --- a/backend/src/main/java/com/funeat/review/persistence/ReviewRepositoryImpl.java +++ b/backend/src/main/java/com/funeat/review/persistence/ReviewRepositoryImpl.java @@ -59,10 +59,10 @@ public List getSortingReview(final Member loginMember, return query.getResultList(); } - private static CompoundSelection getConstruct(final Root root, - final CriteriaBuilder cb, - final Join joinMember, - final Join leftJoinReviewFavorite) { + private CompoundSelection getConstruct(final Root root, + final CriteriaBuilder cb, + final Join joinMember, + final Join leftJoinReviewFavorite) { return cb.construct(TestSortingReviewDto.class, root.get("id"), @@ -77,10 +77,10 @@ private static CompoundSelection getConstruct(final Root getOrderBy(final Root root, - final CriteriaBuilder cb, - final String fieldName, - final String sortOption) { + private List getOrderBy(final Root root, + final CriteriaBuilder cb, + final String fieldName, + final String sortOption) { if ("ASC".equalsIgnoreCase(sortOption)) { final Order order = cb.asc(root.get(fieldName)); return List.of(order, cb.desc(root.get("id"))); From 6706d353e3d4d8962f7f99e0a746d52df329b058 Mon Sep 17 00:00:00 2001 From: 70825 Date: Mon, 16 Oct 2023 10:58:05 +0900 Subject: [PATCH 23/35] =?UTF-8?q?refactor:=20SortingReviewDto=EC=9D=98=20?= =?UTF-8?q?=EB=A9=A4=EB=B2=84=20=EB=B3=80=EC=88=98=EC=97=90=20final=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../funeat/review/dto/SortingReviewDto.java | 37 ++++++------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java b/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java index b3be9bb76..001cd8f2a 100644 --- a/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java +++ b/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java @@ -16,17 +16,17 @@ public class SortingReviewDto { - private Long id; - private String userName; - private String profileImage; - private String image; - private Long rating; - private List tags; - private String content; - private Boolean rebuy; - private Long favoriteCount; - private Boolean favorite; - private LocalDateTime createdAt; + private final Long id; + private final String userName; + private final String profileImage; + private final String image; + private final Long rating; + private final List tags; + private final String content; + private final Boolean rebuy; + private final Long favoriteCount; + private final Boolean favorite; + private final LocalDateTime createdAt; @JsonCreator public SortingReviewDto(final Long id, final String userName, final String profileImage, final String image, @@ -46,21 +46,6 @@ public SortingReviewDto(final Long id, final String userName, final String profi this.createdAt = createdAt; } - public SortingReviewDto(final Long id, final String userName, final String profileImage, final String image, - final Long rating, final String content, final Boolean rebuy, final Long favoriteCount, - final Boolean favorite, final LocalDateTime createdAt) { - this.id = id; - this.userName = userName; - this.profileImage = profileImage; - this.image = image; - this.rating = rating; - this.content = content; - this.rebuy = rebuy; - this.favoriteCount = favoriteCount; - this.favorite = favorite; - this.createdAt = createdAt; - } - public static SortingReviewDto toDto(final TestSortingReviewDto dto) { final Boolean isFavorite = checkingFavorite(dto.getFavorite()); From 36de6f1abcf584ba162e1c30fa828ea2bc1eadfb Mon Sep 17 00:00:00 2001 From: 70825 Date: Mon, 16 Oct 2023 11:02:37 +0900 Subject: [PATCH 24/35] =?UTF-8?q?refactor:=20=EB=8F=99=EC=A0=81=20?= =?UTF-8?q?=EC=BF=BC=EB=A6=AC=20=EC=9D=B4=EC=A0=84=EC=9D=98=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=AA=A9=EB=A1=9D=20=EC=A0=95=EB=A0=AC=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/application/ReviewService.java | 13 +- .../review/application/SortReviewService.java | 157 ------------------ .../review/persistence/ReviewRepository.java | 132 --------------- 3 files changed, 5 insertions(+), 297 deletions(-) delete mode 100644 backend/src/main/java/com/funeat/review/application/SortReviewService.java diff --git a/backend/src/main/java/com/funeat/review/application/ReviewService.java b/backend/src/main/java/com/funeat/review/application/ReviewService.java index 33fb6a17f..764bee776 100644 --- a/backend/src/main/java/com/funeat/review/application/ReviewService.java +++ b/backend/src/main/java/com/funeat/review/application/ReviewService.java @@ -51,11 +51,10 @@ @Transactional(readOnly = true) public class ReviewService { - private static final int TOP = 0; + private static final int START = 0; private static final int ONE = 1; private static final String EMPTY_URL = ""; private static final int PAGE_SIZE = 10; - private static final int START = 0; private static final boolean EXIST_NEXT_DATA = true; private static final boolean NOT_EXIST_NEXT_DATA = false; @@ -66,13 +65,12 @@ public class ReviewService { private final ProductRepository productRepository; private final ReviewFavoriteRepository reviewFavoriteRepository; private final ImageUploader imageUploader; - private final SortReviewService sortReviewService; public ReviewService(final ReviewRepository reviewRepository, final TagRepository tagRepository, final ReviewTagRepository reviewTagRepository, final MemberRepository memberRepository, final ProductRepository productRepository, final ReviewFavoriteRepository reviewFavoriteRepository, - final ImageUploader imageUploader, final SortReviewService sortReviewService) { + final ImageUploader imageUploader) { this.reviewRepository = reviewRepository; this.tagRepository = tagRepository; this.reviewTagRepository = reviewTagRepository; @@ -80,7 +78,6 @@ public ReviewService(final ReviewRepository reviewRepository, final TagRepositor this.productRepository = productRepository; this.reviewFavoriteRepository = reviewFavoriteRepository; this.imageUploader = imageUploader; - this.sortReviewService = sortReviewService; } @Transactional @@ -140,11 +137,11 @@ public void updateProductImage(final Long reviewId) { final Product product = review.getProduct(); final Long productId = product.getId(); - final PageRequest pageRequest = PageRequest.of(TOP, ONE); + final PageRequest pageRequest = PageRequest.of(START, ONE); final List topFavoriteReview = reviewRepository.findPopularReviewWithImage(productId, pageRequest); if (!topFavoriteReview.isEmpty()) { - final String topFavoriteReviewImage = topFavoriteReview.get(TOP).getImage(); + final String topFavoriteReviewImage = topFavoriteReview.get(START).getImage(); product.updateImage(topFavoriteReviewImage); } } @@ -169,7 +166,7 @@ private List getSortingReviews(final Member member, final Prod final Long lastReviewId = request.getLastReviewId(); final String sortOption = request.getSort(); - if (lastReviewId == TOP) { + if (lastReviewId == START) { final Specification specification = SortingReviewSpecification.sortingFirstPageBy(product); final List sortingReviewsTest = reviewRepository.getSortingReview(member, specification, sortOption); diff --git a/backend/src/main/java/com/funeat/review/application/SortReviewService.java b/backend/src/main/java/com/funeat/review/application/SortReviewService.java deleted file mode 100644 index 22b8b7a84..000000000 --- a/backend/src/main/java/com/funeat/review/application/SortReviewService.java +++ /dev/null @@ -1,157 +0,0 @@ -package com.funeat.review.application; - -import static com.funeat.review.exception.ReviewErrorCode.REVIEW_NOT_FOUND; -import static com.funeat.review.exception.ReviewErrorCode.REVIEW_SORTING_OPTION_NOT_FOUND; - -import com.funeat.member.domain.Member; -import com.funeat.product.domain.Product; -import com.funeat.review.domain.Review; -import com.funeat.review.dto.SortingReviewDto; -import com.funeat.review.exception.ReviewException.ReviewNotFoundException; -import com.funeat.review.exception.ReviewException.ReviewSortingOptionNotFoundException; -import com.funeat.review.persistence.ReviewRepository; -import com.funeat.tag.persistence.TagRepository; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; -import org.springframework.data.domain.PageRequest; -import org.springframework.stereotype.Service; - -@Service -public class SortReviewService { - - private static final Long FIRST = 0L; - private static final int TEN = 10; - private static final int ELEVEN = 11; - private static final PageRequest PAGE = PageRequest.ofSize(ELEVEN); - private static final String FAVORITE_COUNT_DESC = "favoriteCount,desc"; - private static final String CREATED_AT_DESC = "createdAt,desc"; - private static final String RATING_ASC = "rating,asc"; - private static final String RATING_DESC = "rating,desc"; - - private final ReviewRepository reviewRepository; - private final TagRepository tagRepository; - - - public SortReviewService(final ReviewRepository reviewRepository, final TagRepository tagRepository) { - this.reviewRepository = reviewRepository; - this.tagRepository = tagRepository; - } - - public List execute(final Product product, final Member member, - final Long lastReviewId, final String sort) { - final List sortingReviewsWithoutTags = getSortingReviews(product, member, lastReviewId, sort); - final List sortingReviews = addTagsToSortingReviews(sortingReviewsWithoutTags); - - return sortingReviews; - } - - private List getSortingReviews(final Product product, final Member member, - final Long lastReviewId, - final String sort) { - if (Objects.equals(lastReviewId, FIRST)) { - return getSortingReviewsFirstPage(product, member, sort); - } - - final Review lastReview = reviewRepository.findById(lastReviewId) - .orElseThrow(() -> new ReviewNotFoundException(REVIEW_NOT_FOUND, lastReviewId)); - - final List sortingReviewByEquals = getSortingReviewsEquals(product, member, lastReview, sort); - if (sortingReviewByEquals.size() >= TEN) { - return sortingReviewByEquals; - } - - final List sortingReviewsByCondition = getSortingReviewsConditions(product, member, - lastReview, sort); - - return completeSortingReviews(sortingReviewByEquals, sortingReviewsByCondition); - } - - private List getSortingReviewsFirstPage(final Product product, final Member member, - final String sort) { - if (FAVORITE_COUNT_DESC.equals(sort)) { - return reviewRepository.sortingReviewsByFavoriteCountDescFirstPage(product, member, PAGE); - } - if (CREATED_AT_DESC.equals(sort)) { - return reviewRepository.sortingReviewsByCreatedAtDescFirstPage(product, member, PAGE); - } - if (RATING_ASC.equals(sort)) { - return reviewRepository.sortingReviewsByRatingAscFirstPage(product, member, PAGE); - } - if (RATING_DESC.equals(sort)) { - return reviewRepository.sortingReviewsByRatingDescFirstPage(product, member, PAGE); - } - throw new ReviewSortingOptionNotFoundException(REVIEW_SORTING_OPTION_NOT_FOUND); - } - - private List getSortingReviewsEquals(final Product product, final Member member, - final Review lastReview, final String sort) { - final Long lastReviewId = lastReview.getId(); - if (FAVORITE_COUNT_DESC.equals(sort)) { - final Long lastReviewFavoriteCount = lastReview.getFavoriteCount(); - return reviewRepository - .sortingReviewsByFavoriteCountDescEquals(product, member, lastReviewFavoriteCount, lastReviewId, PAGE); - } - if (CREATED_AT_DESC.equals(sort)) { - final LocalDateTime lastReviewCreatedAt = lastReview.getCreatedAt(); - return reviewRepository - .sortingReviewsByCreatedAtDescEquals(product, member, lastReviewCreatedAt, lastReviewId, PAGE); - } - if (RATING_ASC.equals(sort)) { - final Long lastReviewRating = lastReview.getRating(); - return reviewRepository - .sortingReviewsByRatingAscEquals(product, member, lastReviewRating, lastReviewId, PAGE); - } - if (RATING_DESC.equals(sort)) { - final Long lastReviewRating = lastReview.getRating(); - return reviewRepository - .sortingReviewsByRatingDescEquals(product, member, lastReviewRating, lastReviewId, PAGE); - } - throw new ReviewSortingOptionNotFoundException(REVIEW_SORTING_OPTION_NOT_FOUND); - } - - private List getSortingReviewsConditions(final Product product, final Member member, - final Review lastReview, final String sort) { - if (FAVORITE_COUNT_DESC.equals(sort)) { - final Long lastReviewFavoriteCount = lastReview.getFavoriteCount(); - return reviewRepository - .sortingReviewByFavoriteCountDescCondition(product, member, lastReviewFavoriteCount, PAGE); - } - if (CREATED_AT_DESC.equals(sort)) { - final LocalDateTime lastReviewCreatedAt = lastReview.getCreatedAt(); - return reviewRepository - .sortingReviewByCreatedAtDescCondition(product, member, lastReviewCreatedAt, PAGE); - } - if (RATING_ASC.equals(sort)) { - final Long lastReviewRating = lastReview.getRating(); - return reviewRepository - .sortingReviewByRatingAscCondition(product, member, lastReviewRating, PAGE); - } - if (RATING_DESC.equals(sort)) { - final Long lastReviewRating = lastReview.getRating(); - return reviewRepository - .sortingReviewByRatingDescCondition(product, member, lastReviewRating, PAGE); - } - throw new ReviewSortingOptionNotFoundException(REVIEW_SORTING_OPTION_NOT_FOUND); - } - - private List completeSortingReviews(final List equals, - final List condition) { - final List sortingReviews = new ArrayList<>(); - sortingReviews.addAll(equals); - sortingReviews.addAll(condition); - - if (sortingReviews.size() <= ELEVEN) { - return sortingReviews; - } - return sortingReviews.subList(0, ELEVEN); - } - - private List addTagsToSortingReviews(final List sortingReviews) { - return sortingReviews.stream() - .map(review -> SortingReviewDto.toDto(review, tagRepository.findTagsByReviewId(review.getId()))) - .collect(Collectors.toList()); - } -} diff --git a/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java b/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java index 8b6fe0c94..1cf889b0b 100644 --- a/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java +++ b/backend/src/main/java/com/funeat/review/persistence/ReviewRepository.java @@ -18,138 +18,6 @@ public interface ReviewRepository extends JpaRepository, ReviewCustomRepository { - @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " - + "FROM Review r " - + "JOIN r.member m " - + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " - + "WHERE r.product = :product " - + "ORDER BY r.favoriteCount DESC, r.id DESC") - List sortingReviewsByFavoriteCountDescFirstPage(@Param("product") final Product product, - @Param("loginMember") final Member member, - final Pageable pageable); - - @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " - + "FROM Review r " - + "JOIN r.member m " - + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " - + "WHERE r.product = :product AND r.favoriteCount = :lastFavoriteCount AND r.id < :lastReviewId " - + "ORDER BY r.favoriteCount DESC, r.id DESC") - List sortingReviewsByFavoriteCountDescEquals(@Param("product") final Product product, - @Param("loginMember") final Member member, - @Param("lastFavoriteCount") final Long lastReviewFavoriteCount, - @Param("lastReviewId") final Long lastReviewId, - final Pageable pageable); - - @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " - + "FROM Review r " - + "JOIN r.member m " - + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " - + "WHERE r.product = :product AND r.favoriteCount < :lastFavoriteCount " - + "ORDER BY r.favoriteCount DESC, r.id DESC") - List sortingReviewByFavoriteCountDescCondition(@Param("product") final Product product, - @Param("loginMember") final Member member, - @Param("lastFavoriteCount") final Long lastFavoriteCount, - final Pageable pageable); - - @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " - + "FROM Review r " - + "JOIN r.member m " - + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " - + "WHERE r.product = :product " - + "ORDER BY r.createdAt DESC, r.id DESC") - List sortingReviewsByCreatedAtDescFirstPage(@Param("product") final Product product, - @Param("loginMember") final Member member, - final Pageable pageable); - - @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " - + "FROM Review r " - + "JOIN r.member m " - + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " - + "WHERE r.product = :product AND r.createdAt = :lastReviewCreatedAt AND r.id < :lastReviewId " - + "ORDER BY r.createdAt DESC, r.id DESC") - List sortingReviewsByCreatedAtDescEquals(@Param("product") final Product product, - @Param("loginMember") final Member member, - @Param("lastReviewCreatedAt") final LocalDateTime createdAt, - @Param("lastReviewId") final Long lastReviewId, - final Pageable pageable); - - @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " - + "FROM Review r " - + "JOIN r.member m " - + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " - + "WHERE r.product = :product AND r.createdAt < :lastReviewCreatedAt " - + "ORDER BY r.createdAt DESC, r.id DESC") - List sortingReviewByCreatedAtDescCondition(@Param("product") final Product product, - @Param("loginMember") final Member member, - @Param("lastReviewCreatedAt") final LocalDateTime createdAt, - final Pageable pageable); - - @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " - + "FROM Review r " - + "JOIN r.member m " - + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " - + "WHERE r.product = :product " - + "ORDER BY r.rating ASC, r.id DESC") - List sortingReviewsByRatingAscFirstPage(@Param("product") final Product product, - @Param("loginMember") final Member member, - final Pageable pageable); - - @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " - + "FROM Review r " - + "JOIN r.member m " - + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " - + "WHERE r.product = :product AND r.rating = :lastReviewRating AND r.id = :lastReviewId " - + "ORDER BY r.rating ASC, r.id DESC") - List sortingReviewsByRatingAscEquals(@Param("product") final Product product, - @Param("loginMember") final Member member, - @Param("lastReviewRating") final Long lastReviewRating, - @Param("lastReviewId") final Long lastReviewId, - final Pageable pageable); - - @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " - + "FROM Review r " - + "JOIN r.member m " - + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " - + "WHERE r.product = :product AND r.rating > :lastReviewRating " - + "ORDER BY r.rating ASC, r.id DESC") - List sortingReviewByRatingAscCondition(@Param("product") final Product product, - @Param("loginMember") final Member member, - @Param("lastReviewRating") final Long lastReviewRating, - final Pageable pageable); - - @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " - + "FROM Review r " - + "JOIN r.member m " - + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " - + "WHERE r.product = :product " - + "ORDER BY r.rating DESC, r.id DESC") - List sortingReviewsByRatingDescFirstPage(@Param("product") final Product product, - @Param("loginMember") final Member member, - final Pageable pageable); - - @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " - + "FROM Review r " - + "JOIN r.member m " - + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " - + "WHERE r.product = :product AND r.rating = :lastReviewRating AND r.id = :lastReviewId " - + "ORDER BY r.rating DESC, r.id DESC") - List sortingReviewsByRatingDescEquals(@Param("product") final Product product, - @Param("loginMember") final Member member, - @Param("lastReviewRating") final Long lastReviewRating, - @Param("lastReviewId") final Long lastReviewId, - final Pageable pageable); - - @Query("SELECT new com.funeat.review.dto.SortingReviewDto(r.id, m.nickname, m.profileImage, r.image, r.rating, r.content, r.reBuy, r.favoriteCount, COALESCE(rf.favorite, false), r.createdAt) " - + "FROM Review r " - + "JOIN r.member m " - + "LEFT JOIN r.reviewFavorites rf ON r.id = rf.review.id AND rf.member = :loginMember " - + "WHERE r.product = :product AND r.rating < :lastReviewRating " - + "ORDER BY r.rating DESC, r.id DESC") - List sortingReviewByRatingDescCondition(@Param("product") final Product product, - @Param("loginMember") final Member member, - @Param("lastReviewRating") final Long lastReviewRating, - final Pageable pageable); - List findTop3ByOrderByFavoriteCountDescIdDesc(); Long countByProduct(final Product product); From e09caee7069ad4e1c29992751ea592c1f2477801 Mon Sep 17 00:00:00 2001 From: 70825 Date: Mon, 16 Oct 2023 11:25:43 +0900 Subject: [PATCH 25/35] =?UTF-8?q?refactor:=20=EC=A0=95=EB=A0=AC=20?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=EC=97=90=20=EC=97=86=EB=8A=94=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=9D=B4=EB=A6=84=EC=9D=B4=20=EC=95=84=EB=8B=88?= =?UTF-8?q?=EB=A9=B4=20=EC=98=88=EC=99=B8=EB=A5=BC=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/exception/ReviewErrorCode.java | 2 +- .../review/exception/ReviewException.java | 4 +- ...iewSortSpec.java => LongTypeSortSpec.java} | 6 +-- .../SortingReviewSpecification.java | 52 +++++++++++++------ 4 files changed, 42 insertions(+), 22 deletions(-) rename backend/src/main/java/com/funeat/review/specification/{ReviewSortSpec.java => LongTypeSortSpec.java} (80%) diff --git a/backend/src/main/java/com/funeat/review/exception/ReviewErrorCode.java b/backend/src/main/java/com/funeat/review/exception/ReviewErrorCode.java index 46cd5ca7f..dc6becab8 100644 --- a/backend/src/main/java/com/funeat/review/exception/ReviewErrorCode.java +++ b/backend/src/main/java/com/funeat/review/exception/ReviewErrorCode.java @@ -5,7 +5,7 @@ public enum ReviewErrorCode { REVIEW_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 리뷰입니다. 리뷰 id를 확인하세요.", "3001"), - REVIEW_SORTING_OPTION_NOT_FOUND(HttpStatus.BAD_REQUEST, "존재하지 않는 정렬 옵션입니다. 정렬 옵션과 정렬하려는 상품 id를 확인하세요.", "3002"), + REVIEW_SORTING_OPTION_NOT_FOUND(HttpStatus.BAD_REQUEST, "존재하지 않는 정렬 옵션입니다. 정렬 옵션을 확인하세요.", "3002"), ; private final HttpStatus status; diff --git a/backend/src/main/java/com/funeat/review/exception/ReviewException.java b/backend/src/main/java/com/funeat/review/exception/ReviewException.java index 3fa43266b..f9d0b88ae 100644 --- a/backend/src/main/java/com/funeat/review/exception/ReviewException.java +++ b/backend/src/main/java/com/funeat/review/exception/ReviewException.java @@ -17,8 +17,8 @@ public ReviewNotFoundException(final ReviewErrorCode errorCode, final Long revie } public static class ReviewSortingOptionNotFoundException extends ReviewException { - public ReviewSortingOptionNotFoundException(final ReviewErrorCode errorCode) { - super(errorCode.getStatus(), new ErrorCode<>(errorCode.getCode(), errorCode.getMessage())); + public ReviewSortingOptionNotFoundException(final ReviewErrorCode errorCode, final String sortFieldName) { + super(errorCode.getStatus(), new ErrorCode<>(errorCode.getCode(), errorCode.getMessage(), sortFieldName)); } } } diff --git a/backend/src/main/java/com/funeat/review/specification/ReviewSortSpec.java b/backend/src/main/java/com/funeat/review/specification/LongTypeSortSpec.java similarity index 80% rename from backend/src/main/java/com/funeat/review/specification/ReviewSortSpec.java rename to backend/src/main/java/com/funeat/review/specification/LongTypeSortSpec.java index a7aa0f66e..a112cb107 100644 --- a/backend/src/main/java/com/funeat/review/specification/ReviewSortSpec.java +++ b/backend/src/main/java/com/funeat/review/specification/LongTypeSortSpec.java @@ -4,7 +4,7 @@ import java.util.Arrays; import java.util.function.Function; -public enum ReviewSortSpec { +public enum LongTypeSortSpec { FAVORITE_COUNT("favoriteCount", Review::getFavoriteCount), RATING("rating", Review::getRating); @@ -12,13 +12,13 @@ public enum ReviewSortSpec { private final String fieldName; private final Function function; - ReviewSortSpec(final String fieldName, final Function function) { + LongTypeSortSpec(final String fieldName, final Function function) { this.fieldName = fieldName; this.function = function; } public static Long find(final String fieldName, final Review lastReview) { - return Arrays.stream(ReviewSortSpec.values()) + return Arrays.stream(LongTypeSortSpec.values()) .filter(reviewSortSpec -> reviewSortSpec.fieldName.equals(fieldName)) .findFirst() .orElseThrow(IllegalArgumentException::new) diff --git a/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java b/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java index 6c04029d5..b8125a2be 100644 --- a/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java +++ b/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java @@ -1,8 +1,12 @@ package com.funeat.review.specification; +import static com.funeat.review.exception.ReviewErrorCode.REVIEW_SORTING_OPTION_NOT_FOUND; + import com.funeat.product.domain.Product; import com.funeat.review.domain.Review; +import com.funeat.review.exception.ReviewException.ReviewSortingOptionNotFoundException; import java.time.LocalDateTime; +import java.util.List; import java.util.Objects; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.Path; @@ -12,6 +16,13 @@ public final class SortingReviewSpecification { + private static final List LOCALDATETIME_TYPE_INCLUDE = List.of("createdAt"); + private static final List LONG_TYPE_INCLUDE = List.of("favoriteCount", "rating"); + private static final String DELIMITER = ","; + private static final String PRODUCT = "product"; + private static final String ID = "id"; + private static final String ASC = "ASC"; + private SortingReviewSpecification() { } @@ -24,7 +35,7 @@ public static Specification sortingFirstPageBy(final Product product) { public static Specification sortingBy(final Product product, final String sortOption, final Review lastReview) { return (root, query, criteriaBuilder) -> { - final String[] sortFieldSplit = sortOption.split(","); + final String[] sortFieldSplit = sortOption.split(DELIMITER); final String field = sortFieldSplit[0]; final String sort = sortFieldSplit[1]; @@ -41,7 +52,7 @@ private static Specification equalsProduct(final Product product) { return null; } - final Path productPath = root.get("product"); + final Path productPath = root.get(PRODUCT); return criteriaBuilder.equal(productPath, product); }; @@ -53,7 +64,7 @@ private static Specification lessThanLastReviewId(final Review lastRevie return null; } - final Path reviewPath = root.get("id"); + final Path reviewPath = root.get(ID); return criteriaBuilder.lessThan(reviewPath, lastReview.getId()); }; @@ -73,19 +84,22 @@ private static Predicate checkEquals(final String fieldName, final Review lastReview, final Root root, final CriteriaBuilder criteriaBuilder) { - if ("createdAt".equalsIgnoreCase(fieldName)) { + if (LOCALDATETIME_TYPE_INCLUDE.contains(fieldName)) { final Path createdAtPath = root.get(fieldName); final LocalDateTime lastReviewCreatedAt = lastReview.getCreatedAt(); return criteriaBuilder.equal(createdAtPath, lastReviewCreatedAt); } - final Path reviewPath = root.get(fieldName); - final Long lastReviewField = ReviewSortSpec.find(fieldName, lastReview); - return criteriaBuilder.equal(reviewPath, lastReviewField); + if (LONG_TYPE_INCLUDE.contains(fieldName)) { + final Path reviewPath = root.get(fieldName); + final Long lastReviewField = LongTypeSortSpec.find(fieldName, lastReview); + return criteriaBuilder.equal(reviewPath, lastReviewField); + } + throw new ReviewSortingOptionNotFoundException(REVIEW_SORTING_OPTION_NOT_FOUND, fieldName); } private static Specification lessOrGreaterThan(final String field, final String sort, final Review lastReview) { - if ("ASC".equalsIgnoreCase(sort)) { + if (ASC.equalsIgnoreCase(sort)) { return greaterThan(field, lastReview); } return lessThan(field, lastReview); @@ -103,14 +117,17 @@ private static Specification greaterThan(final String fieldName, final R private static Predicate checkGreaterThan(final String fieldName, final Review lastReview, final Root root, final CriteriaBuilder criteriaBuilder) { - if ("createdAt".equalsIgnoreCase(fieldName)) { + if (LOCALDATETIME_TYPE_INCLUDE.contains(fieldName)) { final Path createdAtPath = root.get(fieldName); final LocalDateTime lastReviewCreatedAt = lastReview.getCreatedAt(); return criteriaBuilder.greaterThan(createdAtPath, lastReviewCreatedAt); } - final Path reviewPath = root.get(fieldName); - final Long lastReviewField = ReviewSortSpec.find(fieldName, lastReview); - return criteriaBuilder.greaterThan(reviewPath, lastReviewField); + if (LONG_TYPE_INCLUDE.contains(fieldName)) { + final Path reviewPath = root.get(fieldName); + final Long lastReviewField = LongTypeSortSpec.find(fieldName, lastReview); + return criteriaBuilder.greaterThan(reviewPath, lastReviewField); + } + throw new ReviewSortingOptionNotFoundException(REVIEW_SORTING_OPTION_NOT_FOUND, fieldName); } private static Specification lessThan(final String fieldName, final Review lastReview) { @@ -129,13 +146,16 @@ private static boolean validateNull(final String fieldName, final Review lastRev private static Predicate checkLessThan(final String fieldName, final Review lastReview, final Root root, final CriteriaBuilder criteriaBuilder) { - if ("createdAt".equalsIgnoreCase(fieldName)) { + if (LOCALDATETIME_TYPE_INCLUDE.contains(fieldName)) { final Path createdAtPath = root.get(fieldName); final LocalDateTime lastReviewCreatedAt = lastReview.getCreatedAt(); return criteriaBuilder.lessThan(createdAtPath, lastReviewCreatedAt); } - final Path reviewPath = root.get(fieldName); - final Long lastReviewField = ReviewSortSpec.find(fieldName, lastReview); - return criteriaBuilder.lessThan(reviewPath, lastReviewField); + if (LONG_TYPE_INCLUDE.contains(fieldName)) { + final Path reviewPath = root.get(fieldName); + final Long lastReviewField = LongTypeSortSpec.find(fieldName, lastReview); + return criteriaBuilder.lessThan(reviewPath, lastReviewField); + } + throw new ReviewSortingOptionNotFoundException(REVIEW_SORTING_OPTION_NOT_FOUND, fieldName); } } From bbd289a45ca64ecdc458d9a3abfc99a0d9683314 Mon Sep 17 00:00:00 2001 From: 70825 Date: Mon, 16 Oct 2023 11:36:13 +0900 Subject: [PATCH 26/35] =?UTF-8?q?refactor:=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD=20LongTypeS?= =?UTF-8?q?ortSpec=20->=20LongTypeReviewSortSpec?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/application/ReviewService.java | 24 +++++++++---------- ...tSpec.java => LongTypeReviewSortSpec.java} | 6 ++--- .../SortingReviewSpecification.java | 6 ++--- 3 files changed, 17 insertions(+), 19 deletions(-) rename backend/src/main/java/com/funeat/review/specification/{LongTypeSortSpec.java => LongTypeReviewSortSpec.java} (79%) diff --git a/backend/src/main/java/com/funeat/review/application/ReviewService.java b/backend/src/main/java/com/funeat/review/application/ReviewService.java index 764bee776..5c3d34346 100644 --- a/backend/src/main/java/com/funeat/review/application/ReviewService.java +++ b/backend/src/main/java/com/funeat/review/application/ReviewService.java @@ -166,19 +166,7 @@ private List getSortingReviews(final Member member, final Prod final Long lastReviewId = request.getLastReviewId(); final String sortOption = request.getSort(); - if (lastReviewId == START) { - final Specification specification = SortingReviewSpecification.sortingFirstPageBy(product); - final List sortingReviewsTest = reviewRepository.getSortingReview(member, - specification, sortOption); - return sortingReviewsTest.stream() - .map(SortingReviewDto::toDto) - .collect(Collectors.toList()); - } - - final Review lastReview = reviewRepository.findById(lastReviewId) - .orElseThrow(() -> new ReviewNotFoundException(REVIEW_NOT_FOUND, lastReviewId)); - final Specification specification = SortingReviewSpecification.sortingBy(product, sortOption, - lastReview); + final Specification specification = getSortingSpecification(product, sortOption, lastReviewId); final List sortingReviewsTest = reviewRepository.getSortingReview(member, specification, sortOption); return sortingReviewsTest.stream() @@ -186,6 +174,16 @@ private List getSortingReviews(final Member member, final Prod .collect(Collectors.toList()); } + private Specification getSortingSpecification(final Product product, final String sortOption, + final Long lastReviewId) { + if (lastReviewId == START) { + return SortingReviewSpecification.sortingFirstPageBy(product); + } + final Review lastReview = reviewRepository.findById(lastReviewId) + .orElseThrow(() -> new ReviewNotFoundException(REVIEW_NOT_FOUND, lastReviewId)); + return SortingReviewSpecification.sortingBy(product, sortOption, lastReview); + } + public RankingReviewsResponse getTopReviews() { final List rankingReviews = reviewRepository.findTop3ByOrderByFavoriteCountDescIdDesc(); diff --git a/backend/src/main/java/com/funeat/review/specification/LongTypeSortSpec.java b/backend/src/main/java/com/funeat/review/specification/LongTypeReviewSortSpec.java similarity index 79% rename from backend/src/main/java/com/funeat/review/specification/LongTypeSortSpec.java rename to backend/src/main/java/com/funeat/review/specification/LongTypeReviewSortSpec.java index a112cb107..23914e003 100644 --- a/backend/src/main/java/com/funeat/review/specification/LongTypeSortSpec.java +++ b/backend/src/main/java/com/funeat/review/specification/LongTypeReviewSortSpec.java @@ -4,7 +4,7 @@ import java.util.Arrays; import java.util.function.Function; -public enum LongTypeSortSpec { +public enum LongTypeReviewSortSpec { FAVORITE_COUNT("favoriteCount", Review::getFavoriteCount), RATING("rating", Review::getRating); @@ -12,13 +12,13 @@ public enum LongTypeSortSpec { private final String fieldName; private final Function function; - LongTypeSortSpec(final String fieldName, final Function function) { + LongTypeReviewSortSpec(final String fieldName, final Function function) { this.fieldName = fieldName; this.function = function; } public static Long find(final String fieldName, final Review lastReview) { - return Arrays.stream(LongTypeSortSpec.values()) + return Arrays.stream(LongTypeReviewSortSpec.values()) .filter(reviewSortSpec -> reviewSortSpec.fieldName.equals(fieldName)) .findFirst() .orElseThrow(IllegalArgumentException::new) diff --git a/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java b/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java index b8125a2be..a1f8b319d 100644 --- a/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java +++ b/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java @@ -91,7 +91,7 @@ private static Predicate checkEquals(final String fieldName, } if (LONG_TYPE_INCLUDE.contains(fieldName)) { final Path reviewPath = root.get(fieldName); - final Long lastReviewField = LongTypeSortSpec.find(fieldName, lastReview); + final Long lastReviewField = LongTypeReviewSortSpec.find(fieldName, lastReview); return criteriaBuilder.equal(reviewPath, lastReviewField); } throw new ReviewSortingOptionNotFoundException(REVIEW_SORTING_OPTION_NOT_FOUND, fieldName); @@ -124,7 +124,7 @@ private static Predicate checkGreaterThan(final String fieldName, final Review l } if (LONG_TYPE_INCLUDE.contains(fieldName)) { final Path reviewPath = root.get(fieldName); - final Long lastReviewField = LongTypeSortSpec.find(fieldName, lastReview); + final Long lastReviewField = LongTypeReviewSortSpec.find(fieldName, lastReview); return criteriaBuilder.greaterThan(reviewPath, lastReviewField); } throw new ReviewSortingOptionNotFoundException(REVIEW_SORTING_OPTION_NOT_FOUND, fieldName); @@ -153,7 +153,7 @@ private static Predicate checkLessThan(final String fieldName, final Review last } if (LONG_TYPE_INCLUDE.contains(fieldName)) { final Path reviewPath = root.get(fieldName); - final Long lastReviewField = LongTypeSortSpec.find(fieldName, lastReview); + final Long lastReviewField = LongTypeReviewSortSpec.find(fieldName, lastReview); return criteriaBuilder.lessThan(reviewPath, lastReviewField); } throw new ReviewSortingOptionNotFoundException(REVIEW_SORTING_OPTION_NOT_FOUND, fieldName); From e72b3d0660a808feb0dead479dae85e6fc19888c Mon Sep 17 00:00:00 2001 From: 70825 Date: Mon, 16 Oct 2023 12:12:52 +0900 Subject: [PATCH 27/35] =?UTF-8?q?refactor:=20=EC=A0=95=EB=A0=AC=EB=A7=8C?= =?UTF-8?q?=20=ED=95=98=EB=8A=94=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EB=B6=84=EB=A6=AC,=20Tag=EA=B9=8C?= =?UTF-8?q?=EC=A7=80=20=EA=B0=80=EC=A0=B8=EC=98=AC=20=EC=88=98=20=EC=9E=88?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/application/ReviewService.java | 52 ++++++-------- .../application/SortingReviewService.java | 67 +++++++++++++++++++ .../funeat/review/dto/SortingReviewDto.java | 57 +++++----------- ...o.java => SortingReviewDtoWithoutTag.java} | 29 ++++---- .../persistence/ReviewCustomRepository.java | 8 +-- .../persistence/ReviewRepositoryImpl.java | 22 +++--- 6 files changed, 131 insertions(+), 104 deletions(-) create mode 100644 backend/src/main/java/com/funeat/review/application/SortingReviewService.java rename backend/src/main/java/com/funeat/review/dto/{TestSortingReviewDto.java => SortingReviewDtoWithoutTag.java} (57%) diff --git a/backend/src/main/java/com/funeat/review/application/ReviewService.java b/backend/src/main/java/com/funeat/review/application/ReviewService.java index 5c3d34346..42c11eba7 100644 --- a/backend/src/main/java/com/funeat/review/application/ReviewService.java +++ b/backend/src/main/java/com/funeat/review/application/ReviewService.java @@ -28,11 +28,9 @@ import com.funeat.review.dto.SortingReviewDto; import com.funeat.review.dto.SortingReviewRequest; import com.funeat.review.dto.SortingReviewsResponse; -import com.funeat.review.dto.TestSortingReviewDto; import com.funeat.review.exception.ReviewException.ReviewNotFoundException; import com.funeat.review.persistence.ReviewRepository; import com.funeat.review.persistence.ReviewTagRepository; -import com.funeat.review.specification.SortingReviewSpecification; import com.funeat.tag.domain.Tag; import com.funeat.tag.persistence.TagRepository; import java.util.List; @@ -42,7 +40,6 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -54,9 +51,7 @@ public class ReviewService { private static final int START = 0; private static final int ONE = 1; private static final String EMPTY_URL = ""; - private static final int PAGE_SIZE = 10; - private static final boolean EXIST_NEXT_DATA = true; - private static final boolean NOT_EXIST_NEXT_DATA = false; + private static final int REVIEW_PAGE_SIZE = 10; private final ReviewRepository reviewRepository; private final TagRepository tagRepository; @@ -65,12 +60,13 @@ public class ReviewService { private final ProductRepository productRepository; private final ReviewFavoriteRepository reviewFavoriteRepository; private final ImageUploader imageUploader; + private final SortingReviewService sortingReviewService; public ReviewService(final ReviewRepository reviewRepository, final TagRepository tagRepository, final ReviewTagRepository reviewTagRepository, final MemberRepository memberRepository, final ProductRepository productRepository, final ReviewFavoriteRepository reviewFavoriteRepository, - final ImageUploader imageUploader) { + final ImageUploader imageUploader, final SortingReviewService sortingReviewService) { this.reviewRepository = reviewRepository; this.tagRepository = tagRepository; this.reviewTagRepository = reviewTagRepository; @@ -78,6 +74,7 @@ public ReviewService(final ReviewRepository reviewRepository, final TagRepositor this.productRepository = productRepository; this.reviewFavoriteRepository = reviewFavoriteRepository; this.imageUploader = imageUploader; + this.sortingReviewService = sortingReviewService; } @Transactional @@ -148,40 +145,29 @@ public void updateProductImage(final Long reviewId) { public SortingReviewsResponse sortingReviews(final Long productId, final Long memberId, final SortingReviewRequest request) { - final Member member = memberRepository.findById(memberId) + final Member findMember = memberRepository.findById(memberId) .orElseThrow(() -> new MemberNotFoundException(MEMBER_NOT_FOUND, memberId)); - final Product product = productRepository.findById(productId) + final Product findProduct = productRepository.findById(productId) .orElseThrow(() -> new ProductNotFoundException(PRODUCT_NOT_FOUND, productId)); - final List sortingReviews = getSortingReviews(member, product, request); - if (sortingReviews.size() > PAGE_SIZE) { - final List resizeSortingReviews = sortingReviews.subList(START, PAGE_SIZE); - return SortingReviewsResponse.toResponse(resizeSortingReviews, EXIST_NEXT_DATA); - } - return SortingReviewsResponse.toResponse(sortingReviews, NOT_EXIST_NEXT_DATA); - } + final List sortingReviews = sortingReviewService.getSortingReviews(findMember, findProduct, request); + final int resultSize = getResultSize(sortingReviews); - private List getSortingReviews(final Member member, final Product product, - final SortingReviewRequest request) { - final Long lastReviewId = request.getLastReviewId(); - final String sortOption = request.getSort(); + final List resizeSortingReviews = sortingReviews.subList(START, resultSize); + final Boolean hasNext = hasNextPage(sortingReviews); - final Specification specification = getSortingSpecification(product, sortOption, lastReviewId); - final List sortingReviewsTest = reviewRepository.getSortingReview(member, specification, - sortOption); - return sortingReviewsTest.stream() - .map(SortingReviewDto::toDto) - .collect(Collectors.toList()); + return SortingReviewsResponse.toResponse(resizeSortingReviews, hasNext); } - private Specification getSortingSpecification(final Product product, final String sortOption, - final Long lastReviewId) { - if (lastReviewId == START) { - return SortingReviewSpecification.sortingFirstPageBy(product); + private int getResultSize(final List sortingReviews) { + if (sortingReviews.size() <= REVIEW_PAGE_SIZE) { + return sortingReviews.size(); } - final Review lastReview = reviewRepository.findById(lastReviewId) - .orElseThrow(() -> new ReviewNotFoundException(REVIEW_NOT_FOUND, lastReviewId)); - return SortingReviewSpecification.sortingBy(product, sortOption, lastReview); + return REVIEW_PAGE_SIZE; + } + + private Boolean hasNextPage(final List sortingReviews) { + return sortingReviews.size() > REVIEW_PAGE_SIZE; } public RankingReviewsResponse getTopReviews() { diff --git a/backend/src/main/java/com/funeat/review/application/SortingReviewService.java b/backend/src/main/java/com/funeat/review/application/SortingReviewService.java new file mode 100644 index 000000000..89c81856a --- /dev/null +++ b/backend/src/main/java/com/funeat/review/application/SortingReviewService.java @@ -0,0 +1,67 @@ +package com.funeat.review.application; + +import static com.funeat.review.exception.ReviewErrorCode.REVIEW_NOT_FOUND; + +import com.funeat.member.domain.Member; +import com.funeat.product.domain.Product; +import com.funeat.review.domain.Review; +import com.funeat.review.dto.SortingReviewDto; +import com.funeat.review.dto.SortingReviewDtoWithoutTag; +import com.funeat.review.dto.SortingReviewRequest; +import com.funeat.review.exception.ReviewException.ReviewNotFoundException; +import com.funeat.review.persistence.ReviewRepository; +import com.funeat.review.specification.SortingReviewSpecification; +import com.funeat.tag.persistence.TagRepository; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +public class SortingReviewService { + + private static final int FIRST_PAGE = 0; + + private final ReviewRepository reviewRepository; + private final TagRepository tagRepository; + + public SortingReviewService(final ReviewRepository reviewRepository, final TagRepository tagRepository) { + this.reviewRepository = reviewRepository; + this.tagRepository = tagRepository; + } + + public List getSortingReviews(final Member member, final Product product, + final SortingReviewRequest request) { + final Long lastReviewId = request.getLastReviewId(); + final String sortOption = request.getSort(); + + final Specification specification = getSortingSpecification(product, sortOption, lastReviewId); + final List sortingReviewDtoWithoutTags = reviewRepository.getSortingReview(member, + specification, sortOption); + final List sortingReviewDtos = addTagsToSortingReviews(sortingReviewDtoWithoutTags); + + return sortingReviewDtos; + } + + private List addTagsToSortingReviews( + final List sortingReviewDtoWithoutTags) { + return sortingReviewDtoWithoutTags.stream() + .map(reviewDto -> SortingReviewDto.toDto(reviewDto, + tagRepository.findTagsByReviewId(reviewDto.getId()))) + .collect(Collectors.toList()); + } + + private Specification getSortingSpecification(final Product product, final String sortOption, + final Long lastReviewId) { + if (lastReviewId == FIRST_PAGE) { + return SortingReviewSpecification.sortingFirstPageBy(product); + } + + final Review lastReview = reviewRepository.findById(lastReviewId) + .orElseThrow(() -> new ReviewNotFoundException(REVIEW_NOT_FOUND, lastReviewId)); + + return SortingReviewSpecification.sortingBy(product, sortOption, lastReview); + } +} diff --git a/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java b/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java index 001cd8f2a..4a020166e 100644 --- a/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java +++ b/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java @@ -11,7 +11,6 @@ import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.Optional; import java.util.stream.Collectors; public class SortingReviewDto { @@ -46,22 +45,24 @@ public SortingReviewDto(final Long id, final String userName, final String profi this.createdAt = createdAt; } - public static SortingReviewDto toDto(final TestSortingReviewDto dto) { - final Boolean isFavorite = checkingFavorite(dto.getFavorite()); + public static SortingReviewDto toDto(final SortingReviewDtoWithoutTag sortingReviewDto, final List tags) { + final List tagDtos = tags.stream() + .map(TagDto::toDto) + .collect(Collectors.toList()); + final Boolean isFavorite = checkingFavorite(sortingReviewDto.getFavorite()); return new SortingReviewDto( - dto.getId(), - dto.getUserName(), - dto.getProfileImage(), - dto.getImage(), - dto.getRating(), - Collections.emptyList(), - dto.getContent(), - dto.getRebuy(), - dto.getFavoriteCount(), + sortingReviewDto.getId(), + sortingReviewDto.getUserName(), + sortingReviewDto.getProfileImage(), + sortingReviewDto.getImage(), + sortingReviewDto.getRating(), + tagDtos, + sortingReviewDto.getContent(), + sortingReviewDto.getRebuy(), + sortingReviewDto.getFavoriteCount(), isFavorite, - dto.getCreatedAt() - ); + sortingReviewDto.getCreatedAt()); } private static Boolean checkingFavorite(final Boolean favorite) { @@ -71,34 +72,6 @@ private static Boolean checkingFavorite(final Boolean favorite) { return Boolean.TRUE; } - public static SortingReviewDto toDto(final SortingReviewDto sortingReviewDto, final List tags) { - final List tagDtos = tags.stream() - .map(TagDto::toDto) - .collect(Collectors.toList()); - - return new SortingReviewDto(sortingReviewDto.getId(), sortingReviewDto.getUserName(), - sortingReviewDto.getProfileImage(), sortingReviewDto.getImage(), sortingReviewDto.getRating(), tagDtos, - sortingReviewDto.getContent(), sortingReviewDto.isRebuy(), sortingReviewDto.getFavoriteCount(), - sortingReviewDto.isFavorite(), sortingReviewDto.getCreatedAt()); - } - - private static List findTagDtos(final Review review) { - return review.getReviewTags().stream() - .map(ReviewTag::getTag) - .map(TagDto::toDto) - .collect(Collectors.toList()); - } - - private static boolean findReviewFavoriteChecked(final Review review, final Member member) { - return review.getReviewFavorites() - .stream() - .filter(reviewFavorite -> reviewFavorite.getReview().equals(review)) - .filter(reviewFavorite -> reviewFavorite.getMember().equals(member)) - .findFirst() - .map(ReviewFavorite::getFavorite) - .orElse(false); - } - public Long getId() { return id; } diff --git a/backend/src/main/java/com/funeat/review/dto/TestSortingReviewDto.java b/backend/src/main/java/com/funeat/review/dto/SortingReviewDtoWithoutTag.java similarity index 57% rename from backend/src/main/java/com/funeat/review/dto/TestSortingReviewDto.java rename to backend/src/main/java/com/funeat/review/dto/SortingReviewDtoWithoutTag.java index 5a0b1f998..a98fdb947 100644 --- a/backend/src/main/java/com/funeat/review/dto/TestSortingReviewDto.java +++ b/backend/src/main/java/com/funeat/review/dto/SortingReviewDtoWithoutTag.java @@ -2,23 +2,24 @@ import java.time.LocalDateTime; -public class TestSortingReviewDto { +public class SortingReviewDtoWithoutTag { private Long id; - private String userName; - private String profileImage; - private String image; - private Long rating; - private String content; - private Boolean rebuy; - private Long favoriteCount; - private Boolean favorite; - private LocalDateTime createdAt; + private final String userName; + private final String profileImage; + private final String image; + private final Long rating; + private final String content; + private final Boolean rebuy; + private final Long favoriteCount; + private final Boolean favorite; + private final LocalDateTime createdAt; - public TestSortingReviewDto(final Long id, final String userName, final String profileImage, final String image, - final Long rating, final String content, - final Boolean rebuy, final Long favoriteCount, final Boolean favorite, - final LocalDateTime createdAt) { + public SortingReviewDtoWithoutTag(final Long id, final String userName, final String profileImage, + final String image, final Long rating, + final String content, final Boolean rebuy, final Long favoriteCount, + final Boolean favorite, + final LocalDateTime createdAt) { this.id = id; this.userName = userName; this.profileImage = profileImage; diff --git a/backend/src/main/java/com/funeat/review/persistence/ReviewCustomRepository.java b/backend/src/main/java/com/funeat/review/persistence/ReviewCustomRepository.java index cd9d9a908..e2dd79992 100644 --- a/backend/src/main/java/com/funeat/review/persistence/ReviewCustomRepository.java +++ b/backend/src/main/java/com/funeat/review/persistence/ReviewCustomRepository.java @@ -2,13 +2,13 @@ import com.funeat.member.domain.Member; import com.funeat.review.domain.Review; -import com.funeat.review.dto.TestSortingReviewDto; +import com.funeat.review.dto.SortingReviewDtoWithoutTag; import java.util.List; import org.springframework.data.jpa.domain.Specification; public interface ReviewCustomRepository { - List getSortingReview(final Member loginMember, - final Specification specification, - final String sortField); + List getSortingReview(final Member loginMember, + final Specification specification, + final String sortField); } diff --git a/backend/src/main/java/com/funeat/review/persistence/ReviewRepositoryImpl.java b/backend/src/main/java/com/funeat/review/persistence/ReviewRepositoryImpl.java index ad408b0d0..ae47f7127 100644 --- a/backend/src/main/java/com/funeat/review/persistence/ReviewRepositoryImpl.java +++ b/backend/src/main/java/com/funeat/review/persistence/ReviewRepositoryImpl.java @@ -3,7 +3,7 @@ import com.funeat.member.domain.Member; import com.funeat.member.domain.favorite.ReviewFavorite; import com.funeat.review.domain.Review; -import com.funeat.review.dto.TestSortingReviewDto; +import com.funeat.review.dto.SortingReviewDtoWithoutTag; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; @@ -26,11 +26,11 @@ public class ReviewRepositoryImpl implements ReviewCustomRepository { private EntityManager em; @Override - public List getSortingReview(final Member loginMember, - final Specification specification, - final String sortOption) { + public List getSortingReview(final Member loginMember, + final Specification specification, + final String sortOption) { final CriteriaBuilder cb = em.getCriteriaBuilder(); - final CriteriaQuery cq = cb.createQuery(TestSortingReviewDto.class); + final CriteriaQuery cq = cb.createQuery(SortingReviewDtoWithoutTag.class); final Root root = cq.from(Review.class); // sortField, sortOrder @@ -52,19 +52,19 @@ public List getSortingReview(final Member loginMember, .orderBy(getOrderBy(root, cb, sortField, sortOrder)); // limit - final TypedQuery query = em.createQuery(cq); + final TypedQuery query = em.createQuery(cq); query.setMaxResults(11); // result return query.getResultList(); } - private CompoundSelection getConstruct(final Root root, - final CriteriaBuilder cb, - final Join joinMember, - final Join leftJoinReviewFavorite) { + private CompoundSelection getConstruct(final Root root, + final CriteriaBuilder cb, + final Join joinMember, + final Join leftJoinReviewFavorite) { - return cb.construct(TestSortingReviewDto.class, + return cb.construct(SortingReviewDtoWithoutTag.class, root.get("id"), joinMember.get("nickname"), joinMember.get("profileImage"), From 28f2be99a46d4739459ca60db9e22a05f62ee23d Mon Sep 17 00:00:00 2001 From: 70825 Date: Mon, 16 Oct 2023 14:52:38 +0900 Subject: [PATCH 28/35] =?UTF-8?q?fix:=20=EC=B6=A9=EB=8F=8C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/funeat/review/application/ReviewService.java | 2 +- .../java/com/funeat/review/application/ReviewServiceTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/funeat/review/application/ReviewService.java b/backend/src/main/java/com/funeat/review/application/ReviewService.java index 5f5f6bb80..fdfe83a1e 100644 --- a/backend/src/main/java/com/funeat/review/application/ReviewService.java +++ b/backend/src/main/java/com/funeat/review/application/ReviewService.java @@ -138,7 +138,7 @@ public void updateProductImage(final Long productId) { final Product product = productRepository.findById(productId) .orElseThrow(() -> new ProductNotFoundException(PRODUCT_NOT_FOUND, productId)); - final PageRequest pageRequest = PageRequest.of(TOP, ONE); + final PageRequest pageRequest = PageRequest.of(START, ONE); final List topFavoriteReview = reviewRepository.findPopularReviewWithImage(productId, pageRequest); if (!topFavoriteReview.isEmpty()) { diff --git a/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java b/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java index fecfa3d1e..7b41d7efe 100644 --- a/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java +++ b/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java @@ -425,7 +425,7 @@ class sortingReviews_성공_테스트 { } @Test - void 최신순으로_정렬을_할_수_있다() { + void 최신순으로_정렬을_할_수_있다() throws InterruptedException { // given final var member = 멤버_멤버1_생성(); final var memberId = 단일_멤버_저장(member); From 6d7bb1efee2370d8dec7c352733c9d638f0b6816 Mon Sep 17 00:00:00 2001 From: 70825 Date: Mon, 16 Oct 2023 14:53:44 +0900 Subject: [PATCH 29/35] =?UTF-8?q?test:=20Thread.sleep()=EC=9D=84=201?= =?UTF-8?q?=EC=B4=88=EA=B0=80=20=EC=95=84=EB=8B=8C=200.1=EC=B4=88=EB=A1=9C?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/funeat/recipe/application/RecipeServiceTest.java | 4 ++-- .../com/funeat/recipe/persistence/RecipeRepositoryTest.java | 4 ++-- .../java/com/funeat/review/application/ReviewServiceTest.java | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/src/test/java/com/funeat/recipe/application/RecipeServiceTest.java b/backend/src/test/java/com/funeat/recipe/application/RecipeServiceTest.java index 3fecf28bb..c0f68e789 100644 --- a/backend/src/test/java/com/funeat/recipe/application/RecipeServiceTest.java +++ b/backend/src/test/java/com/funeat/recipe/application/RecipeServiceTest.java @@ -337,9 +337,9 @@ class getSortingRecipes_성공_테스트 { 복수_상품_저장(product1, product2, product3); final var recipe1_1 = 레시피_생성(member1, 1L); - Thread.sleep(1000); + Thread.sleep(100); final var recipe1_2 = 레시피_생성(member1, 3L); - Thread.sleep(1000); + Thread.sleep(100); final var recipe1_3 = 레시피_생성(member1, 2L); 복수_꿀조합_저장(recipe1_1, recipe1_2, recipe1_3); diff --git a/backend/src/test/java/com/funeat/recipe/persistence/RecipeRepositoryTest.java b/backend/src/test/java/com/funeat/recipe/persistence/RecipeRepositoryTest.java index f52eae169..494f7b215 100644 --- a/backend/src/test/java/com/funeat/recipe/persistence/RecipeRepositoryTest.java +++ b/backend/src/test/java/com/funeat/recipe/persistence/RecipeRepositoryTest.java @@ -135,9 +135,9 @@ class findAllRecipes_성공_테스트 { 복수_상품_저장(product1, product2, product3); final var recipe1_1 = 레시피_생성(member1, 1L); - Thread.sleep(1000); + Thread.sleep(100); final var recipe1_2 = 레시피_생성(member1, 3L); - Thread.sleep(1000); + Thread.sleep(100); final var recipe1_3 = 레시피_생성(member1, 2L); 복수_꿀조합_저장(recipe1_1, recipe1_2, recipe1_3); diff --git a/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java b/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java index 7b41d7efe..472b1c0c6 100644 --- a/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java +++ b/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java @@ -437,9 +437,9 @@ class sortingReviews_성공_테스트 { final var productId = 단일_상품_저장(product); final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member, product, 351L); - Thread.sleep(1000); + Thread.sleep(100); final var review2 = 리뷰_이미지test4_평점4점_재구매O_생성(member, product, 24L); - Thread.sleep(1000); + Thread.sleep(100); final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 130L); 복수_리뷰_저장(review1, review2, review3); From 4a6af0531961f547483d296577960d05541496df Mon Sep 17 00:00:00 2001 From: 70825 Date: Mon, 16 Oct 2023 15:05:32 +0900 Subject: [PATCH 30/35] =?UTF-8?q?refactor:=20Wrapper=20->=20Primitive=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/SortingReviewService.java | 3 +- .../funeat/review/dto/SortingReviewDto.java | 12 ++--- .../dto/SortingReviewDtoWithoutTag.java | 24 ++++++--- .../review/application/ReviewServiceTest.java | 53 ------------------- 4 files changed, 24 insertions(+), 68 deletions(-) diff --git a/backend/src/main/java/com/funeat/review/application/SortingReviewService.java b/backend/src/main/java/com/funeat/review/application/SortingReviewService.java index 89c81856a..83cf8a8c5 100644 --- a/backend/src/main/java/com/funeat/review/application/SortingReviewService.java +++ b/backend/src/main/java/com/funeat/review/application/SortingReviewService.java @@ -40,9 +40,8 @@ public List getSortingReviews(final Member member, final Produ final Specification specification = getSortingSpecification(product, sortOption, lastReviewId); final List sortingReviewDtoWithoutTags = reviewRepository.getSortingReview(member, specification, sortOption); - final List sortingReviewDtos = addTagsToSortingReviews(sortingReviewDtoWithoutTags); - return sortingReviewDtos; + return addTagsToSortingReviews(sortingReviewDtoWithoutTags); } private List addTagsToSortingReviews( diff --git a/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java b/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java index 4a020166e..8b86c5a9f 100644 --- a/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java +++ b/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java @@ -22,15 +22,15 @@ public class SortingReviewDto { private final Long rating; private final List tags; private final String content; - private final Boolean rebuy; + private final boolean rebuy; private final Long favoriteCount; - private final Boolean favorite; + private final boolean favorite; private final LocalDateTime createdAt; @JsonCreator public SortingReviewDto(final Long id, final String userName, final String profileImage, final String image, final Long rating, final List tags, - final String content, final Boolean rebuy, final Long favoriteCount, final Boolean favorite, + final String content, final boolean rebuy, final Long favoriteCount, final boolean favorite, final LocalDateTime createdAt) { this.id = id; this.userName = userName; @@ -49,7 +49,7 @@ public static SortingReviewDto toDto(final SortingReviewDtoWithoutTag sortingRev final List tagDtos = tags.stream() .map(TagDto::toDto) .collect(Collectors.toList()); - final Boolean isFavorite = checkingFavorite(sortingReviewDto.getFavorite()); + final Boolean isFavorite = checkingFavorite(sortingReviewDto.isFavorite()); return new SortingReviewDto( sortingReviewDto.getId(), @@ -59,7 +59,7 @@ public static SortingReviewDto toDto(final SortingReviewDtoWithoutTag sortingRev sortingReviewDto.getRating(), tagDtos, sortingReviewDto.getContent(), - sortingReviewDto.getRebuy(), + sortingReviewDto.isRebuy(), sortingReviewDto.getFavoriteCount(), isFavorite, sortingReviewDto.getCreatedAt()); @@ -100,7 +100,7 @@ public String getContent() { return content; } - public Boolean isRebuy() { + public boolean isRebuy() { return rebuy; } diff --git a/backend/src/main/java/com/funeat/review/dto/SortingReviewDtoWithoutTag.java b/backend/src/main/java/com/funeat/review/dto/SortingReviewDtoWithoutTag.java index a98fdb947..80b3ec171 100644 --- a/backend/src/main/java/com/funeat/review/dto/SortingReviewDtoWithoutTag.java +++ b/backend/src/main/java/com/funeat/review/dto/SortingReviewDtoWithoutTag.java @@ -1,25 +1,28 @@ package com.funeat.review.dto; import java.time.LocalDateTime; +import java.util.Objects; public class SortingReviewDtoWithoutTag { - private Long id; + private final Long id; private final String userName; private final String profileImage; private final String image; private final Long rating; private final String content; - private final Boolean rebuy; + private final boolean rebuy; private final Long favoriteCount; - private final Boolean favorite; + private final boolean favorite; private final LocalDateTime createdAt; public SortingReviewDtoWithoutTag(final Long id, final String userName, final String profileImage, final String image, final Long rating, - final String content, final Boolean rebuy, final Long favoriteCount, + final String content, final boolean rebuy, final Long favoriteCount, final Boolean favorite, final LocalDateTime createdAt) { + final Boolean isFavorite = checkingFavorite(favorite); + this.id = id; this.userName = userName; this.profileImage = profileImage; @@ -28,10 +31,17 @@ public SortingReviewDtoWithoutTag(final Long id, final String userName, final St this.content = content; this.rebuy = rebuy; this.favoriteCount = favoriteCount; - this.favorite = favorite; + this.favorite = isFavorite; this.createdAt = createdAt; } + private static Boolean checkingFavorite(final Boolean favorite) { + if (Objects.isNull(favorite)) { + return Boolean.FALSE; + } + return Boolean.TRUE; + } + public Long getId() { return id; } @@ -56,7 +66,7 @@ public String getContent() { return content; } - public Boolean getRebuy() { + public boolean isRebuy() { return rebuy; } @@ -64,7 +74,7 @@ public Long getFavoriteCount() { return favoriteCount; } - public Boolean getFavorite() { + public boolean isFavorite() { return favorite; } diff --git a/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java b/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java index 472b1c0c6..0f54dd124 100644 --- a/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java +++ b/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java @@ -343,59 +343,6 @@ class likeReview_실패_테스트 { @Nested class sortingReviews_성공_테스트 { - @Test - void TEST_좋아요_기준으로_내림차순_정렬을_할_수_있다() { - // given - final var member = 멤버_멤버1_생성(); - final var memberId = 단일_멤버_저장(member); - - final var category = 카테고리_즉석조리_생성(); - 단일_카테고리_저장(category); - final var product = 상품_삼각김밥_가격1000원_평점3점_생성(category); - final var productId = 단일_상품_저장(product); - - final var review1 = 리뷰_이미지test3_평점3점_재구매O_생성(member, product, 1L); - final var review2 = 리뷰_이미지test4_평점4점_재구매O_생성(member, product, 2L); - final var review3 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 3L); - final var review4 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 4L); - final var review5 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 5L); - final var review6 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 6L); - final var review7 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 7L); - final var review8 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 8L); - final var review9 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 9L); - final var review10 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 10L); - final var review11 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 11L); - final var review12 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 12L); - final var review13 = 리뷰_이미지test3_평점3점_재구매X_생성(member, product, 13L); - 복수_리뷰_저장(review1, review2, review3, review4, review5, review6, review7, review8, review9, review10, review11, review12, review13); - - reviewService.likeReview(1L, memberId, 리뷰좋아요요청_생성(true)); - reviewService.likeReview(2L, memberId, 리뷰좋아요요청_생성(true)); - reviewService.likeReview(3L, memberId, 리뷰좋아요요청_생성(true)); - reviewService.likeReview(4L, memberId, 리뷰좋아요요청_생성(true)); - reviewService.likeReview(5L, memberId, 리뷰좋아요요청_생성(true)); - reviewService.likeReview(6L, memberId, 리뷰좋아요요청_생성(true)); - reviewService.likeReview(7L, memberId, 리뷰좋아요요청_생성(true)); - reviewService.likeReview(8L, memberId, 리뷰좋아요요청_생성(true)); - reviewService.likeReview(9L, memberId, 리뷰좋아요요청_생성(true)); - reviewService.likeReview(10L, memberId, 리뷰좋아요요청_생성(true)); - reviewService.likeReview(11L, memberId, 리뷰좋아요요청_생성(true)); - reviewService.likeReview(12L, memberId, 리뷰좋아요요청_생성(true)); - reviewService.likeReview(13L, memberId, 리뷰좋아요요청_생성(true)); - - - final var request = 리뷰정렬요청_좋아요수_내림차순_생성(4L); - - final var expected = List.of(review3.getId(), review2.getId(), review1.getId()); - - // when - final var actual = reviewService.sortingReviews(productId, memberId, request).getReviews(); - - // then - assertThat(actual).extracting(SortingReviewDto::getId) - .containsExactlyElementsOf(expected); - } - @Test void 좋아요_기준으로_내림차순_정렬을_할_수_있다() { // given From 69fd2e296979ec6c51854d33c0ae7c260c517292 Mon Sep 17 00:00:00 2001 From: 70825 Date: Mon, 16 Oct 2023 15:12:13 +0900 Subject: [PATCH 31/35] =?UTF-8?q?refactor:=20is@@@=20->=20get@@@=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/funeat/review/dto/SortingReviewDto.java | 16 ++++------------ .../review/dto/SortingReviewDtoWithoutTag.java | 4 ++-- .../review/application/ReviewServiceTest.java | 6 ------ 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java b/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java index 8b86c5a9f..1231d0058 100644 --- a/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java +++ b/backend/src/main/java/com/funeat/review/dto/SortingReviewDto.java @@ -49,7 +49,6 @@ public static SortingReviewDto toDto(final SortingReviewDtoWithoutTag sortingRev final List tagDtos = tags.stream() .map(TagDto::toDto) .collect(Collectors.toList()); - final Boolean isFavorite = checkingFavorite(sortingReviewDto.isFavorite()); return new SortingReviewDto( sortingReviewDto.getId(), @@ -59,19 +58,12 @@ public static SortingReviewDto toDto(final SortingReviewDtoWithoutTag sortingRev sortingReviewDto.getRating(), tagDtos, sortingReviewDto.getContent(), - sortingReviewDto.isRebuy(), + sortingReviewDto.getRebuy(), sortingReviewDto.getFavoriteCount(), - isFavorite, + sortingReviewDto.getFavorite(), sortingReviewDto.getCreatedAt()); } - private static Boolean checkingFavorite(final Boolean favorite) { - if (Objects.isNull(favorite)) { - return Boolean.FALSE; - } - return Boolean.TRUE; - } - public Long getId() { return id; } @@ -100,7 +92,7 @@ public String getContent() { return content; } - public boolean isRebuy() { + public boolean getRebuy() { return rebuy; } @@ -108,7 +100,7 @@ public Long getFavoriteCount() { return favoriteCount; } - public Boolean isFavorite() { + public boolean getFavorite() { return favorite; } diff --git a/backend/src/main/java/com/funeat/review/dto/SortingReviewDtoWithoutTag.java b/backend/src/main/java/com/funeat/review/dto/SortingReviewDtoWithoutTag.java index 80b3ec171..287750e7f 100644 --- a/backend/src/main/java/com/funeat/review/dto/SortingReviewDtoWithoutTag.java +++ b/backend/src/main/java/com/funeat/review/dto/SortingReviewDtoWithoutTag.java @@ -66,7 +66,7 @@ public String getContent() { return content; } - public boolean isRebuy() { + public boolean getRebuy() { return rebuy; } @@ -74,7 +74,7 @@ public Long getFavoriteCount() { return favoriteCount; } - public boolean isFavorite() { + public boolean getFavorite() { return favorite; } diff --git a/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java b/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java index 0f54dd124..2e7c82b43 100644 --- a/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java +++ b/backend/src/test/java/com/funeat/review/application/ReviewServiceTest.java @@ -4,13 +4,8 @@ import static com.funeat.fixture.ImageFixture.이미지_생성; import static com.funeat.fixture.MemberFixture.멤버_멤버1_생성; import static com.funeat.fixture.MemberFixture.멤버_멤버2_생성; -import static com.funeat.fixture.MemberFixture.멤버_멤버3_생성; -import static com.funeat.fixture.PageFixture.좋아요수_내림차순; import static com.funeat.fixture.PageFixture.최신순; -import static com.funeat.fixture.PageFixture.페이지요청_기본_생성; import static com.funeat.fixture.PageFixture.페이지요청_생성; -import static com.funeat.fixture.PageFixture.평점_내림차순; -import static com.funeat.fixture.PageFixture.평점_오름차순; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격1000원_평점2점_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격1000원_평점3점_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격1000원_평점5점_생성; @@ -24,7 +19,6 @@ import static com.funeat.fixture.ReviewFixture.리뷰_이미지test3_평점3점_재구매X_생성; import static com.funeat.fixture.ReviewFixture.리뷰_이미지test4_평점4점_재구매O_생성; import static com.funeat.fixture.ReviewFixture.리뷰_이미지없음_평점1점_재구매O_생성; -import static com.funeat.fixture.ReviewFixture.리뷰정렬요청_존재하지않는정렬_생성; import static com.funeat.fixture.ReviewFixture.리뷰정렬요청_좋아요수_내림차순_생성; import static com.funeat.fixture.ReviewFixture.리뷰정렬요청_최신순_생성; import static com.funeat.fixture.ReviewFixture.리뷰정렬요청_평점_내림차순_생성; From 1c89357ce92b5af9b7d36c082932c381c360495c Mon Sep 17 00:00:00 2001 From: 70825 Date: Mon, 16 Oct 2023 16:06:02 +0900 Subject: [PATCH 32/35] =?UTF-8?q?refactor:=20=EB=8B=A4=EC=9D=8C=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=EA=B0=80=20=EC=A1=B4=EC=9E=AC?= =?UTF-8?q?=ED=95=98=EB=8A=94=EC=A7=80=EB=8A=94=20hasNext=EB=A1=9C=20?= =?UTF-8?q?=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/funeat/review/dto/SortingReviewsResponse.java | 11 +++++------ .../com/funeat/acceptance/common/CommonSteps.java | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/backend/src/main/java/com/funeat/review/dto/SortingReviewsResponse.java b/backend/src/main/java/com/funeat/review/dto/SortingReviewsResponse.java index 4c7fe41d0..1dc082fe0 100644 --- a/backend/src/main/java/com/funeat/review/dto/SortingReviewsResponse.java +++ b/backend/src/main/java/com/funeat/review/dto/SortingReviewsResponse.java @@ -1,16 +1,15 @@ package com.funeat.review.dto; -import com.funeat.common.dto.PageDto; import java.util.List; public class SortingReviewsResponse { private final List reviews; - private final Boolean hasNextReview; + private final Boolean hasNext; - public SortingReviewsResponse(final List reviews, final Boolean hasNextReview) { + public SortingReviewsResponse(final List reviews, final Boolean hasNext) { this.reviews = reviews; - this.hasNextReview = hasNextReview; + this.hasNext = hasNext; } public static SortingReviewsResponse toResponse(final List reviews, final Boolean hasNextReview) { @@ -21,7 +20,7 @@ public List getReviews() { return reviews; } - public Boolean getHasNextReview() { - return hasNextReview; + public Boolean getHasNext() { + return hasNext; } } diff --git a/backend/src/test/java/com/funeat/acceptance/common/CommonSteps.java b/backend/src/test/java/com/funeat/acceptance/common/CommonSteps.java index 932115005..af33aa14f 100644 --- a/backend/src/test/java/com/funeat/acceptance/common/CommonSteps.java +++ b/backend/src/test/java/com/funeat/acceptance/common/CommonSteps.java @@ -76,7 +76,7 @@ public class CommonSteps { } public static void 다음_데이터가_있는지_검증한다(final ExtractableResponse response, final boolean expected) { - final var actual = response.jsonPath().getBoolean("hasNextReview"); + final var actual = response.jsonPath().getBoolean("hasNext"); assertThat(actual).isEqualTo(expected); } From db9b5d3fa4cd3f278e6d741817ff8374d2207190 Mon Sep 17 00:00:00 2001 From: 70825 Date: Mon, 16 Oct 2023 16:12:47 +0900 Subject: [PATCH 33/35] =?UTF-8?q?refactor:=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C,=20=EC=83=81=EC=88=98=20=EC=9D=B4=EB=A6=84=EC=9D=84?= =?UTF-8?q?=20=EB=AA=A9=EC=A0=81=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/application/ReviewService.java | 51 +++++++++++--- .../application/SortingReviewService.java | 66 ------------------- 2 files changed, 42 insertions(+), 75 deletions(-) delete mode 100644 backend/src/main/java/com/funeat/review/application/SortingReviewService.java diff --git a/backend/src/main/java/com/funeat/review/application/ReviewService.java b/backend/src/main/java/com/funeat/review/application/ReviewService.java index fdfe83a1e..320bc13eb 100644 --- a/backend/src/main/java/com/funeat/review/application/ReviewService.java +++ b/backend/src/main/java/com/funeat/review/application/ReviewService.java @@ -27,12 +27,14 @@ import com.funeat.review.dto.ReviewCreateRequest; import com.funeat.review.dto.ReviewFavoriteRequest; import com.funeat.review.dto.SortingReviewDto; +import com.funeat.review.dto.SortingReviewDtoWithoutTag; import com.funeat.review.dto.SortingReviewRequest; import com.funeat.review.dto.SortingReviewsResponse; import com.funeat.review.exception.ReviewException.NotAuthorOfReviewException; import com.funeat.review.exception.ReviewException.ReviewNotFoundException; import com.funeat.review.persistence.ReviewRepository; import com.funeat.review.persistence.ReviewTagRepository; +import com.funeat.review.specification.SortingReviewSpecification; import com.funeat.tag.domain.Tag; import com.funeat.tag.persistence.TagRepository; import java.util.List; @@ -43,6 +45,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -51,7 +54,8 @@ @Transactional(readOnly = true) public class ReviewService { - private static final int START = 0; + private static final int FIRST_PAGE = 0; + private static final int START_INDEX = 0; private static final int ONE = 1; private static final String EMPTY_URL = ""; private static final int REVIEW_PAGE_SIZE = 10; @@ -63,15 +67,13 @@ public class ReviewService { private final ProductRepository productRepository; private final ReviewFavoriteRepository reviewFavoriteRepository; private final ImageUploader imageUploader; - private final SortingReviewService sortingReviewService; private final ApplicationEventPublisher eventPublisher; public ReviewService(final ReviewRepository reviewRepository, final TagRepository tagRepository, final ReviewTagRepository reviewTagRepository, final MemberRepository memberRepository, final ProductRepository productRepository, final ReviewFavoriteRepository reviewFavoriteRepository, - final ImageUploader imageUploader, final SortingReviewService sortingReviewService, - final ApplicationEventPublisher eventPublisher) { + final ImageUploader imageUploader, final ApplicationEventPublisher eventPublisher) { this.reviewRepository = reviewRepository; this.tagRepository = tagRepository; this.reviewTagRepository = reviewTagRepository; @@ -79,7 +81,6 @@ public ReviewService(final ReviewRepository reviewRepository, final TagRepositor this.productRepository = productRepository; this.reviewFavoriteRepository = reviewFavoriteRepository; this.imageUploader = imageUploader; - this.sortingReviewService = sortingReviewService; this.eventPublisher = eventPublisher; } @@ -138,11 +139,11 @@ public void updateProductImage(final Long productId) { final Product product = productRepository.findById(productId) .orElseThrow(() -> new ProductNotFoundException(PRODUCT_NOT_FOUND, productId)); - final PageRequest pageRequest = PageRequest.of(START, ONE); + final PageRequest pageRequest = PageRequest.of(FIRST_PAGE, ONE); final List topFavoriteReview = reviewRepository.findPopularReviewWithImage(productId, pageRequest); if (!topFavoriteReview.isEmpty()) { - final String topFavoriteReviewImage = topFavoriteReview.get(START).getImage(); + final String topFavoriteReviewImage = topFavoriteReview.get(START_INDEX).getImage(); product.updateImage(topFavoriteReviewImage); } } @@ -154,15 +155,47 @@ public SortingReviewsResponse sortingReviews(final Long productId, final Long me final Product findProduct = productRepository.findById(productId) .orElseThrow(() -> new ProductNotFoundException(PRODUCT_NOT_FOUND, productId)); - final List sortingReviews = sortingReviewService.getSortingReviews(findMember, findProduct, request); + final List sortingReviews = getSortingReviews(findMember, findProduct, request); final int resultSize = getResultSize(sortingReviews); - final List resizeSortingReviews = sortingReviews.subList(START, resultSize); + final List resizeSortingReviews = sortingReviews.subList(START_INDEX, resultSize); final Boolean hasNext = hasNextPage(sortingReviews); return SortingReviewsResponse.toResponse(resizeSortingReviews, hasNext); } + private List getSortingReviews(final Member member, final Product product, + final SortingReviewRequest request) { + final Long lastReviewId = request.getLastReviewId(); + final String sortOption = request.getSort(); + + final Specification specification = getSortingSpecification(product, sortOption, lastReviewId); + final List sortingReviewDtoWithoutTags = reviewRepository.getSortingReview(member, + specification, sortOption); + + return addTagsToSortingReviews(sortingReviewDtoWithoutTags); + } + + private List addTagsToSortingReviews( + final List sortingReviewDtoWithoutTags) { + return sortingReviewDtoWithoutTags.stream() + .map(reviewDto -> SortingReviewDto.toDto(reviewDto, + tagRepository.findTagsByReviewId(reviewDto.getId()))) + .collect(Collectors.toList()); + } + + private Specification getSortingSpecification(final Product product, final String sortOption, + final Long lastReviewId) { + if (lastReviewId == FIRST_PAGE) { + return SortingReviewSpecification.sortingFirstPageBy(product); + } + + final Review lastReview = reviewRepository.findById(lastReviewId) + .orElseThrow(() -> new ReviewNotFoundException(REVIEW_NOT_FOUND, lastReviewId)); + + return SortingReviewSpecification.sortingBy(product, sortOption, lastReview); + } + private int getResultSize(final List sortingReviews) { if (sortingReviews.size() <= REVIEW_PAGE_SIZE) { return sortingReviews.size(); diff --git a/backend/src/main/java/com/funeat/review/application/SortingReviewService.java b/backend/src/main/java/com/funeat/review/application/SortingReviewService.java deleted file mode 100644 index 83cf8a8c5..000000000 --- a/backend/src/main/java/com/funeat/review/application/SortingReviewService.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.funeat.review.application; - -import static com.funeat.review.exception.ReviewErrorCode.REVIEW_NOT_FOUND; - -import com.funeat.member.domain.Member; -import com.funeat.product.domain.Product; -import com.funeat.review.domain.Review; -import com.funeat.review.dto.SortingReviewDto; -import com.funeat.review.dto.SortingReviewDtoWithoutTag; -import com.funeat.review.dto.SortingReviewRequest; -import com.funeat.review.exception.ReviewException.ReviewNotFoundException; -import com.funeat.review.persistence.ReviewRepository; -import com.funeat.review.specification.SortingReviewSpecification; -import com.funeat.tag.persistence.TagRepository; -import java.util.List; -import java.util.stream.Collectors; -import org.springframework.data.jpa.domain.Specification; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@Transactional(readOnly = true) -public class SortingReviewService { - - private static final int FIRST_PAGE = 0; - - private final ReviewRepository reviewRepository; - private final TagRepository tagRepository; - - public SortingReviewService(final ReviewRepository reviewRepository, final TagRepository tagRepository) { - this.reviewRepository = reviewRepository; - this.tagRepository = tagRepository; - } - - public List getSortingReviews(final Member member, final Product product, - final SortingReviewRequest request) { - final Long lastReviewId = request.getLastReviewId(); - final String sortOption = request.getSort(); - - final Specification specification = getSortingSpecification(product, sortOption, lastReviewId); - final List sortingReviewDtoWithoutTags = reviewRepository.getSortingReview(member, - specification, sortOption); - - return addTagsToSortingReviews(sortingReviewDtoWithoutTags); - } - - private List addTagsToSortingReviews( - final List sortingReviewDtoWithoutTags) { - return sortingReviewDtoWithoutTags.stream() - .map(reviewDto -> SortingReviewDto.toDto(reviewDto, - tagRepository.findTagsByReviewId(reviewDto.getId()))) - .collect(Collectors.toList()); - } - - private Specification getSortingSpecification(final Product product, final String sortOption, - final Long lastReviewId) { - if (lastReviewId == FIRST_PAGE) { - return SortingReviewSpecification.sortingFirstPageBy(product); - } - - final Review lastReview = reviewRepository.findById(lastReviewId) - .orElseThrow(() -> new ReviewNotFoundException(REVIEW_NOT_FOUND, lastReviewId)); - - return SortingReviewSpecification.sortingBy(product, sortOption, lastReview); - } -} From 0546c7125bed754b06b74469e8c384a12c9e069b Mon Sep 17 00:00:00 2001 From: 70825 Date: Mon, 16 Oct 2023 16:20:54 +0900 Subject: [PATCH 34/35] =?UTF-8?q?refactor:=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/funeat/review/exception/ReviewErrorCode.java | 2 +- .../review/specification/SortingReviewSpecification.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/com/funeat/review/exception/ReviewErrorCode.java b/backend/src/main/java/com/funeat/review/exception/ReviewErrorCode.java index 8af492c75..2f5fb5c64 100644 --- a/backend/src/main/java/com/funeat/review/exception/ReviewErrorCode.java +++ b/backend/src/main/java/com/funeat/review/exception/ReviewErrorCode.java @@ -5,7 +5,7 @@ public enum ReviewErrorCode { REVIEW_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 리뷰입니다. 리뷰 id를 확인하세요.", "3001"), - REVIEW_SORTING_OPTION_NOT_FOUND(HttpStatus.BAD_REQUEST, "존재하지 않는 정렬 옵션입니다. 정렬 옵션을 확인하세요.", "3002"), + NOT_SUPPORTED_REVIEW_SORTING_CONDITION(HttpStatus.BAD_REQUEST, "존재하지 않는 정렬 옵션입니다. 정렬 옵션을 확인하세요.", "3002"), NOT_AUTHOR_OF_REVIEW(HttpStatus.BAD_REQUEST, "해당 리뷰를 작성한 회원이 아닙니다", "3003") ; diff --git a/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java b/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java index a1f8b319d..7a77e8440 100644 --- a/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java +++ b/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java @@ -1,6 +1,6 @@ package com.funeat.review.specification; -import static com.funeat.review.exception.ReviewErrorCode.REVIEW_SORTING_OPTION_NOT_FOUND; +import static com.funeat.review.exception.ReviewErrorCode.NOT_SUPPORTED_REVIEW_SORTING_CONDITION; import com.funeat.product.domain.Product; import com.funeat.review.domain.Review; @@ -94,7 +94,7 @@ private static Predicate checkEquals(final String fieldName, final Long lastReviewField = LongTypeReviewSortSpec.find(fieldName, lastReview); return criteriaBuilder.equal(reviewPath, lastReviewField); } - throw new ReviewSortingOptionNotFoundException(REVIEW_SORTING_OPTION_NOT_FOUND, fieldName); + throw new ReviewSortingOptionNotFoundException(NOT_SUPPORTED_REVIEW_SORTING_CONDITION, fieldName); } private static Specification lessOrGreaterThan(final String field, final String sort, @@ -127,7 +127,7 @@ private static Predicate checkGreaterThan(final String fieldName, final Review l final Long lastReviewField = LongTypeReviewSortSpec.find(fieldName, lastReview); return criteriaBuilder.greaterThan(reviewPath, lastReviewField); } - throw new ReviewSortingOptionNotFoundException(REVIEW_SORTING_OPTION_NOT_FOUND, fieldName); + throw new ReviewSortingOptionNotFoundException(NOT_SUPPORTED_REVIEW_SORTING_CONDITION, fieldName); } private static Specification lessThan(final String fieldName, final Review lastReview) { @@ -156,6 +156,6 @@ private static Predicate checkLessThan(final String fieldName, final Review last final Long lastReviewField = LongTypeReviewSortSpec.find(fieldName, lastReview); return criteriaBuilder.lessThan(reviewPath, lastReviewField); } - throw new ReviewSortingOptionNotFoundException(REVIEW_SORTING_OPTION_NOT_FOUND, fieldName); + throw new ReviewSortingOptionNotFoundException(NOT_SUPPORTED_REVIEW_SORTING_CONDITION, fieldName); } } From 89e3bfedd63f8ef6f74200e3551dba3a8341408e Mon Sep 17 00:00:00 2001 From: 70825 Date: Mon, 16 Oct 2023 16:23:36 +0900 Subject: [PATCH 35/35] =?UTF-8?q?refactor:=20exception=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=EB=8F=84=20=EA=B0=99=EC=9D=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/funeat/review/exception/ReviewException.java | 4 ++-- .../review/specification/SortingReviewSpecification.java | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/com/funeat/review/exception/ReviewException.java b/backend/src/main/java/com/funeat/review/exception/ReviewException.java index 1090ec36b..85fd3f666 100644 --- a/backend/src/main/java/com/funeat/review/exception/ReviewException.java +++ b/backend/src/main/java/com/funeat/review/exception/ReviewException.java @@ -16,8 +16,8 @@ public ReviewNotFoundException(final ReviewErrorCode errorCode, final Long revie } } - public static class ReviewSortingOptionNotFoundException extends ReviewException { - public ReviewSortingOptionNotFoundException(final ReviewErrorCode errorCode, final String sortFieldName) { + public static class NotSupportedReviewSortingConditionException extends ReviewException { + public NotSupportedReviewSortingConditionException(final ReviewErrorCode errorCode, final String sortFieldName) { super(errorCode.getStatus(), new ErrorCode<>(errorCode.getCode(), errorCode.getMessage(), sortFieldName)); } } diff --git a/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java b/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java index 7a77e8440..781032017 100644 --- a/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java +++ b/backend/src/main/java/com/funeat/review/specification/SortingReviewSpecification.java @@ -4,7 +4,7 @@ import com.funeat.product.domain.Product; import com.funeat.review.domain.Review; -import com.funeat.review.exception.ReviewException.ReviewSortingOptionNotFoundException; +import com.funeat.review.exception.ReviewException.NotSupportedReviewSortingConditionException; import java.time.LocalDateTime; import java.util.List; import java.util.Objects; @@ -94,7 +94,7 @@ private static Predicate checkEquals(final String fieldName, final Long lastReviewField = LongTypeReviewSortSpec.find(fieldName, lastReview); return criteriaBuilder.equal(reviewPath, lastReviewField); } - throw new ReviewSortingOptionNotFoundException(NOT_SUPPORTED_REVIEW_SORTING_CONDITION, fieldName); + throw new NotSupportedReviewSortingConditionException(NOT_SUPPORTED_REVIEW_SORTING_CONDITION, fieldName); } private static Specification lessOrGreaterThan(final String field, final String sort, @@ -127,7 +127,7 @@ private static Predicate checkGreaterThan(final String fieldName, final Review l final Long lastReviewField = LongTypeReviewSortSpec.find(fieldName, lastReview); return criteriaBuilder.greaterThan(reviewPath, lastReviewField); } - throw new ReviewSortingOptionNotFoundException(NOT_SUPPORTED_REVIEW_SORTING_CONDITION, fieldName); + throw new NotSupportedReviewSortingConditionException(NOT_SUPPORTED_REVIEW_SORTING_CONDITION, fieldName); } private static Specification lessThan(final String fieldName, final Review lastReview) { @@ -156,6 +156,6 @@ private static Predicate checkLessThan(final String fieldName, final Review last final Long lastReviewField = LongTypeReviewSortSpec.find(fieldName, lastReview); return criteriaBuilder.lessThan(reviewPath, lastReviewField); } - throw new ReviewSortingOptionNotFoundException(NOT_SUPPORTED_REVIEW_SORTING_CONDITION, fieldName); + throw new NotSupportedReviewSortingConditionException(NOT_SUPPORTED_REVIEW_SORTING_CONDITION, fieldName); } }