Skip to content

Commit

Permalink
Merge pull request #108 from jisung-in/feature/90-get-reviews-related…
Browse files Browse the repository at this point in the history
…-book

[Feature] 도서와 관련된 리뷰 조회 API 구현
  • Loading branch information
jwooo authored May 25, 2024
2 parents 154dcad + 5b17ef4 commit 9339927
Show file tree
Hide file tree
Showing 13 changed files with 707 additions and 24 deletions.
14 changes: 14 additions & 0 deletions src/docs/asciidoc/api/review/review.adoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
[[review-create]]

=== 도서와 연관된 리뷰 조회

==== HTTP Request

include::{snippets}/review/get-related-book/http-request.adoc[]
include::{snippets}/review/get-related-book/path-parameters.adoc[]
include::{snippets}/review/get-related-book/query-parameters.adoc[]

==== HTTP Response

include::{snippets}/review/get-related-book/http-response.adoc[]
include::{snippets}/review/get-related-book/response-fields.adoc[]

=== 한줄평 생성

==== HTTP Request
Expand Down
22 changes: 18 additions & 4 deletions src/main/java/com/jisungin/api/review/ReviewController.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,39 @@
import com.jisungin.api.ApiResponse;
import com.jisungin.api.support.Auth;
import com.jisungin.api.review.request.ReviewCreateRequest;
import com.jisungin.application.OffsetLimit;
import com.jisungin.application.SliceResponse;
import com.jisungin.application.review.ReviewService;
import com.jisungin.application.review.response.ReviewWithRatingResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@RequestMapping("/v1/reviews")
@RequestMapping("/v1")
@RequiredArgsConstructor
@RestController
public class ReviewController {

private final ReviewService reviewService;

@PostMapping
public ApiResponse<Void> createReview(@Valid @RequestBody ReviewCreateRequest request, @Auth Long userId) {
@GetMapping("/books/{isbn}/reviews")
public ApiResponse<SliceResponse<ReviewWithRatingResponse>> findBookReviews(
@PathVariable String isbn,
@RequestParam(required = false, defaultValue = "1") Integer page,
@RequestParam(required = false, defaultValue = "8") Integer size,
@RequestParam(required = false, defaultValue = "like") String order
) {
return ApiResponse.ok(reviewService.findBookReviews(isbn, OffsetLimit.of(page, size, order)));
}

@PostMapping("/reviews")
public ApiResponse<Void> createReview(@Valid @RequestBody ReviewCreateRequest request,
@Auth Long userId) {
reviewService.createReview(request.toServiceRequest(), userId);
return ApiResponse.ok();
}

@DeleteMapping("/{reviewId}")
@DeleteMapping("/reviews/{reviewId}")
public ApiResponse<Void> deleteReview(@PathVariable Long reviewId, @Auth Long userId) {
reviewService.deleteReview(reviewId, userId);
return ApiResponse.ok();
Expand Down
42 changes: 42 additions & 0 deletions src/main/java/com/jisungin/application/OffsetLimit.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.jisungin.application;

import static java.lang.Math.*;

import lombok.Builder;
import lombok.Getter;

@Getter
public class OffsetLimit {

private static final Integer MAX_SIZE = 2000;
private Integer offset;
private Integer limit;
private String order;

@Builder
private OffsetLimit(Integer offset, Integer limit, String order) {
this.offset = offset;
this.limit = limit;
this.order = order;
}

public static OffsetLimit of(Integer page, Integer size) {
return OffsetLimit.builder()
.offset(calculateOffset(page, size))
.limit(size)
.build();
}

public static OffsetLimit of(Integer page, Integer size, String order) {
return OffsetLimit.builder()
.offset(calculateOffset(page, size))
.limit(size)
.order(order)
.build();
}

private static Integer calculateOffset(Integer page, Integer size) {
return (max(1, page) - 1) * min(size, MAX_SIZE);
}

}
54 changes: 54 additions & 0 deletions src/main/java/com/jisungin/application/SliceResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.jisungin.application;

import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import lombok.Builder;
import lombok.Getter;

@Getter
public class SliceResponse<T> {

private List<T> content;

private boolean hasContent;

@JsonProperty("isFirst")
private boolean first;

@JsonProperty("isLast")
private boolean last;

private Integer number;

private Integer size;

@Builder
private SliceResponse(List<T> content, boolean hasContent, boolean first, boolean last, Integer number,
Integer size) {
this.content = content;
this.hasContent = hasContent;
this.first = first;
this.last = last;
this.number = number;
this.size = size;
}

public static <T> SliceResponse<T> of(List<T> content, Integer offset, Integer limit, boolean hasNext) {
boolean hasContent = !content.isEmpty();
boolean first = (offset == 0);
boolean last = !hasNext;

Integer number = (offset / limit) + 1;
Integer size = content.size();

return SliceResponse.<T>builder()
.content(content)
.hasContent(hasContent)
.first(first)
.last(last)
.number(number)
.size(size)
.build();
}

}
19 changes: 16 additions & 3 deletions src/main/java/com/jisungin/application/review/ReviewService.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package com.jisungin.application.review;

import static com.jisungin.exception.ErrorCode.BOOK_NOT_FOUND;
import static com.jisungin.exception.ErrorCode.REVIEW_NOT_FOUND;
import static com.jisungin.exception.ErrorCode.UNAUTHORIZED_REQUEST;
import static com.jisungin.exception.ErrorCode.USER_NOT_FOUND;

import com.jisungin.application.OffsetLimit;
import com.jisungin.application.SliceResponse;
import com.jisungin.application.review.request.ReviewCreateServiceRequest;
import com.jisungin.application.review.response.ReviewResponse;
import com.jisungin.application.review.response.ReviewWithRatingResponse;
import com.jisungin.domain.book.Book;
import com.jisungin.domain.book.repository.BookRepository;
import com.jisungin.domain.review.Review;
Expand All @@ -13,8 +20,6 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import static com.jisungin.exception.ErrorCode.*;

@RequiredArgsConstructor
@Transactional(readOnly = true)
@Service
Expand All @@ -24,6 +29,14 @@ public class ReviewService {
private final UserRepository userRepository;
private final BookRepository bookRepository;

public SliceResponse<ReviewWithRatingResponse> findBookReviews(String isbn, OffsetLimit offsetLimit) {
Book book = bookRepository.findById(isbn)
.orElseThrow(() -> new BusinessException(BOOK_NOT_FOUND));

return reviewRepository.findAllByBookId(book.getIsbn(), offsetLimit.getOffset(), offsetLimit.getLimit(),
offsetLimit.getOrder());
}

@Transactional
public void createReview(ReviewCreateServiceRequest request, Long userId) {
User user = userRepository.findById(userId)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.jisungin.application.review.response;

import com.querydsl.core.annotations.QueryProjection;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class ReviewWithRatingResponse {

private Long reviewId;
private Long ratingId;
private String username;
private String profileImage;
private String reviewContent;
private Double starRating;
private Long likeCount;

@Builder
@QueryProjection
public ReviewWithRatingResponse(Long reviewId, Long ratingId, String username, String profileImage,
String reviewContent, Double starRating, Long likeCount) {
this.reviewId = reviewId;
this.ratingId = ratingId;
this.username = username;
this.profileImage = profileImage;
this.reviewContent = reviewContent;
this.starRating = starRating;
this.likeCount = likeCount;
}

public static ReviewWithRatingResponse of(Long reviewId, Long ratingId, String username, String profileImage,
String reviewContent, Double starRating, Long likeCount) {
return ReviewWithRatingResponse.builder()
.reviewId(reviewId)
.ratingId(ratingId)
.username(username)
.profileImage(profileImage)
.reviewContent(reviewContent)
.starRating(starRating)
.likeCount(likeCount)
.build();
}

}
57 changes: 57 additions & 0 deletions src/main/java/com/jisungin/domain/review/ReviewOrderType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.jisungin.domain.review;

import static com.jisungin.domain.rating.QRating.rating1;
import static com.jisungin.domain.review.QReview.review;
import static com.jisungin.domain.reviewlike.QReviewLike.reviewLike;

import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.jpa.impl.JPAQuery;
import java.util.function.Consumer;
import java.util.function.Supplier;

public enum ReviewOrderType {

LIKE(() -> reviewLike.id.count().desc(), ReviewOrderType::leftJoinRating),
RECENT(review.createDateTime::desc, ReviewOrderType::leftJoinRating),
RATING_DESC(rating1.rating::desc, ReviewOrderType::joinRating),
RATING_ASC(rating1.rating::asc, ReviewOrderType::joinRating);

private final Supplier<OrderSpecifier<?>> orderSpecifierSupplier;
private final Consumer<JPAQuery<?>> joinRatingStrategy;

ReviewOrderType(Supplier<OrderSpecifier<?>> orderSpecifierSupplier, Consumer<JPAQuery<?>> joinRatingStrategy) {
this.orderSpecifierSupplier = orderSpecifierSupplier;
this.joinRatingStrategy = joinRatingStrategy;
}

public OrderSpecifier<?> getOrderSpecifier() {
return orderSpecifierSupplier.get();
}

public void applyJoinStrategy(JPAQuery<?> query) {
joinRatingStrategy.accept(query);
applyCommonJoinConditions(query);
}

public static ReviewOrderType fromString(String name) {
try {
return ReviewOrderType.valueOf(name.toUpperCase());
} catch (IllegalArgumentException | NullPointerException e) {
return ReviewOrderType.LIKE;
}
}

private static void joinRating(JPAQuery<?> query) {
query.join(rating1);
}

private static void leftJoinRating(JPAQuery<?> query) {
query.leftJoin(rating1);
}

private static void applyCommonJoinConditions(JPAQuery<?> query) {
query.on(review.user.eq(rating1.user)
.and(review.book.eq(rating1.book)));
}

}
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package com.jisungin.domain.review.repository;

import com.jisungin.application.PageResponse;
import com.jisungin.application.SliceResponse;
import com.jisungin.application.review.response.ReviewContentResponse;
import com.jisungin.application.review.response.ReviewWithRatingResponse;
import com.jisungin.domain.review.RatingOrderType;

public interface ReviewRepositoryCustom {

PageResponse<ReviewContentResponse> findAllReviewContentOrderBy(
Long userId, RatingOrderType orderType, int size, int offset);

SliceResponse<ReviewWithRatingResponse> findAllByBookId(String isbn, Integer offset, Integer limit, String order);

}
Loading

0 comments on commit 9339927

Please sign in to comment.