diff --git a/src/main/java/com/offer/offer/application/OfferService.java b/src/main/java/com/offer/offer/application/OfferService.java index ae26b4c..8361156 100644 --- a/src/main/java/com/offer/offer/application/OfferService.java +++ b/src/main/java/com/offer/offer/application/OfferService.java @@ -134,7 +134,7 @@ private OfferSummary getOfferSummary(Offer offer) { boolean reviewAvailable = !messages.isEmpty(); ReviewInfoResponse review = reviewRepository.findByPostAndReviewer(offer.getPost(), offer.getOfferer()) - .map(ReviewInfoResponse::from) + .map(it -> ReviewInfoResponse.buildReviewInfoResponse(it, offer.getOfferer())) .orElse(null); return OfferSummary.from(offer, reviewAvailable, review); diff --git a/src/main/java/com/offer/offer/application/response/OfferSummary.java b/src/main/java/com/offer/offer/application/response/OfferSummary.java index 1ef67db..1be5c51 100644 --- a/src/main/java/com/offer/offer/application/response/OfferSummary.java +++ b/src/main/java/com/offer/offer/application/response/OfferSummary.java @@ -3,7 +3,6 @@ import com.offer.offer.domain.Offer; import com.offer.post.application.response.EnumResponse; import com.offer.post.domain.Post; -import com.offer.post.domain.TradeStatus; import com.offer.review.application.response.ReviewInfoResponse; import java.time.LocalDateTime; import lombok.Builder; @@ -17,6 +16,8 @@ public class OfferSummary { private Long offerId; private Long postId; + private String sellerNickname; + private String title; private int offerPrice; private String thumbnailImageUrl; private EnumResponse tradeStatus; @@ -27,11 +28,13 @@ public class OfferSummary { private ReviewInfoResponse review; @Builder - public OfferSummary(Long offerId, Long postId, int offerPrice, String thumbnailImageUrl, + public OfferSummary(Long offerId, Long postId, String sellerNickname, String title, int offerPrice, String thumbnailImageUrl, EnumResponse tradeStatus, LocalDateTime createdAt, boolean reviewAvailable, boolean hasReview, ReviewInfoResponse review) { this.offerId = offerId; this.postId = postId; + this.sellerNickname = sellerNickname; + this.title = title; this.offerPrice = offerPrice; this.thumbnailImageUrl = thumbnailImageUrl; this.tradeStatus = tradeStatus; @@ -46,6 +49,8 @@ public static OfferSummary from(Offer offer) { return OfferSummary.builder() .offerId(offer.getId()) .postId(post.getId()) + .title(post.getTitle()) + .sellerNickname(post.getSeller().getNickname()) .offerPrice(offer.getPrice()) .thumbnailImageUrl(post.getThumbnailImageUrl()) .tradeStatus(new EnumResponse(post.getTradeStatus().name(), post.getTradeStatus().getDescription())) @@ -58,6 +63,8 @@ public static OfferSummary from(Offer offer, boolean reviewAvailable, ReviewInfo return OfferSummary.builder() .offerId(offer.getId()) .postId(post.getId()) + .title(post.getTitle()) + .sellerNickname(post.getSeller().getNickname()) .offerPrice(offer.getPrice()) .thumbnailImageUrl(post.getThumbnailImageUrl()) .tradeStatus(new EnumResponse(post.getTradeStatus().name(), post.getTradeStatus().getDescription())) diff --git a/src/main/java/com/offer/post/domain/PostQueryRepository.java b/src/main/java/com/offer/post/domain/PostQueryRepository.java index dc948c3..f092ad4 100644 --- a/src/main/java/com/offer/post/domain/PostQueryRepository.java +++ b/src/main/java/com/offer/post/domain/PostQueryRepository.java @@ -10,6 +10,7 @@ import com.offer.review.application.response.ReviewInfoResponse; import com.offer.review.domain.ReviewRepository; import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; import java.util.Collections; @@ -35,7 +36,6 @@ public PostSummaries searchPost(PostReadParams params, Long loginMemberId) { memberRepository.getById(sellerId) : null; Set likePostIds = getLikePostId(loginMemberId); - boolean priceDesc = "PRICE_DESC".equals(params.getSort()); BooleanBuilder booleanBuilder = new BooleanBuilder(); if (params.getLastId() != null) { @@ -49,7 +49,7 @@ public PostSummaries searchPost(PostReadParams params, Long loginMemberId) { sellerEq(seller), tradeStatus(TradeStatus.from(params.getTradeStatus())) ) - .orderBy(priceDesc ? post.price.desc() : post.id.desc()) + .orderBy(order(params.getSort())) .limit(params.getLimit() + 1) .fetch(); @@ -79,13 +79,23 @@ public PostSummaries searchPost(PostReadParams params, Long loginMemberId) { .build(); } + private OrderSpecifier order(String sort) { + if (sort == null || sort.equals("CREATED_DATE_DESC")) { + return post.id.desc(); + } + if (sort.equals("PRICE_DESC")) { + return post.price.desc(); + } + return post.price.asc(); + } + private ReviewInfoResponse getReviewInfoResponse(Post post, Member seller) { if (seller == null) { return null; } ReviewInfoResponse review = reviewRepository.findByPostAndReviewer(post, seller) - .map(ReviewInfoResponse::from) + .map(it -> ReviewInfoResponse.buildReviewInfoResponse(it, seller)) .orElse(null); return review; } diff --git a/src/main/java/com/offer/review/application/ReviewService.java b/src/main/java/com/offer/review/application/ReviewService.java index 3d79947..c82170e 100644 --- a/src/main/java/com/offer/review/application/ReviewService.java +++ b/src/main/java/com/offer/review/application/ReviewService.java @@ -10,11 +10,15 @@ import com.offer.post.domain.PostRepository; import com.offer.review.application.request.ReviewCreateRequest; import com.offer.review.application.response.ReviewInfoResponse; +import com.offer.review.application.response.ReviewInfoResponse.ReviewTargetMemberResponse; +import com.offer.review.application.response.ReviewInfoResponses; import com.offer.review.domain.Review; import com.offer.review.domain.ReviewRepository; import com.offer.review.domain.Role; import com.offer.utils.SliceUtils; +import java.util.ArrayList; import lombok.RequiredArgsConstructor; +import org.hibernate.query.spi.Limit; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; @@ -55,18 +59,37 @@ public CommonCreationResponse createReview(ReviewCreateRequest request, Long rev } @Transactional(readOnly = true) - public List getReviews(int page, Long memberId, Role role) { - PageRequest pageRequest = PageRequest.of(SliceUtils.getSliceNumber(page), Properties.DEFAULT_SLICE_SIZE); - - Slice reviews = switch (role) { - case BUYER -> reviewRepository.findSliceByRevieweeIdAndIsRevieweeBuyer(pageRequest, memberId, true); - case SELLER -> reviewRepository.findSliceByRevieweeIdAndIsRevieweeBuyer(pageRequest, memberId, false); - case ALL -> reviewRepository.findSliceByRevieweeId(pageRequest, memberId); - }; - - return reviews.stream() - .filter(Objects::nonNull) - .map(ReviewInfoResponse::from) - .collect(Collectors.toList()); + public ReviewInfoResponses getReviews(Long memberId, Role role, Long lastId, int limit) { + + Member member = memberRepository.getById(memberId); + + List result = new ArrayList<>(); + + if (role == Role.ALL) { + result = reviewRepository.findTop10ByReviewerOrRevieweeAndIdGreaterThanEqual(member, member, lastId); + } + if (role == Role.SELLER) { + result = reviewRepository.findTop10ByReviewerAndRevieweeIsBuyerAndIdGreaterThanEqual(member, true, lastId); + } + if (role == Role.BUYER) { + result = reviewRepository.findTop10ByRevieweeAndRevieweeIsBuyerAndIdGreaterThanEqual(member, false, lastId); + } + + if (result.size() > limit) { + result.remove(limit); + return ReviewInfoResponses.builder() + .reviews(result.stream() + .map(review -> ReviewInfoResponse.buildReviewInfoResponse(review, member)) + .collect(Collectors.toList())) + .hasNext(true) + .build(); + } + + return ReviewInfoResponses.builder() + .reviews(result.stream() + .map(review -> ReviewInfoResponse.buildReviewInfoResponse(review, member)) + .collect(Collectors.toList())) + .hasNext(false) + .build(); } } diff --git a/src/main/java/com/offer/review/application/request/ReviewCreateRequest.java b/src/main/java/com/offer/review/application/request/ReviewCreateRequest.java index 2966560..e48b66e 100644 --- a/src/main/java/com/offer/review/application/request/ReviewCreateRequest.java +++ b/src/main/java/com/offer/review/application/request/ReviewCreateRequest.java @@ -26,14 +26,14 @@ public ReviewCreateRequest(Long targetMemberId, Long postId, int score, String c this.content = content; } - public Review toEntity(Member reviewee, Member reviewer, Post post, boolean isRevieweeBuyer) { + public Review toEntity(Member reviewee, Member reviewer, Post post, boolean revieweeIsBuyer) { return Review.builder() .reviewee(reviewee) .reviewer(reviewer) .post(post) .score(score) .content(content) - .isRevieweeBuyer(isRevieweeBuyer) + .revieweeIsBuyer(revieweeIsBuyer) .build(); } } diff --git a/src/main/java/com/offer/review/application/response/ReviewInfoResponse.java b/src/main/java/com/offer/review/application/response/ReviewInfoResponse.java index afd9675..2e55770 100644 --- a/src/main/java/com/offer/review/application/response/ReviewInfoResponse.java +++ b/src/main/java/com/offer/review/application/response/ReviewInfoResponse.java @@ -1,6 +1,6 @@ package com.offer.review.application.response; -import com.fasterxml.jackson.annotation.JsonFormat; +import com.offer.member.Member; import com.offer.review.domain.Review; import lombok.Builder; import lombok.Getter; @@ -15,7 +15,7 @@ public class ReviewInfoResponse { private final Long id; - private final ReviewerBriefResponse reviewer; + private final ReviewTargetMemberResponse reviewTargetMember; private final int score; @@ -28,7 +28,7 @@ public class ReviewInfoResponse { @Getter @RequiredArgsConstructor - public static class ReviewerBriefResponse { + public static class ReviewTargetMemberResponse { private final Long id; private final String profileImageUrl; @@ -44,13 +44,22 @@ public static class PostBriefResponse { private final String title; } - public static ReviewInfoResponse from(Review review) { + public static ReviewInfoResponse buildReviewInfoResponse(Review review, Member member) { + // 내가 reviewer인 경우 reviewee, 내가 reviewee인 경우 reviewer + Member reviewer = review.getReviewer(); + if (member.getId().equals(reviewer.getId())) { + return ReviewInfoResponse.from(review, review.getReviewee()); + } + return ReviewInfoResponse.from(review, review.getReviewer()); + } + + private static ReviewInfoResponse from(Review review, Member targetMember) { return ReviewInfoResponse.builder() .id(review.getId()) - .reviewer(new ReviewerBriefResponse( - review.getReviewer().getId(), - review.getReviewer().getProfileImageUrl(), - review.getReviewer().getNickname()) + .reviewTargetMember(new ReviewTargetMemberResponse( + targetMember.getId(), + targetMember.getProfileImageUrl(), + targetMember.getNickname()) ) .score(review.getScore()) .post(new PostBriefResponse( diff --git a/src/main/java/com/offer/review/application/response/ReviewInfoResponses.java b/src/main/java/com/offer/review/application/response/ReviewInfoResponses.java new file mode 100644 index 0000000..6aae11f --- /dev/null +++ b/src/main/java/com/offer/review/application/response/ReviewInfoResponses.java @@ -0,0 +1,18 @@ +package com.offer.review.application.response; + +import java.util.List; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class ReviewInfoResponses { + + private List reviews; + private boolean hasNext; + + @Builder + public ReviewInfoResponses(List reviews, boolean hasNext) { + this.reviews = reviews; + this.hasNext = hasNext; + } +} diff --git a/src/main/java/com/offer/review/domain/Review.java b/src/main/java/com/offer/review/domain/Review.java index 5e3d4dd..1e59619 100644 --- a/src/main/java/com/offer/review/domain/Review.java +++ b/src/main/java/com/offer/review/domain/Review.java @@ -39,7 +39,7 @@ public class Review { private String content; - private boolean isRevieweeBuyer; + private boolean revieweeIsBuyer; @CreatedDate private LocalDateTime createdAt; @@ -49,12 +49,12 @@ public class Review { @Builder public Review(Member reviewee, Member reviewer, Post post, int score, - String content, boolean isRevieweeBuyer) { + String content, boolean revieweeIsBuyer) { this.reviewee = reviewee; this.reviewer = reviewer; this.post = post; this.score = score; this.content = content; - this.isRevieweeBuyer = isRevieweeBuyer; + this.revieweeIsBuyer = revieweeIsBuyer; } } diff --git a/src/main/java/com/offer/review/domain/ReviewRepository.java b/src/main/java/com/offer/review/domain/ReviewRepository.java index e66903e..16be761 100644 --- a/src/main/java/com/offer/review/domain/ReviewRepository.java +++ b/src/main/java/com/offer/review/domain/ReviewRepository.java @@ -2,8 +2,9 @@ import com.offer.member.Member; import com.offer.post.domain.Post; -import com.querydsl.core.types.Projections; +import java.util.List; import java.util.Optional; +import org.hibernate.query.spi.Limit; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; @@ -14,13 +15,15 @@ public interface ReviewRepository extends JpaRepository { boolean existsByReviewerAndPost(Member reviewer, Post post); - Slice findSliceByRevieweeIdAndIsRevieweeBuyer(PageRequest pageRequest, Long revieweeId, boolean IsRevieweeBuyer); - - Slice findSliceByRevieweeId(PageRequest pageRequest, Long revieweeId); - int countByRevieweeIdOrReviewerId(Long revieweeId, Long reviewerId); Optional findByPost(Post post); Optional findByPostAndReviewer(Post post, Member reviewer); + + List findTop10ByReviewerOrRevieweeAndIdGreaterThanEqual(Member reviewer, Member reviewee, Long id); + + List findTop10ByReviewerAndRevieweeIsBuyerAndIdGreaterThanEqual(Member reviewer, boolean revieweeIsBuyer, Long id); + + List findTop10ByRevieweeAndRevieweeIsBuyerAndIdGreaterThanEqual(Member reviewee, boolean revieweeIsBuyer, Long id); } diff --git a/src/main/java/com/offer/review/domain/Role.java b/src/main/java/com/offer/review/domain/Role.java index b917887..dfbdd07 100644 --- a/src/main/java/com/offer/review/domain/Role.java +++ b/src/main/java/com/offer/review/domain/Role.java @@ -1,24 +1,16 @@ package com.offer.review.domain; -import com.offer.common.response.ResponseMessage; -import com.offer.common.response.exception.BusinessException; +import java.util.Arrays; public enum Role { BUYER, SELLER, ALL ; - public static Role of(String roleStr) { - if (roleStr == null) return ALL; - - roleStr = roleStr.trim(); - - if (roleStr.equalsIgnoreCase(BUYER.name())) { - return BUYER; - } else if (roleStr.equalsIgnoreCase(SELLER.name())) { - return SELLER; - } else { - throw new BusinessException(ResponseMessage.INVALID_ROLE); - } + public static Role of(String role) { + return Arrays.stream(values()) + .filter(it -> it.name().equalsIgnoreCase(role)) + .findAny() + .orElse(ALL); } } diff --git a/src/main/java/com/offer/review/presentation/ReviewController.java b/src/main/java/com/offer/review/presentation/ReviewController.java index fff9c2d..feea798 100644 --- a/src/main/java/com/offer/review/presentation/ReviewController.java +++ b/src/main/java/com/offer/review/presentation/ReviewController.java @@ -8,6 +8,7 @@ import com.offer.review.application.ReviewService; import com.offer.review.application.request.ReviewCreateRequest; import com.offer.review.application.response.ReviewInfoResponse; +import com.offer.review.application.response.ReviewInfoResponses; import com.offer.review.domain.Role; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Schema; @@ -39,13 +40,15 @@ public ResponseEntity> createReview( @Operation(summary = "리뷰 조회", security = {@SecurityRequirement(name = "jwt")}) @GetMapping("/reviews") - public ResponseEntity>> getReviews( + public ResponseEntity> getReviews( @Schema(hidden = true) @AuthenticationPrincipal LoginMember loginMember, - @RequestParam(value = "memberId", required = true) Long memberId, - @RequestParam(value = "role", required = false) String role, - @RequestParam(required = true) int page) { + @RequestParam Long memberId, + @RequestParam(defaultValue = "ALL") String role, + @RequestParam(defaultValue = "1") Long lastId, + @RequestParam(defaultValue = "10") int limit + ) { - List response = reviewService.getReviews(page, memberId, Role.of(role)); + ReviewInfoResponses response = reviewService.getReviews(memberId, Role.of(role), lastId, limit); return ResponseEntity.ok( ApiResponse.of(ResponseMessage.SUCCESS, response) diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index e88113c..5e59fe9 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -4,6 +4,12 @@ values (1, 2036674018, 'KAKAO', '빼어난 가전제품 3호', 'http://k.kakaocd insert into member(offer_level, oauth_id, oauth_type, nickname, profile_image_url, created_date) values (1, 2026674018, 'KAKAO', '지친 어썸오 1호', 'http://k.kakaocdn.net/dn/ia9Y5/btrXSxAKtaj/1yZZWN0bhuA627JBHiN2ck/img_640x640.jpg', now()); +insert into member(offer_level, oauth_id, oauth_type, nickname, profile_image_url, created_date) +values (1, 3221448503, 'KAKAO', '빼어난 다리미 64호', null, now()); + +insert into member(offer_level, oauth_id, oauth_type, nickname, profile_image_url, created_date) +values (1, 3158623659, 'KAKAO', '빼어난 다리미 11호', null, now()); + insert into category(code, name, image_url) values ('MEN_FASHION', '남성패션/잡화', 'https://offer-be.kro.kr/images/7e0099c4-e75d-4636-88c1-d412676bb0b9.png'); insert into category(code, name, image_url) values ('WOMEN_FASHION', '여성패션/잡화', 'https://offer-be.kro.kr/images/ef4e0e85-ef6b-452f-9968-dc57db57a76b.png'); insert into category(code, name, image_url) values ('GAME', '게임', 'https://offer-be.kro.kr/images/c688952b-f585-4df1-8187-d836c0280492.png'); @@ -21,8 +27,10 @@ insert into sort_group(sort_type) values ('POST'); insert into sort_group(sort_type) values ('OFFER'); insert into sort_item(sort_group_id, name, code) values (1, '높은 가격순', 'PRICE_DESC'); +insert into sort_item(sort_group_id, name, code) values (1, '낮은 가격순', 'PRICE_ASC'); insert into sort_item(sort_group_id, name, code) values (1, '최신순', 'CREATED_DATE_DESC'); insert into sort_item(sort_group_id, name, code) values (2, '높은 가격순', 'PRICE_DESC'); +insert into sort_item(sort_group_id, name, code) values (2, '낮은 가격순', 'PRICE_ASC'); insert into sort_item(sort_group_id, name, code) values (2, '최신순', 'CREATED_DATE_DESC'); @@ -85,3 +93,24 @@ values (28000, now(), now(), 1, 'GAME', '닌텐도 스위치 판매중', '동작 insert into post(price, created_at, modified_at, seller_id, category, description, location, product_condition, thumbnail_image_url, title, trade_type, trade_status) values (29000, now(), now(), 1, 'GAME', '닌텐도 스위치 판매중', '동작구 사당동', 'NEW', 'https://picsum.photos/200', '닌텐도 스위치 팝니다.', 'SHIPPING', 'SELLING'); + +insert into post(price, created_at, modified_at, seller_id, category, description, location, product_condition, thumbnail_image_url, title, trade_type, trade_status) +values (234, now(), now(), 4, 'MEN_FASHION', '234', '234', 'NEW', 'https://offer-be.kro.kr/images/17eb19ea-bcab-4280-a755-14326f09ccfd.jpeg', '324', 'FACE_TO_FACE', 'SELLING'); + +insert into post_image(post_id, url) values (21, 'https://offer-be.kro.kr/images/91dc60a5-1934-4538-a6af-417422526cb4.jpeg'); + +insert into favorite(member_id, post_id) values (3, 20); +insert into favorite(member_id, post_id) values (3, 18); + +insert into offer(is_selected, price, created_at, offerer_id, post_id, trade_type) values (false, 30000, now(), 1, 20, 'ALL'); +insert into offer(is_selected, price, created_at, offerer_id, post_id, trade_type) values (false, 30000, now(), 1, 19, 'ALL'); +insert into offer(is_selected, price, created_at, offerer_id, post_id, trade_type) values (false, 30000, now(), 3, 19, 'ALL'); +insert into offer(is_selected, price, created_at, offerer_id, post_id, trade_type) values (false, 29000, now(), 3, 20, 'ALL'); +insert into offer(is_selected, price, created_at, offerer_id, post_id, trade_type) values (false, 27000, now(), 3, 18, 'ALL'); + +insert into msg_room(created_at, offer_id, offerer_id, seller_id) values (now(), 5, 3, 1); + +insert into msg(is_read, created_at, room_id, sender_id, content) values (false, now(), 1, 1, '판매자로부터의 첫 메시지입니다.'); + +insert into review(reviewee_is_buyer, score, created_at, modified_at, post_id, reviewee_id, reviewer_id, content) values (false, 1, now(), now(), 20, 1, 3, '조타이 조타이~?'); +insert into review(reviewee_is_buyer, score, created_at, modified_at, post_id, reviewee_id, reviewer_id, content) values (false, 1, now(), now(), 18, 1, 3, '조타이 조타이~?');