diff --git a/src/main/java/com/example/betteriter/fo_domain/comment/domain/Comment.java b/src/main/java/com/example/betteriter/fo_domain/comment/domain/Comment.java index 81d5787..b7b003f 100644 --- a/src/main/java/com/example/betteriter/fo_domain/comment/domain/Comment.java +++ b/src/main/java/com/example/betteriter/fo_domain/comment/domain/Comment.java @@ -5,17 +5,20 @@ import com.example.betteriter.fo_domain.user.domain.Users; import com.example.betteriter.global.common.entity.BaseEntity; import com.example.betteriter.global.constant.Status; -import lombok.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.Where; import javax.persistence.*; @Slf4j @Getter -@Builder -@AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) +@Where(clause = "status = 'ACTIVE'") @Entity(name = "COMMENT") public class Comment extends BaseEntity { @Id @@ -44,6 +47,17 @@ public class Comment extends BaseEntity { @Enumerated(EnumType.STRING) private Status status; // 댓글 상태: ACTIVE, INACTIVE, DELETED + @Builder + private Comment(Review review, Users users, String comment, + Integer orderNum, Integer groupId, Status status) { + this.review = review; + this.users = users; + this.comment = comment; + this.orderNum = orderNum; + this.groupId = groupId; + this.status = status; + } + public boolean isDeleted() { return this.status == Status.DELETED; } diff --git a/src/main/java/com/example/betteriter/fo_domain/comment/domain/CommentLike.java b/src/main/java/com/example/betteriter/fo_domain/comment/domain/CommentLike.java index e71a901..457b24b 100644 --- a/src/main/java/com/example/betteriter/fo_domain/comment/domain/CommentLike.java +++ b/src/main/java/com/example/betteriter/fo_domain/comment/domain/CommentLike.java @@ -2,15 +2,16 @@ import com.example.betteriter.fo_domain.user.domain.Users; -import lombok.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import javax.persistence.*; @Slf4j @Getter -@Builder -@AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity(name = "COMMENT_LIKE") public class CommentLike { @@ -25,4 +26,10 @@ public class CommentLike { @JoinColumn(name = "user_id", nullable = false) @ManyToOne(fetch = FetchType.LAZY) private Users users; -} + + @Builder + private CommentLike(Comment comment, Users users) { + this.comment = comment; + this.users = users; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/betteriter/fo_domain/review/controller/ReviewController.java b/src/main/java/com/example/betteriter/fo_domain/review/controller/ReviewController.java index b8bb9e6..299b222 100644 --- a/src/main/java/com/example/betteriter/fo_domain/review/controller/ReviewController.java +++ b/src/main/java/com/example/betteriter/fo_domain/review/controller/ReviewController.java @@ -2,6 +2,7 @@ import com.example.betteriter.fo_domain.review.dto.CreateReviewRequestDto; import com.example.betteriter.fo_domain.review.dto.GetReviewSpecResponseDto; +import com.example.betteriter.fo_domain.review.dto.ReviewDetailResponse; import com.example.betteriter.fo_domain.review.dto.ReviewResponse; import com.example.betteriter.fo_domain.review.exception.ReviewHandler; import com.example.betteriter.fo_domain.review.service.ReviewService; @@ -47,17 +48,28 @@ public ResponseDto getReviewSpecDataResponse( /* 카테고리 별 리뷰 조회 */ @GetMapping("/category") public ResponseDto getReviewsByCategory( - @RequestParam String category + @RequestParam String category, + @RequestParam int page ) { - return ResponseDto.onSuccess(this.reviewService.getReviewByCategory(Category.from(category))); + return ResponseDto.onSuccess(this.reviewService.getReviewByCategory(Category.from(category), page)); } - /* 이름으로 리뷰 조회 */ + /* 상품 명 + 필터링 리뷰 조회 */ @GetMapping("/search") public ResponseDto getReviewsBySearch( - @RequestParam String name + @RequestParam String name, + @RequestParam String sort, + @RequestParam int page + ) { + return ResponseDto.onSuccess(this.reviewService.getReviewBySearch(name, sort, page)); + } + + /* 리뷰 상세 조회 */ + @GetMapping("/detail/{reviewId}") + public ResponseDto getReviewDetail( + @PathVariable Long reviewId ) { - return ResponseDto.onSuccess(this.reviewService.getReviewBySearch(name)); + return ResponseDto.onSuccess(this.reviewService.getReviewDetail(reviewId)); } @@ -68,4 +80,4 @@ private void checkRequestValidation(BindingResult bindingResult) { throw new ReviewHandler(_METHOD_ARGUMENT_ERROR); } } -} +} \ No newline at end of file diff --git a/src/main/java/com/example/betteriter/fo_domain/review/domain/Review.java b/src/main/java/com/example/betteriter/fo_domain/review/domain/Review.java index 2357e16..fe8f791 100644 --- a/src/main/java/com/example/betteriter/fo_domain/review/domain/Review.java +++ b/src/main/java/com/example/betteriter/fo_domain/review/domain/Review.java @@ -1,7 +1,7 @@ package com.example.betteriter.fo_domain.review.domain; - import com.example.betteriter.bo_domain.menufacturer.domain.Manufacturer; +import com.example.betteriter.fo_domain.comment.domain.Comment; import com.example.betteriter.fo_domain.review.dto.ReviewResponseDto; import com.example.betteriter.fo_domain.user.domain.Users; import com.example.betteriter.global.common.entity.BaseEntity; @@ -9,6 +9,7 @@ import com.example.betteriter.global.constant.Status; import lombok.*; import lombok.extern.slf4j.Slf4j; +import org.hibernate.annotations.Where; import org.springframework.scheduling.annotation.Scheduled; import javax.persistence.*; @@ -19,14 +20,17 @@ @Slf4j @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -@ToString(of = {"id"}) +@Where(clause = "status = 'ACTIVE'") // ACTIVE 상태인 REVIEW 만 조회 @Entity(name = "REVIEW") public class Review extends BaseEntity { + @OneToMany(mappedBy = "review", cascade = CascadeType.ALL, orphanRemoval = true) + private final List reviewScraped = new ArrayList<>(); @OneToMany(mappedBy = "review", cascade = CascadeType.ALL, orphanRemoval = true) public List specData = new ArrayList<>(); @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Setter @JoinColumn(name = "writer_id") @ManyToOne(fetch = FetchType.LAZY) private Users writer; @@ -37,8 +41,8 @@ public class Review extends BaseEntity { private Category category; @Column(name = "product_name", nullable = false) private String productName; - @Column(name = "amount", nullable = false) - private int amount; + @Column(name = "price") + private int price; @Column(name = "store_name", nullable = false) private int storeName; @Column(name = "bought_at", nullable = false) @@ -47,63 +51,54 @@ public class Review extends BaseEntity { private double starPoint; @Column(name = "short_review", nullable = false) private String shortReview; - @Column(name = "click_cnt") private long clickCount; // 클릭 수 @Column(name = "liked_cnt") private long likedCount; // 좋아요 수 @Column(name = "scraped_cnt") private long scrapedCount; // 스크랩 수 - - @Lob // 최대 500 자 @Column(name = "good_point", nullable = false) private String goodPoint; @Lob // 최대 500 자 @Column(name = "bad_point", nullable = false) private String badPoint; - - @Column(name = "status", nullable = false) @Enumerated(EnumType.STRING) private Status status; // ACTIVE, DELETED - - // --------------- Review 관련 엔티티 ---------------- // @Setter @OneToMany(mappedBy = "review", cascade = CascadeType.ALL, orphanRemoval = true) private List reviewImages = new ArrayList<>(); - @OneToMany(mappedBy = "review", cascade = CascadeType.ALL, orphanRemoval = true) - private List reviewScraped = new ArrayList<>(); @Setter @OneToMany(mappedBy = "review", cascade = CascadeType.ALL, orphanRemoval = true) private List reviewLiked = new ArrayList<>(); + @Setter + @OneToMany(mappedBy = "review", cascade = CascadeType.ALL, orphanRemoval = true) + private List reviewComment = new ArrayList<>(); @Builder private Review(Long id, Users writer, Manufacturer manufacturer, Category category, - String productName, int amount, int storeName, LocalDate boughtAt, - double starPoint, String shortReview, String goodPoint, - String badPoint, long clickCount, List reviewImages, - List reviewScraped, List reviewLiked, long likedCount, long scrapedCount + String productName, int storeName, LocalDate boughtAt, int price, + double starPoint, String shortReview, String goodPoint, Status status, + String badPoint, long clickCount, long likedCount, long scrapedCount ) { this.id = id; this.writer = writer; this.manufacturer = manufacturer; this.category = category; this.productName = productName; - this.amount = amount; this.storeName = storeName; this.boughtAt = boughtAt; + this.price = price; this.starPoint = starPoint; this.shortReview = shortReview; this.goodPoint = goodPoint; this.badPoint = badPoint; - this.reviewImages = reviewImages; - this.reviewScraped = reviewScraped; this.clickCount = clickCount; - this.reviewLiked = reviewLiked; this.likedCount = likedCount; this.scrapedCount = scrapedCount; + this.status = status; } public ReviewResponseDto of(String firstImageUrl) { @@ -115,9 +110,14 @@ public void resetClickCounts() { this.clickCount = 0L; } + + public void setReviewImage(ReviewImage reviewImage) { + this.reviewImages.add(reviewImage); + } + // 매주 월요일 자정 실행 @Scheduled(cron = "0 0 0 ? * MON") public void resetClickCountsScheduler() { this.resetClickCounts(); } -} +} \ No newline at end of file diff --git a/src/main/java/com/example/betteriter/fo_domain/review/domain/ReviewImage.java b/src/main/java/com/example/betteriter/fo_domain/review/domain/ReviewImage.java index 3f141c8..35dd8b4 100644 --- a/src/main/java/com/example/betteriter/fo_domain/review/domain/ReviewImage.java +++ b/src/main/java/com/example/betteriter/fo_domain/review/domain/ReviewImage.java @@ -29,7 +29,8 @@ public class ReviewImage { private int orderNum; @Builder - private ReviewImage(Review review, String imgUrl, int orderNum) { + private ReviewImage(Long id, Review review, String imgUrl, int orderNum) { + this.id = id; this.review = review; this.imgUrl = imgUrl; this.orderNum = orderNum; @@ -42,4 +43,4 @@ public static ReviewImage createReviewImage(Review review, String imgUrl, int or .orderNum(orderNum) .build(); } -} +} \ No newline at end of file diff --git a/src/main/java/com/example/betteriter/fo_domain/review/dto/CreateReviewRequestDto.java b/src/main/java/com/example/betteriter/fo_domain/review/dto/CreateReviewRequestDto.java index 3d06cbd..c6eaeb9 100644 --- a/src/main/java/com/example/betteriter/fo_domain/review/dto/CreateReviewRequestDto.java +++ b/src/main/java/com/example/betteriter/fo_domain/review/dto/CreateReviewRequestDto.java @@ -30,7 +30,7 @@ public class CreateReviewRequestDto { private String manufacturer; // 제조사 이름 @NotNull(message = "가격은 필수 입력 값입니다.") - private int amount; // 가격 + private int price; // 가격 @NotNull(message = "구매처 정보는 필수 입력 값입니다.") private int storeName; // 구매처 @@ -52,14 +52,14 @@ public class CreateReviewRequestDto { @Builder private CreateReviewRequestDto(Category category, String productName, LocalDate boughtAt, - String manufacturerName, int amount, int storeName, String shortReview, + String manufacturer, int price, int storeName, String shortReview, int starPoint, String goodPoint, String badPoint, List specData, List images) { this.category = category; this.productName = productName; this.boughtAt = boughtAt; - this.manufacturer = manufacturerName; - this.amount = amount; + this.manufacturer = manufacturer; + this.price = price; this.storeName = storeName; this.shortReview = shortReview; this.starPoint = starPoint; @@ -76,7 +76,7 @@ public Review toEntity(Users users, Manufacturer manufacturer) { .productName(productName) .boughtAt(boughtAt) .manufacturer(manufacturer) - .amount(amount) + .price(price) .storeName(storeName) .shortReview(shortReview) .starPoint(starPoint) diff --git a/src/main/java/com/example/betteriter/fo_domain/review/dto/GetReviewDetailResponseDto.java b/src/main/java/com/example/betteriter/fo_domain/review/dto/GetReviewDetailResponseDto.java new file mode 100644 index 0000000..86dc475 --- /dev/null +++ b/src/main/java/com/example/betteriter/fo_domain/review/dto/GetReviewDetailResponseDto.java @@ -0,0 +1,73 @@ +package com.example.betteriter.fo_domain.review.dto; + +import com.example.betteriter.fo_domain.review.domain.Review; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.util.List; +import java.util.stream.Collectors; + +@Getter +@NoArgsConstructor +public class GetReviewDetailResponseDto { + private Long id; // 리뷰 아이디 + private String productName; // 리뷰 상품명 + private List reviewSpecData; // 리뷰 스펙 데이터 + private double starPoint; // 리뷰 별점 + private String goodPoint; // 리뷰 좋은점 + private String badPoint; // 리뷰 안좋은점 + private String shortReview; // 리뷰 한줄평 + private String manufacturer; // 제조사 이름 + private int storeName; // 구매처 + private LocalDate boughtAt; // 구매 일 + private LocalDate createdAt; // 작성일 + private List reviewImages; // 리뷰 이미지 + private long scrapedCount; // 리뷰 스크랩 갯수 + + + @Builder + public GetReviewDetailResponseDto(Long id, String productName, List reviewSpecData, double starPoint, + String goodPoint, String badPoint, String shortReview, String manufacturer, + int storeName, LocalDate boughtAt, LocalDate createdAt, List reviewImages, long scrapedCount + ) { + this.id = id; + this.productName = productName; + this.reviewSpecData = reviewSpecData; + this.starPoint = starPoint; + this.goodPoint = goodPoint; + this.badPoint = badPoint; + this.shortReview = shortReview; + this.manufacturer = manufacturer; + this.storeName = storeName; + this.boughtAt = boughtAt; + this.createdAt = createdAt; + this.reviewImages = reviewImages; + this.scrapedCount = scrapedCount; + } + + public static GetReviewDetailResponseDto from(Review review) { + return GetReviewDetailResponseDto.builder() + .id(review.getId()) + .productName(review.getProductName()) + .reviewSpecData(getReviewSpecDataToStr(review)) + .starPoint(review.getStarPoint()) + .goodPoint(review.getGoodPoint()) + .badPoint(review.getBadPoint()) + .shortReview(review.getShortReview()) + .manufacturer(review.getManufacturer().getCoName()) + .storeName(review.getStoreName()) + .boughtAt(review.getBoughtAt()) + .createdAt(review.getCreatedAt() == null ? null : review.getCreatedAt().toLocalDate()) + .reviewImages(GetReviewImageResponseDto.of(review.getReviewImages())) + .scrapedCount(review.getScrapedCount()) + .build(); + } + + private static List getReviewSpecDataToStr(Review review) { + return review.getSpecData().stream() + .map(reviewSpecData -> reviewSpecData.getSpecData().getData()) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/betteriter/fo_domain/review/dto/GetReviewImageResponseDto.java b/src/main/java/com/example/betteriter/fo_domain/review/dto/GetReviewImageResponseDto.java new file mode 100644 index 0000000..87aefe7 --- /dev/null +++ b/src/main/java/com/example/betteriter/fo_domain/review/dto/GetReviewImageResponseDto.java @@ -0,0 +1,28 @@ +package com.example.betteriter.fo_domain.review.dto; + +import com.example.betteriter.fo_domain.review.domain.ReviewImage; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.stream.Collectors; + +@Getter +@NoArgsConstructor +public class GetReviewImageResponseDto { + private String imgUrl; + private int orderNum; + + @Builder + public GetReviewImageResponseDto(String imgUrl, int orderNum) { + this.imgUrl = imgUrl; + this.orderNum = orderNum; + } + + public static List of(List reviewImages) { + return reviewImages.stream() + .map(ri -> new GetReviewImageResponseDto(ri.getImgUrl(), ri.getOrderNum())) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/betteriter/fo_domain/review/dto/ReviewDetailResponse.java b/src/main/java/com/example/betteriter/fo_domain/review/dto/ReviewDetailResponse.java new file mode 100644 index 0000000..b26002a --- /dev/null +++ b/src/main/java/com/example/betteriter/fo_domain/review/dto/ReviewDetailResponse.java @@ -0,0 +1,239 @@ +package com.example.betteriter.fo_domain.review.dto; + +import com.example.betteriter.fo_domain.comment.domain.Comment; +import com.example.betteriter.fo_domain.review.domain.Review; +import com.example.betteriter.fo_domain.review.domain.ReviewImage; +import com.example.betteriter.fo_domain.review.exception.ReviewHandler; +import com.example.betteriter.fo_domain.user.domain.Users; +import com.example.betteriter.global.constant.Job; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.util.List; +import java.util.stream.Collectors; + +import static com.example.betteriter.global.common.code.status.ErrorStatus._REVIEW_IMAGE_NOT_FOUND; + +@JsonPropertyOrder({"reviewDetail", "writerInfo", "reviewLikeInfo", "reviewCommentInfo", "relatedReviews"}) +@Getter +@NoArgsConstructor +public class ReviewDetailResponse { + @JsonProperty("reviewDetail") + private GetReviewDetailResponseDto getReviewDetailResponseDto; // 리뷰 상세 데이터 + private GetUserResponseDto writerInfo; // 리뷰 작성자 장보 + @JsonProperty("relatedReviews") + private List getRelatedReviewResponseDto; // 연관 리뷰 데이터 + private ReviewLikeInfo reviewLikeInfo; // 리뷰 좋아요 데이터 + private ReviewCommentInfo reviewCommentInfo; // 리뷰 댓글 데이터 + + @Builder + public ReviewDetailResponse(GetReviewDetailResponseDto getReviewDetailResponseDto, + GetUserResponseDto writerInfo, + List getRelatedReviewResponseDto, + ReviewLikeInfo reviewLikeInfo, + ReviewCommentInfo reviewCommentInfo + ) { + this.getReviewDetailResponseDto = getReviewDetailResponseDto; + this.writerInfo = writerInfo; + this.getRelatedReviewResponseDto = getRelatedReviewResponseDto; + this.reviewLikeInfo = reviewLikeInfo; + this.reviewCommentInfo = reviewCommentInfo; + } + + public static ReviewDetailResponse of(Review review, List relatedReviews) { + GetReviewDetailResponseDto reviewDetail = GetReviewDetailResponseDto.from(review); // 리뷰 상세 + GetUserResponseDto writerInfo = GetUserResponseDto.from(review.getWriter()); // 리뷰 작성자 데이터 + List getRelatedReviewResponseDto = GetRelatedReviewResponseDto.from(relatedReviews); // 연관 리뷰 데이터 + ReviewLikeInfo reviewLikeInfo = ReviewLikeInfo.from(review); // 리뷰 좋아요 데이터 + ReviewCommentInfo reviewCommentInfo = ReviewCommentInfo.from(review); // 리뷰 댓글 데이터 + + return ReviewDetailResponse.builder() + .getReviewDetailResponseDto(reviewDetail) + .writerInfo(writerInfo) + .getRelatedReviewResponseDto(getRelatedReviewResponseDto) + .reviewLikeInfo(reviewLikeInfo) + .reviewCommentInfo(reviewCommentInfo) + .build(); + } + + + @Getter + @NoArgsConstructor + public static class GetRelatedReviewResponseDto { + private Long id; + private String productName; + private String reviewImage; + private String writerName; + private boolean isExpert; + + + @Builder + public GetRelatedReviewResponseDto(Long id, String productName, String reviewImage, + String writerName, boolean isExpert + ) { + this.id = id; + this.productName = productName; + this.reviewImage = reviewImage; + this.writerName = writerName; + this.isExpert = isExpert; + } + + public static List from(List reviews) { + return reviews.stream() + .map(r -> new GetRelatedReviewResponseDto( + r.getId(), r.getProductName(), getFirstImageWithReview(r), r.getWriter().getUsername(), r.getWriter().isExpert())) + .collect(Collectors.toList()); + } + + private static String getFirstImageWithReview(Review review) { + List reviewImages = review.getReviewImages(); + return reviewImages.stream() + .filter(ri -> ri.getOrderNum() == 0) + .findFirst() + .orElseThrow(() -> new ReviewHandler(_REVIEW_IMAGE_NOT_FOUND)) + .getImgUrl(); + } + } + + @Getter + @NoArgsConstructor + public static class GetUserResponseDto { + private Long id; + private String nickName; + private Job job; + private String profileImage; + private boolean isExpert; + + @Builder + public GetUserResponseDto(Long id, String nickName, Job job, + String profileImage, boolean isExpert) { + this.id = id; + this.nickName = nickName; + this.job = job; + this.profileImage = profileImage; + this.isExpert = isExpert; + } + + public static ReviewDetailResponse.GetUserResponseDto from(Users writer) { + return ReviewDetailResponse.GetUserResponseDto.builder() + .nickName(writer.getUsersDetail().getNickName()) + .job(writer.getUsersDetail().getJob()) + .isExpert(writer.isExpert()) + .profileImage(writer.getUsersDetail().getProfileImage()) + .build(); + } + } + + /** + * - 좋아요한 유저의 닉네임,프로필,직업 + * - 리뷰의 좋아요 총 갯수 + **/ + @Getter + @NoArgsConstructor + public static class ReviewLikeInfo { + private List reviewLikeUserInfo; + private long reviewLikedCount; + + @Builder + public ReviewLikeInfo(List reviewLikeUserInfo, long reviewLikedCount) { + this.reviewLikeUserInfo = reviewLikeUserInfo; + this.reviewLikedCount = reviewLikedCount; + } + + public static ReviewLikeInfo from(Review review) { + return ReviewLikeInfo.builder() + .reviewLikeUserInfo(review.getReviewLiked() + .stream() + .map(r -> GetUserResponseForLikeAndComment.from(r.getUsers())) + .collect(Collectors.toList())) + .reviewLikedCount(review.getLikedCount()) + .build(); + } + } + + /** + * - 댓글을 단 유저의 닉네임,프로필,직업 (ReviewCommentResponse) + * - 댓글 내용, 댓글 작성일 (ReviewCommentResponse) + * - 댓글 갯수 (ReviewCommentCount) + **/ + @Getter + @NoArgsConstructor + public static class ReviewCommentInfo { + private List reviewCommentResponses; + private long reviewCommentCount; + + @Builder + public ReviewCommentInfo(List reviewCommentResponses, long reviewCommentCount) { + this.reviewCommentResponses = reviewCommentResponses; + this.reviewCommentCount = reviewCommentCount; + } + + public static ReviewCommentInfo from(Review review) { + return ReviewCommentInfo.builder() + .reviewCommentResponses(ReviewCommentResponse.from(review.getReviewComment())) + .reviewCommentCount(review.getReviewComment().size()) + .build(); + } + + @Getter + @NoArgsConstructor + public static class ReviewCommentResponse { + private Long id; + private GetUserResponseForLikeAndComment reviewCommentUserInfo; + private String comment; + private LocalDate commentCreatedAt; + + @Builder + public ReviewCommentResponse(Long id, GetUserResponseForLikeAndComment reviewCommentUserInfo, + String comment, LocalDate commentCreatedAt + ) { + this.id = id; + this.reviewCommentUserInfo = reviewCommentUserInfo; + this.comment = comment; + this.commentCreatedAt = commentCreatedAt; + } + + public static List from(List comments) { + return comments.stream() + .map(c -> ReviewCommentResponse.builder() + .id(c.getId()) + .reviewCommentUserInfo(GetUserResponseForLikeAndComment.from(c.getUsers())) + .comment(c.getComment()) + .commentCreatedAt(c.getCreatedAt().toLocalDate()).build() + ) + .collect(Collectors.toList()); + } + } + } + + /* 리뷰 좋아요, 댓글 작성자를 위한 유저 응답 dto */ + @Getter + @NoArgsConstructor + public static class GetUserResponseForLikeAndComment { + private Long id; + private String nickName; + private Job job; + private String profileImage; + + @Builder + public GetUserResponseForLikeAndComment(Long id, String nickName, Job job, String profileImage) { + this.id = id; + this.nickName = nickName; + this.job = job; + this.profileImage = profileImage; + } + + public static GetUserResponseForLikeAndComment from(Users users) { + return GetUserResponseForLikeAndComment.builder() + .id(users.getId()) + .nickName(users.getUsersDetail().getNickName()) + .job(users.getUsersDetail().getJob()) + .profileImage(users.getUsersDetail().getProfileImage()) + .build(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/example/betteriter/fo_domain/review/dto/ReviewResponse.java b/src/main/java/com/example/betteriter/fo_domain/review/dto/ReviewResponse.java index 7b84cef..9ebfdfe 100644 --- a/src/main/java/com/example/betteriter/fo_domain/review/dto/ReviewResponse.java +++ b/src/main/java/com/example/betteriter/fo_domain/review/dto/ReviewResponse.java @@ -7,18 +7,21 @@ import java.util.List; + @Getter @NoArgsConstructor public class ReviewResponse { @JsonProperty("reviews") private List getReviewResponseDtoList; private boolean hasNext; + private boolean isExisted; @Builder public ReviewResponse(List getReviewResponseDtoList, - boolean hasNext + boolean hasNext, boolean isExisted ) { this.getReviewResponseDtoList = getReviewResponseDtoList; this.hasNext = hasNext; + this.isExisted = isExisted; } -} +} \ No newline at end of file diff --git a/src/main/java/com/example/betteriter/fo_domain/review/exception/ReviewHandler.java b/src/main/java/com/example/betteriter/fo_domain/review/exception/ReviewHandler.java index 461de11..7358322 100644 --- a/src/main/java/com/example/betteriter/fo_domain/review/exception/ReviewHandler.java +++ b/src/main/java/com/example/betteriter/fo_domain/review/exception/ReviewHandler.java @@ -7,4 +7,4 @@ public class ReviewHandler extends GeneralException { public ReviewHandler(ErrorStatus errorStatus) { super(errorStatus); } -} +} \ No newline at end of file diff --git a/src/main/java/com/example/betteriter/fo_domain/review/repository/ReviewRepository.java b/src/main/java/com/example/betteriter/fo_domain/review/repository/ReviewRepository.java index a89f00f..ec98e64 100644 --- a/src/main/java/com/example/betteriter/fo_domain/review/repository/ReviewRepository.java +++ b/src/main/java/com/example/betteriter/fo_domain/review/repository/ReviewRepository.java @@ -7,15 +7,19 @@ import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.List; public interface ReviewRepository extends JpaRepository { List findFirst7ByCategoryOrderByCreatedAtDesc(Category category); - List findReviewByWriter(Users users); - - List findFirst7ByWriterInOrderByCreatedAtDesc(List writers); + /* 특정 회원이 팔로우 하는 팔로잉들이 작성한 모든 리뷰 최신순으로 7개 조회 */ + @Query("SELECT r from REVIEW r WHERE r.writer " + + "IN (SELECT u FROM USERS u WHERE u.id " + + "IN (SELECT f.followee from FOLLOW f where f.follower = :users))" + + "ORDER BY r.createdAt DESC ") + List findFirst7WrittenByFollowingCreatedAtDesc(@Param("users") Users users, Pageable pageable); // sum(count (r.reviewLiked),count(r.reviewScraped)) @Query("SELECT r FROM REVIEW r " + @@ -25,27 +29,20 @@ public interface ReviewRepository extends JpaRepository { "ORDER BY (COALESCE(COUNT(rs), 0) + COALESCE(COUNT(rl), 0)) DESC") List findTop7ReviewHavingMostScrapedAndLiked(Pageable pageable); - @Query("select r from REVIEW r " + - "LEFT JOIN r.reviewLiked rl " + - "LEFT JOIN r.reviewScraped rs " + - "WHERE r.category = :category " + - "GROUP BY r.id " + - "ORDER BY (COALESCE(COUNT(rl),0) + COALESCE(COUNT(rs),0)) DESC, " + - "r.createdAt DESC") - Slice findReviewByCategory(Category category, Pageable pageable); + @Query("SELECT r from REVIEW r WHERE r.category =:category " + + "ORDER BY (r.scrapedCount + r.likedCount) DESC, r.createdAt DESC") + Slice findReviewByCategoryOrderByScrapedCountAndLikedCount(@Param("category") Category category, Pageable pageable); List findFirst20ByOrderByClickCountDescCreatedAtDesc(); Slice findByProductNameOrderByCreatedAtDesc(String name, Pageable pageable); - Slice findFirst20ByProductNameOrderByClickCountDescCreatedAtDesc(String productName, Pageable pageable); @Query("SELECT r FROM REVIEW r " + "LEFT JOIN r.reviewLiked rl " + "LEFT JOIN r.reviewScraped rs " + - "WHERE rs.users = :user AND " + - " r.status = 'ACTIVE'" + + "WHERE rs.users = :user " + "GROUP BY r.id " + "ORDER BY r.createdAt DESC") List findAllByTargetId(Long id); @@ -53,8 +50,7 @@ public interface ReviewRepository extends JpaRepository { @Query("SELECT r FROM REVIEW r " + "LEFT JOIN r.reviewLiked rl " + "LEFT JOIN r.reviewScraped rs " + - "WHERE rs.users = :user AND " + - " r.status != 'DELETED'" + + "WHERE rs.users = :user " + "GROUP BY r.id " + "ORDER BY r.createdAt DESC") List findAllByUser(Long id); @@ -62,8 +58,7 @@ public interface ReviewRepository extends JpaRepository { @Query("SELECT r FROM REVIEW r " + "LEFT JOIN r.reviewLiked rl " + "LEFT JOIN r.reviewScraped rs " + - "WHERE rs.users = :user AND " + - " r.status = 'ACTIVE'" + + "WHERE rs.users = :user " + "GROUP BY r.id " + "ORDER BY r.createdAt DESC") List findAllByReviewScrapedUser(Users user); @@ -71,9 +66,39 @@ public interface ReviewRepository extends JpaRepository { @Query("SELECT r FROM REVIEW r " + "LEFT JOIN r.reviewLiked rl " + "LEFT JOIN r.reviewScraped rs " + - "WHERE rl.users = :user AND " + - " r.status = 'ACTIVE'" + + "WHERE rl.users = :user " + "GROUP BY r.id " + "ORDER BY r.createdAt DESC") List findAllByReviewLikedUser(Users user); -} + + + /* 좋아요 수 많은 리뷰 조회 */ + Slice findByProductNameOrderByLikedCountDescCreatedAtDesc(String name, Pageable pageable); + + /* 스크랩 수 많은 리뷰 조회 */ + Slice findByProductNameOrderByScrapedCountDescCreatedAtDesc(String name, Pageable pageable); + + /* 동일한 제품명 리뷰 조회 (좋아요 + 스크랩 수 많은 순) */ + @Query(value = "select * from review where product_name = :productName " + + "order by (scraped_cnt + liked_cnt) DESC, created_at DESC LIMIT 4", nativeQuery = true) + List findTop4ByProductNameOrderByScrapedCntPlusLikedCntDesc(@Param("productName") String productName); + + /* 같은 스펙을 가지는 리뷰 조회 (좋아요 + 스크랩 수 많은 순) */ + @Query("SELECT r from REVIEW r JOIN r.specData rsd JOIN rsd.specData sd " + + "WHERE rsd.id = :reviewSpecDataId " + + "AND sd.data IN (SELECT sd.data FROM ReviewSpecData rsd JOIN rsd.specData sd WHERE rsd.id = :reviewSpecDataId) " + + "ORDER BY (r.likedCount + r.scrapedCount) DESC") + List findRelatedReviewsByReviewSpecData(@Param("reviewSpecData") Long reviewSpecData, Pageable pageable); + + /* 리뷰의 작성자 팔로워가 많은 순으로 리뷰 조회 */ + @Query("SELECT r from REVIEW r JOIN r.writer w LEFT JOIN FOLLOW f " + + "ON f.followee = w WHERE r.productName = :productName " + + "GROUP BY r ORDER BY COUNT(f.id) DESC ,r.createdAt DESC") + Slice findByProductNameOrderByMostWriterFollower(@Param("productName") String productName, Pageable pageable); + + /* 동일 카테고리 중 좋아요 + 스크랩 많은 순 리뷰 조회 */ + @Query(value = "SELECT * FROM review " + + "WHERE category = :category " + + "ORDER BY (scraped_cnt + liked_cnt) DESC, created_at DESC", nativeQuery = true) + List findByProductNameOrderByScrapedCountAndLikedCountDescCreatedAtDesc(@Param("category") Category category); +} \ No newline at end of file diff --git a/src/main/java/com/example/betteriter/fo_domain/review/service/ReviewService.java b/src/main/java/com/example/betteriter/fo_domain/review/service/ReviewService.java index fdad7c4..814e3c5 100644 --- a/src/main/java/com/example/betteriter/fo_domain/review/service/ReviewService.java +++ b/src/main/java/com/example/betteriter/fo_domain/review/service/ReviewService.java @@ -10,13 +10,13 @@ import com.example.betteriter.fo_domain.review.repository.ReviewImageRepository; import com.example.betteriter.fo_domain.review.repository.ReviewRepository; import com.example.betteriter.fo_domain.review.repository.ReviewSpecDataRepository; -import com.example.betteriter.fo_domain.user.domain.Follow; import com.example.betteriter.fo_domain.user.domain.Users; import com.example.betteriter.fo_domain.user.service.UserService; import com.example.betteriter.global.constant.Category; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -24,7 +24,9 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; +import java.util.stream.Stream; import static com.example.betteriter.global.common.code.status.ErrorStatus._REVIEW_IMAGE_NOT_FOUND; import static com.example.betteriter.global.common.code.status.ErrorStatus._REVIEW_NOT_FOUND; @@ -33,6 +35,7 @@ @RequiredArgsConstructor @Service public class ReviewService { + private static final int SIZE = 7; private final UserService userService; private final SpecService specService; private final ManufacturerService manufacturerService; @@ -41,7 +44,6 @@ public class ReviewService { private final ReviewImageRepository reviewImageRepository; private final ReviewSpecDataRepository reviewSpecDataRepository; - /* 리뷰 등록 */ @Transactional public Long createReview(CreateReviewRequestDto request) { @@ -66,41 +68,93 @@ public GetReviewSpecResponseDto getReviewSpecDataResponse(Category category) { /* 카테고리에 해당하는 리뷰 조회 */ @Transactional(readOnly = true) - public ReviewResponse getReviewByCategory(Category category) { - Slice result = this.reviewRepository.findReviewByCategory(category, PageRequest.of(0, 5)); + public ReviewResponse getReviewByCategory(Category category, int page) { + Slice result + = this.reviewRepository.findReviewByCategoryOrderByScrapedCountAndLikedCount(category, PageRequest.of(page, SIZE)); + List reviewResponse = result.getContent().stream() .map(GetReviewResponseDto::of) .collect(Collectors.toList()); - return new ReviewResponse(reviewResponse, result.hasNext()); + + return new ReviewResponse(reviewResponse, result.hasNext(), !result.isEmpty()); } /** - * - 이름에 해당하는 리뷰 조회 + * - 상품 명 + 필터링 리뷰 조회 * case 01 : 없다면 7일 동안 유저들이 많이 클릭한 리뷰 20개 리턴 * case 02 : 있다면 최신순 리뷰 리스트 응답 **/ @Transactional(readOnly = true) - public ReviewResponse getReviewBySearch(String name) { - - // 1. 이름에 해당하는 최신순 리뷰 조회 - Slice latestReview - = this.reviewRepository.findByProductNameOrderByCreatedAtDesc(name, PageRequest.of(0, 5)); + public ReviewResponse getReviewBySearch(String name, String sort, int page) { + // 1. 필터링 따른 상품 이름에 해당하는 리뷰 조회 + Slice reviews = getReviews(name, sort, page); // 2. 데이터 갯수 null 인 경우 - if (latestReview.isEmpty()) { + if (Objects.requireNonNull(reviews).isEmpty()) { List result = this.reviewRepository.findFirst20ByOrderByClickCountDescCreatedAtDesc() .stream() .map(GetReviewResponseDto::of) .collect(Collectors.toList()); - return new ReviewResponse(result, false); + return new ReviewResponse(result, false, false); } // 3. 데이터 갯수 null 아닌 경우 - List getReviewResponseDtos = latestReview.getContent().stream() + List getReviewResponseDtos = reviews.getContent().stream() .map(GetReviewResponseDto::of) .collect(Collectors.toList()); - return new ReviewResponse(getReviewResponseDtos, latestReview.hasNext()); + return new ReviewResponse(getReviewResponseDtos, reviews.hasNext(), true); + } + + /* 리뷰 상세 조회 */ + @Transactional(readOnly = true) + public ReviewDetailResponse getReviewDetail(Long reviewId) { + // 1. reviewId 에 해당하는 리뷰 상세 데이터 + Review review = this.findReviewById(reviewId); + + // 2. 동일한 제품명 리뷰 조회(4) + List relatedReviews + = this.reviewRepository.findTop4ByProductNameOrderByScrapedCntPlusLikedCntDesc(review.getProductName()); + + if (relatedReviews.size() == 4) { + return ReviewDetailResponse.of(review, relatedReviews); + } + int remain = 4 - relatedReviews.size(); + // 3. 동일한 카테고리 중 좋아요 + 스크랩 순 정렬 조회 (나머지) + List restRelatedReviews + = this.reviewRepository.findReviewByCategoryOrderByScrapedCountAndLikedCount(review.getCategory(), PageRequest.of(0, remain)).getContent(); + // 4. 관련 리뷰 결과 + List totalRelatedReviews = Stream.concat(relatedReviews.stream(), restRelatedReviews.stream()) + .collect(Collectors.toList()); + + return ReviewDetailResponse.of(review, totalRelatedReviews); + } + + private Slice getReviews(String name, String sort, int page) { + Slice reviews = null; + Pageable pageable = PageRequest.of(page, SIZE); + switch (sort) { + // 최신순 + case "latest": + reviews = this.reviewRepository + .findByProductNameOrderByCreatedAtDesc(name, pageable); + break; + // 좋아요 많은 순 + case "mostLiked": + reviews = this.reviewRepository + .findByProductNameOrderByLikedCountDescCreatedAtDesc(name, pageable); + break; + // 스크랩 많은 순 + case "mostScraped": + reviews = this.reviewRepository + .findByProductNameOrderByScrapedCountDescCreatedAtDesc(name, pageable); + + // 리뷰 작성자의 팔로워가 많은 순 + case "mostFollowers": + reviews = this.reviewRepository.findByProductNameOrderByMostWriterFollower(name, pageable); + break; + } + return reviews; } private List getReviewSpecData(CreateReviewRequestDto request, Review review) { @@ -129,15 +183,12 @@ private Map> getLatest7ReviewsByCategories(List< return result; } - /* 팔로우 하는 유저가 등록한 리뷰 리스트 조회 메소드 */ + /* 팔로우 하는 유저가 등록한 리뷰 리스트 최신순 7개 조회 메소드 */ public List getFollowingReviews() { Users users = this.getCurrentUser(); - List followee = users.getFollowing().stream() - .map(Follow::getFollowee) - .collect(Collectors.toList()); - - return this.reviewRepository.findFirst7ByWriterInOrderByCreatedAtDesc(followee) - .stream().map(review -> review.of(this.getFirstImageWithReview(review))) + return this.reviewRepository.findFirst7WrittenByFollowingCreatedAtDesc(users, PageRequest.of(0, SIZE)) + .stream() + .map(review -> review.of(this.getFirstImageWithReview(review))) .collect(Collectors.toList()); } @@ -189,4 +240,4 @@ public List getLikeReviewList(Users user) { public List getTargetReviewList(Long id) { return this.reviewRepository.findAllByTargetId(id); } -} +} \ No newline at end of file diff --git a/src/main/java/com/example/betteriter/fo_domain/user/domain/Follow.java b/src/main/java/com/example/betteriter/fo_domain/user/domain/Follow.java index ee083b9..a43ebc0 100644 --- a/src/main/java/com/example/betteriter/fo_domain/user/domain/Follow.java +++ b/src/main/java/com/example/betteriter/fo_domain/user/domain/Follow.java @@ -1,7 +1,10 @@ package com.example.betteriter.fo_domain.user.domain; -import lombok.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import javax.persistence.*; @@ -13,8 +16,6 @@ @Slf4j @Getter -@Builder -@AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity(name = "FOLLOW") public class Follow { @@ -30,4 +31,9 @@ public class Follow { @JoinColumn(name = "followee") private Users followee; // 팔로우 당하는 유저 id ( 상대방 ) -} + @Builder + private Follow(Users follower, Users followee) { + this.follower = follower; + this.followee = followee; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/betteriter/fo_domain/user/domain/Users.java b/src/main/java/com/example/betteriter/fo_domain/user/domain/Users.java index 5410a0b..3e47e54 100644 --- a/src/main/java/com/example/betteriter/fo_domain/user/domain/Users.java +++ b/src/main/java/com/example/betteriter/fo_domain/user/domain/Users.java @@ -6,10 +6,7 @@ import com.example.betteriter.global.common.entity.BaseEntity; import com.example.betteriter.global.constant.Category; import com.example.betteriter.global.constant.RoleType; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import lombok.extern.slf4j.Slf4j; import org.hibernate.annotations.DynamicInsert; import org.springframework.security.core.GrantedAuthority; @@ -38,6 +35,7 @@ public class Users extends BaseEntity implements UserDetails { @Column(name = "usr_email", nullable = false, unique = true) private String email; + @Setter @Column(name = "usr_pwd", unique = true) private String password; @@ -55,12 +53,6 @@ public class Users extends BaseEntity implements UserDetails { @Enumerated(EnumType.STRING) private List categories; - @OneToMany(mappedBy = "followee") - private List following; // 회원이 팔로잉 하는 유저 리스트 - - @OneToMany(mappedBy = "follower") - private List follower; // 회원을 팔로잉 하는 유저 리스트 - @OneToMany(mappedBy = "users", cascade = CascadeType.ALL) private List reviewScraps; // 유저가 스크랩한 리뷰 @@ -70,13 +62,13 @@ public class Users extends BaseEntity implements UserDetails { @OneToMany(mappedBy = "writer", cascade = CascadeType.ALL) private List reviews; // 유저가 작성한 리뷰 + @Setter @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) private UsersDetail usersDetail; // 유저 상세 정보 @Builder protected Users(Long id, String oauthId, String email, String password, RoleType roleType, boolean isExpert, List categories, - List following, List follower, List reviewScraps, List reviewLikes, List reviews, UsersDetail usersDetail ) { @@ -87,8 +79,6 @@ protected Users(Long id, String oauthId, String email, String password, RoleType this.roleType = roleType; this.isExpert = isExpert; this.categories = categories; - this.following = following; - this.follower = follower; this.reviewScraps = reviewScraps; this.reviewLikes = reviewLikes; this.reviews = reviews; @@ -130,15 +120,7 @@ public boolean isEnabled() { return true; } - public void setPassword(String password) { - this.password = password; - } - - public void setUsersDetail(UsersDetail usersDetail) { - this.usersDetail = usersDetail; - } - public void setUsersCategory(List categories) { this.categories = categories; } -} +} \ No newline at end of file diff --git a/src/main/java/com/example/betteriter/fo_domain/user/domain/UsersDetail.java b/src/main/java/com/example/betteriter/fo_domain/user/domain/UsersDetail.java index b5fdce7..df9f090 100644 --- a/src/main/java/com/example/betteriter/fo_domain/user/domain/UsersDetail.java +++ b/src/main/java/com/example/betteriter/fo_domain/user/domain/UsersDetail.java @@ -2,15 +2,16 @@ import com.example.betteriter.global.common.entity.BaseEntity; import com.example.betteriter.global.constant.Job; -import lombok.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import javax.persistence.*; @Slf4j @Getter -@Builder -@AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity(name = "USERS_DETAIL") public class UsersDetail extends BaseEntity { @@ -18,11 +19,11 @@ public class UsersDetail extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(name = "usr_nickname", unique = true) + @Column(name = "usr_nickname", unique = true, nullable = false) private String nickName; @Enumerated(EnumType.STRING) - @Column(name = "usr_job") + @Column(name = "usr_job", nullable = false) private Job job; @Column(name = "usr_profile_img") @@ -42,4 +43,18 @@ public class UsersDetail extends BaseEntity { @Column(name = "usr_review_liked") private int reviewLiked; // 작성한 리뷰 좋아요 총 횟수 -} + + @Builder + private UsersDetail(String nickName, Job job, String profileImage, + int point, int reviewCount, int quizCount, + int reviewScraped, int reviewLiked) { + this.nickName = nickName; + this.job = job; + this.profileImage = profileImage; + this.point = point; + this.reviewCount = reviewCount; + this.quizCount = quizCount; + this.reviewScraped = reviewScraped; + this.reviewLiked = reviewLiked; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/betteriter/fo_domain/user/dto/info/GetUserInfoResponseDto.java b/src/main/java/com/example/betteriter/fo_domain/user/dto/info/GetUserInfoResponseDto.java index 050bd7c..bda1c4c 100644 --- a/src/main/java/com/example/betteriter/fo_domain/user/dto/info/GetUserInfoResponseDto.java +++ b/src/main/java/com/example/betteriter/fo_domain/user/dto/info/GetUserInfoResponseDto.java @@ -27,7 +27,8 @@ private GetUserInfoResponseDto(String nickName, Job job, this.isExpert = isExpert; } - public static GetUserInfoResponseDto from(Users users, UsersDetail usersDetail) { + public static GetUserInfoResponseDto from(Users users) { + UsersDetail usersDetail = users.getUsersDetail(); return GetUserInfoResponseDto.builder() .nickName(usersDetail.getNickName()) .job(usersDetail.getJob()) @@ -35,4 +36,4 @@ public static GetUserInfoResponseDto from(Users users, UsersDetail usersDetail) .isExpert(users.isExpert()) .build(); } -} +} \ No newline at end of file diff --git a/src/main/java/com/example/betteriter/fo_domain/user/service/UserService.java b/src/main/java/com/example/betteriter/fo_domain/user/service/UserService.java index 7375bd2..db5a4a4 100644 --- a/src/main/java/com/example/betteriter/fo_domain/user/service/UserService.java +++ b/src/main/java/com/example/betteriter/fo_domain/user/service/UserService.java @@ -43,7 +43,7 @@ public Long logout() { @Transactional(readOnly = true) public GetUserInfoResponseDto getUserInfo() { Users currentUser = this.getCurrentUser(); - return GetUserInfoResponseDto.from(currentUser, currentUser.getUsersDetail()); + return GetUserInfoResponseDto.from(currentUser); } @@ -92,4 +92,4 @@ public Users getUserByEmail(String email) { return this.usersRepository.findByEmail(email) .orElseThrow(() -> new UserHandler(ErrorStatus._USER_NOT_FOUND)); } -} +} \ No newline at end of file diff --git a/src/test/java/com/example/betteriter/fo_domain/review/controller/ReviewControllerTest.java b/src/test/java/com/example/betteriter/fo_domain/review/controller/ReviewControllerTest.java index ba49e9a..8b02841 100644 --- a/src/test/java/com/example/betteriter/fo_domain/review/controller/ReviewControllerTest.java +++ b/src/test/java/com/example/betteriter/fo_domain/review/controller/ReviewControllerTest.java @@ -1,12 +1,13 @@ package com.example.betteriter.fo_domain.review.controller; import com.example.betteriter.fo_domain.review.domain.Review; -import com.example.betteriter.fo_domain.review.dto.GetReviewResponseDto; -import com.example.betteriter.fo_domain.review.dto.ReviewResponse; +import com.example.betteriter.fo_domain.review.dto.*; +import com.example.betteriter.fo_domain.review.dto.CreateReviewRequestDto.CreateReviewImageRequestDto; import com.example.betteriter.fo_domain.review.service.ReviewService; import com.example.betteriter.fo_domain.user.domain.Users; import com.example.betteriter.fo_domain.user.domain.UsersDetail; import com.example.betteriter.global.config.security.SecurityConfig; +import com.example.betteriter.global.constant.Category; import com.example.betteriter.global.constant.Job; import com.example.betteriter.global.filter.JwtAuthenticationFilter; import com.example.betteriter.global.util.JwtUtil; @@ -22,12 +23,20 @@ import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; +import java.time.LocalDate; import java.util.List; import static com.example.betteriter.global.constant.Category.PC; -import static org.mockito.ArgumentMatchers.anyString; +import static com.example.betteriter.global.constant.RoleType.ROLE_USER; +import static com.example.betteriter.global.constant.Status.ACTIVE; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -68,7 +77,7 @@ void getReviewsBySearchSuccessfulTest() throws Exception { .id(1L) .category(PC) .productName("productName") - .amount(10000) + .price(10000) .storeName(1) .starPoint(1.0) .badPoint("badPoint") @@ -80,12 +89,12 @@ void getReviewsBySearchSuccessfulTest() throws Exception { .firstImage("firstImage") .build(); - given(reviewService.getReviewBySearch(anyString())) + given(reviewService.getReviewBySearch(anyString(), anyString(), anyInt())) .willReturn(ReviewResponse.builder() .getReviewResponseDtoList(List.of(getReviewResponseDto)) .hasNext(false).build()); - ReviewResponse reviewResponse = new ReviewResponse(List.of(getReviewResponseDto), true); + ReviewResponse reviewResponse = new ReviewResponse(List.of(getReviewResponseDto), true, true); // when && then mockMvc.perform(get("/review/search") .param("name", "productName")) @@ -93,6 +102,7 @@ void getReviewsBySearchSuccessfulTest() throws Exception { .andExpect(jsonPath("$.isSuccess").value(true)) .andExpect(jsonPath("$.code").value("SUCCESS_200")) .andExpect(jsonPath("$.message").value("OK")) + .andExpect(jsonPath("$.data.existed").value(true)) .andDo(print()); } @@ -114,7 +124,7 @@ void getReviewsBySearchFailureTest() throws Exception { .id(1L) .category(PC) .productName("productName") - .amount(10000) + .price(10000) .storeName(1) .starPoint(1.0) .badPoint("badPoint") @@ -126,16 +136,305 @@ void getReviewsBySearchFailureTest() throws Exception { .firstImage("firstImage") .build(); - given(reviewService.getReviewBySearch(anyString())) + given(reviewService.getReviewBySearch(anyString(), anyString(), anyInt())) .willReturn(ReviewResponse.builder() .getReviewResponseDtoList(List.of(getReviewResponseDto)) .hasNext(false).build()); - ReviewResponse reviewResponse = new ReviewResponse(List.of(getReviewResponseDto), true); + ReviewResponse reviewResponse = new ReviewResponse(List.of(getReviewResponseDto), true, true); // when && then mockMvc.perform(get("/review/search") .param("name", (String) null)) .andDo(print()) .andExpect(status().isBadRequest()); } + + @Test + @WithMockUser(username = "choi", roles = "USER") + @DisplayName("리뷰를 성공적으로 등록한다.") + void createReviewSuccessfully() throws Exception { + // given + CreateReviewRequestDto requestDto + = CreateReviewRequestDto.builder() + .category(PC) + .productName("productName") + .boughtAt(LocalDate.now()) + .manufacturer("삼성") + .price(1000) + .storeName(1) + .shortReview("shortReview") + .starPoint(2) + .goodPoint("goodPoint") + .badPoint("badPoint") + .specData(List.of(1L, 2L)) + .images(List.of(CreateReviewImageRequestDto.builder().imgUrl("imgUrl").build())) + .build(); + + given(this.reviewService.createReview(any(CreateReviewRequestDto.class))) + .willReturn(1L); // 생성된 리뷰 id + + + // when & then + mockMvc.perform(post("/review") + .with(csrf()) + .content(objectMapper.writeValueAsString(requestDto)) + .contentType(APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("SUCCESS_200")) + .andExpect(jsonPath("$.isSuccess").value(true)) + .andExpect(jsonPath("$.result").value(1L)); + } + + @Test + @WithMockUser + @DisplayName("변경된 상품명 리뷰 조회 테스트를 진행한다.") + void getReviewByProductNameSuccessfully() throws Exception { + // given + Users user = Users.builder() + .usersDetail(UsersDetail.builder() + .nickName("nickName") + .profileImage("profileImage") + .job(Job.DEVELOPER) + .build()) + .build(); + + Review review = Review.builder() + .writer(user) + .id(1L) + .category(PC) + .productName("productName") + .price(10000) + .storeName(1) + .starPoint(1.0) + .badPoint("badPoint") + .build(); + + GetReviewResponseDto getReviewResponseDto = GetReviewResponseDto.builder() + .review(review) + .reviewSpecData(List.of("string", "string02")) + .firstImage("firstImage") + .build(); + + given(reviewService.getReviewBySearch(anyString(), anyString(), anyInt())) + .willReturn(ReviewResponse.builder() + .getReviewResponseDtoList(List.of(getReviewResponseDto)) + .hasNext(false) + .isExisted(true) + .build()); + // when + mockMvc.perform(get("/review/search") + .param("name", "productName") + .param("sort", "mostLiked") + .contentType(APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.isSuccess").value(true)) + .andExpect(jsonPath("$.code").value("SUCCESS_200")) + .andExpect(jsonPath("$.message").value("OK")) + .andExpect(jsonPath("$.result.existed").value(true)); + // then + verify(this.reviewService, times(1)).getReviewBySearch(anyString(), anyString(), anyInt()); + } + + @Test + @WithMockUser + @DisplayName("카테고리별 좋아요 + 스크랩 수로 리뷰 조회 컨트롤러 테스트") + void getReviewByCategoryControllerTest() throws Exception { + // given + Users users = Users.builder() + .email("email") + .roleType(ROLE_USER) + .usersDetail(UsersDetail.builder().nickName("nickname").job(Job.DEVELOPER).build()) + .build(); + + Review review = Review.builder() + .writer(users) + .category(PC) + .productName("productName") + .category(PC) + .price(10) + .storeName(1) + .status(ACTIVE) + .boughtAt(LocalDate.now()) + .starPoint(1) + .goodPoint("goodPoint") + .badPoint("badPoint") + .shortReview("short") + .build(); + + List getReviewResponseList = + List.of(GetReviewResponseDto.builder() + .review(review) + .reviewSpecData(List.of("s", "2")) + .firstImage("firstImage") + .build()); + + ReviewResponse reviewResponse = ReviewResponse.builder() + .getReviewResponseDtoList(getReviewResponseList) + .hasNext(false) + .isExisted(true) + .build(); + + given(this.reviewService.getReviewByCategory(any(Category.class), anyInt())) + .willReturn(reviewResponse); + // when & then + mockMvc.perform(get("/review/category") + .param("category", "PC") + .param("page", "2")) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.isSuccess").value(true)) + .andExpect(jsonPath("$.code").value("SUCCESS_200")) + .andExpect(jsonPath("$.result.hasNext").value(false)); + + } + + @Test + @WithMockUser + @DisplayName("상품 명 + 필터링 리뷰 조회 컨트롤러 API 변동으로 인한 테스트을 진행한다.") + void getReviewsBySearchTest() throws Exception { + // given + Users users = Users.builder() + .email("email") + .roleType(ROLE_USER) + .usersDetail(UsersDetail.builder().nickName("nickname").job(Job.DEVELOPER).build()) + .build(); + + Review review = Review.builder() + .writer(users) + .category(PC) + .productName("productName") + .category(PC) + .price(10) + .storeName(1) + .status(ACTIVE) + .boughtAt(LocalDate.now()) + .starPoint(1) + .goodPoint("goodPoint") + .badPoint("badPoint") + .shortReview("short") + .build(); + + List getReviewResponseList = + List.of(GetReviewResponseDto.builder() + .review(review) + .reviewSpecData(List.of("s", "2")) + .firstImage("firstImage") + .build()); + + ReviewResponse reviewResponse = ReviewResponse.builder() + .getReviewResponseDtoList(getReviewResponseList) + .hasNext(false) + .isExisted(true) + .build(); + + given(this.reviewService.getReviewBySearch(anyString(), anyString(), anyInt())) + .willReturn(reviewResponse); + // when & then + mockMvc.perform(get("/review/search") + .param("name", "name") + .param("sort", "mostScraped") + .param("page", "2")) + .andDo(print()) + .andExpect(status().isOk()); + } + + @Test + @WithMockUser + @DisplayName("리뷰 상세 조회 컨트롤러 테스트를 진행한다.") + void getReviewDetailControllerTest() throws Exception { + // given + Users users = Users.builder() + .email("email") + .roleType(ROLE_USER) + .usersDetail(UsersDetail.builder().nickName("nickname").job(Job.DEVELOPER).build()) + .build(); + + Review review = Review.builder() + .writer(users) + .category(PC) + .productName("productName") + .category(PC) + .price(10) + .storeName(1) + .status(ACTIVE) + .boughtAt(LocalDate.now()) + .starPoint(1) + .goodPoint("goodPoint") + .badPoint("badPoint") + .shortReview("short") + .build(); + + GetReviewDetailResponseDto getReviewDetailResponseDto = GetReviewDetailResponseDto.builder() + .id(1L) + .productName("productName") + .reviewSpecData(List.of("spec1", "spec2")) + .starPoint(2.0) + .goodPoint("goodPoint") + .badPoint("badPoint") + .shortReview("shortReview") + .manufacturer("삼성") + .storeName(1) + .boughtAt(LocalDate.of(2023, 12, 12)) + .createdAt(LocalDate.of(2023, 12, 22)) + .reviewImages(List.of(GetReviewImageResponseDto.builder().imgUrl("imageUrl").orderNum(1).build())) + .scrapedCount(2L) + .build(); + + ReviewDetailResponse.GetUserResponseDto writerInfo = ReviewDetailResponse.GetUserResponseDto.builder() + .id(1L) + .nickName("nickName") + .job(Job.DEVELOPER) + .profileImage("profileImage") + .isExpert(true) + .build(); + + List getRelatedReviewResponseDtos + = List.of(ReviewDetailResponse.GetRelatedReviewResponseDto.builder().productName("productName") + .reviewImage("reviewImage") + .writerName("동근") + .isExpert(true).build()); + + ReviewDetailResponse.ReviewLikeInfo reviewLikeInfo = ReviewDetailResponse.ReviewLikeInfo.builder() + .reviewLikeUserInfo(List.of(ReviewDetailResponse.GetUserResponseForLikeAndComment.builder() + .nickName("nickName") + .job(Job.DEVELOPER) + .profileImage("profileImage") + .build())) + .reviewLikedCount(2L) + .build(); + + ReviewDetailResponse.ReviewCommentInfo reviewCommentInfo = ReviewDetailResponse.ReviewCommentInfo.builder(). + reviewCommentCount(2L) + .reviewCommentResponses(List.of(ReviewDetailResponse.ReviewCommentInfo.ReviewCommentResponse.builder() + .reviewCommentUserInfo(ReviewDetailResponse.GetUserResponseForLikeAndComment.builder() + .nickName("nickName") + .job(Job.DEVELOPER) + .profileImage("profileImage") + .build()) + .comment("comment") + .commentCreatedAt(LocalDate.now()) + .build())) + .build(); + + ReviewDetailResponse response = ReviewDetailResponse.builder() + .getReviewDetailResponseDto(getReviewDetailResponseDto) + .writerInfo(writerInfo) + .getRelatedReviewResponseDto(getRelatedReviewResponseDtos) + .reviewLikeInfo(reviewLikeInfo) + .reviewCommentInfo(reviewCommentInfo) + .build(); + + given(this.reviewService.getReviewDetail(anyLong())) + .willReturn(response); + // when & then + mockMvc.perform(get("/review/detail/{reviewId}", 1L)) + .andExpect(status().isOk()) + .andDo(print()) + .andExpect(jsonPath("$.isSuccess").value(true)) + .andExpect(jsonPath("$.result.writerInfo.id").value(1)) + .andExpect(jsonPath("$.result.relatedReviews[0].productName").value("productName")); + } + } \ No newline at end of file diff --git a/src/test/java/com/example/betteriter/fo_domain/review/repository/ReviewRepositoryTest.java b/src/test/java/com/example/betteriter/fo_domain/review/repository/ReviewRepositoryTest.java index bec1f85..4a15dd0 100644 --- a/src/test/java/com/example/betteriter/fo_domain/review/repository/ReviewRepositoryTest.java +++ b/src/test/java/com/example/betteriter/fo_domain/review/repository/ReviewRepositoryTest.java @@ -3,15 +3,17 @@ import com.example.betteriter.fo_domain.review.domain.Review; import com.example.betteriter.fo_domain.review.domain.ReviewLike; import com.example.betteriter.fo_domain.review.domain.ReviewScrap; +import com.example.betteriter.fo_domain.user.domain.Follow; import com.example.betteriter.fo_domain.user.domain.Users; +import com.example.betteriter.fo_domain.user.repository.FollowRepository; import com.example.betteriter.fo_domain.user.repository.UsersRepository; -import com.example.betteriter.global.constant.RoleType; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; import org.springframework.test.context.ActiveProfiles; import javax.persistence.EntityManager; @@ -20,6 +22,8 @@ import java.util.Optional; import static com.example.betteriter.global.constant.Category.PC; +import static com.example.betteriter.global.constant.RoleType.ROLE_USER; +import static com.example.betteriter.global.constant.Status.ACTIVE; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace.NONE; @@ -43,14 +47,23 @@ public class ReviewRepositoryTest { @Autowired private UsersRepository usersRepository; + @Autowired + private FollowRepository followRepository; + + private static Review createReview(long count) { + return Review.builder() .category(PC) .productName("productName") - .amount(10) + .category(PC) + .price(10) .storeName(1) + .status(ACTIVE) .boughtAt(LocalDate.now()) .starPoint(1) + .likedCount(count) + .scrapedCount(count) .goodPoint("goodPoint") .badPoint("badPoint") .clickCount(count) @@ -66,7 +79,7 @@ void findFirst7ByCategoryOrderByCreatedAtDesc() { Review review01 = Review.builder() .category(PC) .productName("productName01") - .amount(100000) + .price(100000) .storeName(1) .boughtAt(LocalDate.now()) .starPoint(0) @@ -78,7 +91,7 @@ void findFirst7ByCategoryOrderByCreatedAtDesc() { Review review02 = Review.builder() .category(PC) .productName("productName02") - .amount(100000) + .price(100000) .storeName(1) .boughtAt(LocalDate.now()) .starPoint(0) @@ -90,7 +103,7 @@ void findFirst7ByCategoryOrderByCreatedAtDesc() { Review review03 = Review.builder() .category(PC) .productName("productName03") - .amount(100000) + .price(100000) .storeName(1) .boughtAt(LocalDate.now()) .starPoint(0) @@ -116,7 +129,7 @@ void findTop7ReviewHavingMostScrapedAndLikedTest() { .oauthId("oauthId") .email("danaver12@daum.net") .password("1234") - .roleType(RoleType.ROLE_USER) + .roleType(ROLE_USER) .isExpert(true) .build(); @@ -124,7 +137,7 @@ void findTop7ReviewHavingMostScrapedAndLikedTest() { .oauthId("oauthId02") .email("whole34@naver.com") .password("5678") - .roleType(RoleType.ROLE_USER) + .roleType(ROLE_USER) .isExpert(true) .build(); @@ -132,7 +145,7 @@ void findTop7ReviewHavingMostScrapedAndLikedTest() { .oauthId("oauthId03") .email("whole12@naver.com") .password("9999") - .roleType(RoleType.ROLE_USER) + .roleType(ROLE_USER) .isExpert(true) .build(); @@ -142,7 +155,7 @@ void findTop7ReviewHavingMostScrapedAndLikedTest() { Review review01 = Review.builder() .category(PC) .productName("productName01") - .amount(100000) + .price(100000) .storeName(1) .boughtAt(LocalDate.now()) .starPoint(0) @@ -154,7 +167,7 @@ void findTop7ReviewHavingMostScrapedAndLikedTest() { Review review02 = Review.builder() .category(PC) .productName("productName02") - .amount(100000) + .price(100000) .storeName(1) .boughtAt(LocalDate.now()) .starPoint(0) @@ -166,7 +179,7 @@ void findTop7ReviewHavingMostScrapedAndLikedTest() { Review review03 = Review.builder() .category(PC) .productName("productName01") - .amount(100000) + .price(100000) .storeName(1) .boughtAt(LocalDate.now()) .starPoint(0) @@ -178,7 +191,7 @@ void findTop7ReviewHavingMostScrapedAndLikedTest() { Review review04 = Review.builder() .category(PC) .productName("productName01") - .amount(100000) + .price(100000) .storeName(1) .boughtAt(LocalDate.now()) .starPoint(0) @@ -239,7 +252,7 @@ void findReviewsByCategoryByDesc() { .oauthId("oauthId") .email("danaver12@daum.net") .password("1234") - .roleType(RoleType.ROLE_USER) + .roleType(ROLE_USER) .isExpert(true) .build(); @@ -247,7 +260,7 @@ void findReviewsByCategoryByDesc() { .oauthId("oauthId02") .email("danaver12@naver.net") .password("5678") - .roleType(RoleType.ROLE_USER) + .roleType(ROLE_USER) .isExpert(true) .build(); @@ -255,7 +268,7 @@ void findReviewsByCategoryByDesc() { .oauthId("oauthId03") .email("danaver12@kakao.net") .password("11111") - .roleType(RoleType.ROLE_USER) + .roleType(ROLE_USER) .isExpert(true) .build(); @@ -265,7 +278,7 @@ void findReviewsByCategoryByDesc() { Review review01 = Review.builder() .category(PC) .productName("productName01") - .amount(100000) + .price(100000) .storeName(1) .boughtAt(LocalDate.now()) .starPoint(0) @@ -277,7 +290,7 @@ void findReviewsByCategoryByDesc() { Review review02 = Review.builder() .category(PC) .productName("productName02") - .amount(100000) + .price(100000) .storeName(1) .boughtAt(LocalDate.now()) .starPoint(0) @@ -289,7 +302,7 @@ void findReviewsByCategoryByDesc() { Review review03 = Review.builder() .category(PC) .productName("productName03") - .amount(100000) + .price(100000) .storeName(1) .boughtAt(LocalDate.now()) .starPoint(0) @@ -301,7 +314,7 @@ void findReviewsByCategoryByDesc() { Review review04 = Review.builder() .category(PC) .productName("productName04") - .amount(100000) + .price(100000) .storeName(1) .boughtAt(LocalDate.now()) .starPoint(0) @@ -394,7 +407,7 @@ void findReviewsByCategoryByDesc() { System.out.println("jpql 실행"); List result - = this.reviewRepository.findReviewByCategory(PC, PageRequest.of(0, 5)).getContent(); + = this.reviewRepository.findReviewByCategoryOrderByScrapedCountAndLikedCount(PC, PageRequest.of(0, 5)).getContent(); System.out.println("jpql 실행 끝"); // then @@ -413,7 +426,7 @@ void testjpa() { .oauthId("oauthId") .email("danaver12@daum.net") .password("1234") - .roleType(RoleType.ROLE_USER) + .roleType(ROLE_USER) .isExpert(true) .build(); @@ -423,7 +436,7 @@ void testjpa() { Review review01 = Review.builder() .category(PC) .productName("productName01") - .amount(100000) + .price(100000) .storeName(1) .boughtAt(LocalDate.now()) .starPoint(0) @@ -492,4 +505,271 @@ void test() { System.out.println(result); } } + + @Test + @DisplayName("상품명에 해당하는 리뷰를 좋아요 수가 많은 순으로 조회한다.") + void findByProductNameOrderByLikedCountDescAndCreatedAtDesc() { + // given + Review review01 = createReview(1L); + Review review02 = createReview(2L); + Review review03 = createReview(3L); + Review review04 = createReview(4L); + Review review05 = createReview(5L); + Review review06 = createReview(6L); + Review review07 = createReview(7L); + Review review08 = createReview(8L); + Review review09 = createReview(9L); + Review review10 = createReview(10L); + Review review11 = createReview(11L); + Review review12 = createReview(12L); + Review review13 = createReview(13L); + Review review14 = createReview(14L); + Review review15 = createReview(15L); + Review review16 = createReview(16L); + + this.reviewRepository.saveAll(List.of(review01, review02, review03, review04, review05, + review06, review07, review08, review09, review10, review11, review12, review13, review14, review15, review16)); + // when + + Slice result = this.reviewRepository.findByProductNameOrderByLikedCountDescCreatedAtDesc("productName", PageRequest.of(0, 20)); + // then + System.out.println(result.getContent().size()); + } + + @Test + @DisplayName("상품명에 해당하는 리뷰의 스크랩 수가 많은 순으로 리뷰를 조회한다.") + void findByProductNameAndStatusOrderByScrapedCountDescCreatedAtDesc() { + // given + Review review01 = createReview(1L); + Review review02 = createReview(2L); + Review review03 = createReview(3L); + Review review04 = createReview(4L); + Review review05 = createReview(5L); + Review review06 = createReview(6L); + Review review07 = createReview(7L); + Review review08 = createReview(8L); + Review review09 = createReview(9L); + Review review10 = createReview(10L); + Review review11 = createReview(11L); + Review review12 = createReview(12L); + Review review13 = createReview(13L); + Review review14 = createReview(14L); + Review review15 = createReview(15L); + Review review16 = createReview(16L); + + this.reviewRepository.saveAll(List.of(review01, review02, review03, review04, review05, + review06, review07, review08, review09, review10, review11, review12, review13, review14, review15, review16)); + + // when + Slice result = this.reviewRepository.findByProductNameOrderByScrapedCountDescCreatedAtDesc("productName", PageRequest.of(0, 20)); + + // then + System.out.println(result.getContent().size()); + } + + @Test + @DisplayName("동일한 상품명 리뷰 조회하되 좋아요 + 스크랩 많은 top 4 을 조회한다.") + void findTop4ByProductNameOrderByLikedCountAndScrapedCountTest() { + // given + Review review01 = createReview(1L); + Review review02 = createReview(2L); + Review review03 = createReview(3L); + Review review04 = createReview(4L); + Review review05 = createReview(5L); + Review review06 = createReview(6L); + Review review07 = createReview(7L); + Review review08 = createReview(8L); + Review review09 = createReview(9L); + Review review10 = createReview(10L); + Review review11 = createReview(11L); + Review review12 = createReview(12L); + Review review13 = createReview(13L); + Review review14 = createReview(14L); + Review review15 = createReview(15L); + Review review16 = createReview(16L); + + this.reviewRepository.saveAll(List.of(review01, review02, review03, review04, review05, + review06, review07, review08, review09, review10, review11, review12, review13, review14, review15, review16)); + // when + + List result = this.reviewRepository.findTop4ByProductNameOrderByScrapedCntPlusLikedCntDesc("productName"); + + // then + System.out.println(result.size()); + } + + @Test + @DisplayName("동일한 상품명 리뷰 조회하되 작성자의 팔로워가 가장 많은 순으로 리뷰를 조회한다.") + void findByProductNameOrderByMostWriterFollower() { + // given + Review review01 = createReview(1L); + Review review02 = createReview(2L); + Review review03 = createReview(3L); + Review review04 = createReview(4L); + Review review05 = createReview(5L); + + Users writer01 = Users.builder() + .email("email01") + .build(); + Users writer02 = Users.builder() + .email("email02") + .build(); + + this.usersRepository.saveAll(List.of(writer01, writer02)); + + review01.setWriter(writer01); + review02.setWriter(writer02); + review03.setWriter(writer01); + review04.setWriter(writer01); + review05.setWriter(writer01); + + this.reviewRepository.saveAll(List.of(review01, review02, review03, review04, review05)); + + // writer01 -> writer02 팔로우 + Follow follow = Follow.builder() + .follower(writer01) + .followee(writer02) + .build(); + + // when + + // then + } + + @Test + @DisplayName("로그인한 유저가 팔로우하는 팔로잉의 리뷰를 최신순 7개 조회한다.") + void findFirst7WrittenByFollowingCreatedAtDescTest() { + // given + Users follower = Users.builder() + .email("danaver12@daum.net") + .roleType(ROLE_USER) + .build(); + + Users followee = Users.builder() + .email("whole34@naver.com") + .roleType(ROLE_USER) + .build(); + + this.usersRepository.saveAll(List.of(followee, follower)); + + this.followRepository.save(Follow.builder().follower(follower).followee(followee).build()); + + Review review01 = createReview(1L); + Review review02 = createReview(2L); + Review review03 = createReview(3L); + Review review04 = createReview(4L); + + review01.setWriter(followee); + review02.setWriter(followee); + review03.setWriter(followee); + review04.setWriter(followee); + + this.reviewRepository.saveAll(List.of(review01, review02, review03, review04)); + // when + List reviews + = this.reviewRepository.findFirst7WrittenByFollowingCreatedAtDesc(follower, PageRequest.of(0, 7)); + // then + assertThat(reviews).hasSize(4); + } + + @Test + @DisplayName("상품 명에 해당하는 리뷰 중 리뷰의 작성자 팔로워가 많은 순으로 리뷰를 조회한다.") + void findByProductNameOrderByMostWriterFollowerTest() { + // given + Users follower = Users.builder() + .email("danaver12@daum.net") + .roleType(ROLE_USER) + .build(); + + Users followee01 = Users.builder() + .email("whole34@naver.com") + .roleType(ROLE_USER) + .build(); + + Users followee02 = Users.builder() + .email("email03") + .roleType(ROLE_USER) + .build(); + + + Users followee03 = Users.builder() + .email("email04") + .roleType(ROLE_USER) + .build(); + + this.usersRepository.saveAll(List.of(followee01, follower, followee02, followee03)); + + // follower -> followee01 + Follow follow00 = Follow.builder() + .follower(follower) + .followee(followee01) + .build(); + + // follower -> followee02 + Follow follow01 = Follow.builder() + .follower(follower) + .followee(followee02) + .build(); + + // follower -> followee03 + Follow follow02 = Follow.builder() + .follower(follower) + .followee(followee03) + .build(); + + // followee01 -> followee02 + Follow follow03 = Follow.builder() + .follower(followee01) + .followee(followee02) + .build(); + + // followee01 -> followee03 + Follow follow04 = Follow.builder() + .follower(followee01) + .followee(followee03) + .build(); + + // followee02 -> followee03 + Follow follow05 = Follow.builder() + .follower(followee02) + .followee(followee03) + .build(); + + this.followRepository.saveAll(List.of(follow00, follow01, follow02, follow03, follow04)); + + Review review01 = createReview(1L); + Review review02 = createReview(2L); + Review review03 = createReview(3L); + Review review04 = createReview(4L); + + review01.setWriter(follower); + review02.setWriter(followee01); + review03.setWriter(followee02); + review04.setWriter(followee03); + + this.reviewRepository.saveAll(List.of(review01, review02, review03, review04)); + // when + Slice result = this.reviewRepository.findByProductNameOrderByMostWriterFollower("productName", PageRequest.of(0, 10)); + // then + } + + @Test + @DisplayName("카테고리에 해당하는 리뷰를 스크랩 수 + 좋아요 수 순으로 정렬하여 조회한다.") + void findReviewByCategoryOrderByScrapedCountAndLikedCountTest() { + // given + Review review01 = createReview(1L); + Review review02 = createReview(2L); + Review review03 = createReview(3L); + + this.reviewRepository.saveAll(List.of(review01, review02, review03)); + // when + Slice reviews + = this.reviewRepository.findReviewByCategoryOrderByScrapedCountAndLikedCount(PC, PageRequest.of(0, 10)); + // then + List content = reviews.getContent(); + assertThat(content.get(0)).isEqualTo(review03); + assertThat(content.get(1)).isEqualTo(review02); + assertThat(content.get(2)).isEqualTo(review01); + } + } \ No newline at end of file diff --git a/src/test/java/com/example/betteriter/fo_domain/review/service/ReviewServiceIntegratedTest.java b/src/test/java/com/example/betteriter/fo_domain/review/service/ReviewServiceIntegratedTest.java index c6dd11a..0ef1371 100644 --- a/src/test/java/com/example/betteriter/fo_domain/review/service/ReviewServiceIntegratedTest.java +++ b/src/test/java/com/example/betteriter/fo_domain/review/service/ReviewServiceIntegratedTest.java @@ -1,27 +1,18 @@ package com.example.betteriter.fo_domain.review.service; import com.example.betteriter.fo_domain.review.domain.Review; -import com.example.betteriter.fo_domain.review.dto.ReviewResponse; import com.example.betteriter.fo_domain.review.repository.ReviewRepository; import com.example.betteriter.fo_domain.user.domain.Users; import com.example.betteriter.fo_domain.user.domain.UsersDetail; import com.example.betteriter.global.constant.Job; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.data.domain.SliceImpl; import org.springframework.test.context.ActiveProfiles; import java.time.LocalDate; -import java.util.List; import static com.example.betteriter.global.constant.Category.PC; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.given; @SpringBootTest @@ -49,7 +40,7 @@ private static Review createReview(long count) { .writer(users) .category(PC) .productName("productName") - .amount(10) + .price(10) .storeName(1) .boughtAt(LocalDate.now()) .starPoint(1) @@ -59,20 +50,4 @@ private static Review createReview(long count) { .shortReview("short") .build(); } - - @Test - @DisplayName("test") - void test() { - // given - List reviews = List.of(createReview(1L), createReview(2L)); - - given(this.reviewRepository.findByProductNameOrderByCreatedAtDesc(anyString(), any())) - .willReturn(new SliceImpl<>(reviews)); - - // when - ReviewResponse result = this.reviewService.getReviewBySearch("productName"); - - // then - assertThat(result).isNotNull(); - } } \ No newline at end of file diff --git a/src/test/java/com/example/betteriter/fo_domain/review/service/ReviewServiceTest.java b/src/test/java/com/example/betteriter/fo_domain/review/service/ReviewServiceTest.java index 99a518a..72eb04b 100644 --- a/src/test/java/com/example/betteriter/fo_domain/review/service/ReviewServiceTest.java +++ b/src/test/java/com/example/betteriter/fo_domain/review/service/ReviewServiceTest.java @@ -6,11 +6,9 @@ import com.example.betteriter.bo_domain.spec.domain.SpecData; import com.example.betteriter.bo_domain.spec.service.SpecService; import com.example.betteriter.fo_domain.review.domain.Review; -import com.example.betteriter.fo_domain.review.dto.CreateReviewRequestDto; +import com.example.betteriter.fo_domain.review.domain.ReviewImage; +import com.example.betteriter.fo_domain.review.dto.*; import com.example.betteriter.fo_domain.review.dto.CreateReviewRequestDto.CreateReviewImageRequestDto; -import com.example.betteriter.fo_domain.review.dto.GetReviewResponseDto; -import com.example.betteriter.fo_domain.review.dto.GetReviewSpecResponseDto; -import com.example.betteriter.fo_domain.review.dto.ReviewResponse; import com.example.betteriter.fo_domain.review.repository.ReviewImageRepository; import com.example.betteriter.fo_domain.review.repository.ReviewRepository; import com.example.betteriter.fo_domain.review.repository.ReviewSpecDataRepository; @@ -26,17 +24,18 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.domain.SliceImpl; import java.time.LocalDate; -import java.util.Collections; import java.util.List; +import java.util.Optional; import static com.example.betteriter.global.constant.Category.LAPTOP; import static com.example.betteriter.global.constant.Category.PC; import static com.example.betteriter.global.constant.RoleType.ROLE_USER; +import static com.example.betteriter.global.constant.Status.ACTIVE; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.*; import static org.mockito.BDDMockito.given; @@ -45,7 +44,6 @@ @ExtendWith({MockitoExtension.class}) public class ReviewServiceTest { - @InjectMocks private ReviewService reviewService; @@ -70,26 +68,43 @@ public class ReviewServiceTest { private static Review createReview(long count) { Users users = Users.builder() - .usersDetail(UsersDetail.builder() - .nickName("nickName") - .job(Job.DEVELOPER) - .profileImage("profileImage") - .build()) + .email("email") + .roleType(ROLE_USER) + .usersDetail(UsersDetail.builder().nickName("nickname").job(Job.DEVELOPER).build()) .build(); - return Review.builder() + + + Review review = Review.builder() + .id(count) .writer(users) .category(PC) .productName("productName") - .amount(10) + .category(PC) + .price(10) .storeName(1) + .status(ACTIVE) + .manufacturer(Manufacturer.builder().coName("삼성").build()) .boughtAt(LocalDate.now()) .starPoint(1) + .likedCount(count) + .scrapedCount(count) .goodPoint("goodPoint") .badPoint("badPoint") .clickCount(count) .shortReview("short") .build(); + review.setReviewImage(createReviewImage(review)); + return review; + } + + private static ReviewImage createReviewImage(Review review) { + return ReviewImage.builder() + .id(review.getId()) + .review(review) + .orderNum(0) + .imgUrl("imgUrl") + .build(); } @Test @@ -101,8 +116,8 @@ void createReviewTest() { .category(LAPTOP) .productName("맥북1") .boughtAt(LocalDate.now()) - .manufacturerName("기타") - .amount(100000) + .manufacturer("기타") + .price(100000) .storeName(1) .shortReview("한줄 평") .starPoint(1) @@ -139,7 +154,7 @@ void createReviewTest() { .manufacturer(Manufacturer.createManufacturer("삼성")) .category(LAPTOP) .productName("상품명1") - .amount(10000) + .price(10000) .storeName(1) .boughtAt(LocalDate.now()) .starPoint(1) @@ -200,7 +215,7 @@ void getReviewByCategory() { .id(1L) .category(PC) .productName("productName01") - .amount(100000) + .price(100000) .writer(user01) .storeName(1) .boughtAt(LocalDate.now()) @@ -215,7 +230,7 @@ void getReviewByCategory() { .category(PC) .productName("productName02") .writer(user01) - .amount(100000) + .price(100000) .storeName(1) .boughtAt(LocalDate.now()) .starPoint(0) @@ -229,7 +244,7 @@ void getReviewByCategory() { .category(PC) .productName("productName01") .writer(user01) - .amount(100000) + .price(100000) .storeName(1) .boughtAt(LocalDate.now()) .starPoint(0) @@ -243,7 +258,7 @@ void getReviewByCategory() { .category(PC) .productName("productName01") .writer(user01) - .amount(100000) + .price(100000) .storeName(1) .boughtAt(LocalDate.now()) .starPoint(0) @@ -253,11 +268,11 @@ void getReviewByCategory() { .build(); - given(reviewRepository.findReviewByCategory(any(Category.class), any(Pageable.class))) + given(reviewRepository.findReviewByCategoryOrderByScrapedCountAndLikedCount(any(Category.class), any(Pageable.class))) .willReturn(new SliceImpl<>(List.of(review01, review02, review03, review04))); // when - ReviewResponse result = this.reviewService.getReviewByCategory(PC); + ReviewResponse result = this.reviewService.getReviewByCategory(PC, 1); // then for (GetReviewResponseDto getReviewResponseDto : result.getGetReviewResponseDtoList()) { System.out.println("getReviewResponseDto = " + getReviewResponseDto); @@ -265,44 +280,91 @@ void getReviewByCategory() { } @Test - @DisplayName("리뷰 이름 조회 테스트를 진행한다.(비어있는 경우)") - void getReviewBySearchTest() { + @DisplayName("카테고리에 해당하는 모든 리뷰를 리뷰의 스크랩 수 + 좋아요 순으로 정렬해서 조회한다.") + void getReviewByCategoryServiceTest() { // given - List reviewResult = - List.of(createReview(1L), createReview(2L), createReview(3L)); + Review review00 = createReview(1L); + Review review01 = createReview(2L); + Review review02 = createReview(3L); - given(this.reviewRepository.findByProductNameOrderByCreatedAtDesc(anyString(), any())) - .willReturn(new SliceImpl<>(Collections.emptyList())); - given(this.reviewRepository.findFirst20ByOrderByClickCountDescCreatedAtDesc()) - .willReturn(reviewResult); + Slice result = new SliceImpl<>(List.of(review00, review01, review02)); + given(this.reviewRepository.findReviewByCategoryOrderByScrapedCountAndLikedCount(any(Category.class), any(Pageable.class))) + .willReturn(result); // when - ReviewResponse result = this.reviewService.getReviewBySearch("productName"); - + ReviewResponse response = this.reviewService.getReviewByCategory(PC, 2); // then - assertThat(result.isHasNext()).isFalse(); - assertThat(result.getGetReviewResponseDtoList()).hasSize(3); - verify(reviewRepository, times(1)).findFirst20ByOrderByClickCountDescCreatedAtDesc(); - verify(reviewRepository, times(1)).findByProductNameOrderByCreatedAtDesc(anyString(), any(PageRequest.class)); + List getReviewResponseDtoList = response.getGetReviewResponseDtoList(); + assertThat(response.isHasNext()).isFalse(); + assertThat(response.isExisted()).isTrue(); + assertThat(getReviewResponseDtoList).hasSize(3); } @Test - @DisplayName("리뷰 이름 조회 테스트를 진행한다.(비어있지 않은 경우)") - void getReviewBySearchTest02() { + @DisplayName("카테고리에 해당하는 모든 리뷰를 리뷰의 스크랩 수 + 좋아요 순으로 정렬해서 조회한다.(조회 결과가 없는 경우 isExisted = false") + void getReviewByCategoryServiceTest02() { // given - List reviewResult = - List.of(createReview(1L), createReview(2L), createReview(3L)); + Slice result = new SliceImpl<>(List.of()); + given(this.reviewRepository.findReviewByCategoryOrderByScrapedCountAndLikedCount(any(Category.class), any(Pageable.class))) + .willReturn(result); + // when + ReviewResponse response = this.reviewService.getReviewByCategory(PC, 2); + // then + List getReviewResponseDtoList = response.getGetReviewResponseDtoList(); + assertThat(response.isHasNext()).isFalse(); + assertThat(response.isExisted()).isFalse(); + assertThat(getReviewResponseDtoList).hasSize(0); + } - given(this.reviewRepository.findByProductNameOrderByCreatedAtDesc(anyString(), any())) - .willReturn(new SliceImpl<>(reviewResult)); + @Test + @DisplayName("리뷰 상세조회를 한다.(동일한 제품명 리뷰 조회 4개 조회되는 경우)") + void getReviewDetailTest01() { + // given + Review review = createReview(1L); + given(this.reviewRepository.findById(review.getId())) + .willReturn(Optional.of(review)); + /* 동일한 제품명 리뷰 조회 */ + given(this.reviewRepository.findTop4ByProductNameOrderByScrapedCntPlusLikedCntDesc(anyString())) + .willReturn(List.of(createReview(2L), createReview(3L), createReview(4L), createReview(5L))); // when - ReviewResponse result = this.reviewService.getReviewBySearch("productName"); + ReviewDetailResponse reviewDetail = this.reviewService.getReviewDetail(1L); + // then + assertThat(reviewDetail.getGetReviewDetailResponseDto().getId()).isEqualTo(1L); + assertThat(reviewDetail.getGetReviewDetailResponseDto().getProductName()).isEqualTo(review.getProductName()); + assertThat(reviewDetail.getGetReviewDetailResponseDto().getScrapedCount()).isEqualTo(1L); + assertThat(reviewDetail.getWriterInfo().getId()).isEqualTo(review.getWriter().getId()); + assertThat(reviewDetail.getWriterInfo().getNickName()).isEqualTo(review.getWriter().getUsersDetail().getNickName()); + verify(reviewRepository, times(1)).findById(anyLong()); + verify(reviewRepository, times(0)).findReviewByCategoryOrderByScrapedCountAndLikedCount(any(Category.class), any(Pageable.class)); + } + @Test + @DisplayName("리뷰 상세조회를 한다.(동일한 제품명 리뷰 조회 2개 + 같은 카테고리 리뷰 2개 조회되는 경우") + void getReviewDetailTest02() { + // given + Review review = createReview(1L); + + given(this.reviewRepository.findById(review.getId())) + .willReturn(Optional.of(review)); + + /* 동일한 제품명 리뷰 조회 (2개) */ + given(this.reviewRepository.findTop4ByProductNameOrderByScrapedCntPlusLikedCntDesc(anyString())) + .willReturn(List.of(createReview(2L), createReview(3L))); + /* 동일한 카테고리 리뷰 조회 (2개) */ + given(this.reviewRepository.findReviewByCategoryOrderByScrapedCountAndLikedCount(any(), any())) + .willReturn(new SliceImpl<>(List.of(createReview(3L), createReview(4L)))); + // when + ReviewDetailResponse reviewDetail = this.reviewService.getReviewDetail(1L); // then - assertThat(result.getGetReviewResponseDtoList()).hasSize(3); - assertThat(result.isHasNext()).isFalse(); - verify(reviewRepository, times(0)).findFirst20ByOrderByClickCountDescCreatedAtDesc(); + assertThat(reviewDetail.getGetReviewDetailResponseDto().getId()).isEqualTo(1L); + assertThat(reviewDetail.getGetReviewDetailResponseDto().getProductName()).isEqualTo(review.getProductName()); + assertThat(reviewDetail.getGetReviewDetailResponseDto().getScrapedCount()).isEqualTo(1L); + assertThat(reviewDetail.getWriterInfo().getId()).isEqualTo(review.getWriter().getId()); + assertThat(reviewDetail.getWriterInfo().getNickName()).isEqualTo(review.getWriter().getUsersDetail().getNickName()); + assertThat(reviewDetail.getReviewLikeInfo().getReviewLikedCount()).isEqualTo(review.getLikedCount()); + verify(reviewRepository, times(1)).findById(anyLong()); + verify(reviewRepository, times(1)).findReviewByCategoryOrderByScrapedCountAndLikedCount(any(Category.class), any(Pageable.class)); } -} +} \ No newline at end of file diff --git a/src/test/java/com/example/betteriter/global/DeserializerTest.java b/src/test/java/com/example/betteriter/global/DeserializerTest.java index abdb5a0..04d03fc 100644 --- a/src/test/java/com/example/betteriter/global/DeserializerTest.java +++ b/src/test/java/com/example/betteriter/global/DeserializerTest.java @@ -57,7 +57,7 @@ void serializerTest01() throws JsonProcessingException { .category(LAPTOP) .productName("맥북1") .boughtAt(LocalDate.now()) - .amount(10000) + .price(10000) .storeName(1) .shortReview("한줄 평") .starPoint(1) @@ -124,4 +124,4 @@ void joinDtoSerializerTest() throws JsonProcessingException { System.out.println("json = " + json); // then } -} +} \ No newline at end of file