diff --git a/backend/src/docs/asciidoc/highlight-answers.adoc b/backend/src/docs/asciidoc/highlight-answers.adoc new file mode 100644 index 000000000..7f0fd988b --- /dev/null +++ b/backend/src/docs/asciidoc/highlight-answers.adoc @@ -0,0 +1,3 @@ +==== 리뷰 하이라이트 변경 (추가, 삭제, 수정 포함) + +operation::highlight-answer[snippets="curl-request,request-cookies,http-response,request-fields"] diff --git a/backend/src/docs/asciidoc/index.adoc b/backend/src/docs/asciidoc/index.adoc index 616bf03bb..4d67754a7 100644 --- a/backend/src/docs/asciidoc/index.adoc +++ b/backend/src/docs/asciidoc/index.adoc @@ -35,4 +35,8 @@ include::review-list.adoc[] === 리뷰 모아보기 -include::review-gather.adoc[] \ No newline at end of file +include::review-gather.adoc[] + +=== 답변 하이라이트 + +include::highlight-answers.adoc[] diff --git a/backend/src/main/java/reviewme/highlight/controller/HighlightController.java b/backend/src/main/java/reviewme/highlight/controller/HighlightController.java new file mode 100644 index 000000000..8a9a7f0b2 --- /dev/null +++ b/backend/src/main/java/reviewme/highlight/controller/HighlightController.java @@ -0,0 +1,25 @@ +package reviewme.highlight.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.SessionAttribute; +import reviewme.highlight.service.HighlightService; +import reviewme.highlight.service.dto.HighlightsRequest; + +@RestController +@RequiredArgsConstructor +public class HighlightController { + + private final HighlightService highlightService; + + @PostMapping("/v2/highlight") + public ResponseEntity highlight(@Valid @RequestBody HighlightsRequest request, + @SessionAttribute("reviewRequestCode") String reviewRequestCode) { + highlightService.highlight(request); + return ResponseEntity.ok().build(); + } +} diff --git a/backend/src/main/java/reviewme/highlight/service/HighlightService.java b/backend/src/main/java/reviewme/highlight/service/HighlightService.java new file mode 100644 index 000000000..e14ea984d --- /dev/null +++ b/backend/src/main/java/reviewme/highlight/service/HighlightService.java @@ -0,0 +1,12 @@ +package reviewme.highlight.service; + +import org.springframework.stereotype.Service; +import reviewme.highlight.service.dto.HighlightsRequest; + +@Service +public class HighlightService { + + public void highlight(HighlightsRequest request) { + // TODO: implement method + } +} diff --git a/backend/src/main/java/reviewme/highlight/service/dto/HighlightIndexRangeRequest.java b/backend/src/main/java/reviewme/highlight/service/dto/HighlightIndexRangeRequest.java new file mode 100644 index 000000000..fde581308 --- /dev/null +++ b/backend/src/main/java/reviewme/highlight/service/dto/HighlightIndexRangeRequest.java @@ -0,0 +1,13 @@ +package reviewme.highlight.service.dto; + +import jakarta.validation.constraints.NotNull; + +public record HighlightIndexRangeRequest( + + @NotNull(message = "시작 인덱스를 입력해주세요.") + Long startIndex, + + @NotNull(message = "끝 인덱스를 입력해주세요.") + Long endIndex +) { +} diff --git a/backend/src/main/java/reviewme/highlight/service/dto/HighlightRequest.java b/backend/src/main/java/reviewme/highlight/service/dto/HighlightRequest.java new file mode 100644 index 000000000..673cc8e6a --- /dev/null +++ b/backend/src/main/java/reviewme/highlight/service/dto/HighlightRequest.java @@ -0,0 +1,16 @@ +package reviewme.highlight.service.dto; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import java.util.List; + +public record HighlightRequest( + + @NotNull(message = "답변 ID를 입력해주세요.") + Long answerId, + + @Valid @NotEmpty(message = "하이라이트 된 라인을 입력해주세요.") + List lines +) { +} diff --git a/backend/src/main/java/reviewme/highlight/service/dto/HighlightedLineRequest.java b/backend/src/main/java/reviewme/highlight/service/dto/HighlightedLineRequest.java new file mode 100644 index 000000000..a8275aea3 --- /dev/null +++ b/backend/src/main/java/reviewme/highlight/service/dto/HighlightedLineRequest.java @@ -0,0 +1,16 @@ +package reviewme.highlight.service.dto; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import java.util.List; + +public record HighlightedLineRequest( + + @NotNull(message = "인덱스를 입력해주세요.") + Long index, + + @Valid @NotEmpty(message = "하이라이트 범위를 입력해주세요.") + List ranges +) { +} diff --git a/backend/src/main/java/reviewme/highlight/service/dto/HighlightsRequest.java b/backend/src/main/java/reviewme/highlight/service/dto/HighlightsRequest.java new file mode 100644 index 000000000..7b41526d9 --- /dev/null +++ b/backend/src/main/java/reviewme/highlight/service/dto/HighlightsRequest.java @@ -0,0 +1,15 @@ +package reviewme.highlight.service.dto; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import java.util.List; + +public record HighlightsRequest( + + @NotNull(message = "질문 ID를 입력해주세요.") + Long questionId, + + @Valid @NotNull(message = "하이라이트할 부분을 입력해주세요.") + List highlights +) { +} diff --git a/backend/src/test/java/reviewme/api/ApiTest.java b/backend/src/test/java/reviewme/api/ApiTest.java index 94a0e15e8..2d919e645 100644 --- a/backend/src/test/java/reviewme/api/ApiTest.java +++ b/backend/src/test/java/reviewme/api/ApiTest.java @@ -26,6 +26,8 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; +import reviewme.highlight.controller.HighlightController; +import reviewme.highlight.service.HighlightService; import reviewme.review.controller.ReviewController; import reviewme.review.service.GatheredReviewLookupService; import reviewme.review.service.ReviewDetailLookupService; @@ -44,7 +46,8 @@ ReviewGroupController.class, ReviewController.class, TemplateController.class, - SectionController.class + SectionController.class, + HighlightController.class }) @ExtendWith(RestDocumentationExtension.class) public abstract class ApiTest { @@ -78,6 +81,9 @@ public abstract class ApiTest { @MockBean protected GatheredReviewLookupService gatheredReviewLookupService; + @MockBean + protected HighlightService highlightService; + Filter sessionCookieFilter = (request, response, chain) -> { chain.doFilter(request, response); HttpSession session = ((HttpServletRequest) request).getSession(false); diff --git a/backend/src/test/java/reviewme/api/HighlightApiTest.java b/backend/src/test/java/reviewme/api/HighlightApiTest.java new file mode 100644 index 000000000..c908d828f --- /dev/null +++ b/backend/src/test/java/reviewme/api/HighlightApiTest.java @@ -0,0 +1,62 @@ +package reviewme.api; + +import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName; +import static org.springframework.restdocs.cookies.CookieDocumentation.requestCookies; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; + +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; +import org.springframework.restdocs.cookies.CookieDescriptor; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.restdocs.payload.FieldDescriptor; + +class HighlightApiTest extends ApiTest { + + @Test + void 존재하는_답변에_하이라이트를_생성한다() { + String request = """ + { + "questionId": 1, + "highlights": [{ + "answerId": 3, + "lines": [{ + "index": 5, + "ranges": [{ + "startIndex": 6, + "endIndex": 9 + }] + }] + }] + } + """; + + CookieDescriptor[] cookieDescriptors = { + cookieWithName("JSESSIONID").description("세션 ID") + }; + + FieldDescriptor[] requestFields = { + fieldWithPath("questionId").description("질문 ID"), + fieldWithPath("highlights").description("하이라이트 목록"), + fieldWithPath("highlights[].answerId").description("답변 ID"), + fieldWithPath("highlights[].lines[].index").description("개행으로 구분되는 라인 번호, 0-based"), + fieldWithPath("highlights[].lines[].ranges[].startIndex").description("하이라이트 시작 인덱스, 0-based"), + fieldWithPath("highlights[].lines[].ranges[].endIndex").description("하이라이트 끝 인덱스, 0-based") + }; + + RestDocumentationResultHandler handler = document( + "highlight-answer", + requestFields(requestFields), + requestCookies(cookieDescriptors) + ); + + givenWithSpec().log().all() + .cookie("JSESSIONID", "AVEBNKLCL13TNVZ") + .body(request) + .when().post("/v2/highlight") + .then().log().all() + .apply(handler) + .status(HttpStatus.OK); + } +}