Skip to content

Commit

Permalink
Merge pull request #54 from jisung-in/feature/43-my-review-read-api
Browse files Browse the repository at this point in the history
[Feature] 사용자 별점 조회 API 구현
  • Loading branch information
pdohyung authored Mar 31, 2024
2 parents 54db0cc + 4b0c724 commit ca3b062
Show file tree
Hide file tree
Showing 13 changed files with 694 additions and 2 deletions.
33 changes: 33 additions & 0 deletions src/main/java/com/jisungin/api/user/UserController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.jisungin.api.user;

import com.jisungin.api.ApiResponse;
import com.jisungin.api.oauth.Auth;
import com.jisungin.api.oauth.AuthContext;
import com.jisungin.api.user.request.UserRatingGetAllRequest;
import com.jisungin.application.PageResponse;
import com.jisungin.application.review.response.RatingFindAllResponse;
import com.jisungin.application.user.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/v1/users")
@RequiredArgsConstructor
@RestController
public class UserController {

private final UserService userService;

@GetMapping("/ratings")
public ApiResponse<PageResponse<RatingFindAllResponse>> getUserRatings(
@ModelAttribute UserRatingGetAllRequest request,
@Auth AuthContext authContext
) {
PageResponse<RatingFindAllResponse> response = userService.getUserRatings(
authContext.getUserId(), request.toService());
return ApiResponse.ok(response);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.jisungin.api.user.request;

import com.jisungin.application.user.request.UserRatingGetAllServiceRequest;
import com.jisungin.domain.review.RatingOrderType;
import lombok.*;

@Getter
@Setter
@NoArgsConstructor
public class UserRatingGetAllRequest {

private Integer page;

private Integer size;

private String order;

private Double rating;

@Builder
public UserRatingGetAllRequest(Integer page, Integer size, String order, String rating) {
this.page = page != null ? page : 1;
this.size = size != null ? size : 10;
this.order = order != null ? order : "date";
this.rating = rating != null ? Double.parseDouble(rating) : null;
}

public UserRatingGetAllServiceRequest toService() {
return UserRatingGetAllServiceRequest.builder()
.page(page)
.size(size)
.orderType(RatingOrderType.fromName(order))
.rating(rating)
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
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 RatingFindAllResponse {

private String isbn;

private String title;

private String image;

private Double rating;

@Builder
@QueryProjection
public RatingFindAllResponse(String isbn, String title, String image, Double rating) {
this.isbn = isbn;
this.title = title;
this.image = image;
this.rating = rating;
}

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

import com.jisungin.application.PageResponse;
import com.jisungin.application.review.response.RatingFindAllResponse;
import com.jisungin.application.user.request.UserRatingGetAllServiceRequest;
import com.jisungin.domain.review.repository.ReviewRepository;
import com.jisungin.domain.user.User;
import com.jisungin.domain.user.repository.UserRepository;
import com.jisungin.exception.BusinessException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

@RequiredArgsConstructor
@Transactional(readOnly = true)
@Service
public class UserService {

private final UserRepository userRepository;

private final ReviewRepository reviewRepository;

public PageResponse<RatingFindAllResponse> getUserRatings(Long userId, UserRatingGetAllServiceRequest request) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new BusinessException(USER_NOT_FOUND));

return reviewRepository.findAllRatingOrderBy(
user.getId(), request.getOrderType(), request.getRating(), request.getSize(), request.getOffset());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.jisungin.application.user.request;

import com.jisungin.domain.review.RatingOrderType;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import static java.lang.Math.*;

@Getter
@NoArgsConstructor
public class UserRatingGetAllServiceRequest {

private static final int MAX_SIZE = 2_000;

private Integer page;

private Integer size;

private RatingOrderType orderType;

private Double rating;

@Builder
public UserRatingGetAllServiceRequest(Integer page, Integer size, RatingOrderType orderType, Double rating) {
this.page = page;
this.size = size;
this.orderType = orderType;
this.rating = rating;
}

public int getOffset() {
return (max(1, page) - 1) * min(size, MAX_SIZE);
}

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

import java.util.Locale;

public enum RatingOrderType {

DATE,
RATING_ASC,
RATING_DESC,
RATING_AVG_ASC,
RATING_AVG_DESC;

public static RatingOrderType fromName(String name) {
return RatingOrderType.valueOf(name.toUpperCase(Locale.ENGLISH));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import org.springframework.stereotype.Repository;

@Repository
public interface ReviewRepository extends JpaRepository<Review, Long> {
public interface ReviewRepository extends JpaRepository<Review, Long>, ReviewRepositoryCustom {

@Query("SELECT AVG(r.rating) FROM Review r WHERE r.book.isbn = :bookId")
Double findAverageRatingByBookId(@Param("bookId") String bookId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.jisungin.domain.review.repository;

import com.jisungin.application.PageResponse;
import com.jisungin.application.review.response.RatingFindAllResponse;
import com.jisungin.domain.review.RatingOrderType;

public interface ReviewRepositoryCustom {

PageResponse<RatingFindAllResponse> findAllRatingOrderBy(
Long userId, RatingOrderType ratingSortType, Double rating, int size, int offset);

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

import com.jisungin.application.PageResponse;
import com.jisungin.application.review.response.QRatingFindAllResponse;
import com.jisungin.application.review.response.RatingFindAllResponse;
import com.jisungin.domain.review.RatingOrderType;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.util.List;

import static com.jisungin.domain.book.QBook.book;
import static com.jisungin.domain.review.QReview.review;
import static com.jisungin.domain.review.RatingOrderType.*;

@Slf4j
@RequiredArgsConstructor
public class ReviewRepositoryImpl implements ReviewRepositoryCustom {

private final JPAQueryFactory queryFactory;

@Override
public PageResponse<RatingFindAllResponse> findAllRatingOrderBy(
Long userId, RatingOrderType ratingSortType, Double rating, int size, int offset) {
log.info("--------------start--------------");
// 리뷰 조회, 쿼리 1회
List<RatingFindAllResponse> ratings = getRatings(userId, ratingSortType, rating, size, offset);

return PageResponse.<RatingFindAllResponse>builder()
.queryResponse(ratings)
.totalCount(getTotalCount(userId, rating)) // 해당 유저의 리뷰 총 개수, 쿼리 1회
.size(size)
.build();
}

private List<RatingFindAllResponse> getRatings(
Long userId, RatingOrderType ratingSortType, Double rating, int size, int offset) {
return queryFactory
.select(new QRatingFindAllResponse(
review.book.isbn, review.book.title, review.book.imageUrl, review.rating))
.from(review)
.leftJoin(book).on(review.book.eq(book))
.where(review.user.id.eq(userId), ratingCondition(rating))
.groupBy(review.book.isbn)
.orderBy(createSpecifier(ratingSortType), review.id.asc())
.offset(offset)
.limit(size)
.fetch();
}

private long getTotalCount(Long userId, Double rating) {
return queryFactory
.select(review.count())
.from(review)
.where(review.user.id.eq(userId), ratingCondition(rating))
.fetchOne();
}

private OrderSpecifier createSpecifier(RatingOrderType ratingSortType) {
if (ratingSortType.equals(RATING_ASC)) {
return review.rating.asc();
}
if (ratingSortType.equals(RATING_DESC)) {
return review.rating.desc();
}
if (ratingSortType.equals(RATING_AVG_ASC)) {
return review.rating.avg().asc();
}
if (ratingSortType.equals(RATING_AVG_DESC)) {
return review.rating.avg().desc();
}

return review.createDateTime.desc();
}

// 만약 별점 필터링 조건이 존재하면 해당하는 별점만 가져온다.
private BooleanExpression ratingCondition(Double rating) {
if (rating == null) {
return null;
}

return review.rating.eq(rating);
}

}
8 changes: 7 additions & 1 deletion src/test/java/com/jisungin/ControllerTestSupport.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
import com.jisungin.api.review.ReviewController;
import com.jisungin.api.talkroom.TalkRoomController;
import com.jisungin.api.talkroomlike.TalkRoomLikeController;
import com.jisungin.api.user.UserController;
import com.jisungin.application.comment.CommentService;
import com.jisungin.application.commentlike.CommentLikeService;
import com.jisungin.application.review.ReviewService;
import com.jisungin.application.talkroom.TalkRoomService;
import com.jisungin.application.talkroomlike.TalkRoomLikeService;
import com.jisungin.application.user.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
Expand All @@ -22,7 +24,8 @@
CommentController.class,
ReviewController.class,
TalkRoomLikeController.class,
CommentLikeController.class
CommentLikeController.class,
UserController.class
})
public abstract class ControllerTestSupport {

Expand Down Expand Up @@ -50,4 +53,7 @@ public abstract class ControllerTestSupport {
@MockBean
protected CommentLikeService commentLikeService;

@MockBean
protected UserService userService;

}
29 changes: 29 additions & 0 deletions src/test/java/com/jisungin/api/user/UserControllerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.jisungin.api.user;

import com.jisungin.ControllerTestSupport;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.springframework.http.MediaType.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

class UserControllerTest extends ControllerTestSupport {

@DisplayName("사용자의 모든 리뷰 별점을 조회한다.")
@Test
void getUserRatingAll() throws Exception {
//given
//when //then
mockMvc.perform(get("/v1/users/ratings?page=1&size=4&order=rating_asc&rating=")
.contentType(APPLICATION_JSON)
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value("200"))
.andExpect(jsonPath("$.status").value("OK"))
.andExpect(jsonPath("$.message").value("OK"))
.andDo(print());
}

}
Loading

0 comments on commit ca3b062

Please sign in to comment.