Skip to content

Commit

Permalink
Feat: 리뷰 등록 가능 여부 조회 API 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
onetuks committed Nov 4, 2023
1 parent db2bf89 commit b8e2278
Show file tree
Hide file tree
Showing 12 changed files with 163 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
package org.guzzing.studayserver.domain.review.controller;

import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.OK;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;

import org.guzzing.studayserver.domain.auth.memberId.MemberId;
import org.guzzing.studayserver.domain.review.controller.dto.ReviewPostRequest;
import org.guzzing.studayserver.domain.review.controller.dto.ReviewPostResponse;
import org.guzzing.studayserver.domain.review.controller.dto.request.ReviewPostRequest;
import org.guzzing.studayserver.domain.review.controller.dto.response.ReviewPostResponse;
import org.guzzing.studayserver.domain.review.controller.dto.response.ReviewableResponse;
import org.guzzing.studayserver.domain.review.service.ReviewService;
import org.guzzing.studayserver.domain.review.service.dto.ReviewPostParam;
import org.guzzing.studayserver.domain.review.service.dto.ReviewPostResult;
import org.guzzing.studayserver.domain.review.service.dto.request.ReviewPostParam;
import org.guzzing.studayserver.domain.review.service.dto.response.ReviewPostResult;
import org.guzzing.studayserver.domain.review.service.dto.response.ReviewableResult;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
Expand All @@ -25,10 +31,10 @@ public ReviewRestController(ReviewService reviewService) {
this.reviewService = reviewService;
}

@PostMapping
@PostMapping(consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
public ResponseEntity<ReviewPostResponse> registerReview(
@Validated @RequestBody ReviewPostRequest request,
@MemberId Long memberId
@MemberId Long memberId,
@Validated @RequestBody ReviewPostRequest request
) {
ReviewPostParam param = ReviewPostRequest.to(memberId, request);
ReviewPostResult result = reviewService.createReviewOfAcademy(param);
Expand All @@ -39,4 +45,18 @@ public ResponseEntity<ReviewPostResponse> registerReview(
.body(response);
}

@GetMapping(path = "/reviewable", produces = APPLICATION_JSON_VALUE)
public ResponseEntity<ReviewableResponse> getReviewable(
@MemberId Long memberId,
@RequestParam Long academyId
) {
ReviewableResult result = reviewService.isReviewableToAcademy(memberId, academyId);
ReviewableResponse response = ReviewableResponse.from(result);

return ResponseEntity
.status(OK)
.body(response);
}


}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package org.guzzing.studayserver.domain.review.controller.dto;
package org.guzzing.studayserver.domain.review.controller.dto.request;

import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import org.guzzing.studayserver.domain.review.service.dto.ReviewPostParam;
import org.guzzing.studayserver.domain.review.service.dto.request.ReviewPostParam;

public record ReviewPostRequest(
@Positive Long academyId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.guzzing.studayserver.domain.review.controller.dto;
package org.guzzing.studayserver.domain.review.controller.dto.response;

import org.guzzing.studayserver.domain.review.service.dto.ReviewPostResult;
import org.guzzing.studayserver.domain.review.service.dto.response.ReviewPostResult;

public record ReviewPostResponse(
Long reviewId,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.guzzing.studayserver.domain.review.controller.dto.response;

import org.guzzing.studayserver.domain.review.service.dto.response.ReviewableResult;

public record ReviewableResponse(
long academyId,
boolean reviewable
) {

public static ReviewableResponse from(final ReviewableResult result) {
return new ReviewableResponse(
result.academyId(),
result.reviewable());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.guzzing.studayserver.domain.review.service.dto.ReviewPostParam;
import org.guzzing.studayserver.domain.review.service.dto.request.ReviewPostParam;
import org.guzzing.studayserver.global.exception.ReviewException;

public enum ReviewType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
import org.guzzing.studayserver.domain.review.model.Review;
import org.guzzing.studayserver.domain.review.model.ReviewType;
import org.guzzing.studayserver.domain.review.repository.ReviewRepository;
import org.guzzing.studayserver.domain.review.service.dto.ReviewPostParam;
import org.guzzing.studayserver.domain.review.service.dto.ReviewPostResult;
import org.guzzing.studayserver.domain.review.service.dto.request.ReviewPostParam;
import org.guzzing.studayserver.domain.review.service.dto.response.ReviewPostResult;
import org.guzzing.studayserver.domain.review.service.dto.response.ReviewableResult;
import org.guzzing.studayserver.global.exception.AcademyException;
import org.guzzing.studayserver.global.exception.MemberException;
import org.guzzing.studayserver.global.exception.ReviewException;
Expand All @@ -31,11 +32,18 @@ public ReviewService(
this.memberAccessService = memberAccessService;
}

@Transactional
public ReviewPostResult createReviewOfAcademy(final ReviewPostParam reviewPostParam) {
validateMember(reviewPostParam.memberId());
validateAcademy(reviewPostParam.academyId());

validateReviewedYet(reviewPostParam);
ReviewableResult reviewableResult = isReviewableToAcademy(
reviewPostParam.memberId(),
reviewPostParam.academyId());

if (!reviewableResult.reviewable()) {
throw new ReviewException("이미 리뷰를 남겼습니다.");
}

final Review review = Review.of(
reviewPostParam.academyId(),
Expand All @@ -47,14 +55,13 @@ public ReviewPostResult createReviewOfAcademy(final ReviewPostParam reviewPostPa
return ReviewPostResult.from(savedReview);
}

private void validateReviewedYet(ReviewPostParam reviewPostParam) {
boolean existsReview = reviewRepository.existsByMemberIdAndAcademyId(
reviewPostParam.memberId(),
reviewPostParam.academyId());
public ReviewableResult isReviewableToAcademy(final Long memberId, final Long academyId) {
validateMember(memberId);
validateAcademy(academyId);

if (existsReview) {
throw new ReviewException("이미 리뷰를 남겼습니다.");
}
boolean existsReview = reviewRepository.existsByMemberIdAndAcademyId(memberId, academyId);

return ReviewableResult.of(memberId, academyId, !existsReview);
}

private void validateMember(final Long memberId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.guzzing.studayserver.domain.review.service.dto;
package org.guzzing.studayserver.domain.review.service.dto.request;

public record ReviewPostParam(
Long memberId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.guzzing.studayserver.domain.review.service.dto;
package org.guzzing.studayserver.domain.review.service.dto.response;

import static org.guzzing.studayserver.domain.review.model.ReviewType.CHEAP_FEE;
import static org.guzzing.studayserver.domain.review.model.ReviewType.GOOD_FACILITY;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.guzzing.studayserver.domain.review.service.dto.response;

public record ReviewableResult(
Long memberId,
Long academyId,
boolean reviewable
) {

public static ReviewableResult of(final Long memberId, final Long academyId, final boolean reviewable) {
return new ReviewableResult(memberId, academyId, reviewable);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
Expand All @@ -17,6 +18,8 @@
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.queryParameters;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
Expand All @@ -25,7 +28,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import org.guzzing.studayserver.domain.academy.service.AcademyAccessService;
import org.guzzing.studayserver.domain.member.service.MemberAccessService;
import org.guzzing.studayserver.domain.review.controller.dto.ReviewPostRequest;
import org.guzzing.studayserver.domain.review.controller.dto.request.ReviewPostRequest;
import org.guzzing.studayserver.testutil.WithMockCustomOAuth2LoginUser;
import org.guzzing.studayserver.testutil.fixture.ReviewFixture;
import org.guzzing.studayserver.testutil.fixture.TestConfig;
Expand Down Expand Up @@ -106,4 +109,41 @@ void registerReview_Success() throws Exception {
));
}

@Test
@DisplayName("리뷰를 등록한 적 없다면 리뷰 등록 가능함을 응답한다.")
@WithMockCustomOAuth2LoginUser(memberId = 1L)
void getReviewable_NotExistsReview_Reviewable() throws Exception {
// Given
given(memberAccessService.existsMember(any())).willReturn(true);
given(academyAccessService.existsAcademy(any())).willReturn(true);

// When
ResultActions perform = mockMvc.perform(get("/reviews/reviewable")
.param("academyId", String.valueOf(1L))
.header(AUTHORIZATION_HEADER, BEARER + testConfig.getJwt())
.accept(APPLICATION_JSON_VALUE)
.contentType(APPLICATION_JSON_VALUE));

// Then
perform.andDo(print())
.andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith(APPLICATION_JSON_VALUE))
.andExpect(jsonPath("$.academyId").value(1L))
.andExpect(jsonPath("$.reviewable").value(true))
.andDo(document("get-reviewable",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
requestHeaders(
headerWithName("Authorization").description("JWT 토큰 (Bearer)")
),
queryParameters(
parameterWithName("academyId").description("학원 아이디")
),
responseFields(
fieldWithPath("academyId").type(NUMBER).description("학원 아이디"),
fieldWithPath("reviewable").type(BOOLEAN).description("리뷰 등록 가능 여부")
)
));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
import org.guzzing.studayserver.domain.academy.service.AcademyAccessService;
import org.guzzing.studayserver.domain.member.service.MemberAccessService;
import org.guzzing.studayserver.domain.review.model.ReviewType;
import org.guzzing.studayserver.domain.review.service.dto.ReviewPostParam;
import org.guzzing.studayserver.domain.review.service.dto.ReviewPostResult;
import org.guzzing.studayserver.domain.review.service.dto.request.ReviewPostParam;
import org.guzzing.studayserver.domain.review.service.dto.response.ReviewPostResult;
import org.guzzing.studayserver.domain.review.service.dto.response.ReviewableResult;
import org.guzzing.studayserver.global.exception.ReviewException;
import org.guzzing.studayserver.testutil.WithMockCustomOAuth2LoginUser;
import org.guzzing.studayserver.testutil.fixture.ReviewFixture;
Expand Down Expand Up @@ -62,7 +63,8 @@ void createReviewOfAcademy_NotReviewYet_RegisterReview() {
}

@Test
@DisplayName("해당 학원에 리뷰를 남겼다면 리뷰를 등록할 수 없다.")
@DisplayName("해당 학원에 리뷰를 남겼다면 리뷰 등록에 실패한다.")
@WithMockCustomOAuth2LoginUser(memberId = 1L)
void createReviewOfAcademy_Reviewed_Fail() {
// Given
given(memberAccessService.existsMember(any())).willReturn(true);
Expand All @@ -80,7 +82,8 @@ void createReviewOfAcademy_Reviewed_Fail() {
}

@Test
@DisplayName("리뷰를 3 항목 초과로 남겼다면 리뷰를 등록할 수 없다.")
@DisplayName("리뷰를 3 항목 초과로 남겼다면 리뷰 등록에 실패한다.")
@WithMockCustomOAuth2LoginUser(memberId = 1L)
void createReviewOfAcademy_GreaterThanThreeReivewTypes_Fail() {
// Given
given(memberAccessService.existsMember(any())).willReturn(true);
Expand All @@ -95,4 +98,37 @@ void createReviewOfAcademy_GreaterThanThreeReivewTypes_Fail() {
.hasMessage("리뷰는 3개까지만 가능합니다.");
}

@Test
@DisplayName("해당 학원에 리뷰를 남긴 적 없으면 리뷰 등록 가능하다.")
@WithMockCustomOAuth2LoginUser(memberId = 1L)
void isReviewableToAcademy_NotExistsReview_Reviewable() {
// Given
given(memberAccessService.existsMember(any())).willReturn(true);
given(academyAccessService.existsAcademy(any())).willReturn(true);

// When & Then
ReviewableResult result = reviewService.isReviewableToAcademy(100L, 100L);

assertThat(result.reviewable()).isTrue();
}

@Test
@DisplayName("해당 학원에 리뷰를 남겼다면 리뷰 등록 불가하다.")
@WithMockCustomOAuth2LoginUser(memberId = 1L)
void isReviewableToAcademy_ExistsReview_NotReviewable() {
// Given
given(memberAccessService.existsMember(any())).willReturn(true);
given(academyAccessService.existsAcademy(any())).willReturn(true);

boolean isValid = true;
ReviewPostParam param = ReviewFixture.makeReviewPostParam(isValid);

reviewService.createReviewOfAcademy(param);

// When & Then
ReviewableResult result = reviewService.isReviewableToAcademy(param.memberId(), param.academyId());

assertThat(result.reviewable()).isFalse();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.guzzing.studayserver.domain.review.controller.dto.ReviewPostRequest;
import org.guzzing.studayserver.domain.review.controller.dto.request.ReviewPostRequest;
import org.guzzing.studayserver.domain.review.model.ReviewType;
import org.guzzing.studayserver.domain.review.service.dto.ReviewPostParam;
import org.guzzing.studayserver.domain.review.service.dto.request.ReviewPostParam;

public class ReviewFixture {

Expand Down

0 comments on commit b8e2278

Please sign in to comment.