diff --git a/back/src/main/java/com/back/domain/post/comment/controller/PostCommentController.java b/back/src/main/java/com/back/domain/post/comment/controller/PostCommentController.java index 5fcc5af3..b89da14a 100644 --- a/back/src/main/java/com/back/domain/post/comment/controller/PostCommentController.java +++ b/back/src/main/java/com/back/domain/post/comment/controller/PostCommentController.java @@ -1,16 +1,23 @@ package com.back.domain.post.comment.controller; import com.back.domain.member.member.entity.Member; +import com.back.domain.post.comment.dto.CommentAllResponse; import com.back.domain.post.comment.dto.CommentCreateRequest; +import com.back.domain.post.comment.dto.CommentDeleteRequest; +import com.back.domain.post.comment.dto.CommentModifyRequest; import com.back.domain.post.comment.service.PostCommentService; import com.back.global.rq.Rq; import com.back.global.rsData.RsData; import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; +import java.util.List; + @RestController @RequestMapping("/post/comment") @RequiredArgsConstructor @@ -21,13 +28,44 @@ public class PostCommentController { private PostCommentService postCommentService; @Operation(summary = "댓글 생성") - @PostMapping("/{post_id}") + @PostMapping("/post/{post_id}") public RsData createComment(@PathVariable Long post_id, - @Valid @RequestBody CommentCreateRequest commentCreateRequest - ) { + @Valid @RequestBody CommentCreateRequest commentCreateRequest + ) { Member member = rq.getActor(); postCommentService.createComment(member, post_id, commentCreateRequest); - return new RsData<>("200", "댓글 작성 완료" , null); + return new RsData<>("200", "댓글 작성 완료", null); + } + + @Operation(summary = "댓글 다건 조회") + @GetMapping("/post/{post_id}") + @Transactional(readOnly = true) + public RsData> getAllPostComment(@PathVariable Long post_id) { + List postAllResponse = postCommentService.getAllPostCommentResponse(post_id); + return new RsData<>("200", "게시글 다건 조회 성공", postAllResponse); + } + + @Operation(summary = "댓글 삭제") + @DeleteMapping("/post/{post_id}/comment") + public RsData removePostComment(@PathVariable @Positive Long post_id + , @RequestBody @Valid CommentDeleteRequest commentDeleteRequest) { + Member member = rq.getActor(); + + postCommentService.removePostComment(post_id, commentDeleteRequest, member); + + return new RsData<>("200", "게시글 삭제 성공", null); } + + @Operation(summary = "댓글 수정") + @PutMapping("/post/{post_id}/comment/") + public RsData updatePostComment(@PathVariable Long post_id + , @Valid @RequestBody CommentModifyRequest commentModifyRequest) { + Member member = rq.getActor(); + + postCommentService.updatePostComment(post_id, commentModifyRequest, member); + + return new RsData<>("200", "댓글 수정 성공", null); + } + } diff --git a/back/src/main/java/com/back/domain/post/comment/dto/CommentAllResponse.java b/back/src/main/java/com/back/domain/post/comment/dto/CommentAllResponse.java new file mode 100644 index 00000000..562601bb --- /dev/null +++ b/back/src/main/java/com/back/domain/post/comment/dto/CommentAllResponse.java @@ -0,0 +1,23 @@ +package com.back.domain.post.comment.dto; + +import com.back.domain.post.comment.entity.PostComment; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class CommentAllResponse { + Long id; + String content; + String authorName; + LocalDateTime createdAt; + + public static CommentAllResponse from(PostComment comment) { + CommentAllResponse response = new CommentAllResponse(); + response.setId(comment.getId()); + response.setContent(comment.getContent()); + response.setAuthorName(comment.getMember().getName()); // Member에서 이름 가져오기 + response.setCreatedAt(comment.getCreateDate()); + return response; + } +} diff --git a/back/src/main/java/com/back/domain/post/comment/dto/CommentCreateRequest.java b/back/src/main/java/com/back/domain/post/comment/dto/CommentCreateRequest.java index 95286991..12ea86d8 100644 --- a/back/src/main/java/com/back/domain/post/comment/dto/CommentCreateRequest.java +++ b/back/src/main/java/com/back/domain/post/comment/dto/CommentCreateRequest.java @@ -1,10 +1,12 @@ package com.back.domain.post.comment.dto; +import jakarta.validation.constraints.NotBlank; import lombok.Data; @Data public class CommentCreateRequest { private Long postId; private String role; + @NotBlank(message = "댓글을 입력해주세요") private String comment; } diff --git a/back/src/main/java/com/back/domain/post/comment/dto/CommentDeleteRequest.java b/back/src/main/java/com/back/domain/post/comment/dto/CommentDeleteRequest.java new file mode 100644 index 00000000..4f95ea0d --- /dev/null +++ b/back/src/main/java/com/back/domain/post/comment/dto/CommentDeleteRequest.java @@ -0,0 +1,10 @@ +package com.back.domain.post.comment.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class CommentDeleteRequest { + @NotNull + private Long CommentId; +} diff --git a/back/src/main/java/com/back/domain/post/comment/dto/CommentModifyRequest.java b/back/src/main/java/com/back/domain/post/comment/dto/CommentModifyRequest.java new file mode 100644 index 00000000..ee3d79ac --- /dev/null +++ b/back/src/main/java/com/back/domain/post/comment/dto/CommentModifyRequest.java @@ -0,0 +1,13 @@ +package com.back.domain.post.comment.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class CommentModifyRequest { + @NotNull + private Long commentId; + @NotBlank(message = "공백일 수 없습니다.") + private String content; +} diff --git a/back/src/main/java/com/back/domain/post/comment/service/PostCommentService.java b/back/src/main/java/com/back/domain/post/comment/service/PostCommentService.java index 0f75febb..424fa485 100644 --- a/back/src/main/java/com/back/domain/post/comment/service/PostCommentService.java +++ b/back/src/main/java/com/back/domain/post/comment/service/PostCommentService.java @@ -1,7 +1,10 @@ package com.back.domain.post.comment.service; import com.back.domain.member.member.entity.Member; +import com.back.domain.post.comment.dto.CommentAllResponse; import com.back.domain.post.comment.dto.CommentCreateRequest; +import com.back.domain.post.comment.dto.CommentDeleteRequest; +import com.back.domain.post.comment.dto.CommentModifyRequest; import com.back.domain.post.comment.entity.PostComment; import com.back.domain.post.comment.repository.PostCommentRepository; import com.back.domain.post.post.entity.Post; @@ -11,6 +14,9 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.util.List; +import java.util.Objects; + @Service @RequiredArgsConstructor public class PostCommentService { @@ -32,4 +38,45 @@ public void createComment(Member member, Long postId, CommentCreateRequest comme postCommentRepository.save(postComment); } + + public List getAllPostCommentResponse(Long postId) { + Post post = postRepository.findById(postId).orElseThrow(() -> new ServiceException("400", "해당 Id의 게시글이 없습니다.")); + + List listPostComment = post.getComments(); + + + return listPostComment.stream() + .map(CommentAllResponse::from) + .toList(); + } + + public void removePostComment(Long postId, CommentDeleteRequest commentDeleteRequest, Member member) { + Post post = postRepository.findById(postId).orElseThrow(() -> new ServiceException("400", "해당 Id의 게시글이 없습니다.")); + + PostComment postComment = postCommentRepository.findById(commentDeleteRequest.getCommentId()).orElseThrow(() -> new ServiceException("400", "해당 Id의 댓글이 없습니다.")); + Member author = postComment.getMember(); + + + if(!Objects.equals(member.getId(), author.getId())) { + throw new ServiceException("400", "삭제 권한이 없습니다."); + } + + postCommentRepository.delete(postComment); + + } + + @Transactional + public void updatePostComment(Long postId, CommentModifyRequest commentModifyRequest, Member member) { + Post post = postRepository.findById(postId).orElseThrow(() -> new ServiceException("400", "해당 Id의 게시글이 없습니다.")); + + PostComment postComment = postCommentRepository.findById(commentModifyRequest.getCommentId()).orElseThrow(() -> new ServiceException("400", "해당 Id의 댓글이 없습니다.")); + Member author = postComment.getMember(); + + + if(!Objects.equals(member.getId(), author.getId())) { + throw new ServiceException("400", "수정 권한이 없습니다."); + } + + postComment.setContent(commentModifyRequest.getContent()); + } } diff --git a/back/src/main/java/com/back/domain/post/post/controller/InformationPostController.java b/back/src/main/java/com/back/domain/post/post/controller/InformationPostController.java index 679b7ad9..63513c42 100644 --- a/back/src/main/java/com/back/domain/post/post/controller/InformationPostController.java +++ b/back/src/main/java/com/back/domain/post/post/controller/InformationPostController.java @@ -4,7 +4,6 @@ import com.back.domain.post.post.dto.*; import com.back.domain.post.post.entity.Post; import com.back.domain.post.post.service.PostService; -import com.back.global.auth.CurrentUser; import com.back.global.rq.Rq; import com.back.global.rsData.RsData; import io.swagger.v3.oas.annotations.Operation; @@ -45,7 +44,7 @@ public RsData> getAllPost() { @Operation(summary = "게시글 단건 조회") @GetMapping("/{post_id}") - public RsData getSinglePost(@PathVariable long post_id) { + public RsData getSinglePost(@PathVariable Long post_id) { Post post = postService.findById(post_id); PostSingleResponse postSingleResponse = new PostSingleResponse(post); @@ -55,7 +54,7 @@ public RsData getSinglePost(@PathVariable long post_id) { @Operation(summary = "게시글 삭제") @DeleteMapping("/{post_id}") - public RsData removePost(@PathVariable long post_id) { + public RsData removePost(@PathVariable Long post_id) { Member member = rq.getActor(); postService.removePost(post_id, member); @@ -65,10 +64,9 @@ public RsData removePost(@PathVariable long post_id) { @Operation(summary = "게시글 수정") @PutMapping("/{post_id}") - public RsData updatePost(@PathVariable long post_id - ,@CurrentUser Member member + public RsData updatePost(@PathVariable Long post_id ,@Valid @RequestBody PostCreateRequest postCreateRequest) { - + Member member = rq.getActor(); postService.updatePost(post_id, member, postCreateRequest); return new RsData<>("200", "게시글 수정 성공", null); @@ -76,7 +74,7 @@ public RsData updatePost(@PathVariable long post_id @Operation(summary = "게시글 좋아요 + ") @PostMapping("/{post_id}/liked") - public RsData likePost(@PathVariable long post_id) { + public RsData likePost(@PathVariable Long post_id) { postService.likePost(post_id); return new RsData<>("200", "게시글 좋아요 성공", null); @@ -84,7 +82,7 @@ public RsData likePost(@PathVariable long post_id) { @Operation(summary = "게시글 좋아요 (Show)") @GetMapping("/{post_id}/liked") - public RsData getlike(@PathVariable long post_id) { + public RsData getlike(@PathVariable Long post_id) { int likeCount = postService.showLikeCount(post_id); PostLikedResponse postLikedResponse = new PostLikedResponse(likeCount); @@ -93,7 +91,7 @@ public RsData getlike(@PathVariable long post_id) { @Operation(summary = "게시글 싫어요") @PostMapping("/{post_id}/disliked") - public RsData disLikePost(@PathVariable long post_id) { + public RsData disLikePost(@PathVariable Long post_id) { postService.disLikePost(post_id); return new RsData<>("200", "게시글 싫어요 성공", null); diff --git a/back/src/main/java/com/back/global/init/PostInitData.java b/back/src/main/java/com/back/global/init/PostInitData.java index eff62f2b..3aa680de 100644 --- a/back/src/main/java/com/back/global/init/PostInitData.java +++ b/back/src/main/java/com/back/global/init/PostInitData.java @@ -2,6 +2,8 @@ import com.back.domain.member.member.entity.Member; import com.back.domain.member.member.service.MemberService; +import com.back.domain.post.comment.entity.PostComment; +import com.back.domain.post.comment.repository.PostCommentRepository; import com.back.domain.post.post.entity.Post; import com.back.domain.post.post.repository.PostRepository; import com.back.global.exception.ServiceException; @@ -20,12 +22,13 @@ public class PostInitData implements ApplicationRunner { private final PostRepository postRepository; private final MemberService memberService; + private final PostCommentRepository postCommentRepository; @Override public void run(ApplicationArguments args) throws Exception { log.info("postinit데이터 생성"); - initPostData(); + initDateForPostDataAndPostCommentData(); log.info("postRepo개수는 " + postRepository.count()); @@ -34,9 +37,8 @@ public void run(ApplicationArguments args) throws Exception { } - @Transactional - protected void initPostData() { + protected void initDateForPostDataAndPostCommentData() { if (postRepository.count() > 0) return; Member member2 = memberService.joinMentee("user2", "사용자1", "nickname1","password123",""); @@ -47,6 +49,16 @@ protected void initPostData() { createPost("정보글 제목", "정보글 내용", member2, Post.PostType.INFORMATIONPOST); createPost("연습글 제목", "연습글 내용", member3, Post.PostType.PRACTICEPOST); createPost("질문글 제목", "질문글 내용", member4, Post.PostType.QUESTIONPOST); + + createComment(member2, 1L, "1번댓글"); + createComment(member2, 1L, "2댓글"); + createComment(member2, 1L, "3번댓글"); + createComment(member2, 1L, "4번댓글"); + createComment(member2, 1L, "5번댓글"); + createComment(member2, 1L, "6번댓글"); + createComment(member2, 1L, "7번댓글"); + + } private void createPost(String title, String content, Member member, Post.PostType type) { @@ -62,6 +74,19 @@ private void createPost(String title, String content, Member member, Post.PostTy postRepository.save(post); } + public void createComment(Member member, Long postId, String content) { + Post post = postRepository.findById(postId) + .orElseThrow(() -> new ServiceException("400", "해당 Id의 게시글이 없습니다.")); + + PostComment postComment = new PostComment(); + postComment.setContent(content); + postComment.setMember(member); + postComment.setRole(String.valueOf(member.getRole())); + postComment.setPost(post); // 직접 설정 + + postCommentRepository.save(postComment); + + } private void validPostType(String postTypeStr) { boolean eq = false; @@ -75,4 +100,5 @@ private void validPostType(String postTypeStr) { if(!eq) throw new ServiceException("400-2", "유효하지 않은 PostType입니다."); } + } diff --git a/back/src/main/java/com/back/global/security/CustomAuthenticationFilter.java b/back/src/main/java/com/back/global/security/CustomAuthenticationFilter.java index eec82343..73b83ad8 100644 --- a/back/src/main/java/com/back/global/security/CustomAuthenticationFilter.java +++ b/back/src/main/java/com/back/global/security/CustomAuthenticationFilter.java @@ -37,7 +37,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse try { work(request, response, filterChain); } catch (Exception e) { - log.error("CustomAuthenticationFilter에서 예외 발생: ",e); + log.error("CustomAuthenticationFilter에서 예외 발생: ",e); //401 에러로 빠지는거 추적 가능 RsData rsData = new RsData<>("401-1", "인증 오류가 발생했습니다."); response.setContentType("application/json;charset=UTF-8"); response.setStatus(rsData.statusCode()); diff --git a/back/src/test/java/com/back/domain/post/comment/PostCommentControllerTest.java b/back/src/test/java/com/back/domain/post/comment/PostCommentControllerTest.java index 3b6be8d1..017ab35a 100644 --- a/back/src/test/java/com/back/domain/post/comment/PostCommentControllerTest.java +++ b/back/src/test/java/com/back/domain/post/comment/PostCommentControllerTest.java @@ -25,7 +25,9 @@ import java.util.List; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.hasSize; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @@ -75,13 +77,13 @@ void setUp() { void t1() throws Exception { ResultActions resultActions = mvc .perform( - post("/post/comment/{post_id}", 1L) + post("/post/comment/post/{post_id}", 1L) .contentType(MediaType.APPLICATION_JSON) .content(""" { "memberId": 123, "postId": 1, - "role": "mentor", + "role": "mentor", "comment": "댓글 내용" } """.stripIndent()) @@ -98,4 +100,200 @@ void t1() throws Exception { .andExpect(jsonPath("$.msg").value("댓글 작성 완료")); } + @Test + @DisplayName("댓글 생성 실패 - comment blank") + void t2() throws Exception { + ResultActions resultActions = mvc + .perform( + post("/post/comment/post/{post_id}", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "memberId": 123, + "postId": 1, + "role": "mentor", + "comment": "" + } + """.stripIndent()) + ) + .andDo(print()); + + + resultActions + .andExpect(handler().handlerType(PostCommentController.class)) + .andExpect(handler().methodName("createComment")) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.msg").value("comment-NotBlank-댓글을 입력해주세요")); + } + + + @Test + @DisplayName("댓글 다건조회") + void t3() throws Exception { + ResultActions resultActions = mvc + .perform( + get("/post/comment/post/{post_id}", 1L) + ) + .andDo(print()); + + + resultActions + .andExpect(handler().handlerType(PostCommentController.class)) + .andExpect(handler().methodName("getAllPostComment")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").isArray()) + .andExpect(jsonPath("$.msg").value("게시글 다건 조회 성공")) + .andExpect(jsonPath("$.data").exists()) + .andExpect(jsonPath("$.data", hasSize(greaterThan(0)))); + + } + + @Test + @DisplayName("댓글 삭제") + void t4() throws Exception { + mvc + .perform( + post("/post/comment/post/{post_id}", 2L) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "memberId": 7, + "postId": 2, + "role": "mentor", + "comment": "댓글 내용" + } + """) + ); + + + + + // 댓글 삭제 + ResultActions resultActions = mvc + .perform( + delete("/post/comment/post/{post_id}/comment", 2L) // URL 수정 + .contentType(MediaType.APPLICATION_JSON) // Content-Type 추가 + .content(""" + { + "commentId": 9 + } + """) // Request Body 추가 + ) + .andDo(print()); + + resultActions + .andExpect(status().isOk()) // 먼저 상태 확인 + .andExpect(handler().handlerType(PostCommentController.class)) + .andExpect(handler().methodName("removePostComment")) + .andExpect(jsonPath("$.resultCode").value("200")) + .andExpect(jsonPath("$.msg").value("게시글 삭제 성공")); + } + + @Test + @DisplayName("댓글 삭제 실패 - 권한 없는 사용자") + void t5() throws Exception { + ResultActions resultActions = mvc + .perform( + delete("/post/comment/post/{post_id}/comment", 1L) // URL 수정 + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "commentId": 1 + } + """) // Request Body 추가 + ) + .andDo(print()); + + resultActions + .andExpect(status().isBadRequest()) // 먼저 상태 확인 + .andExpect(handler().handlerType(PostCommentController.class)) + .andExpect(handler().methodName("removePostComment")) + .andExpect(jsonPath("$.resultCode").value("400")) + .andExpect(jsonPath("$.msg").value("삭제 권한이 없습니다.")); + } + + @Test + @DisplayName("댓글 수정") + void t6() throws Exception { + mvc + .perform( + post("/post/comment/post/{post_id}", 2L) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "memberId": 7, + "postId": 2, + "role": "mentor", + "comment": "댓글 내용" + } + """) + ); + + + ResultActions resultActions = mvc + .perform( + put("/post/comment/post/{post_id}/comment/", 2L) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "commentId": 10, + "content": "수정용 내용" + } + """) + ) + .andDo(print()); + + resultActions + .andExpect(handler().handlerType(PostCommentController.class)) + .andExpect(handler().methodName("updatePostComment")) + .andExpect(jsonPath("$.resultCode").value("200")) + .andExpect(jsonPath("$.msg").value("댓글 수정 성공")); + } + + @Test + @DisplayName("댓글 수정 실패 - 권한 없는 사용자 ") + void t7() throws Exception { + ResultActions resultActions = mvc + .perform( + put("/post/comment/post/{post_id}/comment/", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "commentId": 1, + "content": "수정용 내용" + } + """) + ) + .andDo(print()); + + resultActions + .andExpect(handler().handlerType(PostCommentController.class)) + .andExpect(handler().methodName("updatePostComment")) + .andExpect(jsonPath("$.resultCode").value("400")) + .andExpect(jsonPath("$.msg").value("수정 권한이 없습니다.")); + } + + @Test + @DisplayName("댓글 수정 실패 - Blank Content") + void t8() throws Exception { + ResultActions resultActions = mvc + .perform( + put("/post/comment/post/{post_id}/comment/", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "commentId": 1, + "content": "" + } + """) + ) + .andDo(print()); + + resultActions + .andExpect(handler().handlerType(PostCommentController.class)) + .andExpect(handler().methodName("updatePostComment")) + .andExpect(jsonPath("$.resultCode").value("400-1")) + .andExpect(jsonPath("$.msg").value("content-NotBlank-공백일 수 없습니다.")); + } + }