From f4d720fd14d9585e7a9b5ecef0572541d74b8862 Mon Sep 17 00:00:00 2001 From: joyewon0705 <77885098+joyewon0705@users.noreply.github.com> Date: Thu, 2 Oct 2025 15:26:39 +0900 Subject: [PATCH 1/3] =?UTF-8?q?Feat:=20=EB=8C=93=EA=B8=80=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/controller/CommentController.java | 36 ++++++++++++++ .../controller/CommentControllerDocs.java | 7 +++ .../back/domain/board/dto/CommentRequest.java | 13 +++++ .../domain/board/dto/CommentResponse.java | 35 ++++++++++++++ .../com/back/domain/board/entity/Comment.java | 7 +++ .../board/repository/CommentRepository.java | 9 ++++ .../domain/board/service/CommentService.java | 48 +++++++++++++++++++ 7 files changed, 155 insertions(+) create mode 100644 src/main/java/com/back/domain/board/controller/CommentController.java create mode 100644 src/main/java/com/back/domain/board/controller/CommentControllerDocs.java create mode 100644 src/main/java/com/back/domain/board/dto/CommentRequest.java create mode 100644 src/main/java/com/back/domain/board/dto/CommentResponse.java create mode 100644 src/main/java/com/back/domain/board/repository/CommentRepository.java create mode 100644 src/main/java/com/back/domain/board/service/CommentService.java diff --git a/src/main/java/com/back/domain/board/controller/CommentController.java b/src/main/java/com/back/domain/board/controller/CommentController.java new file mode 100644 index 00000000..9933aa2c --- /dev/null +++ b/src/main/java/com/back/domain/board/controller/CommentController.java @@ -0,0 +1,36 @@ +package com.back.domain.board.controller; + +import com.back.domain.board.dto.CommentRequest; +import com.back.domain.board.dto.CommentResponse; +import com.back.domain.board.service.CommentService; +import com.back.global.common.dto.RsData; +import com.back.global.security.user.CustomUserDetails; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/posts/{postId}/comments") +@RequiredArgsConstructor +public class CommentController implements CommentControllerDocs { + private final CommentService commentService; + + // 댓글 생성 + @PostMapping + public ResponseEntity> createComment( + @PathVariable Long postId, + @RequestBody @Valid CommentRequest request, + @AuthenticationPrincipal CustomUserDetails user + ) { + CommentResponse response = commentService.createComment(postId, request, user.getUserId()); + return ResponseEntity + .status(HttpStatus.CREATED) + .body(RsData.success( + "댓글이 생성되었습니다.", + response + )); + } +} diff --git a/src/main/java/com/back/domain/board/controller/CommentControllerDocs.java b/src/main/java/com/back/domain/board/controller/CommentControllerDocs.java new file mode 100644 index 00000000..0daf3ccc --- /dev/null +++ b/src/main/java/com/back/domain/board/controller/CommentControllerDocs.java @@ -0,0 +1,7 @@ +package com.back.domain.board.controller; + +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "Comment API", description = "댓글 관련 API") +public interface CommentControllerDocs { +} diff --git a/src/main/java/com/back/domain/board/dto/CommentRequest.java b/src/main/java/com/back/domain/board/dto/CommentRequest.java new file mode 100644 index 00000000..24a1a2eb --- /dev/null +++ b/src/main/java/com/back/domain/board/dto/CommentRequest.java @@ -0,0 +1,13 @@ +package com.back.domain.board.dto; + +import jakarta.validation.constraints.NotBlank; + +/** + * 댓글 작성 및 수정을 위한 요청 DTO + * + * @param content 댓글 내용 + */ +public record CommentRequest( + @NotBlank String content +) { +} diff --git a/src/main/java/com/back/domain/board/dto/CommentResponse.java b/src/main/java/com/back/domain/board/dto/CommentResponse.java new file mode 100644 index 00000000..6b76feba --- /dev/null +++ b/src/main/java/com/back/domain/board/dto/CommentResponse.java @@ -0,0 +1,35 @@ +package com.back.domain.board.dto; + +import com.back.domain.board.entity.Comment; + +import java.time.LocalDateTime; + +/** + * 댓글 응답 DTO + * + * @param commentId 댓글 Id + * @param postId 게시글 Id + * @param author 작성자 정보 + * @param content 댓글 내용 + * @param createdAt 댓글 생성 일시 + * @param updatedAt 댓글 수정 일시 + */ +public record CommentResponse( + Long commentId, + Long postId, + AuthorResponse author, + String content, + LocalDateTime createdAt, + LocalDateTime updatedAt +) { + public static CommentResponse from(Comment comment) { + return new CommentResponse( + comment.getId(), + comment.getPost().getId(), + AuthorResponse.from(comment.getUser()), + comment.getContent(), + comment.getCreatedAt(), + comment.getUpdatedAt() + ); + } +} diff --git a/src/main/java/com/back/domain/board/entity/Comment.java b/src/main/java/com/back/domain/board/entity/Comment.java index 8ed14c93..90f16d34 100644 --- a/src/main/java/com/back/domain/board/entity/Comment.java +++ b/src/main/java/com/back/domain/board/entity/Comment.java @@ -34,4 +34,11 @@ public class Comment extends BaseEntity { @OneToMany(mappedBy = "comment", cascade = CascadeType.ALL, orphanRemoval = true) private List commentLikes = new ArrayList<>(); + + // -------------------- 생성자 -------------------- + public Comment(Post post, User user, String content) { + this.post = post; + this.user = user; + this.content = content; + } } diff --git a/src/main/java/com/back/domain/board/repository/CommentRepository.java b/src/main/java/com/back/domain/board/repository/CommentRepository.java new file mode 100644 index 00000000..1a606af0 --- /dev/null +++ b/src/main/java/com/back/domain/board/repository/CommentRepository.java @@ -0,0 +1,9 @@ +package com.back.domain.board.repository; + +import com.back.domain.board.entity.Comment; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface CommentRepository extends JpaRepository { +} diff --git a/src/main/java/com/back/domain/board/service/CommentService.java b/src/main/java/com/back/domain/board/service/CommentService.java new file mode 100644 index 00000000..0dbccf6d --- /dev/null +++ b/src/main/java/com/back/domain/board/service/CommentService.java @@ -0,0 +1,48 @@ +package com.back.domain.board.service; + +import com.back.domain.board.dto.CommentRequest; +import com.back.domain.board.dto.CommentResponse; +import com.back.domain.board.entity.Comment; +import com.back.domain.board.entity.Post; +import com.back.domain.board.repository.CommentRepository; +import com.back.domain.board.repository.PostRepository; +import com.back.domain.user.entity.User; +import com.back.domain.user.repository.UserRepository; +import com.back.global.exception.CustomException; +import com.back.global.exception.ErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional +public class CommentService { + private final CommentRepository commentRepository; + private final UserRepository userRepository; + private final PostRepository postRepository; + + /** + * 댓글 생성 서비스 + * 1. User 조회 + * 2. Post 조회 + * 3. Comment 생성 + * 4. Comment 저장 및 CommentResponse 반환 + */ + public CommentResponse createComment(Long postId, CommentRequest request, Long userId) { + // User 조회 + User user = userRepository.findById(userId) + .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); + + // Post 조회 + Post post = postRepository.findById(postId) + .orElseThrow(() -> new CustomException(ErrorCode.POST_NOT_FOUND)); + + // Comment 생성 + Comment comment = new Comment(post, user, request.content()); + + // Comment 저장 및 응답 반환 + commentRepository.save(comment); + return CommentResponse.from(comment); + } +} From e972e7847d45ecd91eac2324f18626734daedb80 Mon Sep 17 00:00:00 2001 From: joyewon0705 <77885098+joyewon0705@users.noreply.github.com> Date: Thu, 2 Oct 2025 15:37:15 +0900 Subject: [PATCH 2/3] =?UTF-8?q?Test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CommentControllerTest.java | 204 ++++++++++++++++++ .../board/service/CommentServiceTest.java | 101 +++++++++ 2 files changed, 305 insertions(+) create mode 100644 src/test/java/com/back/domain/board/controller/CommentControllerTest.java create mode 100644 src/test/java/com/back/domain/board/service/CommentServiceTest.java diff --git a/src/test/java/com/back/domain/board/controller/CommentControllerTest.java b/src/test/java/com/back/domain/board/controller/CommentControllerTest.java new file mode 100644 index 00000000..33022ded --- /dev/null +++ b/src/test/java/com/back/domain/board/controller/CommentControllerTest.java @@ -0,0 +1,204 @@ +package com.back.domain.board.controller; + +import com.back.domain.board.dto.CommentRequest; +import com.back.domain.board.entity.Post; +import com.back.domain.board.repository.PostRepository; +import com.back.domain.user.entity.User; +import com.back.domain.user.entity.UserProfile; +import com.back.domain.user.entity.UserStatus; +import com.back.domain.user.repository.UserRepository; +import com.back.fixture.TestJwtTokenProvider; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@SpringBootTest +@AutoConfigureMockMvc +@ActiveProfiles("test") +@Transactional +class CommentControllerTest { + + @Autowired + private MockMvc mvc; + + @Autowired + private UserRepository userRepository; + + @Autowired + private PostRepository postRepository; + + @Autowired + private TestJwtTokenProvider testJwtTokenProvider; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Autowired + private ObjectMapper objectMapper; + + private String generateAccessToken(User user) { + return testJwtTokenProvider.createAccessToken( + user.getId(), + user.getUsername(), + user.getRole().name() + ); + } + + // ====================== 댓글 생성 테스트 ====================== + + @Test + @DisplayName("댓글 생성 성공 → 201 Created") + void createComment_success() throws Exception { + // given: 정상 유저 + 게시글 + User user = User.createUser("writer", "writer@example.com", passwordEncoder.encode("P@ssw0rd!")); + user.setUserProfile(new UserProfile(user, "홍길동", null, "소개글", LocalDate.of(2000, 1, 1), 1000)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + Post post = new Post(user, "첫 글", "내용"); + postRepository.save(post); + + String accessToken = generateAccessToken(user); + + CommentRequest request = new CommentRequest("좋은 글 감사합니다!"); + + // when + ResultActions resultActions = mvc.perform( + post("/api/posts/{postId}/comments", post.getId()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request)) + ).andDo(print()); + + // then + resultActions + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.code").value("SUCCESS_200")) + .andExpect(jsonPath("$.data.content").value("좋은 글 감사합니다!")) + .andExpect(jsonPath("$.data.author.nickname").value("홍길동")) + .andExpect(jsonPath("$.data.postId").value(post.getId())); + } + + @Test + @DisplayName("댓글 생성 실패 - 존재하지 않는 사용자 → 404 Not Found") + void createComment_userNotFound() throws Exception { + // given: 게시글 저장 + User user = User.createUser("temp", "temp@example.com", passwordEncoder.encode("P@ssw0rd!")); + user.setUserProfile(new UserProfile(user, "임시", null, null, null, 0)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + Post post = new Post(user, "제목", "내용"); + postRepository.save(post); + + // DB에 없는 userId 기반 토큰 + String fakeToken = testJwtTokenProvider.createAccessToken(999L, "ghost", "USER"); + + CommentRequest request = new CommentRequest("댓글 내용"); + + // when & then + mvc.perform(post("/api/posts/{postId}/comments", post.getId()) + .header("Authorization", "Bearer " + fakeToken) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value("USER_001")) + .andExpect(jsonPath("$.message").value("존재하지 않는 사용자입니다.")); + } + + @Test + @DisplayName("댓글 생성 실패 - 존재하지 않는 게시글 → 404 Not Found") + void createComment_postNotFound() throws Exception { + // given: 정상 유저 + User user = User.createUser("writer2", "writer2@example.com", passwordEncoder.encode("P@ssw0rd!")); + user.setUserProfile(new UserProfile(user, "작성자", null, null, null, 0)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + String accessToken = generateAccessToken(user); + + CommentRequest request = new CommentRequest("댓글 내용"); + + // when & then + mvc.perform(post("/api/posts/{postId}/comments", 999L) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value("POST_001")) + .andExpect(jsonPath("$.message").value("존재하지 않는 게시글입니다.")); + } + + @Test + @DisplayName("댓글 생성 실패 - 잘못된 요청(필드 누락) → 400 Bad Request") + void createComment_badRequest() throws Exception { + // given: 정상 유저 + 게시글 + User user = User.createUser("writer", "writer@example.com", passwordEncoder.encode("P@ssw0rd!")); + user.setUserProfile(new UserProfile(user, "홍길동", null, null, null, 0)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + Post post = new Post(user, "제목", "내용"); + postRepository.save(post); + + String accessToken = generateAccessToken(user); + + // content 누락 + String invalidJson = """ + { + } + """; + + // when & then + mvc.perform(post("/api/posts/{postId}/comments", post.getId()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(invalidJson)) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("COMMON_400")) + .andExpect(jsonPath("$.message").value("잘못된 요청입니다.")); + } + + @Test + @DisplayName("댓글 생성 실패 - 토큰 없음 → 401 Unauthorized") + void createComment_noToken() throws Exception { + // given: 게시글 + User user = User.createUser("writer", "writer@example.com", passwordEncoder.encode("P@ssw0rd!")); + user.setUserProfile(new UserProfile(user, "작성자", null, null, null, 0)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + Post post = new Post(user, "제목", "내용"); + postRepository.save(post); + + CommentRequest request = new CommentRequest("댓글 내용"); + + // when & then + mvc.perform(post("/api/posts/{postId}/comments", post.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.code").value("AUTH_001")) + .andExpect(jsonPath("$.message").value("인증이 필요합니다.")); + } +} diff --git a/src/test/java/com/back/domain/board/service/CommentServiceTest.java b/src/test/java/com/back/domain/board/service/CommentServiceTest.java new file mode 100644 index 00000000..67a9fe5e --- /dev/null +++ b/src/test/java/com/back/domain/board/service/CommentServiceTest.java @@ -0,0 +1,101 @@ +package com.back.domain.board.service; + +import com.back.domain.board.dto.CommentRequest; +import com.back.domain.board.dto.CommentResponse; +import com.back.domain.board.entity.Post; +import com.back.domain.board.repository.CommentRepository; +import com.back.domain.board.repository.PostRepository; +import com.back.domain.user.entity.User; +import com.back.domain.user.entity.UserProfile; +import com.back.domain.user.entity.UserStatus; +import com.back.domain.user.repository.UserRepository; +import com.back.global.exception.CustomException; +import com.back.global.exception.ErrorCode; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +import static org.assertj.core.api.Assertions.*; + +@SpringBootTest +@Transactional +@ActiveProfiles("test") +class CommentServiceTest { + + @Autowired + private CommentService commentService; + + @Autowired + private CommentRepository commentRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private PostRepository postRepository; + + // ====================== 댓글 생성 테스트 ====================== + + @Test + @DisplayName("댓글 생성 성공") + void createComment_success() { + // given: 유저 + 게시글 저장 + User user = User.createUser("writer", "writer@example.com", "encodedPwd"); + user.setUserProfile(new UserProfile(user, "작성자", null, null, null, 0)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + Post post = new Post(user, "제목", "내용"); + postRepository.save(post); + + CommentRequest request = new CommentRequest("댓글 내용"); + + // when + CommentResponse response = commentService.createComment(post.getId(), request, user.getId()); + + // then + assertThat(response.content()).isEqualTo("댓글 내용"); + assertThat(response.author().nickname()).isEqualTo("작성자"); + assertThat(response.postId()).isEqualTo(post.getId()); + } + + @Test + @DisplayName("댓글 생성 실패 - 존재하지 않는 유저") + void createComment_fail_userNotFound() { + // given: 게시글 저장 + User user = User.createUser("temp", "temp@example.com", "encodedPwd"); + user.setUserProfile(new UserProfile(user, "임시유저", null, null, null, 0)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + Post post = new Post(user, "제목", "내용"); + postRepository.save(post); + + CommentRequest request = new CommentRequest("댓글 내용"); + + // when & then + assertThatThrownBy(() -> commentService.createComment(post.getId(), request, 999L)) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.USER_NOT_FOUND.getMessage()); + } + + @Test + @DisplayName("댓글 생성 실패 - 존재하지 않는 게시글") + void createComment_fail_postNotFound() { + // given: 유저 저장 + User user = User.createUser("writer2", "writer2@example.com", "encodedPwd"); + user.setUserProfile(new UserProfile(user, "작성자2", null, null, null, 0)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + CommentRequest request = new CommentRequest("댓글 내용"); + + // when & then + assertThatThrownBy(() -> commentService.createComment(999L, request, user.getId())) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.POST_NOT_FOUND.getMessage()); + } +} From b6e3c0817d2bee71579996f9b8ed648b88b58a51 Mon Sep 17 00:00:00 2001 From: joyewon0705 <77885098+joyewon0705@users.noreply.github.com> Date: Thu, 2 Oct 2025 15:38:18 +0900 Subject: [PATCH 3/3] =?UTF-8?q?Docs:=20Swagger=20=EB=AC=B8=EC=84=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CommentControllerDocs.java | 139 +++++++++++++++++- 1 file changed, 138 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/back/domain/board/controller/CommentControllerDocs.java b/src/main/java/com/back/domain/board/controller/CommentControllerDocs.java index 0daf3ccc..a249d222 100644 --- a/src/main/java/com/back/domain/board/controller/CommentControllerDocs.java +++ b/src/main/java/com/back/domain/board/controller/CommentControllerDocs.java @@ -1,7 +1,144 @@ package com.back.domain.board.controller; +import com.back.domain.board.dto.CommentRequest; +import com.back.domain.board.dto.CommentResponse; +import com.back.global.common.dto.RsData; +import com.back.global.security.user.CustomUserDetails; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; @Tag(name = "Comment API", description = "댓글 관련 API") public interface CommentControllerDocs { -} + + @Operation( + summary = "댓글 생성", + description = "로그인한 사용자가 특정 게시글에 댓글을 작성합니다." + ) + @ApiResponses({ + @ApiResponse( + responseCode = "201", + description = "댓글 생성 성공", + content = @Content( + mediaType = "application/json", + examples = @ExampleObject(value = """ + { + "success": true, + "code": "SUCCESS_200", + "message": "댓글이 생성되었습니다.", + "data": { + "commentId": 25, + "postId": 101, + "author": { + "id": 5, + "nickname": "홍길동" + }, + "content": "좋은 글 감사합니다!", + "createdAt": "2025-09-22T11:30:00", + "updatedAt": "2025-09-22T11:30:00" + } + } + """) + ) + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청 (필드 누락 등)", + content = @Content( + mediaType = "application/json", + examples = @ExampleObject(value = """ + { + "success": false, + "code": "COMMON_400", + "message": "잘못된 요청입니다.", + "data": null + } + """) + ) + ), + @ApiResponse( + responseCode = "401", + description = "인증 실패 (토큰 없음/잘못됨/만료)", + content = @Content( + mediaType = "application/json", + examples = { + @ExampleObject(name = "토큰 없음", value = """ + { + "success": false, + "code": "AUTH_001", + "message": "인증이 필요합니다.", + "data": null + } + """), + @ExampleObject(name = "잘못된 토큰", value = """ + { + "success": false, + "code": "AUTH_002", + "message": "유효하지 않은 액세스 토큰입니다.", + "data": null + } + """), + @ExampleObject(name = "만료된 토큰", value = """ + { + "success": false, + "code": "AUTH_004", + "message": "만료된 액세스 토큰입니다.", + "data": null + } + """) + } + ) + ), + @ApiResponse( + responseCode = "404", + description = "존재하지 않는 사용자 또는 게시글", + content = @Content( + mediaType = "application/json", + examples = { + @ExampleObject(name = "존재하지 않는 사용자", value = """ + { + "success": false, + "code": "USER_001", + "message": "존재하지 않는 사용자입니다.", + "data": null + } + """), + @ExampleObject(name = "존재하지 않는 게시글", value = """ + { + "success": false, + "code": "POST_001", + "message": "존재하지 않는 게시글입니다.", + "data": null + } + """) + } + ) + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류", + content = @Content( + mediaType = "application/json", + examples = @ExampleObject(value = """ + { + "success": false, + "code": "COMMON_500", + "message": "서버 오류가 발생했습니다.", + "data": null + } + """) + ) + ) + }) + ResponseEntity> createComment( + @PathVariable Long postId, + @RequestBody CommentRequest request, + @AuthenticationPrincipal CustomUserDetails user + ); +} \ No newline at end of file