diff --git a/src/main/java/com/example/solidconnection/comment/dto/PostFindCommentResponse.java b/src/main/java/com/example/solidconnection/comment/dto/PostFindCommentResponse.java index 8524eb95a..2335b68ad 100644 --- a/src/main/java/com/example/solidconnection/comment/dto/PostFindCommentResponse.java +++ b/src/main/java/com/example/solidconnection/comment/dto/PostFindCommentResponse.java @@ -3,15 +3,15 @@ import com.example.solidconnection.comment.domain.Comment; import com.example.solidconnection.siteuser.dto.PostFindSiteUserResponse; -import java.time.LocalDateTime; +import java.time.ZonedDateTime; public record PostFindCommentResponse( Long id, Long parentId, String content, Boolean isOwner, - LocalDateTime createdAt, - LocalDateTime updatedAt, + ZonedDateTime createdAt, + ZonedDateTime updatedAt, PostFindSiteUserResponse postFindSiteUserResponse ) { diff --git a/src/main/java/com/example/solidconnection/comment/service/CommentService.java b/src/main/java/com/example/solidconnection/comment/service/CommentService.java index 8003ab26b..8c0b0458f 100644 --- a/src/main/java/com/example/solidconnection/comment/service/CommentService.java +++ b/src/main/java/com/example/solidconnection/comment/service/CommentService.java @@ -13,11 +13,9 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; -import java.util.Optional; import java.util.stream.Collectors; -import static com.example.solidconnection.custom.exception.ErrorCode.CAN_NOT_UPDATE_DEPRECATED_COMMENT; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_POST_ACCESS; +import static com.example.solidconnection.custom.exception.ErrorCode.*; @Service @RequiredArgsConstructor @@ -43,6 +41,13 @@ private void validateDeprecated(Comment comment) { } } + // 대대댓글부터 허용하지 않음 + private void validateCommentDepth(Comment parentComment) { + if (parentComment.getParentComment() != null) { + throw new CustomException(INVALID_COMMENT_LEVEL); + } + } + @Transactional(readOnly = true) public List findCommentsByPostId(String email, Long postId) { return commentRepository.findCommentTreeByPostId(postId) @@ -60,6 +65,7 @@ public CommentCreateResponse createComment(String email, Long postId, CommentCre Comment parentComment = null; if (commentCreateRequest.parentId() != null) { parentComment = commentRepository.getById(commentCreateRequest.parentId()); + validateCommentDepth(parentComment); } Comment createdComment = commentRepository.save(commentCreateRequest.toEntity(siteUser, post, parentComment)); @@ -87,15 +93,28 @@ public CommentDeleteResponse deleteCommentById(String email, Long postId, Long c Comment comment = commentRepository.getById(commentId); validateOwnership(comment, email); - if (comment.getCommentList().isEmpty()) { - // 하위 댓글이 없다면 삭제한다. + if (comment.getParentComment() != null) { + // 대댓글인 경우 + Comment parentComment = comment.getParentComment(); + // 대댓글을 삭제합니다. comment.resetPostAndSiteUserAndParentComment(); commentRepository.deleteById(commentId); + // 대댓글 삭제 이후, 부모댓글이 무의미하다면 이역시 삭제합니다. + if (parentComment.getCommentList().isEmpty() && parentComment.getContent() == null) { + parentComment.resetPostAndSiteUserAndParentComment(); + commentRepository.deleteById(parentComment.getId()); + } } else { - // 하위 댓글 있으면 value만 null로 수정한다. - comment.deprecateComment(); + // 댓글인 경우 + if (comment.getCommentList().isEmpty()) { + // 대댓글이 없는 경우 + comment.resetPostAndSiteUserAndParentComment(); + commentRepository.deleteById(commentId); + } else { + // 대댓글이 있는 경우 + comment.deprecateComment(); + } } - return new CommentDeleteResponse(commentId); } } diff --git a/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java index c8bc38acc..b04c08b35 100644 --- a/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java @@ -59,6 +59,7 @@ public enum ErrorCode { CAN_NOT_DELETE_OR_UPDATE_QUESTION(HttpStatus.BAD_REQUEST.value(), "질문글은 수정이나 삭제할 수 없습니다."), CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES(HttpStatus.BAD_REQUEST.value(), "5개 이상의 파일을 업로드할 수 없습니다."), INVALID_COMMENT_ID(HttpStatus.BAD_REQUEST.value(), "존재하지 않는 댓글입니다."), + INVALID_COMMENT_LEVEL(HttpStatus.BAD_REQUEST.value(), "최대 대댓글까지만 작성할 수 있습니다."), INVALID_COMMENT_ACCESS(HttpStatus.BAD_REQUEST.value(), "자신의 댓글만 제어할 수 있습니다."), CAN_NOT_UPDATE_DEPRECATED_COMMENT(HttpStatus.BAD_REQUEST.value(),"이미 삭제된 댓글을 수정할 수 없습니다."), INVALID_POST_LIKE(HttpStatus.BAD_REQUEST.value(), "존재하지 않는 게시글 좋아요입니다."), diff --git a/src/main/java/com/example/solidconnection/entity/common/BaseEntity.java b/src/main/java/com/example/solidconnection/entity/common/BaseEntity.java index 5f1283c64..27493f1be 100644 --- a/src/main/java/com/example/solidconnection/entity/common/BaseEntity.java +++ b/src/main/java/com/example/solidconnection/entity/common/BaseEntity.java @@ -2,14 +2,15 @@ import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.PrePersist; +import jakarta.persistence.PreUpdate; import lombok.Getter; import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; @MappedSuperclass @EntityListeners(AuditingEntityListener.class) @@ -18,9 +19,17 @@ @DynamicInsert public abstract class BaseEntity { - @CreatedDate - private LocalDateTime createdAt; + private ZonedDateTime createdAt; + private ZonedDateTime updatedAt; - @LastModifiedDate - private LocalDateTime updatedAt; + @PrePersist + public void onPrePersist() { + this.createdAt = ZonedDateTime.now(ZoneId.of("UTC")); + this.updatedAt = this.createdAt; + } + + @PreUpdate + public void onPreUpdate() { + this.updatedAt = ZonedDateTime.now(ZoneId.of("UTC")); + } } diff --git a/src/main/java/com/example/solidconnection/post/dto/BoardFindPostResponse.java b/src/main/java/com/example/solidconnection/post/dto/BoardFindPostResponse.java index 89c931925..8e6d3202a 100644 --- a/src/main/java/com/example/solidconnection/post/dto/BoardFindPostResponse.java +++ b/src/main/java/com/example/solidconnection/post/dto/BoardFindPostResponse.java @@ -3,7 +3,7 @@ import com.example.solidconnection.entity.PostImage; import com.example.solidconnection.post.domain.Post; -import java.time.LocalDateTime; +import java.time.ZonedDateTime; import java.util.List; import java.util.stream.Collectors; @@ -13,8 +13,8 @@ public record BoardFindPostResponse( String content, Long likeCount, Integer commentCount, - LocalDateTime createdAt, - LocalDateTime updatedAt, + ZonedDateTime createdAt, + ZonedDateTime updatedAt, String postCategory, String url ) { diff --git a/src/main/java/com/example/solidconnection/post/dto/PostFindResponse.java b/src/main/java/com/example/solidconnection/post/dto/PostFindResponse.java index 7f5f703af..45e4e5dc7 100644 --- a/src/main/java/com/example/solidconnection/post/dto/PostFindResponse.java +++ b/src/main/java/com/example/solidconnection/post/dto/PostFindResponse.java @@ -6,7 +6,7 @@ import com.example.solidconnection.post.domain.Post; import com.example.solidconnection.siteuser.dto.PostFindSiteUserResponse; -import java.time.LocalDateTime; +import java.time.ZonedDateTime; import java.util.List; public record PostFindResponse( @@ -20,8 +20,8 @@ public record PostFindResponse( String postCategory, Boolean isOwner, Boolean isLiked, - LocalDateTime createdAt, - LocalDateTime updatedAt, + ZonedDateTime createdAt, + ZonedDateTime updatedAt, PostFindBoardResponse postFindBoardResponse, PostFindSiteUserResponse postFindSiteUserResponse, List postFindCommentResponses, diff --git a/src/test/java/com/example/solidconnection/unit/service/CommentServiceTest.java b/src/test/java/com/example/solidconnection/unit/service/CommentServiceTest.java index 8a90b275b..9ced8bcd8 100644 --- a/src/test/java/com/example/solidconnection/unit/service/CommentServiceTest.java +++ b/src/test/java/com/example/solidconnection/unit/service/CommentServiceTest.java @@ -44,9 +44,10 @@ class CommentServiceTest { private SiteUser siteUser; private Board board; private Post post; - private Comment parentComment_1; - private Comment parentComment_2; - private Comment p1s_childComment; + private Comment parentComment; + private Comment parentCommentWithNullContent; + private Comment childComment; + private Comment childCommentOfNullContentParent; @BeforeEach @@ -54,9 +55,10 @@ void setUp() { siteUser = createSiteUser(); board = createBoard(); post = createPost(board, siteUser); - parentComment_1 = createParentComment(); - parentComment_2 = createParentComment(); - p1s_childComment = createChildComment(); + parentComment = createParentComment("parent"); + parentCommentWithNullContent = createParentComment(null); + childComment = createChildComment(parentComment); + childCommentOfNullContentParent = createChildComment(parentCommentWithNullContent); } private SiteUser createSiteUser() { @@ -89,19 +91,19 @@ private Post createPost(Board board, SiteUser siteUser) { return post; } - private Comment createParentComment() { + private Comment createParentComment(String content) { Comment comment = new Comment( - "parent" + content ); comment.setPostAndSiteUser(post, siteUser); return comment; } - private Comment createChildComment() { + private Comment createChildComment(Comment parentComment) { Comment comment = new Comment( "child" ); - comment.setParentCommentAndPostAndSiteUser(parentComment_1, post, siteUser); + comment.setParentCommentAndPostAndSiteUser(parentComment, post, siteUser); return comment; } @@ -112,7 +114,7 @@ private Comment createChildComment() { @Test void 특정_게시글의_댓글들을_조회한다() { // Given - List commentList = List.of(parentComment_1, p1s_childComment, parentComment_2); + List commentList = List.of(parentComment, childComment, parentCommentWithNullContent); when(commentRepository.findCommentTreeByPostId(post.getId())).thenReturn(commentList); // When @@ -141,18 +143,18 @@ private Boolean isOwner(Comment comment, String email) { ); when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); when(postRepository.getById(post.getId())).thenReturn(post); - when(commentRepository.save(any(Comment.class))).thenReturn(parentComment_1); + when(commentRepository.save(any(Comment.class))).thenReturn(parentComment); // When CommentCreateResponse commentCreateResponse = commentService.createComment( siteUser.getEmail(), post.getId(), commentCreateRequest); // Then - assertEquals(commentCreateResponse, CommentCreateResponse.from(parentComment_1)); + assertEquals(commentCreateResponse, CommentCreateResponse.from(parentComment)); verify(commentRepository, times(0)) .getById(any(Long.class)); verify(commentRepository, times(1)) - .save(commentCreateRequest.toEntity(siteUser, post, parentComment_1)); + .save(commentCreateRequest.toEntity(siteUser, post, parentComment)); } @Test @@ -164,19 +166,19 @@ private Boolean isOwner(Comment comment, String email) { ); when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); when(postRepository.getById(post.getId())).thenReturn(post); - when(commentRepository.getById(parentCommentId)).thenReturn(parentComment_1); - when(commentRepository.save(any(Comment.class))).thenReturn(p1s_childComment); + when(commentRepository.getById(parentCommentId)).thenReturn(parentComment); + when(commentRepository.save(any(Comment.class))).thenReturn(childComment); // When CommentCreateResponse commentCreateResponse = commentService.createComment( siteUser.getEmail(), post.getId(), commentCreateRequest); // Then - assertEquals(commentCreateResponse, CommentCreateResponse.from(p1s_childComment)); + assertEquals(commentCreateResponse, CommentCreateResponse.from(childComment)); verify(commentRepository, times(1)) .getById(parentCommentId); verify(commentRepository, times(1)) - .save(commentCreateRequest.toEntity(siteUser, post, parentComment_1)); + .save(commentCreateRequest.toEntity(siteUser, post, parentComment)); } @@ -225,6 +227,29 @@ private Boolean isOwner(Comment comment, String email) { .save(any(Comment.class)); } + @Test + void 댓글을_등록할_때_대대댓글_부터는_예외_응답을_반환한다() { + // Given + Long childCommentId = 1L; + CommentCreateRequest commentCreateRequest = new CommentCreateRequest( + "child's child", childCommentId + ); + when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); + when(postRepository.getById(post.getId())).thenReturn(post); + when(commentRepository.getById(childCommentId)).thenReturn(childComment); + + // When & Then + CustomException exception = assertThrows(CustomException.class, () -> + commentService.createComment(siteUser.getEmail(), post.getId(), commentCreateRequest) + ); + assertThat(exception.getMessage()) + .isEqualTo(INVALID_COMMENT_LEVEL.getMessage()); + assertThat(exception.getCode()) + .isEqualTo(INVALID_COMMENT_LEVEL.getCode()); + verify(commentRepository, times(0)) + .save(any(Comment.class)); + } + /** * 댓글 수정 */ @@ -236,14 +261,14 @@ private Boolean isOwner(Comment comment, String email) { ); when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); when(postRepository.getById(post.getId())).thenReturn(post); - when(commentRepository.getById(any())).thenReturn(parentComment_1); + when(commentRepository.getById(any())).thenReturn(parentComment); // When CommentUpdateResponse commentUpdateResponse = commentService.updateComment( - siteUser.getEmail(), post.getId(), parentComment_1.getId(), commentUpdateRequest); + siteUser.getEmail(), post.getId(), parentComment.getId(), commentUpdateRequest); // Then - assertEquals(commentUpdateResponse.id(), parentComment_1.getId()); + assertEquals(commentUpdateResponse.id(), parentComment.getId()); } @Test @@ -258,7 +283,7 @@ private Boolean isOwner(Comment comment, String email) { // When & Then CustomException exception = assertThrows(CustomException.class, () -> - commentService.updateComment(siteUser.getEmail(), invalidPostId, parentComment_1.getId(), commentUpdateRequest) + commentService.updateComment(siteUser.getEmail(), invalidPostId, parentComment.getId(), commentUpdateRequest) ); assertThat(exception.getMessage()) .isEqualTo(INVALID_POST_ID.getMessage()); @@ -290,17 +315,17 @@ private Boolean isOwner(Comment comment, String email) { @Test void 댓글을_수정할_때_이미_삭제된_댓글이라면_예외_응답을_반환한다() { // Given - parentComment_1.deprecateComment(); + parentComment.deprecateComment(); CommentUpdateRequest commentUpdateRequest = new CommentUpdateRequest( "update" ); when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); when(postRepository.getById(post.getId())).thenReturn(post); - when(commentRepository.getById(any())).thenReturn(parentComment_1); + when(commentRepository.getById(any())).thenReturn(parentComment); // When & Then CustomException exception = assertThrows(CustomException.class, () -> - commentService.updateComment(siteUser.getEmail(), post.getId(), parentComment_1.getId(), commentUpdateRequest) + commentService.updateComment(siteUser.getEmail(), post.getId(), parentComment.getId(), commentUpdateRequest) ); assertThat(exception.getMessage()) .isEqualTo(CAN_NOT_UPDATE_DEPRECATED_COMMENT.getMessage()); @@ -317,11 +342,11 @@ private Boolean isOwner(Comment comment, String email) { ); when(siteUserRepository.getByEmail(invalidEmail)).thenReturn(siteUser); when(postRepository.getById(post.getId())).thenReturn(post); - when(commentRepository.getById(any())).thenReturn(parentComment_1); + when(commentRepository.getById(any())).thenReturn(parentComment); // When & Then CustomException exception = assertThrows(CustomException.class, () -> - commentService.updateComment(invalidEmail, post.getId(), parentComment_1.getId(), commentUpdateRequest) + commentService.updateComment(invalidEmail, post.getId(), parentComment.getId(), commentUpdateRequest) ); assertThat(exception.getMessage()) .isEqualTo(INVALID_POST_ACCESS.getMessage()); @@ -339,14 +364,14 @@ private Boolean isOwner(Comment comment, String email) { Long parentCommentId = 1L; when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); when(postRepository.getById(post.getId())).thenReturn(post); - when(commentRepository.getById(any())).thenReturn(parentComment_1); + when(commentRepository.getById(any())).thenReturn(parentComment); // When CommentDeleteResponse commentDeleteResponse = commentService.deleteCommentById( siteUser.getEmail(), post.getId(), parentCommentId); // Then - assertEquals(parentComment_1.getContent(), null); + assertEquals(parentComment.getContent(), null); assertEquals(commentDeleteResponse.id(), parentCommentId); verify(commentRepository, times(0)).deleteById(parentCommentId); } @@ -357,7 +382,24 @@ private Boolean isOwner(Comment comment, String email) { Long childCommentId = 1L; when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); when(postRepository.getById(post.getId())).thenReturn(post); - when(commentRepository.getById(any())).thenReturn(p1s_childComment); + when(commentRepository.getById(any())).thenReturn(childComment); + + // When + CommentDeleteResponse commentDeleteResponse = commentService.deleteCommentById( + siteUser.getEmail(), post.getId(), childCommentId); + + // Then + assertEquals(commentDeleteResponse.id(), childCommentId); + verify(commentRepository, times(1)).deleteById(childCommentId); + } + + @Test + void 대댓글을_삭제한다_부모댓글_유효() { + // Given + Long childCommentId = 1L; + when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); + when(postRepository.getById(post.getId())).thenReturn(post); + when(commentRepository.getById(any())).thenReturn(childComment); // When CommentDeleteResponse commentDeleteResponse = commentService.deleteCommentById( @@ -368,6 +410,24 @@ private Boolean isOwner(Comment comment, String email) { verify(commentRepository, times(1)).deleteById(childCommentId); } + @Test + void 대댓글을_삭제한다_부모댓글_유효하지_않음() { + // Given + + Long childCommentId = 1L; + when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); + when(postRepository.getById(post.getId())).thenReturn(post); + when(commentRepository.getById(any())).thenReturn(childCommentOfNullContentParent); + + // When + CommentDeleteResponse commentDeleteResponse = commentService.deleteCommentById( + siteUser.getEmail(), post.getId(), childCommentId); + + // Then + assertEquals(commentDeleteResponse.id(), childCommentId); + verify(commentRepository, times(2)).deleteById(any()); + } + @Test void 댓글을_삭제할_때_유효한_게시글이_아니라면_예외_응답을_반환한다() { // Given @@ -377,7 +437,7 @@ private Boolean isOwner(Comment comment, String email) { // When & Then CustomException exception = assertThrows(CustomException.class, () -> - commentService.deleteCommentById(siteUser.getEmail(), invalidPostId, parentComment_1.getId()) + commentService.deleteCommentById(siteUser.getEmail(), invalidPostId, parentComment.getId()) ); assertThat(exception.getMessage()) .isEqualTo(INVALID_POST_ID.getMessage()); @@ -409,11 +469,11 @@ private Boolean isOwner(Comment comment, String email) { String invalidEmail = "invalidEmail@test.com"; when(siteUserRepository.getByEmail(invalidEmail)).thenReturn(siteUser); when(postRepository.getById(post.getId())).thenReturn(post); - when(commentRepository.getById(any())).thenReturn(parentComment_1); + when(commentRepository.getById(any())).thenReturn(parentComment); // When & Then CustomException exception = assertThrows(CustomException.class, () -> - commentService.deleteCommentById(invalidEmail, post.getId(), parentComment_1.getId()) + commentService.deleteCommentById(invalidEmail, post.getId(), parentComment.getId()) ); assertThat(exception.getMessage()) .isEqualTo(INVALID_POST_ACCESS.getMessage());