From ae428732639ea736e64c587c54eb1ed8cf86d46f Mon Sep 17 00:00:00 2001 From: Minseong Park <52368015+pminsung12@users.noreply.github.com> Date: Sun, 25 Aug 2024 23:30:58 +0900 Subject: [PATCH 1/4] =?UTF-8?q?[BSVR-241]=20=EB=B8=94=EB=A1=9D=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=EC=97=90=20=EC=9C=A0=EC=A0=80=EA=B0=80=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94,=20=EC=8A=A4=ED=81=AC=EB=9E=A9=EC=9D=84=20?= =?UTF-8?q?=EB=88=8C=EB=A0=80=EB=8A=94=20=EC=A7=80=20=EC=95=88=20=EB=88=8C?= =?UTF-8?q?=EB=A0=80=EB=8A=94=EC=A7=80=20=ED=99=95=EC=9D=B8=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80=20(#161)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 리뷰 도메인과 response에 isScrapped, isLiked 필드 추가 * feat: memberId로 조회해 온 리뷰들의 좋아요/스크랩 여부 업데이트하는 서비스 로직 구현 * feat: 유저별 리뷰들 좋아요, 스크랩 여부 조회해오는 레포지토리 메서드 구현 * test: 후에 작성할 ReviewScrapRepository의 existsByMemberIdAndReviewIds 오버라이딩만 구현 --- .../review/ReadReviewController.java | 2 ++ .../dto/response/BaseReviewResponse.java | 12 +++++++--- .../depromeet/spot/domain/review/Review.java | 7 ++++++ .../like/ReviewLikeJpaRepository.java | 9 +++++++ .../like/ReviewLikeRepositoryImpl.java | 12 ++++++++++ .../scrap/ReviewScrapJpaRepository.java | 9 +++++++ .../scrap/ReviewScrapRepositoryImpl.java | 10 ++++++++ .../port/in/review/ReadReviewUsecase.java | 1 + .../port/out/review/ReviewLikeRepository.java | 5 ++++ .../out/review/ReviewScrapRepository.java | 3 +++ .../service/review/ReadReviewService.java | 24 +++++++++++++++++++ .../fake/FakeReviewScrapRepository.java | 6 +++++ 12 files changed, 97 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/depromeet/spot/application/review/ReadReviewController.java b/application/src/main/java/org/depromeet/spot/application/review/ReadReviewController.java index e3e8d18d..dc435486 100644 --- a/application/src/main/java/org/depromeet/spot/application/review/ReadReviewController.java +++ b/application/src/main/java/org/depromeet/spot/application/review/ReadReviewController.java @@ -41,6 +41,7 @@ public class ReadReviewController { @GetMapping("/stadiums/{stadiumId}/blocks/{blockCode}/reviews") @Operation(summary = "특정 야구장의 특정 블록에 대한 리뷰 목록을 조회한다.") public BlockReviewListResponse findReviewsByBlockId( + @Parameter(hidden = true) Long memberId, @PathVariable("stadiumId") @NotNull @Positive @@ -52,6 +53,7 @@ public BlockReviewListResponse findReviewsByBlockId( BlockReviewListResult result = readReviewUsecase.findReviewsByStadiumIdAndBlockCode( + memberId, stadiumId, blockCode, request.rowNumber(), diff --git a/application/src/main/java/org/depromeet/spot/application/review/dto/response/BaseReviewResponse.java b/application/src/main/java/org/depromeet/spot/application/review/dto/response/BaseReviewResponse.java index eb627771..21c816cb 100644 --- a/application/src/main/java/org/depromeet/spot/application/review/dto/response/BaseReviewResponse.java +++ b/application/src/main/java/org/depromeet/spot/application/review/dto/response/BaseReviewResponse.java @@ -29,7 +29,9 @@ public record BaseReviewResponse( List keywords, int likesCount, int scrapsCount, - ReviewType reviewType) { + ReviewType reviewType, + boolean isLiked, + boolean isScrapped) { public static BaseReviewResponse from(CreateReviewResult result) { Review review = result.review(); @@ -59,7 +61,9 @@ public static BaseReviewResponse from(CreateReviewResult result) { .toList(), review.getLikesCount(), review.getScrapsCount(), - review.getReviewType()); + review.getReviewType(), + review.isLiked(), + review.isScrapped()); } public static BaseReviewResponse from(Review review) { @@ -89,7 +93,9 @@ public static BaseReviewResponse from(Review review) { .collect(Collectors.toList()), review.getLikesCount(), review.getScrapsCount(), - review.getReviewType()); + review.getReviewType(), + review.isLiked(), + review.isScrapped()); } public record StadiumResponse(Long id, String name) { diff --git a/domain/src/main/java/org/depromeet/spot/domain/review/Review.java b/domain/src/main/java/org/depromeet/spot/domain/review/Review.java index 4a20fb07..72b09683 100644 --- a/domain/src/main/java/org/depromeet/spot/domain/review/Review.java +++ b/domain/src/main/java/org/depromeet/spot/domain/review/Review.java @@ -52,6 +52,8 @@ public enum SortCriteria { public static final int DEFAULT_LIKE_COUNT = 0; public static final int DEFAULT_SCRAPS_COUNT = 0; + public boolean isLiked; + public boolean isScrapped; @Builder public Review( @@ -142,6 +144,11 @@ public void setDeletedAt(LocalDateTime now) { this.deletedAt = now; } + public void setLikedAndScrapped(boolean liked, boolean scraped) { + this.isLiked = liked; + this.isScrapped = scraped; + } + public Review withLimitedImages(int limit) { List limitedImages = this.images.stream().limit(limit).collect(Collectors.toList()); diff --git a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/like/ReviewLikeJpaRepository.java b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/like/ReviewLikeJpaRepository.java index 64df3697..8b395305 100644 --- a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/like/ReviewLikeJpaRepository.java +++ b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/like/ReviewLikeJpaRepository.java @@ -1,8 +1,12 @@ package org.depromeet.spot.infrastructure.jpa.review.repository.like; +import java.util.List; + import org.depromeet.spot.infrastructure.jpa.review.entity.like.ReviewLikeEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface ReviewLikeJpaRepository extends JpaRepository { @@ -12,4 +16,9 @@ public interface ReviewLikeJpaRepository extends JpaRepository findReviewIdsByMemberIdAndReviewIdIn( + @Param("memberId") Long memberId, @Param("reviewIds") List reviewIds); } diff --git a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/like/ReviewLikeRepositoryImpl.java b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/like/ReviewLikeRepositoryImpl.java index f0eb3b91..a0df4858 100644 --- a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/like/ReviewLikeRepositoryImpl.java +++ b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/like/ReviewLikeRepositoryImpl.java @@ -1,5 +1,9 @@ package org.depromeet.spot.infrastructure.jpa.review.repository.like; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + import org.depromeet.spot.domain.review.like.ReviewLike; import org.depromeet.spot.infrastructure.jpa.review.entity.like.ReviewLikeEntity; import org.depromeet.spot.usecase.port.out.review.ReviewLikeRepository; @@ -33,4 +37,12 @@ public void save(ReviewLike like) { ReviewLikeEntity entity = ReviewLikeEntity.from(like); reviewLikeJpaRepository.save(entity); } + + @Override + public Map existsByMemberIdAndReviewIds(Long memberId, List reviewIds) { + List likedReviewIds = + reviewLikeJpaRepository.findReviewIdsByMemberIdAndReviewIdIn(memberId, reviewIds); + return reviewIds.stream() + .collect(Collectors.toMap(reviewId -> reviewId, likedReviewIds::contains)); + } } diff --git a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/scrap/ReviewScrapJpaRepository.java b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/scrap/ReviewScrapJpaRepository.java index 76f66944..db7838c9 100644 --- a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/scrap/ReviewScrapJpaRepository.java +++ b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/scrap/ReviewScrapJpaRepository.java @@ -1,8 +1,12 @@ package org.depromeet.spot.infrastructure.jpa.review.repository.scrap; +import java.util.List; + import org.depromeet.spot.infrastructure.jpa.review.entity.scrap.ReviewScrapEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface ReviewScrapJpaRepository extends JpaRepository { long countByReviewId(long reviewId); @@ -11,4 +15,9 @@ public interface ReviewScrapJpaRepository extends JpaRepository findReviewIdsByMemberIdAndReviewIdIn( + @Param("memberId") Long memberId, @Param("reviewIds") List reviewIds); } diff --git a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/scrap/ReviewScrapRepositoryImpl.java b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/scrap/ReviewScrapRepositoryImpl.java index e889e708..fa1bf6ae 100644 --- a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/scrap/ReviewScrapRepositoryImpl.java +++ b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/scrap/ReviewScrapRepositoryImpl.java @@ -1,6 +1,8 @@ package org.depromeet.spot.infrastructure.jpa.review.repository.scrap; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import org.depromeet.spot.domain.review.Review; import org.depromeet.spot.domain.review.Review.SortCriteria; @@ -66,4 +68,12 @@ public void save(ReviewScrap scrap) { ReviewScrapEntity entity = ReviewScrapEntity.from(scrap); reviewScrapJpaRepository.save(entity); } + + @Override + public Map existsByMemberIdAndReviewIds(Long memberId, List reviewIds) { + List scrappedReviewIds = + reviewScrapJpaRepository.findReviewIdsByMemberIdAndReviewIdIn(memberId, reviewIds); + return reviewIds.stream() + .collect(Collectors.toMap(reviewId -> reviewId, scrappedReviewIds::contains)); + } } diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/port/in/review/ReadReviewUsecase.java b/usecase/src/main/java/org/depromeet/spot/usecase/port/in/review/ReadReviewUsecase.java index 254868fd..835b511a 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/port/in/review/ReadReviewUsecase.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/port/in/review/ReadReviewUsecase.java @@ -12,6 +12,7 @@ public interface ReadReviewUsecase { BlockReviewListResult findReviewsByStadiumIdAndBlockCode( + Long memberId, Long stadiumId, String blockCode, Integer rowNumber, diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/port/out/review/ReviewLikeRepository.java b/usecase/src/main/java/org/depromeet/spot/usecase/port/out/review/ReviewLikeRepository.java index 0dee4d21..41cf1dac 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/port/out/review/ReviewLikeRepository.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/port/out/review/ReviewLikeRepository.java @@ -1,5 +1,8 @@ package org.depromeet.spot.usecase.port.out.review; +import java.util.List; +import java.util.Map; + import org.depromeet.spot.domain.review.like.ReviewLike; public interface ReviewLikeRepository { @@ -11,4 +14,6 @@ public interface ReviewLikeRepository { void deleteBy(long memberId, long reviewId); void save(ReviewLike like); + + Map existsByMemberIdAndReviewIds(Long memberId, List reviewIds); } diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/port/out/review/ReviewScrapRepository.java b/usecase/src/main/java/org/depromeet/spot/usecase/port/out/review/ReviewScrapRepository.java index 6adfa576..5cd5b7cd 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/port/out/review/ReviewScrapRepository.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/port/out/review/ReviewScrapRepository.java @@ -1,6 +1,7 @@ package org.depromeet.spot.usecase.port.out.review; import java.util.List; +import java.util.Map; import org.depromeet.spot.domain.review.Review; import org.depromeet.spot.domain.review.Review.SortCriteria; @@ -32,4 +33,6 @@ Long getTotalCount( void deleteBy(long memberId, long reviewId); void save(ReviewScrap like); + + Map existsByMemberIdAndReviewIds(Long memberId, List reviewIds); } diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/review/ReadReviewService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/ReadReviewService.java index 88e9ac98..690df1e8 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/service/review/ReadReviewService.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/ReadReviewService.java @@ -16,7 +16,9 @@ import org.depromeet.spot.usecase.port.out.review.BlockTopKeywordRepository; import org.depromeet.spot.usecase.port.out.review.KeywordRepository; import org.depromeet.spot.usecase.port.out.review.ReviewImageRepository; +import org.depromeet.spot.usecase.port.out.review.ReviewLikeRepository; import org.depromeet.spot.usecase.port.out.review.ReviewRepository; +import org.depromeet.spot.usecase.port.out.review.ReviewScrapRepository; import org.depromeet.spot.usecase.port.out.team.BaseballTeamRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -34,12 +36,15 @@ public class ReadReviewService implements ReadReviewUsecase { private final KeywordRepository keywordRepository; private final MemberRepository memberRepository; private final BaseballTeamRepository baseballTeamRepository; + private final ReviewLikeRepository reviewLikeRepository; + private final ReviewScrapRepository reviewScrapRepository; private static final int TOP_KEYWORDS_LIMIT = 5; private static final int TOP_IMAGES_LIMIT = 5; @Override public BlockReviewListResult findReviewsByStadiumIdAndBlockCode( + Long memberId, Long stadiumId, String blockCode, Integer rowNumber, @@ -87,6 +92,9 @@ public BlockReviewListResult findReviewsByStadiumIdAndBlockCode( List reviewsWithKeywords = mapKeywordsToReviews(reviews); + // 유저의 리뷰 좋아요, 스크랩 여부 + setLikedAndScrappedStatus(reviewsWithKeywords, memberId); + long totalElements = reviewRepository.countByStadiumIdAndBlockCode( stadiumId, blockCode, rowNumber, seatNumber, year, month); @@ -289,4 +297,20 @@ public Review mapKeywordsToReview(Review review) { return mappedReview; } + + private void setLikedAndScrappedStatus(List reviews, Long memberId) { + List reviewIds = reviews.stream().map(Review::getId).collect(Collectors.toList()); + + Map likedMap = + reviewLikeRepository.existsByMemberIdAndReviewIds(memberId, reviewIds); + Map scrappedMap = + reviewScrapRepository.existsByMemberIdAndReviewIds(memberId, reviewIds); + + reviews.forEach( + review -> { + boolean isLiked = likedMap.getOrDefault(review.getId(), false); + boolean isScrapped = scrappedMap.getOrDefault(review.getId(), false); + review.setLikedAndScrapped(isLiked, isScrapped); + }); + } } diff --git a/usecase/src/test/java/org/depromeet/spot/usecase/service/fake/FakeReviewScrapRepository.java b/usecase/src/test/java/org/depromeet/spot/usecase/service/fake/FakeReviewScrapRepository.java index 6da0937f..21f29492 100644 --- a/usecase/src/test/java/org/depromeet/spot/usecase/service/fake/FakeReviewScrapRepository.java +++ b/usecase/src/test/java/org/depromeet/spot/usecase/service/fake/FakeReviewScrapRepository.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import org.depromeet.spot.domain.review.Review; @@ -90,6 +91,11 @@ public void save(ReviewScrap scrap) { scraps.add(scrap); } + @Override + public Map existsByMemberIdAndReviewIds(Long memberId, List reviewIds) { + return Map.of(); + } + public void addReview(Review review) { reviews.add(review); } From 6e3e810555dfe3b3ba72227180701ecbb8320c69 Mon Sep 17 00:00:00 2001 From: Minseong Park <52368015+pminsung12@users.noreply.github.com> Date: Mon, 26 Aug 2024 00:21:45 +0900 Subject: [PATCH 2/4] =?UTF-8?q?[BSVR-242]=20=EC=8A=A4=ED=81=AC=EB=9E=A9=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20get=EC=9A=94=EC=B2=AD=EC=97=90=20request?= =?UTF-8?q?=20body=EC=93=B4=20=EC=9D=B4=EC=8A=88=20=20+=20scrapsCount=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=95=88=EB=90=98=EB=8A=94=20=EC=9D=B4?= =?UTF-8?q?=EC=8A=88=20=ED=94=BD=EC=8A=A4=20(#162)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 스크랩 조회 시 get요청에 request body를 사용한 것을 param으로 수정 * fix: keyword 매핑부분에서 scrapsCount 빌더에 추가 * fix: keyword 매핑부분에서 scrapsCount 빌더에 추가 * feat: 스크랩 조회 시 isScrapped, isLiked 업데이트 로직 추가 * test: mocking 추가 --- .../review/scrap/ReviewScrapController.java | 6 ++-- .../service/review/ReadReviewService.java | 6 +++- .../review/processor/ReadReviewProcessor.java | 10 ++++++ .../processor/ReadReviewProcessorImpl.java | 36 +++++++++++++++++++ .../review/scrap/ReviewScrapService.java | 5 +++ .../review/ReviewScrapServiceTest.java | 5 ++- 6 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 usecase/src/main/java/org/depromeet/spot/usecase/service/review/processor/ReadReviewProcessor.java create mode 100644 usecase/src/main/java/org/depromeet/spot/usecase/service/review/processor/ReadReviewProcessorImpl.java diff --git a/application/src/main/java/org/depromeet/spot/application/review/scrap/ReviewScrapController.java b/application/src/main/java/org/depromeet/spot/application/review/scrap/ReviewScrapController.java index 72c72ae8..1d7af9ff 100644 --- a/application/src/main/java/org/depromeet/spot/application/review/scrap/ReviewScrapController.java +++ b/application/src/main/java/org/depromeet/spot/application/review/scrap/ReviewScrapController.java @@ -12,10 +12,8 @@ import org.depromeet.spot.usecase.port.in.review.scrap.ReviewScrapUsecase.MyScrapListResult; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; @@ -51,8 +49,8 @@ public boolean toggleScrap( description = "stadiumId, months, good, bad로 필터링 가능하다.") public MyScrapListResponse findMyReviews( @Parameter(hidden = true) Long memberId, - @RequestBody @Valid MyScrapRequest request, - @ModelAttribute @Valid PageRequest pageRequest) { + @Valid MyScrapRequest request, + @Valid PageRequest pageRequest) { MyScrapListResult result = reviewScrapUsecase.findMyScrappedReviews( diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/review/ReadReviewService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/ReadReviewService.java index 690df1e8..f71850c7 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/service/review/ReadReviewService.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/ReadReviewService.java @@ -20,6 +20,7 @@ import org.depromeet.spot.usecase.port.out.review.ReviewRepository; import org.depromeet.spot.usecase.port.out.review.ReviewScrapRepository; import org.depromeet.spot.usecase.port.out.team.BaseballTeamRepository; +import org.depromeet.spot.usecase.service.review.processor.ReadReviewProcessor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -38,6 +39,7 @@ public class ReadReviewService implements ReadReviewUsecase { private final BaseballTeamRepository baseballTeamRepository; private final ReviewLikeRepository reviewLikeRepository; private final ReviewScrapRepository reviewScrapRepository; + private final ReadReviewProcessor readReviewProcessor; private static final int TOP_KEYWORDS_LIMIT = 5; private static final int TOP_IMAGES_LIMIT = 5; @@ -93,7 +95,7 @@ public BlockReviewListResult findReviewsByStadiumIdAndBlockCode( List reviewsWithKeywords = mapKeywordsToReviews(reviews); // 유저의 리뷰 좋아요, 스크랩 여부 - setLikedAndScrappedStatus(reviewsWithKeywords, memberId); + readReviewProcessor.setLikedAndScrappedStatus(reviewsWithKeywords, memberId); long totalElements = reviewRepository.countByStadiumIdAndBlockCode( @@ -244,6 +246,7 @@ public Review mapKeywordsToSingleReview(Review review) { .images(review.getImages()) .keywords(mappedKeywords) .likesCount(review.getLikesCount()) + .scrapsCount(review.getScrapsCount()) .build(); mappedReview.setKeywordMap(keywordMap); @@ -289,6 +292,7 @@ public Review mapKeywordsToReview(Review review) { .images(review.getImages()) .keywords(mappedKeywords) // 리뷰 키워드 담당 .likesCount(review.getLikesCount()) + .scrapsCount(review.getScrapsCount()) .build(); // Keyword 정보를 Review 객체에 추가 diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/review/processor/ReadReviewProcessor.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/processor/ReadReviewProcessor.java new file mode 100644 index 00000000..0ff738b3 --- /dev/null +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/processor/ReadReviewProcessor.java @@ -0,0 +1,10 @@ +package org.depromeet.spot.usecase.service.review.processor; + +import java.util.List; + +import org.depromeet.spot.domain.review.Review; + +public interface ReadReviewProcessor { + + void setLikedAndScrappedStatus(List reviews, Long memberId); +} diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/review/processor/ReadReviewProcessorImpl.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/processor/ReadReviewProcessorImpl.java new file mode 100644 index 00000000..94a5fc81 --- /dev/null +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/processor/ReadReviewProcessorImpl.java @@ -0,0 +1,36 @@ +package org.depromeet.spot.usecase.service.review.processor; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.depromeet.spot.domain.review.Review; +import org.depromeet.spot.usecase.port.out.review.ReviewLikeRepository; +import org.depromeet.spot.usecase.port.out.review.ReviewScrapRepository; +import org.springframework.stereotype.Component; + +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class ReadReviewProcessorImpl implements ReadReviewProcessor { + + private final ReviewLikeRepository reviewLikeRepository; + private final ReviewScrapRepository reviewScrapRepository; + + public void setLikedAndScrappedStatus(List reviews, Long memberId) { + List reviewIds = reviews.stream().map(Review::getId).collect(Collectors.toList()); + + Map likedMap = + reviewLikeRepository.existsByMemberIdAndReviewIds(memberId, reviewIds); + Map scrappedMap = + reviewScrapRepository.existsByMemberIdAndReviewIds(memberId, reviewIds); + + reviews.forEach( + review -> { + boolean isLiked = likedMap.getOrDefault(review.getId(), false); + boolean isScrapped = scrappedMap.getOrDefault(review.getId(), false); + review.setLikedAndScrapped(isLiked, isScrapped); + }); + } +} diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/review/scrap/ReviewScrapService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/scrap/ReviewScrapService.java index 3ea19a41..75060176 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/service/review/scrap/ReviewScrapService.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/scrap/ReviewScrapService.java @@ -10,6 +10,7 @@ import org.depromeet.spot.usecase.port.in.review.scrap.ReviewScrapUsecase; import org.depromeet.spot.usecase.port.out.review.ReviewScrapRepository; import org.depromeet.spot.usecase.service.review.ReadReviewService; +import org.depromeet.spot.usecase.service.review.processor.ReadReviewProcessor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -24,6 +25,7 @@ public class ReviewScrapService implements ReviewScrapUsecase { private final UpdateReviewUsecase updateReviewUsecase; private final ReviewScrapRepository scrapRepository; private final ReadReviewService readReviewService; + private final ReadReviewProcessor readReviewProcessor; @Override public MyScrapListResult findMyScrappedReviews( @@ -53,6 +55,9 @@ public MyScrapListResult findMyScrappedReviews( List reviewsWithKeywords = readReviewService.mapKeywordsToReviews(reviews); + // 유저의 리뷰 좋아요, 스크랩 여부 + readReviewProcessor.setLikedAndScrappedStatus(reviewsWithKeywords, memberId); + Long totalScrapCount = scrapRepository.getTotalCount( memberId, diff --git a/usecase/src/test/java/org/depromeet/spot/usecase/service/review/ReviewScrapServiceTest.java b/usecase/src/test/java/org/depromeet/spot/usecase/service/review/ReviewScrapServiceTest.java index f6c5b7e4..92e08792 100644 --- a/usecase/src/test/java/org/depromeet/spot/usecase/service/review/ReviewScrapServiceTest.java +++ b/usecase/src/test/java/org/depromeet/spot/usecase/service/review/ReviewScrapServiceTest.java @@ -19,6 +19,7 @@ import org.depromeet.spot.usecase.port.in.review.scrap.ReviewScrapUsecase.MyScrapCommand; import org.depromeet.spot.usecase.port.in.review.scrap.ReviewScrapUsecase.MyScrapListResult; import org.depromeet.spot.usecase.service.fake.FakeReviewScrapRepository; +import org.depromeet.spot.usecase.service.review.processor.ReadReviewProcessor; import org.depromeet.spot.usecase.service.review.scrap.ReviewScrapService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -33,6 +34,7 @@ class ReviewScrapServiceTest { @Mock private ReadReviewUsecase readReviewUsecase; @Mock private UpdateReviewUsecase updateReviewUsecase; @Mock private ReadReviewService readReviewService; + @Mock private ReadReviewProcessor readReviewProcessor; @BeforeEach void init() { @@ -43,7 +45,8 @@ void init() { readReviewUsecase, updateReviewUsecase, fakeReviewScrapRepository, - readReviewService); + readReviewService, + readReviewProcessor); } @Test From 75513b27988d33a44d38fe0e71af3498eb64ef1c Mon Sep 17 00:00:00 2001 From: Minseong Park <52368015+pminsung12@users.noreply.github.com> Date: Mon, 26 Aug 2024 06:40:24 +0900 Subject: [PATCH 3/4] =?UTF-8?q?[BSVR-234]=20=EC=8B=9C=EC=95=BC=20=EC=95=84?= =?UTF-8?q?=EC=B9=B4=EC=9D=B4=EB=B9=99=20=EC=8B=9C=EC=95=BC=20=ED=9B=84?= =?UTF-8?q?=EA=B8=B0/=EC=A7=81=EA=B4=80=20=ED=9B=84=EA=B8=B0=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20(#163)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 내 리뷰 조회 request에 review type 추가 * feat: 내 리뷰 조회 컨트롤러에 review type 추가 * feat: 내 리뷰 조회 서비스 로직에 review type 추가 및 좋아요, 스크랩 flag 로직도 추가 * feat: 내 리뷰 조회 repository를 review type에 따라 조회하도록 업데이트 * feat: 내 리뷰 조회 reviewType 디폴트를 VIEW로 설정 * feat: 유저별 리뷰 총 개수와 좋아요 합계를 가지는 ReviewCount 도메인 구현 * feat: ReviewCount를 조회해오는 새로운 쿼리 메서드구현 * feat: 서비스에 ReviewCount 적용 * feat: MyReviewListResponse에서 MemberInfoOnMyReviewResponse 분리 및 생성 * feat: 내 리뷰 조회에서 유저 정보 API 분리 * fix: review type 관련한 쿼리문 버그 픽스 --- .../review/ReadReviewController.java | 41 +++++++++----- .../review/dto/request/MyReviewRequest.java | 4 +- .../MemberInfoOnMyReviewResponse.java | 31 +++++++++++ .../dto/response/MyReviewListResponse.java | 14 +---- .../spot/domain/review/ReviewCount.java | 3 ++ .../repository/ReviewCustomRepository.java | 4 +- .../repository/ReviewJpaRepository.java | 15 +++++- .../repository/ReviewRepositoryImpl.java | 16 ++++-- .../port/in/review/ReadReviewUsecase.java | 20 ++++--- .../port/out/review/ReviewRepository.java | 9 +++- .../service/review/ReadReviewService.java | 53 ++++++++++++------- 11 files changed, 151 insertions(+), 59 deletions(-) create mode 100644 application/src/main/java/org/depromeet/spot/application/review/dto/response/MemberInfoOnMyReviewResponse.java create mode 100644 domain/src/main/java/org/depromeet/spot/domain/review/ReviewCount.java diff --git a/application/src/main/java/org/depromeet/spot/application/review/ReadReviewController.java b/application/src/main/java/org/depromeet/spot/application/review/ReadReviewController.java index dc435486..aba78b4a 100644 --- a/application/src/main/java/org/depromeet/spot/application/review/ReadReviewController.java +++ b/application/src/main/java/org/depromeet/spot/application/review/ReadReviewController.java @@ -11,9 +11,11 @@ import org.depromeet.spot.application.review.dto.request.MyReviewRequest; import org.depromeet.spot.application.review.dto.response.BaseReviewResponse; import org.depromeet.spot.application.review.dto.response.BlockReviewListResponse; +import org.depromeet.spot.application.review.dto.response.MemberInfoOnMyReviewResponse; import org.depromeet.spot.application.review.dto.response.MyRecentReviewResponse; import org.depromeet.spot.application.review.dto.response.MyReviewListResponse; import org.depromeet.spot.application.review.dto.response.ReviewMonthsResponse; +import org.depromeet.spot.domain.review.Review.ReviewType; import org.depromeet.spot.domain.review.ReviewYearMonth; import org.depromeet.spot.usecase.port.in.review.ReadReviewUsecase; import org.depromeet.spot.usecase.port.in.review.ReadReviewUsecase.BlockReviewListResult; @@ -67,16 +69,38 @@ public BlockReviewListResponse findReviewsByBlockId( result, request.rowNumber(), request.seatNumber(), request.year(), request.month()); } + @CurrentMember + @ResponseStatus(HttpStatus.OK) + @GetMapping("/reviews/recentReview") + @Operation(summary = "자신이 작성한 가장 최근 리뷰 1개를 조회한다.") + public MyRecentReviewResponse findMyRecentReview(@Parameter(hidden = true) Long memberId) { + + MyRecentReviewResult result = readReviewUsecase.findLastReviewByMemberId(memberId); + return MyRecentReviewResponse.from(result); + } + @CurrentMember @ResponseStatus(HttpStatus.OK) @GetMapping("/reviews/months") @Operation(summary = "리뷰가 작성된 년도와 월 정보를 조회한다.") - public ReviewMonthsResponse findReviewMonths(@Parameter(hidden = true) Long memberId) { + public ReviewMonthsResponse findReviewMonths( + @Parameter(hidden = true) Long memberId, + @Parameter(description = "리뷰 타입: VIEW/FEED") ReviewType reviewType) { - List yearMonths = readReviewUsecase.findReviewMonths(memberId); + List yearMonths = readReviewUsecase.findReviewMonths(memberId, reviewType); return ReviewMonthsResponse.from(yearMonths); } + @CurrentMember + @ResponseStatus(HttpStatus.OK) + @GetMapping("/reviews/userInfo") + @Operation(summary = "사용자의 리뷰 관련 정보를 조회한다.") + public MemberInfoOnMyReviewResponse findMemberInfoOnMyReview( + @Parameter(hidden = true) Long memberId) { + return MemberInfoOnMyReviewResponse.from( + readReviewUsecase.findMemberInfoOnMyReview(memberId)); + } + @CurrentMember @ResponseStatus(HttpStatus.OK) @GetMapping("/reviews") @@ -94,20 +118,11 @@ public MyReviewListResponse findMyReviews( request.month(), request.cursor(), request.sortBy(), - request.size()); + request.size(), + request.reviewType()); return MyReviewListResponse.from(result, request.year(), request.month()); } - @CurrentMember - @ResponseStatus(HttpStatus.OK) - @GetMapping("/reviews/recentReview") - @Operation(summary = "자신이 작성한 가장 최근 리뷰 1개를 조회한다.") - public MyRecentReviewResponse findMyRecentReview(@Parameter(hidden = true) Long memberId) { - - MyRecentReviewResult result = readReviewUsecase.findLastReviewByMemberId(memberId); - return MyRecentReviewResponse.from(result); - } - @CurrentMember @ResponseStatus(HttpStatus.OK) @GetMapping("/reviews/{reviewId}") diff --git a/application/src/main/java/org/depromeet/spot/application/review/dto/request/MyReviewRequest.java b/application/src/main/java/org/depromeet/spot/application/review/dto/request/MyReviewRequest.java index 78f5a99e..9c2c7295 100644 --- a/application/src/main/java/org/depromeet/spot/application/review/dto/request/MyReviewRequest.java +++ b/application/src/main/java/org/depromeet/spot/application/review/dto/request/MyReviewRequest.java @@ -3,6 +3,7 @@ import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; +import org.depromeet.spot.domain.review.Review.ReviewType; import org.depromeet.spot.domain.review.Review.SortCriteria; import io.swagger.v3.oas.annotations.Parameter; @@ -12,4 +13,5 @@ public record MyReviewRequest( @Min(1) @Max(12) @Parameter(description = "월 (1-12)") Integer month, @Parameter(description = "다음 페이지 커서") String cursor, @Parameter(description = "정렬 기준", example = "DATE_TIME") SortCriteria sortBy, - @Parameter(description = "페이지 크기") Integer size) {} + @Parameter(description = "페이지 크기") Integer size, + @Parameter(description = "리뷰 타입: VIEW/FEED") ReviewType reviewType) {} diff --git a/application/src/main/java/org/depromeet/spot/application/review/dto/response/MemberInfoOnMyReviewResponse.java b/application/src/main/java/org/depromeet/spot/application/review/dto/response/MemberInfoOnMyReviewResponse.java new file mode 100644 index 00000000..eec22fa3 --- /dev/null +++ b/application/src/main/java/org/depromeet/spot/application/review/dto/response/MemberInfoOnMyReviewResponse.java @@ -0,0 +1,31 @@ +package org.depromeet.spot.application.review.dto.response; + +import org.depromeet.spot.usecase.port.in.review.ReadReviewUsecase.MemberInfoOnMyReviewResult; + +import lombok.Builder; + +@Builder +public record MemberInfoOnMyReviewResponse( + Long userId, + String profileImageUrl, + Integer level, + String levelTitle, + String nickname, + Long reviewCount, + Long totalLikes, + Long teamId, + String teamName) { + public static MemberInfoOnMyReviewResponse from(MemberInfoOnMyReviewResult result) { + return MemberInfoOnMyReviewResponse.builder() + .userId(result.userId()) + .profileImageUrl(result.profileImageUrl()) + .level(result.level()) + .levelTitle(result.levelTitle()) + .nickname(result.nickname()) + .reviewCount(result.reviewCount()) + .totalLikes(result.totalLikes()) + .teamId(result.teamId()) + .teamName(result.teamName()) + .build(); + } +} diff --git a/application/src/main/java/org/depromeet/spot/application/review/dto/response/MyReviewListResponse.java b/application/src/main/java/org/depromeet/spot/application/review/dto/response/MyReviewListResponse.java index 60b8be64..4c75fdda 100644 --- a/application/src/main/java/org/depromeet/spot/application/review/dto/response/MyReviewListResponse.java +++ b/application/src/main/java/org/depromeet/spot/application/review/dto/response/MyReviewListResponse.java @@ -5,15 +5,10 @@ import org.depromeet.spot.application.review.dto.response.BlockReviewListResponse.BlockFilter; import org.depromeet.spot.domain.review.Review; -import org.depromeet.spot.usecase.port.in.review.ReadReviewUsecase.MemberInfoOnMyReviewResult; import org.depromeet.spot.usecase.port.in.review.ReadReviewUsecase.MyReviewListResult; public record MyReviewListResponse( - MemberInfoOnMyReviewResult memberInfoOnMyReview, - List reviews, - String nextCursor, - boolean hasNext, - BlockFilter filter) { + List reviews, String nextCursor, boolean hasNext, BlockFilter filter) { public static MyReviewListResponse from( MyReviewListResult result, Integer year, Integer month) { @@ -22,12 +17,7 @@ public static MyReviewListResponse from( result.reviews().stream().map(MyReviewResponse::from).collect(Collectors.toList()); BlockFilter filter = new BlockFilter(null, null, year, month); - return new MyReviewListResponse( - result.memberInfoOnMyReviewResult(), - reviews, - result.nextCursor(), - result.hasNext(), - filter); + return new MyReviewListResponse(reviews, result.nextCursor(), result.hasNext(), filter); } public record MyReviewResponse( diff --git a/domain/src/main/java/org/depromeet/spot/domain/review/ReviewCount.java b/domain/src/main/java/org/depromeet/spot/domain/review/ReviewCount.java new file mode 100644 index 00000000..f46a4d8a --- /dev/null +++ b/domain/src/main/java/org/depromeet/spot/domain/review/ReviewCount.java @@ -0,0 +1,3 @@ +package org.depromeet.spot.domain.review; + +public record ReviewCount(long reviewCount, long totalLikes) {} diff --git a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/ReviewCustomRepository.java b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/ReviewCustomRepository.java index db7bb66c..e830e8e6 100644 --- a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/ReviewCustomRepository.java +++ b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/ReviewCustomRepository.java @@ -86,13 +86,15 @@ public List findAllByUserId( Integer month, String cursor, SortCriteria sortBy, - int size) { + int size, + ReviewType reviewType) { BooleanBuilder builder = new BooleanBuilder(); builder.and(reviewEntity.member.id.eq(userId)); builder.and(eqYear(year)); builder.and(eqMonth(month)); builder.and(reviewEntity.deletedAt.isNull()); + builder.and(reviewEntity.reviewType.eq(reviewType)); OrderSpecifier[] orderBy = getOrderBy(sortBy); BooleanExpression cursorCondition = getCursorCondition(sortBy, cursor); diff --git a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/ReviewJpaRepository.java b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/ReviewJpaRepository.java index d70549d6..fbfe8c21 100644 --- a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/ReviewJpaRepository.java +++ b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/ReviewJpaRepository.java @@ -3,6 +3,8 @@ import java.time.LocalDateTime; import java.util.List; +import org.depromeet.spot.domain.review.Review.ReviewType; +import org.depromeet.spot.domain.review.ReviewCount; import org.depromeet.spot.domain.review.ReviewYearMonth; import org.depromeet.spot.infrastructure.jpa.review.entity.ReviewEntity; import org.springframework.data.jpa.repository.JpaRepository; @@ -17,9 +19,20 @@ public interface ReviewJpaRepository extends JpaRepository { "SELECT new org.depromeet.spot.domain.review.ReviewYearMonth(YEAR(r.dateTime), MONTH(r.dateTime)) " + "FROM ReviewEntity r WHERE r.member.id = :memberId " + "AND r.deletedAt IS NULL " + + "AND r.reviewType = :reviewType " + "GROUP BY YEAR(r.dateTime), MONTH(r.dateTime) " + "ORDER BY YEAR(r.dateTime) DESC, MONTH(r.dateTime) DESC") - List findReviewMonthsByMemberId(@Param("memberId") Long memberId); + List findReviewMonthsByMemberId( + @Param("memberId") Long memberId, @Param("reviewType") ReviewType reviewType); + + @Query( + "SELECT new org.depromeet.spot.domain.review.ReviewCount(" + + "COUNT(r), " + + "COALESCE(SUM(CASE WHEN r.reviewType = :viewType THEN r.likesCount ELSE 0 END), 0)) " + + "FROM ReviewEntity r " + + "WHERE r.member.id = :memberId AND r.deletedAt IS NULL") + ReviewCount countAndSumLikesByMemberId( + @Param("memberId") Long memberId, @Param("viewType") ReviewType viewType); @Modifying @Query( diff --git a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/ReviewRepositoryImpl.java b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/ReviewRepositoryImpl.java index 07261aa3..f3d6ac3d 100644 --- a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/ReviewRepositoryImpl.java +++ b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/ReviewRepositoryImpl.java @@ -7,6 +7,7 @@ import org.depromeet.spot.domain.review.Review; import org.depromeet.spot.domain.review.Review.ReviewType; import org.depromeet.spot.domain.review.Review.SortCriteria; +import org.depromeet.spot.domain.review.ReviewCount; import org.depromeet.spot.domain.review.ReviewYearMonth; import org.depromeet.spot.infrastructure.jpa.review.entity.ReviewEntity; import org.depromeet.spot.usecase.port.in.review.ReadReviewUsecase.LocationInfo; @@ -55,6 +56,11 @@ public long countByUserId(Long id) { return reviewJpaRepository.countByMemberIdAndDeletedAtIsNull(id); } + @Override + public ReviewCount countAndSumLikesByUserId(Long id) { + return reviewJpaRepository.countAndSumLikesByMemberId(id, ReviewType.VIEW); + } + @Override public List findByStadiumIdAndBlockCode( Long stadiumId, @@ -88,15 +94,17 @@ public List findAllByUserId( Integer month, String cursor, SortCriteria sortBy, - Integer size) { + Integer size, + ReviewType reviewType) { List reviewEntities = - reviewCustomRepository.findAllByUserId(userId, year, month, cursor, sortBy, size); + reviewCustomRepository.findAllByUserId( + userId, year, month, cursor, sortBy, size, reviewType); return reviewEntities.stream().map(ReviewEntity::toDomain).toList(); } @Override - public List findReviewMonthsByMemberId(Long memberId) { - return reviewJpaRepository.findReviewMonthsByMemberId(memberId); + public List findReviewMonthsByMemberId(Long memberId, ReviewType reviewType) { + return reviewJpaRepository.findReviewMonthsByMemberId(memberId, reviewType); } @Override diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/port/in/review/ReadReviewUsecase.java b/usecase/src/main/java/org/depromeet/spot/usecase/port/in/review/ReadReviewUsecase.java index 835b511a..865e2ca2 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/port/in/review/ReadReviewUsecase.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/port/in/review/ReadReviewUsecase.java @@ -4,7 +4,9 @@ import org.depromeet.spot.domain.member.Member; import org.depromeet.spot.domain.review.Review; +import org.depromeet.spot.domain.review.Review.ReviewType; import org.depromeet.spot.domain.review.Review.SortCriteria; +import org.depromeet.spot.domain.review.ReviewCount; import org.depromeet.spot.domain.review.ReviewYearMonth; import lombok.Builder; @@ -29,9 +31,12 @@ MyReviewListResult findMyReviewsByUserId( Integer month, String cursor, SortCriteria sortBy, - Integer size); + Integer size, + ReviewType reviewType); + + MemberInfoOnMyReviewResult findMemberInfoOnMyReview(Long memberId); - List findReviewMonths(Long memberId); + List findReviewMonths(Long memberId, ReviewType reviewType); MyRecentReviewResult findLastReviewByMemberId(Long memberId); @@ -74,30 +79,33 @@ record MemberInfoOnMyReviewResult( String levelTitle, String nickname, Long reviewCount, + Long totalLikes, Long teamId, String teamName) { - public static MemberInfoOnMyReviewResult of(Member member, long totalReviewCount) { + public static MemberInfoOnMyReviewResult of(Member member, ReviewCount reviewCount) { return MemberInfoOnMyReviewResult.builder() .userId(member.getId()) .profileImageUrl(member.getProfileImage()) .level(member.getLevel().getValue()) .levelTitle(member.getLevel().getTitle()) .nickname(member.getNickname()) - .reviewCount(totalReviewCount) + .reviewCount(reviewCount.reviewCount()) + .totalLikes(reviewCount.totalLikes()) .teamId(null) .teamName(null) .build(); } public static MemberInfoOnMyReviewResult of( - Member member, long totalReviewCount, String teamName) { + Member member, ReviewCount reviewCount, String teamName) { return MemberInfoOnMyReviewResult.builder() .userId(member.getId()) .profileImageUrl(member.getProfileImage()) .level(member.getLevel().getValue()) .levelTitle(member.getLevel().getTitle()) .nickname(member.getNickname()) - .reviewCount(totalReviewCount) + .reviewCount(reviewCount.reviewCount()) + .totalLikes(reviewCount.totalLikes()) .teamId(member.getTeamId()) .teamName(teamName) .build(); diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/port/out/review/ReviewRepository.java b/usecase/src/main/java/org/depromeet/spot/usecase/port/out/review/ReviewRepository.java index bf2de6dc..306a9b19 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/port/out/review/ReviewRepository.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/port/out/review/ReviewRepository.java @@ -3,7 +3,9 @@ import java.util.List; import org.depromeet.spot.domain.review.Review; +import org.depromeet.spot.domain.review.Review.ReviewType; import org.depromeet.spot.domain.review.Review.SortCriteria; +import org.depromeet.spot.domain.review.ReviewCount; import org.depromeet.spot.domain.review.ReviewYearMonth; import org.depromeet.spot.usecase.port.in.review.ReadReviewUsecase.LocationInfo; @@ -18,6 +20,8 @@ public interface ReviewRepository { long countByUserId(Long userId); + ReviewCount countAndSumLikesByUserId(Long id); + List findByStadiumIdAndBlockCode( Long stadiumId, String blockCode, @@ -35,9 +39,10 @@ List findAllByUserId( Integer month, String cursor, SortCriteria sortBy, - Integer size); + Integer size, + ReviewType reviewType); - List findReviewMonthsByMemberId(Long memberId); + List findReviewMonthsByMemberId(Long memberId, ReviewType reviewType); Long softDeleteByIdAndMemberId(Long reviewId, Long memberId); diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/review/ReadReviewService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/ReadReviewService.java index f71850c7..717ccf0b 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/service/review/ReadReviewService.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/ReadReviewService.java @@ -6,7 +6,9 @@ import org.depromeet.spot.domain.member.Member; import org.depromeet.spot.domain.review.Review; +import org.depromeet.spot.domain.review.Review.ReviewType; import org.depromeet.spot.domain.review.Review.SortCriteria; +import org.depromeet.spot.domain.review.ReviewCount; import org.depromeet.spot.domain.review.ReviewYearMonth; import org.depromeet.spot.domain.review.keyword.Keyword; import org.depromeet.spot.domain.review.keyword.ReviewKeyword; @@ -112,17 +114,36 @@ public BlockReviewListResult findReviewsByStadiumIdAndBlockCode( .build(); } + @Override + public MemberInfoOnMyReviewResult findMemberInfoOnMyReview(Long memberId) { + Member member = memberRepository.findById(memberId); + ReviewCount reviewCount = reviewRepository.countAndSumLikesByUserId(memberId); + + if (member.getTeamId() == null) { + return MemberInfoOnMyReviewResult.of(member, reviewCount); + } else { + BaseballTeam baseballTeam = baseballTeamRepository.findById(member.getTeamId()); + return MemberInfoOnMyReviewResult.of(member, reviewCount, baseballTeam.getName()); + } + } + @Override public MyReviewListResult findMyReviewsByUserId( - Long userId, + Long memberId, Integer year, Integer month, String cursor, SortCriteria sortBy, - Integer size) { + Integer size, + ReviewType reviewType) { + + if (reviewType == null) { + reviewType = ReviewType.VIEW; + } List reviews = - reviewRepository.findAllByUserId(userId, year, month, cursor, sortBy, size + 1); + reviewRepository.findAllByUserId( + memberId, year, month, cursor, sortBy, size + 1, reviewType); boolean hasNext = reviews.size() > size; if (hasNext) { @@ -133,23 +154,12 @@ public MyReviewListResult findMyReviewsByUserId( List reviewsWithKeywords = mapKeywordsToReviews(reviews); - Member member = memberRepository.findById(userId); - - MemberInfoOnMyReviewResult memberInfo; - if (member.getTeamId() == null) { - memberInfo = - MemberInfoOnMyReviewResult.of(member, reviewRepository.countByUserId(userId)); - - } else { - BaseballTeam baseballTeam = baseballTeamRepository.findById(member.getTeamId()); - - memberInfo = - MemberInfoOnMyReviewResult.of( - member, reviewRepository.countByUserId(userId), baseballTeam.getName()); + if (reviewType.equals(ReviewType.VIEW)) { + // 유저의 리뷰 좋아요, 스크랩 여부 + readReviewProcessor.setLikedAndScrappedStatus(reviewsWithKeywords, memberId); } return MyReviewListResult.builder() - .memberInfoOnMyReviewResult(memberInfo) .reviews(reviewsWithKeywords) .nextCursor(nextCursor) .hasNext(hasNext) @@ -171,8 +181,11 @@ public String getCursor(Review review, SortCriteria sortBy) { } @Override - public List findReviewMonths(Long memberId) { - return reviewRepository.findReviewMonthsByMemberId(memberId); + public List findReviewMonths(Long memberId, ReviewType reviewType) { + if (reviewType == null) { + reviewType = ReviewType.VIEW; + } + return reviewRepository.findReviewMonthsByMemberId(memberId, reviewType); } @Override @@ -247,6 +260,7 @@ public Review mapKeywordsToSingleReview(Review review) { .keywords(mappedKeywords) .likesCount(review.getLikesCount()) .scrapsCount(review.getScrapsCount()) + .reviewType(review.getReviewType()) .build(); mappedReview.setKeywordMap(keywordMap); @@ -293,6 +307,7 @@ public Review mapKeywordsToReview(Review review) { .keywords(mappedKeywords) // 리뷰 키워드 담당 .likesCount(review.getLikesCount()) .scrapsCount(review.getScrapsCount()) + .reviewType(review.getReviewType()) .build(); // Keyword 정보를 Review 객체에 추가 From f743b8f29d09b3353e087e50320c510f49ed3067 Mon Sep 17 00:00:00 2001 From: Minseong Park <52368015+pminsung12@users.noreply.github.com> Date: Mon, 26 Aug 2024 18:30:55 +0900 Subject: [PATCH 4/4] =?UTF-8?q?[BSVR-244]=20=EC=8A=A4=ED=81=AC=EB=9E=A9=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=ED=8E=98=EC=9D=B4=EC=A7=80=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=BB=A4=EC=84=9C=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=ED=94=BD=EC=8A=A4=20(#164)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 페이지네이션 getCursor 메서드 processor로 분리 * fix: getCursorCondition 조건 위치 버그 픽스 * fix: ReviewScrapServiceTest processor 의존성 추가 --- .../scrap/ReviewScrapCustomRepository.java | 94 +++++++++++-------- .../service/review/ReadReviewService.java | 26 ++--- .../review/processor/PaginationProcessor.java | 9 ++ .../processor/PaginationProcessorImpl.java | 26 +++++ .../review/scrap/ReviewScrapService.java | 4 +- .../review/ReviewScrapServiceTest.java | 5 +- 6 files changed, 108 insertions(+), 56 deletions(-) create mode 100644 usecase/src/main/java/org/depromeet/spot/usecase/service/review/processor/PaginationProcessor.java create mode 100644 usecase/src/main/java/org/depromeet/spot/usecase/service/review/processor/PaginationProcessorImpl.java diff --git a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/scrap/ReviewScrapCustomRepository.java b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/scrap/ReviewScrapCustomRepository.java index 78a41b7c..457c6f9f 100644 --- a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/scrap/ReviewScrapCustomRepository.java +++ b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/repository/scrap/ReviewScrapCustomRepository.java @@ -12,9 +12,9 @@ import org.depromeet.spot.infrastructure.jpa.review.entity.ReviewEntity; import org.springframework.stereotype.Repository; +import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.dsl.BooleanExpression; -import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; @@ -35,27 +35,28 @@ public List findScrappedReviewsByMemberId( SortCriteria sortBy, Integer size) { - JPAQuery query = - queryFactory - .selectDistinct(reviewEntity) - .from(reviewEntity) - .join(reviewScrapEntity) - .on(reviewScrapEntity.reviewId.eq(reviewEntity.id)) - .leftJoin(reviewKeywordEntity) - .on(reviewKeywordEntity.review.eq(reviewEntity)) - .leftJoin(keywordEntity) - .on(keywordEntity.id.eq(reviewKeywordEntity.keywordId)) - .where( - reviewScrapEntity.memberId.eq(memberId), - stadiumIdEq(stadiumId), - monthsIn(months), - keywordsIn(good, true), - keywordsIn(bad, false), - cursorCondition(cursor, sortBy)) - .orderBy(getOrderSpecifiers(sortBy)) - .limit(size); - - return query.fetch(); + BooleanBuilder builder = buildConditions(memberId, stadiumId, months, good, bad); + + OrderSpecifier[] orderBy = getOrderBy(sortBy); + BooleanExpression cursorCondition = getCursorCondition(sortBy, cursor); + + if (cursorCondition != null) { + builder.and(cursorCondition); + } + + return queryFactory + .selectDistinct(reviewEntity) + .from(reviewEntity) + .join(reviewScrapEntity) + .on(reviewScrapEntity.reviewId.eq(reviewEntity.id)) + .leftJoin(reviewKeywordEntity) + .on(reviewKeywordEntity.review.eq(reviewEntity)) + .leftJoin(keywordEntity) + .on(keywordEntity.id.eq(reviewKeywordEntity.keywordId)) + .where(builder) + .orderBy(orderBy) + .limit(size) + .fetch(); } public Long getTotalCount( @@ -65,6 +66,8 @@ public Long getTotalCount( List good, List bad) { + BooleanBuilder builder = buildConditions(memberId, stadiumId, months, good, bad); + return queryFactory .select(reviewEntity.countDistinct()) .from(reviewEntity) @@ -74,15 +77,27 @@ public Long getTotalCount( .on(reviewKeywordEntity.review.eq(reviewEntity)) .leftJoin(keywordEntity) .on(keywordEntity.id.eq(reviewKeywordEntity.keywordId)) - .where( - reviewScrapEntity.memberId.eq(memberId), - stadiumIdEq(stadiumId), - monthsIn(months), - keywordsIn(good, true), - keywordsIn(bad, false)) + .where(builder) .fetchOne(); } + private BooleanBuilder buildConditions( + Long memberId, + Long stadiumId, + List months, + List good, + List bad) { + BooleanBuilder builder = new BooleanBuilder(); + + builder.and(reviewScrapEntity.memberId.eq(memberId)); + builder.and(stadiumIdEq(stadiumId)); + builder.and(monthsIn(months)); + builder.and(keywordsIn(good, true)); + builder.and(keywordsIn(bad, false)); + + return builder; + } + private BooleanExpression stadiumIdEq(Long stadiumId) { return stadiumId != null ? reviewEntity.stadium.id.eq(stadiumId) : null; } @@ -101,29 +116,29 @@ private BooleanExpression keywordsIn(List keywords, boolean isPositive) return keywordEntity.content.in(keywords).and(keywordEntity.isPositive.eq(isPositive)); } - private BooleanExpression cursorCondition(String cursor, SortCriteria sortBy) { + private BooleanExpression getCursorCondition(SortCriteria sortBy, String cursor) { if (cursor == null) { return null; } String[] parts = cursor.split("_"); - if (parts.length != 3) { - return null; - } - LocalDateTime dateTime = LocalDateTime.parse(parts[0]); - Integer likesCount = Integer.parseInt(parts[1]); - Long id = Long.parseLong(parts[2]); + LocalDateTime dateTime; + Long id; switch (sortBy) { case LIKES_COUNT: + if (parts.length != 3) return null; + int likeCount = Integer.parseInt(parts[0]); + dateTime = LocalDateTime.parse(parts[1]); + id = Long.parseLong(parts[2]); return reviewEntity .likesCount - .lt(likesCount) + .lt(likeCount) .or( reviewEntity .likesCount - .eq(likesCount) + .eq(likeCount) .and( reviewEntity .dateTime @@ -137,6 +152,9 @@ private BooleanExpression cursorCondition(String cursor, SortCriteria sortBy) { id))))); case DATE_TIME: default: + if (parts.length != 2) return null; + dateTime = LocalDateTime.parse(parts[0]); + id = Long.parseLong(parts[1]); return reviewEntity .dateTime .lt(dateTime) @@ -144,7 +162,7 @@ private BooleanExpression cursorCondition(String cursor, SortCriteria sortBy) { } } - private OrderSpecifier[] getOrderSpecifiers(SortCriteria sortBy) { + private OrderSpecifier[] getOrderBy(SortCriteria sortBy) { switch (sortBy) { case LIKES_COUNT: return new OrderSpecifier[] { diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/review/ReadReviewService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/ReadReviewService.java index 717ccf0b..40e2420f 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/service/review/ReadReviewService.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/ReadReviewService.java @@ -22,6 +22,7 @@ import org.depromeet.spot.usecase.port.out.review.ReviewRepository; import org.depromeet.spot.usecase.port.out.review.ReviewScrapRepository; import org.depromeet.spot.usecase.port.out.team.BaseballTeamRepository; +import org.depromeet.spot.usecase.service.review.processor.PaginationProcessor; import org.depromeet.spot.usecase.service.review.processor.ReadReviewProcessor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -42,6 +43,7 @@ public class ReadReviewService implements ReadReviewUsecase { private final ReviewLikeRepository reviewLikeRepository; private final ReviewScrapRepository reviewScrapRepository; private final ReadReviewProcessor readReviewProcessor; + private final PaginationProcessor paginationProcessor; private static final int TOP_KEYWORDS_LIMIT = 5; private static final int TOP_IMAGES_LIMIT = 5; @@ -80,7 +82,10 @@ public BlockReviewListResult findReviewsByStadiumIdAndBlockCode( reviews = reviews.subList(0, size); } - String nextCursor = hasNext ? getCursor(reviews.get(reviews.size() - 1), sortBy) : null; + String nextCursor = + hasNext + ? paginationProcessor.getCursor(reviews.get(reviews.size() - 1), sortBy) + : null; // stadiumId랑 blockCode로 blockId를 조회 후 이걸 통해 topKeywords를 조회 List topKeywords = @@ -150,7 +155,10 @@ public MyReviewListResult findMyReviewsByUserId( reviews = reviews.subList(0, size); } - String nextCursor = hasNext ? getCursor(reviews.get(reviews.size() - 1), sortBy) : null; + String nextCursor = + hasNext + ? paginationProcessor.getCursor(reviews.get(reviews.size() - 1), sortBy) + : null; List reviewsWithKeywords = mapKeywordsToReviews(reviews); @@ -166,20 +174,6 @@ public MyReviewListResult findMyReviewsByUserId( .build(); } - public String getCursor(Review review, SortCriteria sortBy) { - switch (sortBy) { - case LIKES_COUNT: - return review.getLikesCount() - + "_" - + review.getDateTime().toString() - + "_" - + review.getId(); - case DATE_TIME: - default: - return review.getDateTime().toString() + "_" + review.getId(); - } - } - @Override public List findReviewMonths(Long memberId, ReviewType reviewType) { if (reviewType == null) { diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/review/processor/PaginationProcessor.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/processor/PaginationProcessor.java new file mode 100644 index 00000000..cb324843 --- /dev/null +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/processor/PaginationProcessor.java @@ -0,0 +1,9 @@ +package org.depromeet.spot.usecase.service.review.processor; + +import org.depromeet.spot.domain.review.Review; +import org.depromeet.spot.domain.review.Review.SortCriteria; + +public interface PaginationProcessor { + + String getCursor(Review review, SortCriteria sortBy); +} diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/review/processor/PaginationProcessorImpl.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/processor/PaginationProcessorImpl.java new file mode 100644 index 00000000..7907e6be --- /dev/null +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/processor/PaginationProcessorImpl.java @@ -0,0 +1,26 @@ +package org.depromeet.spot.usecase.service.review.processor; + +import org.depromeet.spot.domain.review.Review; +import org.depromeet.spot.domain.review.Review.SortCriteria; +import org.springframework.stereotype.Component; + +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class PaginationProcessorImpl implements PaginationProcessor { + + public String getCursor(Review review, SortCriteria sortBy) { + switch (sortBy) { + case LIKES_COUNT: + return review.getLikesCount() + + "_" + + review.getDateTime().toString() + + "_" + + review.getId(); + case DATE_TIME: + default: + return review.getDateTime().toString() + "_" + review.getId(); + } + } +} diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/review/scrap/ReviewScrapService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/scrap/ReviewScrapService.java index 75060176..36ef93f2 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/service/review/scrap/ReviewScrapService.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/scrap/ReviewScrapService.java @@ -10,6 +10,7 @@ import org.depromeet.spot.usecase.port.in.review.scrap.ReviewScrapUsecase; import org.depromeet.spot.usecase.port.out.review.ReviewScrapRepository; import org.depromeet.spot.usecase.service.review.ReadReviewService; +import org.depromeet.spot.usecase.service.review.processor.PaginationProcessor; import org.depromeet.spot.usecase.service.review.processor.ReadReviewProcessor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -26,6 +27,7 @@ public class ReviewScrapService implements ReviewScrapUsecase { private final ReviewScrapRepository scrapRepository; private final ReadReviewService readReviewService; private final ReadReviewProcessor readReviewProcessor; + private final PaginationProcessor paginationProcessor; @Override public MyScrapListResult findMyScrappedReviews( @@ -49,7 +51,7 @@ public MyScrapListResult findMyScrappedReviews( String nextCursor = hasNext - ? readReviewService.getCursor( + ? paginationProcessor.getCursor( reviews.get(reviews.size() - 1), pageCommand.sortBy()) : null; diff --git a/usecase/src/test/java/org/depromeet/spot/usecase/service/review/ReviewScrapServiceTest.java b/usecase/src/test/java/org/depromeet/spot/usecase/service/review/ReviewScrapServiceTest.java index 92e08792..458de70f 100644 --- a/usecase/src/test/java/org/depromeet/spot/usecase/service/review/ReviewScrapServiceTest.java +++ b/usecase/src/test/java/org/depromeet/spot/usecase/service/review/ReviewScrapServiceTest.java @@ -19,6 +19,7 @@ import org.depromeet.spot.usecase.port.in.review.scrap.ReviewScrapUsecase.MyScrapCommand; import org.depromeet.spot.usecase.port.in.review.scrap.ReviewScrapUsecase.MyScrapListResult; import org.depromeet.spot.usecase.service.fake.FakeReviewScrapRepository; +import org.depromeet.spot.usecase.service.review.processor.PaginationProcessor; import org.depromeet.spot.usecase.service.review.processor.ReadReviewProcessor; import org.depromeet.spot.usecase.service.review.scrap.ReviewScrapService; import org.junit.jupiter.api.BeforeEach; @@ -35,6 +36,7 @@ class ReviewScrapServiceTest { @Mock private UpdateReviewUsecase updateReviewUsecase; @Mock private ReadReviewService readReviewService; @Mock private ReadReviewProcessor readReviewProcessor; + @Mock private PaginationProcessor paginationProcessor; @BeforeEach void init() { @@ -46,7 +48,8 @@ void init() { updateReviewUsecase, fakeReviewScrapRepository, readReviewService, - readReviewProcessor); + readReviewProcessor, + paginationProcessor); } @Test