From e35e7999eafbaa9307f1d8f1d8b43ac29e7c586c Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 7 Nov 2023 23:32:13 +0900 Subject: [PATCH 1/7] =?UTF-8?q?refactor:=20=EB=B3=84=EC=A0=90=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=9D=84=20=EC=9C=84=ED=95=9C=20Dto=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/domain/starRating/dto/StarRatingReqDto.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/domain/starRating/dto/StarRatingReqDto.java b/src/main/java/io/oduck/api/domain/starRating/dto/StarRatingReqDto.java index cf7a86d3..a072325f 100644 --- a/src/main/java/io/oduck/api/domain/starRating/dto/StarRatingReqDto.java +++ b/src/main/java/io/oduck/api/domain/starRating/dto/StarRatingReqDto.java @@ -12,7 +12,7 @@ public class StarRatingReqDto { @Builder @NoArgsConstructor @AllArgsConstructor - public static class CreateReq { + public static class CreateAndPatchReq { @Min(value = 1, message = "score must be greater than or equal to 1") @Max(value = 10, message = "score must be less than or equal to 10") private int score; From 4a1b4b70acd267d7d75e056b64be50c6ad5e2789 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 7 Nov 2023 23:33:22 +0900 Subject: [PATCH 2/7] =?UTF-8?q?refactor:=20=EB=B3=84=EC=A0=90=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20=20=EC=88=98=EC=A0=95=20=EB=A9=94=EC=86=8C?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80=20#90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/domain/starRating/entity/StarRating.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/io/oduck/api/domain/starRating/entity/StarRating.java b/src/main/java/io/oduck/api/domain/starRating/entity/StarRating.java index 3d6dfe9f..65200327 100644 --- a/src/main/java/io/oduck/api/domain/starRating/entity/StarRating.java +++ b/src/main/java/io/oduck/api/domain/starRating/entity/StarRating.java @@ -49,4 +49,8 @@ public void relateMember(Member member) { public void relateAnime(Anime anime) { this.anime = anime; } + + public void updateScore(int score) { + this.score = score; + } } From c925c340b07ccf7b68a283b78c63d07a0fb24cac Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 7 Nov 2023 23:34:45 +0900 Subject: [PATCH 3/7] =?UTF-8?q?refactor:=20StarRating=20=EB=B3=84=EC=A0=90?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../starRating/service/StarRatingService.java | 1 + .../starRating/service/StarRatingServiceImpl.java | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/main/java/io/oduck/api/domain/starRating/service/StarRatingService.java b/src/main/java/io/oduck/api/domain/starRating/service/StarRatingService.java index ca5db69a..d472ae20 100644 --- a/src/main/java/io/oduck/api/domain/starRating/service/StarRatingService.java +++ b/src/main/java/io/oduck/api/domain/starRating/service/StarRatingService.java @@ -5,4 +5,5 @@ public interface StarRatingService { boolean createScore(Long memberId, Long animeId, int score); RatedDateTimeRes checkRated(Long memberId, Long animeId); + boolean updateScore(Long memberId, Long animeId, int score); } diff --git a/src/main/java/io/oduck/api/domain/starRating/service/StarRatingServiceImpl.java b/src/main/java/io/oduck/api/domain/starRating/service/StarRatingServiceImpl.java index 82338a8a..3d166bbf 100644 --- a/src/main/java/io/oduck/api/domain/starRating/service/StarRatingServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/starRating/service/StarRatingServiceImpl.java @@ -58,6 +58,21 @@ public RatedDateTimeRes checkRated(Long memberId, Long animeId) { .build(); } + @Override + public boolean updateScore(Long memberId, Long animeId, int score) { + StarRating foundStarRating = findByMemberIdAndAnimeId(memberId, animeId) + .orElseThrow(() -> new NotFoundException("StarRating")); + + if (foundStarRating.getScore() == score) { + return false; + } + + foundStarRating.updateScore(score); + + starRatingRepository.save(foundStarRating); + return true; + } + private Optional findByMemberIdAndAnimeId(Long memberId, Long animeId) { return starRatingRepository.findByMemberIdAndAnimeId(memberId, animeId); } From 9b2fb6e566cf9e6831add4a7b167bf2e25b6c679 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 7 Nov 2023 23:47:08 +0900 Subject: [PATCH 4/7] =?UTF-8?q?refactor:=20StarRating=20Controller=20?= =?UTF-8?q?=EB=B3=84=EC=A0=90=20=EC=88=98=EC=A0=95=20api=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/StarRatingController.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/starRating/controller/StarRatingController.java b/src/main/java/io/oduck/api/domain/starRating/controller/StarRatingController.java index f6bac5ce..3462be52 100644 --- a/src/main/java/io/oduck/api/domain/starRating/controller/StarRatingController.java +++ b/src/main/java/io/oduck/api/domain/starRating/controller/StarRatingController.java @@ -1,6 +1,6 @@ package io.oduck.api.domain.starRating.controller; -import io.oduck.api.domain.starRating.dto.StarRatingReqDto.CreateReq; +import io.oduck.api.domain.starRating.dto.StarRatingReqDto.CreateAndPatchReq; import io.oduck.api.domain.starRating.service.StarRatingService; import io.oduck.api.global.security.auth.dto.AuthUser; import io.oduck.api.global.security.auth.dto.LoginUser; @@ -11,6 +11,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -27,7 +28,7 @@ public class StarRatingController { @PostMapping("/{animeId}") public ResponseEntity PostScore( @PathVariable("animeId") @Positive Long animeId, - @RequestBody @Valid CreateReq body, + @RequestBody @Valid CreateAndPatchReq body, @LoginUser AuthUser user ) { boolean res = starRatingService.createScore(user.getId(), animeId, body.getScore()); @@ -41,4 +42,14 @@ public ResponseEntity GetScore( ) { return ResponseEntity.ok(starRatingService.checkRated(user.getId(), animeId)); } + + @PatchMapping("/{animeId}") + public ResponseEntity PatchScore( + @PathVariable("animeId") @Positive Long animeId, + @RequestBody @Valid CreateAndPatchReq body, + @LoginUser AuthUser user + ) { + boolean res = starRatingService.updateScore(user.getId(), animeId, body.getScore()); + return ResponseEntity.status(res ? HttpStatus.NO_CONTENT : HttpStatus.CONFLICT).build(); + } } From 9a2c0a5f0dab932f28b95dfd0795df2a045697dd Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 8 Nov 2023 00:04:38 +0900 Subject: [PATCH 5/7] =?UTF-8?q?test:=20StarRating=20Controller=20=EB=B3=84?= =?UTF-8?q?=EC=A0=90=20=EC=88=98=EC=A0=95=20Test=20=EC=B6=94=EA=B0=80=20#9?= =?UTF-8?q?0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../starRating/StarRatingControllerTest.java | 112 +++++++++++++++++- 1 file changed, 109 insertions(+), 3 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/starRating/StarRatingControllerTest.java b/src/test/java/io/oduck/api/e2e/starRating/StarRatingControllerTest.java index 6b10952d..976ce9ec 100644 --- a/src/test/java/io/oduck/api/e2e/starRating/StarRatingControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/starRating/StarRatingControllerTest.java @@ -5,6 +5,7 @@ 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.patch; 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; @@ -20,7 +21,7 @@ import com.google.gson.Gson; import io.oduck.api.domain.member.entity.Role; -import io.oduck.api.domain.starRating.dto.StarRatingReqDto.CreateReq; +import io.oduck.api.domain.starRating.dto.StarRatingReqDto.CreateAndPatchReq; import io.oduck.api.global.mockMember.WithCustomMockMember; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -64,7 +65,7 @@ void createScore() throws Exception { Long animeId = 2L; int score = 5; - CreateReq body = CreateReq.builder() + CreateAndPatchReq body = CreateAndPatchReq.builder() .score(score) .build(); @@ -115,7 +116,7 @@ void createScoreIfAlreadyExist() throws Exception { Long animeId = 1L; int score = 5; - CreateReq body = CreateReq.builder() + CreateAndPatchReq body = CreateAndPatchReq.builder() .score(score) .build(); @@ -247,6 +248,111 @@ void checkRatedNotExist() throws Exception { ) ); } + } + + @Nested + @DisplayName("PATCH /ratings/{animeId}") + class PatchScore { + @Test + @DisplayName("평점 수정 성공시 204 No Content 반환") + @WithCustomMockMember(id = 2L, email = "john", password = "Qwer!234", role = Role.MEMBER) + void patchScore() throws Exception { + // given + Long animeId = 1L; + int score = 5; + + CreateAndPatchReq body = CreateAndPatchReq.builder() + .score(score) + .build(); + + String content = gson.toJson(body); + + // when + ResultActions actions = mockMvc.perform( + patch(BASE_URL + "/{animeId}", animeId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.COOKIE, "oDuckio.sid={SESSION_VALUE}") + .content(content) + ); + + // then + actions + .andExpect(status().isNoContent()) + .andDo(document("starRating/patchScore/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("animeId") + .description("애니메이션 식별자") + ), + requestHeaders( + headerWithName(HttpHeaders.COOKIE) + .attributes(field("constraints", "oDuckio.sid={SESSION_VALUE}")) + .optional() + .description("Header Cookie, 세션 쿠키") + ), + requestFields( + attributes(key("title") + .value("Fields for starRating creation")), + fieldWithPath("score") + .type(JsonFieldType.NUMBER) + .attributes(field("constraints", "1~10 사이의 정수")) + .description("애니메 별점") + ) + ) + ); + } + + @Test + @DisplayName("기존 평점과 동일하다면 없다면 409 Conflict 반환") + @WithCustomMockMember(id = 3L, email = "john", password = "Qwer!234", role = Role.MEMBER) + void patchScoreIfNotExist() throws Exception { + // given + Long animeId = 1L; + int score = 3; + CreateAndPatchReq body = CreateAndPatchReq.builder() + .score(score) + .build(); + + String content = gson.toJson(body); + + // when + ResultActions actions = mockMvc.perform( + patch(BASE_URL + "/{animeId}", animeId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.COOKIE, "oDuckio.sid={SESSION_VALUE}") + .content(content) + ); + + // then + actions + .andExpect(status().isConflict()) + .andDo(document("starRating/patchScore/failure", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("animeId") + .description("애니메이션 식별자") + ), + requestHeaders( + headerWithName(HttpHeaders.COOKIE) + .attributes(field("constraints", "oDuckio.sid={SESSION_VALUE}")) + .optional() + .description("Header Cookie, 세션 쿠키") + ), + requestFields( + attributes(key("title") + .value("Fields for starRating creation")), + fieldWithPath("score") + .type(JsonFieldType.NUMBER) + .attributes(field("constraints", "1~10 사이의 정수")) + .description("애니메 별점") + ) + ) + ); + } } } From ab3343cdb85c0d9db717cf9cfdf880c6bbb1e5fd Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 8 Nov 2023 00:04:57 +0900 Subject: [PATCH 6/7] =?UTF-8?q?test:=20Star=20Rating=20Service=20=EB=B3=84?= =?UTF-8?q?=EC=A0=90=20=EC=88=98=EC=A0=95=20Test=20=EC=B6=94=EA=B0=80=20#9?= =?UTF-8?q?0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/StarRatingServiceTest.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/test/java/io/oduck/api/unit/starRating/service/StarRatingServiceTest.java b/src/test/java/io/oduck/api/unit/starRating/service/StarRatingServiceTest.java index 0508f7a8..44006e42 100644 --- a/src/test/java/io/oduck/api/unit/starRating/service/StarRatingServiceTest.java +++ b/src/test/java/io/oduck/api/unit/starRating/service/StarRatingServiceTest.java @@ -147,4 +147,61 @@ void chekRatedIfNotExist() { assertThrows(NotFoundException.class, () -> starRatingService.checkRated(memberId, animeId)); } } + + @Nested + @DisplayName("별점 수정") + class UpdateScore { + Long memberId = 1L; + Long animeId = 1L; + int score = 2; + + Member member = Member.builder() + .id(memberId) + .build(); + Anime anime = Anime.builder() + .id(animeId) + .build(); + + @Test + @DisplayName("별점 수정 성공") + void updateScore() { + // given + StarRating starRating = StarRating.builder() + .member(member) + .anime(anime) + .score(score) + .build(); + + given(starRatingRepository.findByMemberIdAndAnimeId(memberId, animeId)) + .willReturn(Optional.ofNullable(starRating)); + + // when + boolean result = starRatingService.updateScore(memberId, animeId, 5); + + // then + assertDoesNotThrow(() -> starRatingService.updateScore(memberId, animeId, score)); + assertTrue(result); + } + + @Test + @DisplayName("별점 수정 실패") + void updateScoreIfNotExist() { + // given + StarRating starRating = StarRating.builder() + .member(member) + .anime(anime) + .score(score) + .build(); + + given(starRatingRepository.findByMemberIdAndAnimeId(memberId, animeId)) + .willReturn(Optional.ofNullable(starRating)); + + // when + boolean result = starRatingService.updateScore(memberId, animeId, score); + + // then + assertDoesNotThrow(() -> starRatingService.updateScore(memberId, animeId, score)); + assertFalse(result); + } + } } From 7e49f95552f1800d546a9fe63d58a996d3ca3fe6 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 8 Nov 2023 00:05:26 +0900 Subject: [PATCH 7/7] =?UTF-8?q?docs:=20=EB=B3=84=EC=A0=90=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20api=20=EB=AC=B8=EC=84=9C=20=EC=B6=94=EA=B0=80=20#90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/index.adoc | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index f287050b..1b7b8020 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -1095,3 +1095,30 @@ include::{snippets}/starRating/checkRated/notExist/response-body.adoc[] .response-fields include::{snippets}/starRating/checkRated/notExist/response-fields.adoc[] + +=== PATCH api/v1/ratings/:animeId +.curl-request +include::{snippets}/starRating/patchScore/success/curl-request.adoc[] + +.http-request +include::{snippets}/starRating/patchScore/success/http-request.adoc[] + +.path-parameters +include::{snippets}/starRating/patchScore/success/path-parameters.adoc[] + +.request-header +include::{snippets}/starRating/patchScore/success/request-headers.adoc[] + +.request-body +include::{snippets}/starRating/patchScore/success/request-body.adoc[] + +.request-fields +include::{snippets}/starRating/patchScore/success/request-fields.adoc[] + +==== 성공시 +.http-response +include::{snippets}/starRating/patchScore/success/http-response.adoc[] + +==== 실패시 +.http-response +include::{snippets}/starRating/patchScore/failure/http-response.adoc[]