From 1b26e3691923f4b332afd4105b0d6aad592f3a7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 02:44:16 +0900 Subject: [PATCH 01/52] =?UTF-8?q?[refactor]=20status=20=EC=A0=91=EA=B7=BC?= =?UTF-8?q?=EC=A0=9C=EC=96=B4=EC=9E=90=20=ED=95=84=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java b/src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java index 613f2f07c..c306ebdcd 100644 --- a/src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java +++ b/src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java @@ -13,6 +13,6 @@ public class BaseDomainEntity { private LocalDateTime modifiedAt; - private StatusType status; + protected StatusType status; } From d921cb2e61fb2cf1890161aef7080d9d45284828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 02:44:24 +0900 Subject: [PATCH 02/52] =?UTF-8?q?[refactor]=20status=20=EC=A0=91=EA=B7=BC?= =?UTF-8?q?=EC=A0=9C=EC=96=B4=EC=9E=90=20=ED=95=84=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/common/entity/BaseJpaEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/common/entity/BaseJpaEntity.java b/src/main/java/konkuk/thip/common/entity/BaseJpaEntity.java index b1f619ef1..93ec51963 100644 --- a/src/main/java/konkuk/thip/common/entity/BaseJpaEntity.java +++ b/src/main/java/konkuk/thip/common/entity/BaseJpaEntity.java @@ -34,5 +34,5 @@ public abstract class BaseJpaEntity { @Enumerated(EnumType.STRING) @Column(nullable = false) - private StatusType status = StatusType.ACTIVE; + protected StatusType status = StatusType.ACTIVE; } From e033c782b5e2ae94670351cfaee816664e9d8a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 02:44:43 +0900 Subject: [PATCH 03/52] =?UTF-8?q?[feat]=20=EB=8C=93=EA=B8=80=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EA=B4=80=EB=A0=A8=20=EB=8C=93=EA=B8=80=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EC=B1=85=EC=9E=84=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/comment/domain/Comment.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/main/java/konkuk/thip/comment/domain/Comment.java b/src/main/java/konkuk/thip/comment/domain/Comment.java index 865b73292..eef4decdf 100644 --- a/src/main/java/konkuk/thip/comment/domain/Comment.java +++ b/src/main/java/konkuk/thip/comment/domain/Comment.java @@ -1,6 +1,7 @@ package konkuk.thip.comment.domain; import konkuk.thip.common.entity.BaseDomainEntity; +import konkuk.thip.common.entity.StatusType; import konkuk.thip.common.exception.InvalidStateException; import konkuk.thip.common.post.PostType; import lombok.Builder; @@ -141,4 +142,23 @@ public void validateCanUnlike(boolean alreadyLiked) { throw new InvalidStateException(COMMENT_NOT_LIKED_CANNOT_CANCEL); } } + + private boolean validateCreator(Long userId) { + return this.creatorId.equals(userId); + } + + private void validateDeletable(Long userId) { + if (!validateCreator(userId)) { + throw new InvalidStateException(COMMENT_DELETE_FORBIDDEN); + } + } + + public void softDelete(Long userId) { + validateDeletable(userId); + if (this.status == StatusType.INACTIVE) { + throw new InvalidStateException(COMMENT_ALREADY_DELETED); + } + this.status = StatusType.INACTIVE; + } + } From bf1d222e808d28526678d9667dcd551eb4e6103e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 02:45:42 +0900 Subject: [PATCH 04/52] =?UTF-8?q?[feat]=20=EB=8C=93=EA=B8=80=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/CommentCommandController.java | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/main/java/konkuk/thip/comment/adapter/in/web/CommentCommandController.java b/src/main/java/konkuk/thip/comment/adapter/in/web/CommentCommandController.java index 492856503..dd705f7bd 100644 --- a/src/main/java/konkuk/thip/comment/adapter/in/web/CommentCommandController.java +++ b/src/main/java/konkuk/thip/comment/adapter/in/web/CommentCommandController.java @@ -9,18 +9,15 @@ import konkuk.thip.comment.adapter.in.web.response.CommentIdResponse; import konkuk.thip.comment.adapter.in.web.response.CommentIsLikeResponse; import konkuk.thip.comment.application.port.in.CommentCreateUseCase; +import konkuk.thip.comment.application.port.in.CommentDeleteUseCase; import konkuk.thip.comment.application.port.in.CommentLikeUseCase; import konkuk.thip.common.dto.BaseResponse; import konkuk.thip.common.security.annotation.UserId; import konkuk.thip.common.swagger.annotation.ExceptionDescription; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.PathVariable; -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.*; -import static konkuk.thip.common.swagger.SwaggerResponseDescription.CHANGE_COMMENT_LIKE_STATE; -import static konkuk.thip.common.swagger.SwaggerResponseDescription.COMMENT_CREATE; +import static konkuk.thip.common.swagger.SwaggerResponseDescription.*; @Tag(name = "Comment Command API", description = "댓글 상태변경 관련 API") @RestController @@ -29,6 +26,7 @@ public class CommentCommandController { private final CommentCreateUseCase commentCreateUseCase; private final CommentLikeUseCase commentLikeUseCase; + private final CommentDeleteUseCase commentDeleteUseCase; /** * 댓글/답글 작성 @@ -63,4 +61,17 @@ public BaseResponse likeComment( return BaseResponse.ok(CommentIsLikeResponse.of(commentLikeUseCase.changeLikeStatusComment(request.toCommand(userId, commentId)))); } + @Operation( + summary = "댓글 삭제", + description = "사용자가 댓글을 삭제합니다." + ) + @ExceptionDescription(COMMENT_DELETE) + @DeleteMapping("/comments/{commentId}") + public BaseResponse deleteComment( + @Parameter(description = "삭제하려는 댓글 ID", example = "1") @PathVariable("commentId") final Long commentId, + @Parameter(hidden = true) @UserId final Long userId) { + commentDeleteUseCase.deleteComment(commentId,userId); + return BaseResponse.ok(null); + } + } From fe7a0e6f456afb8cca87b5fa0e2bbc3fbfb17e7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 02:45:58 +0900 Subject: [PATCH 05/52] =?UTF-8?q?[feat]=20=EB=8C=93=EA=B8=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=8B=9C=20active=ED=95=9C=20=EB=8C=93=EA=B8=80?= =?UTF-8?q?=EB=A7=8C=20=EC=B0=BE=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95(#1?= =?UTF-8?q?03)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../out/persistence/CommentCommandPersistenceAdapter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentCommandPersistenceAdapter.java b/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentCommandPersistenceAdapter.java index d19de9d45..ff120cdfc 100644 --- a/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentCommandPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentCommandPersistenceAdapter.java @@ -18,6 +18,7 @@ import java.util.Optional; +import static konkuk.thip.common.entity.StatusType.ACTIVE; import static konkuk.thip.common.exception.code.ErrorCode.*; @Repository @@ -67,7 +68,7 @@ private PostJpaEntity findPostJpaEntity(PostType postType, Long postId) { @Override public Optional findById(Long id) { - return commentJpaRepository.findById(id) + return commentJpaRepository.findByCommentIdAndStatus(id, ACTIVE) .map(commentMapper::toDomainEntity); } From bd6144f29c38212bd8f11b58a0f6795f6ff84167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 02:46:17 +0900 Subject: [PATCH 06/52] =?UTF-8?q?[feat]=20decreaseCommentCount()=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/common/post/CommentCountUpdatable.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/konkuk/thip/common/post/CommentCountUpdatable.java b/src/main/java/konkuk/thip/common/post/CommentCountUpdatable.java index 3ac397005..6337bce98 100644 --- a/src/main/java/konkuk/thip/common/post/CommentCountUpdatable.java +++ b/src/main/java/konkuk/thip/common/post/CommentCountUpdatable.java @@ -2,4 +2,5 @@ public interface CommentCountUpdatable { void increaseCommentCount(); + void decreaseCommentCount(); } \ No newline at end of file From 94be498521d7d0382137c4b98fce7ef996f17da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 02:57:15 +0900 Subject: [PATCH 07/52] =?UTF-8?q?[feat]=20CommentDeleteUseCase=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/application/port/in/CommentDeleteUseCase.java | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/main/java/konkuk/thip/comment/application/port/in/CommentDeleteUseCase.java diff --git a/src/main/java/konkuk/thip/comment/application/port/in/CommentDeleteUseCase.java b/src/main/java/konkuk/thip/comment/application/port/in/CommentDeleteUseCase.java new file mode 100644 index 000000000..791ce9c26 --- /dev/null +++ b/src/main/java/konkuk/thip/comment/application/port/in/CommentDeleteUseCase.java @@ -0,0 +1,6 @@ +package konkuk.thip.comment.application.port.in; + + +public interface CommentDeleteUseCase { + void deleteComment(Long commentId, Long userId); +} From 3b9831252ca5ca57b3080583783e28fcba66a20d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 02:57:31 +0900 Subject: [PATCH 08/52] =?UTF-8?q?[feat]=20CommentDeleteUseCase=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=EC=B2=B4=20CommentDeleteService=20=EC=9E=91=EC=84=B1(?= =?UTF-8?q?#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CommentDeleteService.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/main/java/konkuk/thip/comment/application/service/CommentDeleteService.java diff --git a/src/main/java/konkuk/thip/comment/application/service/CommentDeleteService.java b/src/main/java/konkuk/thip/comment/application/service/CommentDeleteService.java new file mode 100644 index 000000000..2de272ee8 --- /dev/null +++ b/src/main/java/konkuk/thip/comment/application/service/CommentDeleteService.java @@ -0,0 +1,50 @@ +package konkuk.thip.comment.application.service; + +import jakarta.transaction.Transactional; +import konkuk.thip.comment.application.port.in.CommentDeleteUseCase; +import konkuk.thip.comment.application.port.out.CommentCommandPort; +import konkuk.thip.comment.application.port.out.CommentLikeCommandPort; +import konkuk.thip.comment.application.service.validator.CommentAuthorizationValidator; +import konkuk.thip.comment.domain.Comment; +import konkuk.thip.common.post.CommentCountUpdatable; +import konkuk.thip.common.post.service.PostQueryService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CommentDeleteService implements CommentDeleteUseCase { + + private final CommentCommandPort commentCommandPort; + private final CommentLikeCommandPort commentLikeCommandPort; + + private final PostQueryService postQueryService; + private final CommentAuthorizationValidator commentAuthorizationValidator; + + @Override + @Transactional + public void deleteComment(Long commentId, Long userId) { + + // 1. 댓글 조회 및 권한 검증 + Comment comment = commentCommandPort.getByIdOrThrow(commentId); + // 1-1. 게시글 타입에 따른 댓글 삭제 권한 검증 + CommentCountUpdatable post = postQueryService.findPost(comment.getPostType(), comment.getTargetPostId()); + commentAuthorizationValidator.validateUserCanAccessPostForComment(comment.getPostType(), post, userId); + + // 2. 댓글 Soft Delete 처리 + comment.softDelete(userId); + commentCommandPort.update(comment); + + //TODO 게시물의 댓글 수 증가/감소 동시성 제어 로직 추가해야됨 + + // 3. 게시글 댓글 수 감소 + // 3-1. 도메인 게시물 댓글 수 감소 + post.decreaseCommentCount(); + // 3-2 Jpa엔티티 게시물 댓글 수 감소 + postQueryService.updatePost(comment.getPostType(), post); + + // 4. 댓글 좋아요 삭제 + commentLikeCommandPort.deleteAllByCommentId(commentId); + } + +} From bb070744f0875ae3f7c1d80722786128b20b02cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 02:57:50 +0900 Subject: [PATCH 09/52] =?UTF-8?q?[feat]=20updateFrom=EC=8B=9C=EC=97=90=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EB=B3=80=EA=B2=BD=20=EC=B6=94=EA=B0=80(#1?= =?UTF-8?q?03)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/comment/adapter/out/jpa/CommentJpaEntity.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/konkuk/thip/comment/adapter/out/jpa/CommentJpaEntity.java b/src/main/java/konkuk/thip/comment/adapter/out/jpa/CommentJpaEntity.java index 997285e0d..1ebfff91c 100644 --- a/src/main/java/konkuk/thip/comment/adapter/out/jpa/CommentJpaEntity.java +++ b/src/main/java/konkuk/thip/comment/adapter/out/jpa/CommentJpaEntity.java @@ -53,6 +53,7 @@ public class CommentJpaEntity extends BaseJpaEntity { public CommentJpaEntity updateFrom(Comment comment) { this.reportCount = comment.getReportCount(); this.likeCount = comment.getLikeCount(); + this.status = comment.getStatus(); return this; } From 19d2ffaf44b5d1725b2fe2ebf302683538b7fde3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 02:58:37 +0900 Subject: [PATCH 10/52] =?UTF-8?q?[feat]=20findByCommentIdAndStatus=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../out/persistence/repository/CommentJpaRepository.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentJpaRepository.java b/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentJpaRepository.java index 405e7e099..4e08628f9 100644 --- a/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentJpaRepository.java +++ b/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentJpaRepository.java @@ -1,8 +1,11 @@ package konkuk.thip.comment.adapter.out.persistence.repository; import konkuk.thip.comment.adapter.out.jpa.CommentJpaEntity; +import konkuk.thip.common.entity.StatusType; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface CommentJpaRepository extends JpaRepository { - int countByPostJpaEntity_PostId(Long postId); + Optional findByCommentIdAndStatus(Long commentId, StatusType status); } From c71bf524f49263296df430829a180da9f3b76845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 02:59:04 +0900 Subject: [PATCH 11/52] =?UTF-8?q?[feat]=20CommentLikeCommandPersistenceAda?= =?UTF-8?q?pter.deleteAllByCommentId=20=EC=B6=94=EA=B0=80=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/CommentLikeCommandPersistenceAdapter.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentLikeCommandPersistenceAdapter.java b/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentLikeCommandPersistenceAdapter.java index 528b3aa4d..138e08c98 100644 --- a/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentLikeCommandPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentLikeCommandPersistenceAdapter.java @@ -41,4 +41,10 @@ public void save(Long userId, Long commentId) { public void delete(Long userId, Long commentId) { commentLikeJpaRepository.deleteByUserIdAndCommentId(userId, commentId); } + + @Override + public void deleteAllByCommentId(Long commentId) { + commentLikeJpaRepository.deleteAllByCommentId(commentId); + } + } From 5340f2a96f0abc542b9834fa0c0667a881d98416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 03:00:18 +0900 Subject: [PATCH 12/52] =?UTF-8?q?[feat]=20CommentLikeCommandPort.deleteAll?= =?UTF-8?q?ByCommentId=20=EC=B6=94=EA=B0=80=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/application/port/out/CommentLikeCommandPort.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/konkuk/thip/comment/application/port/out/CommentLikeCommandPort.java b/src/main/java/konkuk/thip/comment/application/port/out/CommentLikeCommandPort.java index bc8b2800e..d776cef26 100644 --- a/src/main/java/konkuk/thip/comment/application/port/out/CommentLikeCommandPort.java +++ b/src/main/java/konkuk/thip/comment/application/port/out/CommentLikeCommandPort.java @@ -4,4 +4,5 @@ public interface CommentLikeCommandPort { void save(Long userId, Long commentId); void delete(Long userId, Long commentId); + void deleteAllByCommentId(Long commentId); } From 7ba26f387c4e6c16f090ca7fa86ef836e3e0ac24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 03:00:31 +0900 Subject: [PATCH 13/52] =?UTF-8?q?[feat]=20CommentLikeJpaRepository=20.dele?= =?UTF-8?q?teAllByCommentId=20=EC=B6=94=EA=B0=80=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/repository/CommentLikeJpaRepository.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentLikeJpaRepository.java b/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentLikeJpaRepository.java index 1ec42c080..95f12281c 100644 --- a/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentLikeJpaRepository.java +++ b/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentLikeJpaRepository.java @@ -17,5 +17,10 @@ public interface CommentLikeJpaRepository extends JpaRepository Date: Sun, 3 Aug 2025 03:00:56 +0900 Subject: [PATCH 14/52] =?UTF-8?q?[fix]=20CommentLikeQueryPersistenceAdapte?= =?UTF-8?q?r=20.isLikedCommentByUser=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../out/persistence/CommentLikeQueryPersistenceAdapter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentLikeQueryPersistenceAdapter.java b/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentLikeQueryPersistenceAdapter.java index ce173d259..6d878b414 100644 --- a/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentLikeQueryPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentLikeQueryPersistenceAdapter.java @@ -19,6 +19,7 @@ public class CommentLikeQueryPersistenceAdapter implements CommentLikeQueryPort @Override public boolean isLikedCommentByUser(Long userId, Long commentId) { - return commentLikeJpaRepository.existsByUserIdAndCommentId(userId, commentId); + Long exists = commentLikeJpaRepository.existsByUserIdAndCommentId(userId, commentId); + return exists != null && exists > 0; } } From 9c00a8e00440ede3d18188eb568206affbb5fd0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 03:01:10 +0900 Subject: [PATCH 15/52] =?UTF-8?q?[feat]=20=EB=8C=93=EA=B8=80=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=97=90=EB=9F=AC=20=EC=BD=94=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/konkuk/thip/common/exception/code/ErrorCode.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java index 5abd3ee2a..b21ca541a 100644 --- a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java +++ b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java @@ -175,6 +175,10 @@ public enum ErrorCode implements ResponseCode { COMMENT_LIKE_COUNT_UNDERFLOW(HttpStatus.BAD_REQUEST, 190002, "좋아요 수는 0 이하로 감소할 수 없습니다."), COMMENT_ALREADY_LIKED(HttpStatus.BAD_REQUEST, 190003, "사용자가 이미 좋아요한 댓글입니다."), COMMENT_NOT_LIKED_CANNOT_CANCEL(HttpStatus.BAD_REQUEST, 190004, "사용자가 좋아요하지 않은 댓글은 좋아요 취소 할 수 없습니다."), + COMMENT_DELETE_FORBIDDEN(HttpStatus.FORBIDDEN, 190005, "댓글 삭제 권한이 없습니다."), + COMMENT_ALREADY_DELETED(HttpStatus.BAD_REQUEST, 190006, "이미 삭제된 댓글 입니다."), + COMMENT_COUNT_UNDERFLOW(HttpStatus.BAD_REQUEST, 190007, "댓글 수는 0 이하로 감소할 수 없습니다."), + ; private final HttpStatus httpStatus; From 5c52ef1f1244fe800fc8a4227950a6f397342b11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 03:01:22 +0900 Subject: [PATCH 16/52] =?UTF-8?q?[feat]=20=ED=94=BC=EB=93=9C=20=EB=8C=93?= =?UTF-8?q?=EA=B8=80=20=EA=B4=80=EB=A0=A8=20=ED=95=A8=EC=88=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/feed/domain/Feed.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/feed/domain/Feed.java b/src/main/java/konkuk/thip/feed/domain/Feed.java index 943eeddfb..f828bb062 100644 --- a/src/main/java/konkuk/thip/feed/domain/Feed.java +++ b/src/main/java/konkuk/thip/feed/domain/Feed.java @@ -116,7 +116,7 @@ public void validateCreateComment(Long userId){ private void validateCreator(Long userId) { if (!this.creatorId.equals(userId)) { - throw new InvalidStateException(FEED_ACCESS_FORBIDDEN); + throw new InvalidStateException(FEED_ACCESS_FORBIDDEN, new IllegalArgumentException("피드 작성자만 피드를 수정할 수 있습니다.")); } } @@ -161,4 +161,16 @@ public void increaseCommentCount() { commentCount++; } + @Override + public void decreaseCommentCount() { + checkCommentCountNotUnderflow(); + commentCount--; + } + + private void checkCommentCountNotUnderflow() { + if (commentCount <= 0) { + throw new InvalidStateException(COMMENT_COUNT_UNDERFLOW); + } + } + } From 5bf535f1c55d97ac2feda64a86175894422123d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 03:01:40 +0900 Subject: [PATCH 17/52] =?UTF-8?q?[feat]=20=ED=94=BC=EB=93=9C=20=EB=8C=93?= =?UTF-8?q?=EA=B8=80=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java b/src/main/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java index cd184408e..648ec3868 100644 --- a/src/main/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java +++ b/src/main/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java @@ -1,6 +1,7 @@ package konkuk.thip.feed.adapter.out.jpa; +import com.google.common.annotations.VisibleForTesting; import jakarta.persistence.*; import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; import konkuk.thip.feed.domain.Feed; @@ -49,4 +50,10 @@ public void updateFrom(Feed feed) { this.likeCount = feed.getLikeCount(); this.commentCount = feed.getCommentCount(); } + + @VisibleForTesting + public void updateCommentCount(int commentCount) { + this.commentCount = commentCount; + } + } \ No newline at end of file From f89a4f4e2fac7739ce6a6ad72f8665e87494b317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 03:01:57 +0900 Subject: [PATCH 18/52] =?UTF-8?q?[feat]=20=EA=B8=B0=EB=A1=9D=20=EB=8C=93?= =?UTF-8?q?=EA=B8=80=20=EA=B4=80=EB=A0=A8=20=ED=95=A8=EC=88=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/konkuk/thip/record/domain/Record.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/konkuk/thip/record/domain/Record.java b/src/main/java/konkuk/thip/record/domain/Record.java index f014765dc..fde6543b7 100644 --- a/src/main/java/konkuk/thip/record/domain/Record.java +++ b/src/main/java/konkuk/thip/record/domain/Record.java @@ -8,8 +8,7 @@ import lombok.Getter; import lombok.experimental.SuperBuilder; -import static konkuk.thip.common.exception.code.ErrorCode.INVALID_RECORD_PAGE_RANGE; -import static konkuk.thip.common.exception.code.ErrorCode.RECORD_CANNOT_BE_OVERVIEW; +import static konkuk.thip.common.exception.code.ErrorCode.*; @Getter @SuperBuilder @@ -79,4 +78,16 @@ public void validatePage(int totalPageCount) { public void increaseCommentCount() { commentCount++; } + + @Override + public void decreaseCommentCount() { + checkCommentCountNotUnderflow(); + commentCount--; + } + + private void checkCommentCountNotUnderflow() { + if (commentCount <= 0) { + throw new InvalidStateException(COMMENT_COUNT_UNDERFLOW); + } + } } From dbac3ebcc29fe1216258c0bb1b5d25d6f1f99314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 03:02:20 +0900 Subject: [PATCH 19/52] =?UTF-8?q?[feat]=20=EB=8C=93=EA=B8=80=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EA=B4=80=EB=A0=A8=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20?= =?UTF-8?q?=EB=AC=B8=EC=84=9C=20=EC=97=90=EB=9F=AC=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../swagger/SwaggerResponseDescription.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java b/src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java index 6b311ff82..831a2f4ac 100644 --- a/src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java +++ b/src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java @@ -190,7 +190,21 @@ public enum SwaggerResponseDescription { VOTE_NOT_FOUND, COMMENT_ALREADY_LIKED, COMMENT_NOT_LIKED_CANNOT_CANCEL, - COMMENT_LIKE_COUNT_UNDERFLOW + COMMENT_LIKE_COUNT_UNDERFLOW, + FEED_ACCESS_FORBIDDEN, + ROOM_ACCESS_FORBIDDEN + ))), + COMMENT_DELETE(new LinkedHashSet<>(Set.of( + USER_NOT_FOUND, + COMMENT_NOT_FOUND, + FEED_NOT_FOUND, + RECORD_NOT_FOUND, + VOTE_NOT_FOUND, + COMMENT_DELETE_FORBIDDEN, + COMMENT_ALREADY_DELETED, + COMMENT_COUNT_UNDERFLOW, + FEED_ACCESS_FORBIDDEN, + ROOM_ACCESS_FORBIDDEN ))), // Book From 9be50ed88e4ce993239c068ff931b23a6a53ada7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 03:02:28 +0900 Subject: [PATCH 20/52] =?UTF-8?q?[feat]=20=ED=88=AC=ED=91=9C=20=EB=8C=93?= =?UTF-8?q?=EA=B8=80=20=EA=B4=80=EB=A0=A8=20=ED=95=A8=EC=88=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/vote/domain/Vote.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/konkuk/thip/vote/domain/Vote.java b/src/main/java/konkuk/thip/vote/domain/Vote.java index 700f94704..fd56781a0 100644 --- a/src/main/java/konkuk/thip/vote/domain/Vote.java +++ b/src/main/java/konkuk/thip/vote/domain/Vote.java @@ -8,8 +8,7 @@ import lombok.Getter; import lombok.experimental.SuperBuilder; -import static konkuk.thip.common.exception.code.ErrorCode.INVALID_VOTE_PAGE_RANGE; -import static konkuk.thip.common.exception.code.ErrorCode.VOTE_CANNOT_BE_OVERVIEW; +import static konkuk.thip.common.exception.code.ErrorCode.*; @Getter @SuperBuilder @@ -73,4 +72,16 @@ public void validatePage(int totalPageCount) { public void increaseCommentCount() { commentCount++; } + + @Override + public void decreaseCommentCount() { + checkCommentCountNotUnderflow(); + commentCount--; + } + + private void checkCommentCountNotUnderflow() { + if (commentCount <= 0) { + throw new InvalidStateException(COMMENT_COUNT_UNDERFLOW); + } + } } From 71b471fd460e90c15a18644de6f57b9d8c04612f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 03:30:46 +0900 Subject: [PATCH 21/52] =?UTF-8?q?[test]=20=EB=8C=93=EA=B8=80=20=EB=8B=A8?= =?UTF-8?q?=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ....java => CommentCreateControllerTest.java} | 55 +------------------ 1 file changed, 1 insertion(+), 54 deletions(-) rename src/test/java/konkuk/thip/comment/adapter/in/web/{CommentControllerTest.java => CommentCreateControllerTest.java} (79%) diff --git a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentControllerTest.java b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentCreateControllerTest.java similarity index 79% rename from src/test/java/konkuk/thip/comment/adapter/in/web/CommentControllerTest.java rename to src/test/java/konkuk/thip/comment/adapter/in/web/CommentCreateControllerTest.java index 027d11d13..22bd3bb63 100644 --- a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentControllerTest.java +++ b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentCreateControllerTest.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; import konkuk.thip.book.adapter.out.persistence.repository.BookJpaRepository; -import konkuk.thip.comment.adapter.out.jpa.CommentJpaEntity; import konkuk.thip.comment.adapter.out.persistence.repository.CommentJpaRepository; import konkuk.thip.common.util.TestEntityFactory; import konkuk.thip.feed.adapter.out.jpa.FeedJpaEntity; @@ -39,7 +38,6 @@ import java.util.Map; import static konkuk.thip.common.exception.code.ErrorCode.*; -import static konkuk.thip.common.post.PostType.FEED; import static org.hamcrest.Matchers.containsString; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -51,7 +49,7 @@ @Transactional @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) @DisplayName("[단위] 댓글 생성 api controller 단위 테스트") -class CommentControllerTest { +class CommentCreateControllerTest { @Autowired private MockMvc mockMvc; @@ -217,56 +215,5 @@ void voteNotFound() throws Exception { .andExpect(jsonPath("$.message", containsString("존재하지 않는 VOTE 입니다."))); } - @Test - @DisplayName("답글인데 parentId가 null일 경우 400 반환") - void replyWithoutParentId() throws Exception { - Map req = buildValidRequest(); - req.put("isReplyRequest", true); - req.put("parentId", null); // 필수인데 없음 - assertBadCommentCreateRequest(req, "답글 작성 시 parentId는 필수입니다."); - } - - @Test - @DisplayName("일반 댓글인데 parentId가 존재할 경우 400 반환") - void rootCommentWithParentId() throws Exception { - Map req = buildValidRequest(); - req.put("isReplyRequest", false); - req.put("parentId", 1L); // 있으면 안 됨 - assertBadCommentCreateRequest(req, "일반 댓글에는 parentId가 없어야 합니다."); - } - - @Test - @DisplayName("parentId가 존재하지만 댓글이 실제 존재하지 않을 때 400 반환") - void replyToNonExistentParent() throws Exception { - Map req = buildValidRequest(); - req.put("isReplyRequest", true); - req.put("parentId", 99999L); // 존재하지 않는 parent - assertBadCommentCreateRequest(req, "parentId에 해당하는 부모 댓글이 존재해야 합니다."); - } - - @Test - @DisplayName("댓글과 부모 댓글의 게시글이 일치하지 않을 경우 400 반환") - void parentPostMismatch() throws Exception { - - // 1. 부모 댓글을 FEED에 작성 - CommentJpaEntity parentComment = commentJpaRepository.save( - TestEntityFactory.createComment(feed, user, FEED) - ); - - // 2. 답글 요청은 RECORD에 대해 요청 - Map req = new HashMap<>(); - req.put("content", "게시글 불일치"); - req.put("isReplyRequest", true); - req.put("parentId", parentComment.getCommentId()); - req.put("postType", "record"); - - mockMvc.perform(post("/comments/{postId}", record.getPostId()) - .requestAttr("userId", user.getUserId()) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsBytes(req))) - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.code").value(INVALID_COMMENT_CREATE.getCode())) - .andExpect(jsonPath("$.message", containsString("댓글과 부모 댓글의 게시글이 일치하지 않습니다."))); - } } } From d443b2d83fca919e6b281ee0153908a05ed9b3b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 03:31:05 +0900 Subject: [PATCH 22/52] =?UTF-8?q?[test]=20=ED=88=AC=ED=91=9C=20=EB=8B=A8?= =?UTF-8?q?=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/vote/domain/VoteTest.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/test/java/konkuk/thip/vote/domain/VoteTest.java b/src/test/java/konkuk/thip/vote/domain/VoteTest.java index 8992a8dd5..95b871931 100644 --- a/src/test/java/konkuk/thip/vote/domain/VoteTest.java +++ b/src/test/java/konkuk/thip/vote/domain/VoteTest.java @@ -4,11 +4,41 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import static konkuk.thip.common.exception.code.ErrorCode.COMMENT_COUNT_UNDERFLOW; import static org.junit.jupiter.api.Assertions.*; @DisplayName("[단위] Vote 도메인 테스트") class VoteTest { + private final Long CREATOR_ID = 1L; + + private Vote createWithCommentVote() { + return Vote.builder() + .id(100L) + .content("댓글이 존재하는 투표입니다.") + .creatorId(CREATOR_ID) + .page(10) + .isOverview(false) + .likeCount(0) + .commentCount(1) + .roomId(100L) + .build(); + } + + private Vote createNotCommentVote() { + return Vote.builder() + .id(100L) + .content("댓글이 존재하지 않는 투표입니다.") + .creatorId(CREATOR_ID) + .page(10) + .isOverview(false) + .likeCount(0) + .commentCount(0) + .roomId(100L) + .build(); + } + + @Test @DisplayName("validatePage: 유효한 페이지 범위일 때, 예외가 발생하지 않는다.") void validate_page_valid_range() { @@ -60,4 +90,38 @@ void validate_overview_ratio_below_80_percent() { assertInstanceOf(IllegalStateException.class, ex.getCause()); assertTrue(ex.getCause().getMessage().contains("현재 진행률 = 75.00% (15/20)")); } + + @Test + @DisplayName("increaseCommentCount: 투표의 댓글 수가 정상적으로 1 증가한다.") + void increaseCommentCount_increments() { + Vote vote = createWithCommentVote(); + int before = vote.getCommentCount(); + + vote.increaseCommentCount(); + + assertEquals(before + 1, vote.getCommentCount()); + } + + @Test + @DisplayName("decreaseCommentCount: 투표의 댓글 수가 정상적으로 1 감소한다.") + void decreaseCommentCount_decrements() { + Vote vote = createWithCommentVote(); + int before = vote.getCommentCount(); + + vote.decreaseCommentCount(); + + assertEquals(before - 1, vote.getCommentCount()); + } + + @Test + @DisplayName("decreaseCommentCount: 투표의 댓글 수가 0 이하로 내려가면 InvalidStateException이 발생한다.") + void decreaseCommentCount_belowZero_throws() { + Vote vote = createNotCommentVote(); + + InvalidStateException ex = assertThrows(InvalidStateException.class, + () -> vote.decreaseCommentCount()); + + assertEquals(COMMENT_COUNT_UNDERFLOW, ex.getErrorCode()); + } + } From 60859af406226c97bb301b3666c7f65e104ccb6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 03:31:30 +0900 Subject: [PATCH 23/52] =?UTF-8?q?[test]=20=EB=8B=B5=EA=B8=80=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/common/util/TestEntityFactory.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/test/java/konkuk/thip/common/util/TestEntityFactory.java b/src/test/java/konkuk/thip/common/util/TestEntityFactory.java index 94e00078a..dd9fc44fe 100644 --- a/src/test/java/konkuk/thip/common/util/TestEntityFactory.java +++ b/src/test/java/konkuk/thip/common/util/TestEntityFactory.java @@ -185,6 +185,18 @@ public static CommentJpaEntity createComment(PostJpaEntity post, UserJpaEntity u .build(); } + public static CommentJpaEntity createReplyComment(PostJpaEntity post, UserJpaEntity user,PostType postType,CommentJpaEntity parentComment) { + return CommentJpaEntity.builder() + .content("댓글 내용") + .postJpaEntity(post) + .userJpaEntity(user) + .likeCount(0) + .reportCount(0) + .postType(postType) + .parent(parentComment) + .build(); + } + public static CommentLikeJpaEntity createCommentLike(CommentJpaEntity comment, UserJpaEntity user) { return CommentLikeJpaEntity.builder() .userJpaEntity(user) From d5b09811d5f73db486fbed4c7db5816a025f7374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 03:31:37 +0900 Subject: [PATCH 24/52] =?UTF-8?q?[test]=20=EA=B8=B0=EB=A1=9D=20=EB=8B=A8?= =?UTF-8?q?=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/record/domain/RecordTest.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/test/java/konkuk/thip/record/domain/RecordTest.java b/src/test/java/konkuk/thip/record/domain/RecordTest.java index 75b14fdf2..1a5485c4a 100644 --- a/src/test/java/konkuk/thip/record/domain/RecordTest.java +++ b/src/test/java/konkuk/thip/record/domain/RecordTest.java @@ -4,11 +4,41 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import static konkuk.thip.common.exception.code.ErrorCode.COMMENT_COUNT_UNDERFLOW; import static org.junit.jupiter.api.Assertions.*; @DisplayName("[단위] Record 도메인 테스트") class RecordTest { + private final Long CREATOR_ID = 1L; + + private Record createWithCommentRecord() { + return Record.builder() + .id(100L) + .content("댓글이 존재하는 기록입니다.") + .creatorId(CREATOR_ID) + .page(10) + .isOverview(false) + .likeCount(0) + .commentCount(1) + .roomId(100L) + .build(); + } + + private Record createNotCommentRecord() { + return Record.builder() + .id(100L) + .content("댓글이 존재하지 않는 기록입니다.") + .creatorId(CREATOR_ID) + .page(10) + .isOverview(false) + .likeCount(0) + .commentCount(0) + .roomId(100L) + .build(); + } + + @Test @DisplayName("validatePage: 유효한 페이지 범위일 때, 예외가 발생하지 않는다.") void validate_page_valid_range() { @@ -60,4 +90,39 @@ void validate_overview_page_is_not_book_page_count() { assertInstanceOf(IllegalArgumentException.class, ex.getCause()); assertTrue(ex.getCause().getMessage().contains("현재 페이지 = 15")); } + + @Test + @DisplayName("increaseCommentCount: 기록의 댓글 수가 정상적으로 1 증가한다.") + void increaseCommentCount_increments() { + Record record = createWithCommentRecord(); + int before = record.getCommentCount(); + + record.increaseCommentCount(); + + assertEquals(before + 1, record.getCommentCount()); + } + + @Test + @DisplayName("decreaseCommentCount: 기록의 댓글 수가 정상적으로 1 감소한다.") + void decreaseCommentCount_decrements() { + Record record = createWithCommentRecord(); + int before = record.getCommentCount(); + + record.decreaseCommentCount(); + + assertEquals(before - 1, record.getCommentCount()); + } + + @Test + @DisplayName("decreaseCommentCount: 기록의 댓글 수가 0 이하로 내려가면 InvalidStateException이 발생한다.") + void decreaseCommentCount_belowZero_throws() { + Record record = createNotCommentRecord(); + + InvalidStateException ex = assertThrows(InvalidStateException.class, + () -> record.decreaseCommentCount()); + + assertEquals(COMMENT_COUNT_UNDERFLOW, ex.getErrorCode()); + } + + } \ No newline at end of file From 0d655406851cad15ec6dfe3054aa1f9d0ed2ebb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 03:31:52 +0900 Subject: [PATCH 25/52] =?UTF-8?q?[test]=20=ED=94=BC=EB=93=9C=20=EB=8B=A8?= =?UTF-8?q?=EC=9C=84=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/feed/domain/FeedTest.java | 221 ++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 src/test/java/konkuk/thip/feed/domain/FeedTest.java diff --git a/src/test/java/konkuk/thip/feed/domain/FeedTest.java b/src/test/java/konkuk/thip/feed/domain/FeedTest.java new file mode 100644 index 000000000..82da7f6c0 --- /dev/null +++ b/src/test/java/konkuk/thip/feed/domain/FeedTest.java @@ -0,0 +1,221 @@ +package konkuk.thip.feed.domain; + +import konkuk.thip.common.exception.InvalidStateException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static konkuk.thip.common.exception.code.ErrorCode.*; +import static konkuk.thip.feed.domain.Tag.BOOK_RECOMMEND; +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("[단위] Feed 도메인 테스트") +class FeedTest { + + private final Long CREATOR_ID = 1L; + private final Long OTHER_USER_ID = 2L; + + private Feed createPublicFeed() { + return Feed.builder() + .id(100L) + .creatorId(CREATOR_ID) + .content("공개 피드 입니다.") + .isPublic(true) + .tagList(List.of(Tag.from(BOOK_RECOMMEND.getValue()))) + .contentList(List.of(Content.builder() + .contentUrl("url1") + .targetPostId(100L).build() + + )) + .commentCount(1) + .build(); + } + + private Feed createNotCommentFeed() { + return Feed.builder() + .id(101L) + .creatorId(CREATOR_ID) + .content("댓글이 없는 공개피드입니다.") + .isPublic(true) + .commentCount(0) + .build(); + } + + private Feed createPrivateFeed() { + return Feed.builder() + .id(102L) + .creatorId(CREATOR_ID) + .content("비공개피드 입니다.") + .isPublic(false) + .commentCount(0) + .build(); + } + + @Test + @DisplayName("validateTags: 태그가 5개 초과 시 InvalidStateException이 발생한다.") + void validateTags_exceedsMax_throws() { + List tags = List.of("a", "b", "c", "d", "e", "f"); + + InvalidStateException ex = assertThrows(InvalidStateException.class, + () -> Feed.validateTags(tags)); + + assertEquals(INVALID_FEED_COMMAND, ex.getErrorCode()); + assertTrue(ex.getCause().getMessage().contains("최대 5개")); + } + + @Test + @DisplayName("validateTags: 중복 태그 있을 경우 InvalidStateException이 발생한다.") + void validateTags_withDuplicates_throws() { + List tags = List.of("a", "b", "a"); + + InvalidStateException ex = assertThrows(InvalidStateException.class, + () -> Feed.validateTags(tags)); + + assertEquals(INVALID_FEED_COMMAND, ex.getErrorCode()); + assertTrue(ex.getCause().getMessage().contains("중복")); + } + + + @Test + @DisplayName("validateImageCount: 3개 초과 이미지 업로드 시 InvalidStateException이 발생한다.") + void validateImageCount_exceedsMax_throws() { + int imageCount = 4; + + InvalidStateException ex = assertThrows(InvalidStateException.class, + () -> Feed.validateImageCount(imageCount)); + + assertEquals(INVALID_FEED_COMMAND, ex.getErrorCode()); + assertTrue(ex.getCause().getMessage().contains("최대 3개")); + } + + + @Test + @DisplayName("validateCreateComment: 공개 피드면 누구나 댓글을 작성 할 수 있다") + void validateCreateComment_publicFeed_passes() { + Feed feed = createPublicFeed(); + + assertDoesNotThrow(() -> feed.validateCreateComment(OTHER_USER_ID)); + } + + @Test + @DisplayName("validateCreateComment: 비공개 피드시에 작성자가 아닌 유저가 댓글을 작성하려고 하면 InvalidStateException이 발생한다.") + void validateCreateComment_nonCreatorOnPrivateFeed_throws() { + Feed feed = createPrivateFeed(); + + InvalidStateException ex = assertThrows(InvalidStateException.class, + () -> feed.validateCreateComment(OTHER_USER_ID)); + + assertEquals(FEED_ACCESS_FORBIDDEN, ex.getErrorCode()); + assertFalse(ex.getCause().getMessage().contains("비공개 글은 작성자만")); + } + + @Test + @DisplayName("validateCreateComment: 비공개 피드시에 작성자만 댓글을 작성 할 수 있다.") + void validateCreateComment_creatorOnPrivateFeed_passes() { + Feed feed = createPrivateFeed(); + + assertDoesNotThrow(() -> feed.validateCreateComment(CREATOR_ID)); + } + + + @Test + @DisplayName("updateContent: 작성자가 아닌 경우 피드 내용을 수정하려고 하면 InvalidStateException이 발생한다.") + void updateContent_byNonCreator_throws() { + Feed feed = createPublicFeed(); + String newContent = "새로운 피드 내용"; + + InvalidStateException ex = assertThrows(InvalidStateException.class, + () -> feed.updateContent(OTHER_USER_ID, newContent)); + + assertEquals(FEED_ACCESS_FORBIDDEN,ex.getErrorCode()); + assertTrue(ex.getCause().getMessage().contains("피드 작성자만")); + } + + + @Test + @DisplayName("updateVisibility: 작성자가 아닌 경우 피드 공개여부를 수정하려고 하면 InvalidStateException이 발생한다.") + void updateVisibility_byNonCreator_throws() { + Feed feed = createPublicFeed(); + + InvalidStateException ex = assertThrows(InvalidStateException.class, + () -> feed.updateVisibility(OTHER_USER_ID, false)); + + assertEquals(FEED_ACCESS_FORBIDDEN, ex.getErrorCode()); + } + + @Test + @DisplayName("updateTags: 작성자가 아닌 경우 피드의 태그를 수정하려고 하면 InvalidStateException이 발생한다.") + void updateTags_byNonCreator_throws(){ + Feed feed = createPublicFeed(); + List tags = List.of(BOOK_RECOMMEND.getValue()); + + InvalidStateException ex = assertThrows(InvalidStateException.class, + () -> feed.updateTags(OTHER_USER_ID, tags)); + + assertEquals(FEED_ACCESS_FORBIDDEN, ex.getErrorCode()); + } + + + @Test + @DisplayName("updateImages: 작성자가 아닌 경우 피드의 이미지를 수정하려고 하면 InvalidStateException이 발생한다.") + void updateImages_nonCreator_throws() { + Feed feed = createPublicFeed(); + List images = List.of("url1", "url2"); + + InvalidStateException ex = assertThrows(InvalidStateException.class, + () -> feed.updateImages(OTHER_USER_ID, images)); + + assertEquals(FEED_ACCESS_FORBIDDEN, ex.getErrorCode()); + } + + @Test + @DisplayName("validateOwnsImages: 피드 수정 시에 존재하지 않는 이미지 URL 포함하여 수정하려고 하면 InvalidStateException이 발생한다.") + void validateOwnsImages_withInvalidUrl_throws() { + Feed feed = createPublicFeed(); + + // feed.contentList에는 "url1"만 있음 + List candidateImageUrls = List.of("url1", "invalidUrl"); + + InvalidStateException ex = assertThrows(InvalidStateException.class, + () -> feed.validateOwnsImages(candidateImageUrls)); + + assertEquals(INVALID_FEED_COMMAND, ex.getErrorCode()); + assertTrue(ex.getCause().getMessage().contains("해당 이미지는 이 피드에 존재하지 않습니다")); + } + + + @Test + @DisplayName("increaseCommentCount: 피드의 댓글 수가 정상적으로 1 증가한다.") + void increaseCommentCount_increments() { + Feed feed = createPublicFeed(); + int before = feed.getCommentCount(); + + feed.increaseCommentCount(); + + assertEquals(before + 1, feed.getCommentCount()); + } + + @Test + @DisplayName("decreaseCommentCount: 피드의 댓글 수가 정상적으로 1 감소한다.") + void decreaseCommentCount_decrements() { + Feed feed = createPublicFeed(); + int before = feed.getCommentCount(); + + feed.decreaseCommentCount(); + + assertEquals(before - 1, feed.getCommentCount()); + } + + @Test + @DisplayName("decreaseCommentCount: 피드의 댓글 수가 0 이하로 내려가면 InvalidStateException이 발생한다.") + void decreaseCommentCount_belowZero_throws() { + Feed feed = createNotCommentFeed(); + + InvalidStateException ex = assertThrows(InvalidStateException.class, + () -> feed.decreaseCommentCount()); + + assertEquals(COMMENT_COUNT_UNDERFLOW, ex.getErrorCode()); + } + +} From c756b3cb520b9ce37eb634815d47f32e3541516b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 03:32:04 +0900 Subject: [PATCH 26/52] =?UTF-8?q?[test]=20=EB=8C=93=EA=B8=80=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=EA=B4=80=EB=A0=A8=20=EB=8C=93=EA=B8=80=20=EB=8B=A8?= =?UTF-8?q?=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/comment/domain/CommentTest.java | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/src/test/java/konkuk/thip/comment/domain/CommentTest.java b/src/test/java/konkuk/thip/comment/domain/CommentTest.java index 586ae0051..0fa17c2e7 100644 --- a/src/test/java/konkuk/thip/comment/domain/CommentTest.java +++ b/src/test/java/konkuk/thip/comment/domain/CommentTest.java @@ -4,7 +4,9 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import static konkuk.thip.common.exception.code.ErrorCode.COMMENT_LIKE_COUNT_UNDERFLOW; +import static konkuk.thip.common.entity.StatusType.ACTIVE; +import static konkuk.thip.common.entity.StatusType.INACTIVE; +import static konkuk.thip.common.exception.code.ErrorCode.*; import static konkuk.thip.common.post.PostType.FEED; import static org.junit.jupiter.api.Assertions.*; @@ -14,6 +16,7 @@ class CommentTest { private final String CONTENT = "댓글 본문"; private final Long CREATOR_ID = 1L; private final Long POST_ID = 100L; + private final Long OTHER_USER_ID = 2L; private Comment createParentComment(Long postId) { return Comment.builder() @@ -25,6 +28,22 @@ private Comment createParentComment(Long postId) { .parentCommentId(null) .reportCount(0) .likeCount(0) + .status(ACTIVE) + .build(); + } + + + private Comment createInactiveComment(Long postId) { + return Comment.builder() + .id(124L) //ID 임의 주입 + .content(CONTENT) + .targetPostId(postId) + .creatorId(CREATOR_ID) + .postType(FEED) + .parentCommentId(null) + .reportCount(0) + .likeCount(0) + .status(INACTIVE) .build(); } @@ -175,4 +194,39 @@ void updateLikeCount_likeFalse_underflow_throws() { assertEquals(COMMENT_LIKE_COUNT_UNDERFLOW, ex.getErrorCode()); } + @Test + @DisplayName("softDelete: 정상적인 작성자가 호출하면 상태가 INACTIVE로 변경된다") + void softDelete_byCreator_changesStatusToInactive() { + Comment comment = createParentComment(POST_ID); + + comment.softDelete(CREATOR_ID); + + assertEquals(INACTIVE, comment.getStatus()); + } + + @Test + @DisplayName("softDelete: 작성자가 아니면 InvalidStateException 예외가 발생한다") + void softDelete_byNonCreator_throwsInvalidStateException() { + Comment comment = createParentComment(POST_ID); + + InvalidStateException ex = assertThrows(InvalidStateException.class, () -> { + comment.softDelete(OTHER_USER_ID); + }); + + assertEquals(COMMENT_DELETE_FORBIDDEN, ex.getErrorCode()); + } + + @Test + @DisplayName("softDelete: 이미 삭제된 댓글에 다시 호출하면 InvalidStateException 예외가 발생한다") + void softDelete_alreadyInactive_throwsInvalidStateException() { + + Comment comment = createInactiveComment(POST_ID); + + InvalidStateException ex = assertThrows(InvalidStateException.class, () -> { + comment.softDelete(CREATOR_ID); + }); + + assertEquals(COMMENT_ALREADY_DELETED, ex.getErrorCode()); + } + } From 76c08bce0f06d728b5b2a7ce48aad83ddf0fd4ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 03:32:16 +0900 Subject: [PATCH 27/52] =?UTF-8?q?[test]=20=EB=8C=93=EA=B8=80=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/CommentDeleteAPITest.java | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 src/test/java/konkuk/thip/comment/adapter/in/web/CommentDeleteAPITest.java diff --git a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentDeleteAPITest.java b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentDeleteAPITest.java new file mode 100644 index 000000000..70c12c643 --- /dev/null +++ b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentDeleteAPITest.java @@ -0,0 +1,159 @@ +package konkuk.thip.comment.adapter.in.web; + +import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; +import konkuk.thip.book.adapter.out.persistence.repository.BookJpaRepository; +import konkuk.thip.comment.adapter.out.jpa.CommentJpaEntity; +import konkuk.thip.comment.adapter.out.persistence.repository.CommentJpaRepository; +import konkuk.thip.comment.adapter.out.persistence.repository.CommentLikeJpaRepository; +import konkuk.thip.common.util.TestEntityFactory; +import konkuk.thip.feed.adapter.out.jpa.FeedJpaEntity; +import konkuk.thip.feed.adapter.out.persistence.repository.FeedJpaRepository; +import konkuk.thip.record.adapter.out.jpa.RecordJpaEntity; +import konkuk.thip.record.adapter.out.persistence.repository.RecordJpaRepository; +import konkuk.thip.room.adapter.out.jpa.CategoryJpaEntity; +import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; +import konkuk.thip.room.adapter.out.jpa.RoomParticipantRole; +import konkuk.thip.room.adapter.out.persistence.repository.RoomJpaRepository; +import konkuk.thip.room.adapter.out.persistence.repository.category.CategoryJpaRepository; +import konkuk.thip.room.adapter.out.persistence.repository.roomparticipant.RoomParticipantJpaRepository; +import konkuk.thip.user.adapter.out.jpa.AliasJpaEntity; +import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; +import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; +import konkuk.thip.user.adapter.out.persistence.repository.alias.AliasJpaRepository; +import konkuk.thip.vote.adapter.out.jpa.VoteJpaEntity; +import konkuk.thip.vote.adapter.out.persistence.repository.VoteJpaRepository; +import org.junit.jupiter.api.BeforeEach; +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.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import static konkuk.thip.common.entity.StatusType.INACTIVE; +import static konkuk.thip.common.post.PostType.*; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@ActiveProfiles("test") +@AutoConfigureMockMvc(addFilters = false) +@Transactional +@DisplayName("[통합] 댓글 삭제 api 통합 테스트") +class CommentDeleteAPITest { + + @Autowired + private MockMvc mockMvc; + + @Autowired private AliasJpaRepository aliasJpaRepository; + @Autowired private UserJpaRepository userJpaRepository; + @Autowired private CategoryJpaRepository categoryJpaRepository; + @Autowired private BookJpaRepository bookJpaRepository; + @Autowired private FeedJpaRepository feedJpaRepository; + @Autowired private VoteJpaRepository voteJpaRepository; + @Autowired private RecordJpaRepository recordJpaRepository; + @Autowired private CommentJpaRepository commentJpaRepository; + @Autowired private RoomJpaRepository roomJpaRepository; + @Autowired private RoomParticipantJpaRepository roomParticipantJpaRepository; + @Autowired private CommentLikeJpaRepository commentLikeJpaRepository; + + + + private AliasJpaEntity alias; + private UserJpaEntity user; + private CategoryJpaEntity category; + private FeedJpaEntity feed; + private BookJpaEntity book; + private RecordJpaEntity record; + private VoteJpaEntity vote; + private RoomJpaEntity room; + + @BeforeEach + void setUp() { + alias = aliasJpaRepository.save(TestEntityFactory.createLiteratureAlias()); + user = userJpaRepository.save(TestEntityFactory.createUser(alias)); + category = categoryJpaRepository.save(TestEntityFactory.createLiteratureCategory(alias)); + book = bookJpaRepository.save(TestEntityFactory.createBookWithISBN("9788954682152")); + room = roomJpaRepository.save(TestEntityFactory.createRoom(book,category)); + feed = feedJpaRepository.save(TestEntityFactory.createFeed(user,book, true)); + record = recordJpaRepository.save(TestEntityFactory.createRecord(user,room)); + vote = voteJpaRepository.save(TestEntityFactory.createVote(user,room)); + roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room, user, RoomParticipantRole.HOST, 0.0)); + } + + + @Test + @DisplayName("루트댓글을 삭제하면 [soft delete 처리]된다") + void deleteRootComment_success() throws Exception { + + // given + CommentJpaEntity comment = commentJpaRepository.save(TestEntityFactory.createComment(feed, user, FEED)); + feed.updateCommentCount(1); + Long commentId = comment.getCommentId(); + + // when + mockMvc.perform(delete("/comments/{commentId}", commentId) + .requestAttr("userId", user.getUserId())) + .andExpect(status().isOk()); + + // then + CommentJpaEntity found = commentJpaRepository.findById(commentId).get(); + assertThat(found.getStatus()).isEqualTo(INACTIVE); + } + + @Test + @DisplayName("대댓글을 삭제하면 [soft delete 처리]된다") + void deleteReplyComment_success() throws Exception { + + // given: 부모 댓글/대댓글 생성 + CommentJpaEntity parent = commentJpaRepository.save(TestEntityFactory.createComment(feed, user, FEED)); + CommentJpaEntity reply = commentJpaRepository.save(TestEntityFactory.createReplyComment(feed, user, FEED, parent)); + feed.updateCommentCount(2); + Long replyId = reply.getCommentId(); + + + // when + mockMvc.perform(delete("/comments/{commentId}", replyId) + .requestAttr("userId", user.getUserId())) + .andExpect(status().isOk()); + + // then + CommentJpaEntity found = commentJpaRepository.findById(replyId).get(); + assertThat(found.getStatus()).isEqualTo(INACTIVE); + } + + @Test + @DisplayName("댓글 삭제 시에 댓글을 성공적으로 삭제하면 댓글의 상태가 soft delete 처리되고" + + "게시물의 댓글 수가 1감소하고, 댓글에 달린 좋아요가 전체 삭제 된다.") + void deleteComment_success() throws Exception { + + // given + CommentJpaEntity comment = commentJpaRepository.save(TestEntityFactory.createComment(feed, user, FEED)); + commentLikeJpaRepository.save(TestEntityFactory.createCommentLike(comment, user)); + feed.updateCommentCount(1); + Long commentId = comment.getCommentId(); + int beforeCount = feed.getCommentCount(); + + // when + mockMvc.perform(delete("/comments/{commentId}", commentId) + .requestAttr("userId", user.getUserId())) + .andExpect(status().isOk()); + + // then + CommentJpaEntity found = commentJpaRepository.findById(commentId).get(); + assertThat(found.getStatus()).isEqualTo(INACTIVE); + + // Feed 댓글수 감소 확인 + FeedJpaEntity updatedFeed = feedJpaRepository.findById(feed.getPostId()).get(); + assertThat(updatedFeed.getCommentCount()).isEqualTo(beforeCount - 1); + + // 댓글 좋아요가 모두 삭제됐는지 확인 + boolean like = (boolean) commentLikeJpaRepository.existsByUserIdAndCommentId(user.getUserId(), commentId); + assertThat(like).isFalse(); + } + + +} From c92b45dce1569e64833c99648dc79ec4989da01f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 03:33:11 +0900 Subject: [PATCH 28/52] =?UTF-8?q?[feat]=20PostQueryService.updatePost=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/common/post/service/PostQueryService.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/konkuk/thip/common/post/service/PostQueryService.java b/src/main/java/konkuk/thip/common/post/service/PostQueryService.java index 758c08c1e..ee6956554 100644 --- a/src/main/java/konkuk/thip/common/post/service/PostQueryService.java +++ b/src/main/java/konkuk/thip/common/post/service/PostQueryService.java @@ -3,8 +3,11 @@ import konkuk.thip.common.post.CommentCountUpdatable; import konkuk.thip.common.post.PostType; import konkuk.thip.feed.application.port.out.FeedCommandPort; +import konkuk.thip.feed.domain.Feed; import konkuk.thip.record.application.port.out.RecordCommandPort; +import konkuk.thip.record.domain.Record; import konkuk.thip.vote.application.port.out.VoteCommandPort; +import konkuk.thip.vote.domain.Vote; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -23,4 +26,12 @@ public CommentCountUpdatable findPost(PostType type, Long postId) { case VOTE -> voteCommandPort.getByIdOrThrow(postId); }; } + + public void updatePost(PostType type, CommentCountUpdatable post) { + switch (type) { + case FEED -> feedCommandPort.update((Feed) post); + case RECORD -> recordCommandPort.update((Record) post); + case VOTE -> voteCommandPort.updateVote((Vote) post); + } + } } From f4d54d079330085375b273fd2c9c6fd26b81cc80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 03:33:41 +0900 Subject: [PATCH 29/52] =?UTF-8?q?[refactor]=20=ED=94=BC=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EA=B4=80=EB=A0=A8=20request=20dto=20?= =?UTF-8?q?=EC=84=A4=EB=AA=85=20=EC=B6=94=EA=B0=80=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feed/adapter/in/web/request/FeedUpdateRequest.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/feed/adapter/in/web/request/FeedUpdateRequest.java b/src/main/java/konkuk/thip/feed/adapter/in/web/request/FeedUpdateRequest.java index d18433731..2a8310f15 100644 --- a/src/main/java/konkuk/thip/feed/adapter/in/web/request/FeedUpdateRequest.java +++ b/src/main/java/konkuk/thip/feed/adapter/in/web/request/FeedUpdateRequest.java @@ -1,17 +1,22 @@ package konkuk.thip.feed.adapter.in.web.request; +import io.swagger.v3.oas.annotations.media.Schema; import konkuk.thip.feed.application.port.in.dto.FeedUpdateCommand; import java.util.List; public record FeedUpdateRequest( + @Schema(description = "수정한 피드 내용", example = "이 책은 정말 좋습니다!") String contentBody, + @Schema(description = "수정한 방 공개 설정 여부 (true: 공개, false: 비공개)", example = "true") Boolean isPublic, + @Schema(description = "수정된 피드에 남아있는 태그들", example = "[\"한국소설\", \"외국소설\", \"시\"]") List tagList, + @Schema(description = "수정된 피드에 남아있는 이미지 URL들", example = "[\"https://img.domain.com/1.jpg\", \"https://img.domain.com/2.jpg\"]") List remainImageUrls ) { public FeedUpdateCommand toCommand(Long userId, Long feedId) { @@ -24,4 +29,4 @@ public FeedUpdateCommand toCommand(Long userId, Long feedId) { feedId ); } -} +} \ No newline at end of file From d9cdc8b7efe737373abcde8a62f70b6ef1fbd44f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 03:33:55 +0900 Subject: [PATCH 30/52] =?UTF-8?q?[refactor]=20=ED=94=BC=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=84=A4=EB=AA=85=20=EC=B6=94=EA=B0=80=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/feed/adapter/in/web/FeedCommandController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/konkuk/thip/feed/adapter/in/web/FeedCommandController.java b/src/main/java/konkuk/thip/feed/adapter/in/web/FeedCommandController.java index 0513d0eae..70970ec72 100644 --- a/src/main/java/konkuk/thip/feed/adapter/in/web/FeedCommandController.java +++ b/src/main/java/konkuk/thip/feed/adapter/in/web/FeedCommandController.java @@ -46,8 +46,9 @@ public BaseResponse createFeed( } @Operation( - summary = "피드 수정 (책 빼고 변경가능)", - description = "사용자가 피드를 수정합니다." + summary = "피드 수정", + description = "사용자가 피드를 수정합니다. 책을 제외하고 모든 피드의 정보를 수정가능합니다.\n" + + "이미지는 삭제만 가능하며, 태그,이미지의 경우 수정 시 변경된 값 즉, DB에 존재해야하는 값들을 보내주시면 됩니다." ) @ExceptionDescription(FEED_UPDATE) @PatchMapping("/feeds/{feedId}") From 27a609d1dc3c413b148a8b0e85d7b7e83639b9a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 03:34:27 +0900 Subject: [PATCH 31/52] =?UTF-8?q?[refactor]=20=EA=B0=9C=EB=B0=9C=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=20=EB=B3=84=EB=A1=9C=20=EB=8B=A4=EB=A5=B8=20?= =?UTF-8?q?db=EB=B0=98=ED=99=98=EA=B0=92=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=EB=B6=84=EA=B8=B0=EC=B2=98=EB=A6=AC=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommentLikeQueryPersistenceAdapter.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentLikeQueryPersistenceAdapter.java b/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentLikeQueryPersistenceAdapter.java index 6d878b414..7dd52bd8a 100644 --- a/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentLikeQueryPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentLikeQueryPersistenceAdapter.java @@ -19,7 +19,15 @@ public class CommentLikeQueryPersistenceAdapter implements CommentLikeQueryPort @Override public boolean isLikedCommentByUser(Long userId, Long commentId) { - Long exists = commentLikeJpaRepository.existsByUserIdAndCommentId(userId, commentId); - return exists != null && exists > 0; + Object result = commentLikeJpaRepository.existsByUserIdAndCommentId(userId, commentId); + + // 테스트 환경 H2에서의 반환값 + if (result instanceof Boolean) { + return (Boolean) result; + } // 로컬, 배포 환경 Mysql에서의 반환값 + else if (result instanceof Number) { + return ((Number) result).longValue() > 0; + } + return false; } } From 96544bfcf559ec5a02ebb50c83f077f9cd665fc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 03:34:29 +0900 Subject: [PATCH 32/52] =?UTF-8?q?[refactor]=20=EA=B0=9C=EB=B0=9C=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=20=EB=B3=84=EB=A1=9C=20=EB=8B=A4=EB=A5=B8=20?= =?UTF-8?q?db=EB=B0=98=ED=99=98=EA=B0=92=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=EB=B6=84=EA=B8=B0=EC=B2=98=EB=A6=AC=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../out/persistence/repository/CommentLikeJpaRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentLikeJpaRepository.java b/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentLikeJpaRepository.java index 95f12281c..42c5bb326 100644 --- a/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentLikeJpaRepository.java +++ b/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentLikeJpaRepository.java @@ -17,7 +17,7 @@ public interface CommentLikeJpaRepository extends JpaRepository Date: Sun, 3 Aug 2025 03:34:38 +0900 Subject: [PATCH 33/52] =?UTF-8?q?[feat]=20PostQueryService.updatePost=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/service/CommentCreateService.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java b/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java index c15ab1345..14fac6cf2 100644 --- a/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java +++ b/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java @@ -53,11 +53,13 @@ public Long createComment(CommentCreateCommand command) { // 3. 댓글 생성 Long commentId = createCommentDomain(command); + //TODO 게시물의 댓글 수 증가/감소 동시성 제어 로직 추가해야됨 + // 4. 게시글 댓글 수 증가 // 4-1. 도메인 게시물 댓글 수 증가 post.increaseCommentCount(); // 4-2 Jpa엔티티 게시물 댓글 수 증가 - updatePost(type, post); + postQueryService.updatePost(type, post); return commentId; } @@ -85,12 +87,4 @@ private Long createCommentDomain(CommentCreateCommand command) { return commentCommandPort.save(comment); } - private void updatePost(PostType type, CommentCountUpdatable post) { - switch (type) { - case FEED -> feedCommandPort.update((Feed) post); - case RECORD -> recordCommandPort.update((Record) post); - case VOTE -> voteCommandPort.updateVote((Vote) post); - } - } - } From b7ccf95544319a9cd3c5f428a72bc06c77a259f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 3 Aug 2025 03:38:12 +0900 Subject: [PATCH 34/52] =?UTF-8?q?[refactor]=20=EC=95=88=EC=93=B0=EB=8A=94?= =?UTF-8?q?=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=82=AD=EC=A0=9C=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/service/CommentCreateService.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java b/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java index 14fac6cf2..82bd52d1e 100644 --- a/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java +++ b/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java @@ -8,13 +8,7 @@ import konkuk.thip.common.exception.InvalidStateException; import konkuk.thip.common.post.CommentCountUpdatable; import konkuk.thip.common.post.service.PostQueryService; -import konkuk.thip.feed.application.port.out.FeedCommandPort; -import konkuk.thip.feed.domain.Feed; import konkuk.thip.common.post.PostType; -import konkuk.thip.record.application.port.out.RecordCommandPort; -import konkuk.thip.record.domain.Record; -import konkuk.thip.vote.application.port.out.VoteCommandPort; -import konkuk.thip.vote.domain.Vote; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -27,9 +21,6 @@ public class CommentCreateService implements CommentCreateUseCase { private final CommentCommandPort commentCommandPort; - private final FeedCommandPort feedCommandPort; - private final RecordCommandPort recordCommandPort; - private final VoteCommandPort voteCommandPort; private final PostQueryService postQueryService; private final CommentAuthorizationValidator commentAuthorizationValidator; From 8ef7fbee4e9ad732c0a8d27a2a91856cd35e228d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 4 Aug 2025 18:28:58 +0900 Subject: [PATCH 35/52] =?UTF-8?q?[refactor]=20status=20=EC=A0=91=EA=B7=BC?= =?UTF-8?q?=20=EC=A0=9C=EC=96=B4=EC=9E=90=20=ED=83=80=EC=9E=85=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java b/src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java index c306ebdcd..613f2f07c 100644 --- a/src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java +++ b/src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java @@ -13,6 +13,6 @@ public class BaseDomainEntity { private LocalDateTime modifiedAt; - protected StatusType status; + private StatusType status; } From fc288efc06ce58ddd783a9b43d5552fc62329d6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 4 Aug 2025 18:29:19 +0900 Subject: [PATCH 36/52] =?UTF-8?q?[refactor]=20=EC=95=88=EC=93=B0=EB=8A=94?= =?UTF-8?q?=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=ED=95=A8=EC=88=98=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/comment/domain/Comment.java | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/main/java/konkuk/thip/comment/domain/Comment.java b/src/main/java/konkuk/thip/comment/domain/Comment.java index eef4decdf..3aeb48ef9 100644 --- a/src/main/java/konkuk/thip/comment/domain/Comment.java +++ b/src/main/java/konkuk/thip/comment/domain/Comment.java @@ -1,7 +1,6 @@ package konkuk.thip.comment.domain; import konkuk.thip.common.entity.BaseDomainEntity; -import konkuk.thip.common.entity.StatusType; import konkuk.thip.common.exception.InvalidStateException; import konkuk.thip.common.post.PostType; import lombok.Builder; @@ -147,18 +146,10 @@ private boolean validateCreator(Long userId) { return this.creatorId.equals(userId); } - private void validateDeletable(Long userId) { + public void validateDeletable(Long userId) { if (!validateCreator(userId)) { throw new InvalidStateException(COMMENT_DELETE_FORBIDDEN); } } - public void softDelete(Long userId) { - validateDeletable(userId); - if (this.status == StatusType.INACTIVE) { - throw new InvalidStateException(COMMENT_ALREADY_DELETED); - } - this.status = StatusType.INACTIVE; - } - } From 0305e48aad58235d71c1134276052d96a0a2dca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 4 Aug 2025 18:29:44 +0900 Subject: [PATCH 37/52] =?UTF-8?q?[refactor]=20postId=EB=B0=98=ED=99=98?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/adapter/in/web/CommentCommandController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/konkuk/thip/comment/adapter/in/web/CommentCommandController.java b/src/main/java/konkuk/thip/comment/adapter/in/web/CommentCommandController.java index dd705f7bd..6f9977727 100644 --- a/src/main/java/konkuk/thip/comment/adapter/in/web/CommentCommandController.java +++ b/src/main/java/konkuk/thip/comment/adapter/in/web/CommentCommandController.java @@ -6,6 +6,7 @@ import jakarta.validation.Valid; import konkuk.thip.comment.adapter.in.web.request.CommentCreateRequest; import konkuk.thip.comment.adapter.in.web.request.CommentIsLikeRequest; +import konkuk.thip.comment.adapter.in.web.response.CommentDeleteResponse; import konkuk.thip.comment.adapter.in.web.response.CommentIdResponse; import konkuk.thip.comment.adapter.in.web.response.CommentIsLikeResponse; import konkuk.thip.comment.application.port.in.CommentCreateUseCase; @@ -67,11 +68,10 @@ public BaseResponse likeComment( ) @ExceptionDescription(COMMENT_DELETE) @DeleteMapping("/comments/{commentId}") - public BaseResponse deleteComment( + public BaseResponse deleteComment( @Parameter(description = "삭제하려는 댓글 ID", example = "1") @PathVariable("commentId") final Long commentId, @Parameter(hidden = true) @UserId final Long userId) { - commentDeleteUseCase.deleteComment(commentId,userId); - return BaseResponse.ok(null); + return BaseResponse.ok(CommentDeleteResponse.of(commentDeleteUseCase.deleteComment(commentId,userId))); } } From 07e1dae673d5a96b52dace8d0a2139a1a4305424 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 4 Aug 2025 18:30:39 +0900 Subject: [PATCH 38/52] =?UTF-8?q?[feat]=20getId()=20=EC=B6=94=EA=B0=80=20(?= =?UTF-8?q?#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/common/post/CommentCountUpdatable.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/konkuk/thip/common/post/CommentCountUpdatable.java b/src/main/java/konkuk/thip/common/post/CommentCountUpdatable.java index 6337bce98..accccc90e 100644 --- a/src/main/java/konkuk/thip/common/post/CommentCountUpdatable.java +++ b/src/main/java/konkuk/thip/common/post/CommentCountUpdatable.java @@ -3,4 +3,5 @@ public interface CommentCountUpdatable { void increaseCommentCount(); void decreaseCommentCount(); + Long getId(); } \ No newline at end of file From d2412da26117257bc35e081f0c83182f0b3094f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 4 Aug 2025 18:30:56 +0900 Subject: [PATCH 39/52] =?UTF-8?q?[feat]=20CommentDeleteResponse=20dto=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/response/CommentDeleteResponse.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/java/konkuk/thip/comment/adapter/in/web/response/CommentDeleteResponse.java diff --git a/src/main/java/konkuk/thip/comment/adapter/in/web/response/CommentDeleteResponse.java b/src/main/java/konkuk/thip/comment/adapter/in/web/response/CommentDeleteResponse.java new file mode 100644 index 000000000..440f3f76d --- /dev/null +++ b/src/main/java/konkuk/thip/comment/adapter/in/web/response/CommentDeleteResponse.java @@ -0,0 +1,7 @@ +package konkuk.thip.comment.adapter.in.web.response; + +public record CommentDeleteResponse(Long postId) { + public static CommentDeleteResponse of(Long postId) { + return new CommentDeleteResponse(postId); + } +} From 58cc5d51e520d2d751a88e577512875cc8e55c6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 4 Aug 2025 18:31:39 +0900 Subject: [PATCH 40/52] =?UTF-8?q?[feat]=20CommentCommandPort.delete=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/comment/application/port/out/CommentCommandPort.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/konkuk/thip/comment/application/port/out/CommentCommandPort.java b/src/main/java/konkuk/thip/comment/application/port/out/CommentCommandPort.java index 22bff6d06..ffe41f1ac 100644 --- a/src/main/java/konkuk/thip/comment/application/port/out/CommentCommandPort.java +++ b/src/main/java/konkuk/thip/comment/application/port/out/CommentCommandPort.java @@ -21,4 +21,6 @@ default Comment getByIdOrThrow(Long id) { void update(Comment comment); + void delete(Comment comment); + } From 116ec805dc7f065a115829e683eab55fa6a4abfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 4 Aug 2025 18:31:53 +0900 Subject: [PATCH 41/52] =?UTF-8?q?[feat]=20CommentCommandPersistenceAdapter?= =?UTF-8?q?.delete=20=EA=B5=AC=ED=98=84=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/CommentCommandPersistenceAdapter.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentCommandPersistenceAdapter.java b/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentCommandPersistenceAdapter.java index ff120cdfc..24861fe43 100644 --- a/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentCommandPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentCommandPersistenceAdapter.java @@ -74,7 +74,6 @@ public Optional findById(Long id) { @Override public void update(Comment comment) { - CommentJpaEntity commentJpaEntity = commentJpaRepository.findById(comment.getId()).orElseThrow( () -> new EntityNotFoundException(COMMENT_NOT_FOUND) ); @@ -82,4 +81,12 @@ public void update(Comment comment) { commentJpaRepository.save(commentJpaEntity.updateFrom(comment)); } + @Override + public void delete(Comment comment) { + CommentJpaEntity commentJpaEntity = commentJpaRepository.findById(comment.getId()).orElseThrow( + () -> new EntityNotFoundException(COMMENT_NOT_FOUND) + ); + commentJpaRepository.delete(commentJpaEntity); + } + } From 318a5e76a4edb69f5bdad93940288f636aa3ef97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 4 Aug 2025 18:31:59 +0900 Subject: [PATCH 42/52] =?UTF-8?q?[refactor]=20postId=EB=B0=98=ED=99=98?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/comment/application/port/in/CommentDeleteUseCase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/comment/application/port/in/CommentDeleteUseCase.java b/src/main/java/konkuk/thip/comment/application/port/in/CommentDeleteUseCase.java index 791ce9c26..ab110db7c 100644 --- a/src/main/java/konkuk/thip/comment/application/port/in/CommentDeleteUseCase.java +++ b/src/main/java/konkuk/thip/comment/application/port/in/CommentDeleteUseCase.java @@ -2,5 +2,5 @@ public interface CommentDeleteUseCase { - void deleteComment(Long commentId, Long userId); + Long deleteComment(Long commentId, Long userId); } From ae2074f887c7697769b54c4719a4394fc7ea537d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 4 Aug 2025 18:32:18 +0900 Subject: [PATCH 43/52] =?UTF-8?q?[feat]=20Comment=20@SQLDelete=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/comment/adapter/out/jpa/CommentJpaEntity.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/konkuk/thip/comment/adapter/out/jpa/CommentJpaEntity.java b/src/main/java/konkuk/thip/comment/adapter/out/jpa/CommentJpaEntity.java index 1ebfff91c..513e79b1f 100644 --- a/src/main/java/konkuk/thip/comment/adapter/out/jpa/CommentJpaEntity.java +++ b/src/main/java/konkuk/thip/comment/adapter/out/jpa/CommentJpaEntity.java @@ -8,6 +8,7 @@ import konkuk.thip.post.adapter.out.jpa.PostJpaEntity; import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import lombok.*; +import org.hibernate.annotations.SQLDelete; @Entity @Table(name = "comments") @@ -15,6 +16,7 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor @Builder +@SQLDelete(sql = "UPDATE comments SET status = 'INACTIVE' WHERE comment_id = ?") public class CommentJpaEntity extends BaseJpaEntity { @Id From 84320d60d268a3b8b3eaa26aa831cd00ac171ba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 4 Aug 2025 18:33:31 +0900 Subject: [PATCH 44/52] =?UTF-8?q?[feat]=20CommentLikeJpaEntity=EC=97=90=20?= =?UTF-8?q?cascade=20=3D=20CascadeType.ALL=20=EC=86=8D=EC=84=B1=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/comment/adapter/out/jpa/CommentLikeJpaEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/comment/adapter/out/jpa/CommentLikeJpaEntity.java b/src/main/java/konkuk/thip/comment/adapter/out/jpa/CommentLikeJpaEntity.java index 15f46203c..880aa231d 100644 --- a/src/main/java/konkuk/thip/comment/adapter/out/jpa/CommentLikeJpaEntity.java +++ b/src/main/java/konkuk/thip/comment/adapter/out/jpa/CommentLikeJpaEntity.java @@ -22,7 +22,7 @@ public class CommentLikeJpaEntity extends BaseJpaEntity { @JoinColumn(name = "user_id") private UserJpaEntity userJpaEntity; - @ManyToOne(fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinColumn(name = "comment_id") private CommentJpaEntity commentJpaEntity; } \ No newline at end of file From cb298d1b4b5730933f448473641d54e79b7fef10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 4 Aug 2025 18:33:49 +0900 Subject: [PATCH 45/52] =?UTF-8?q?[refactor]=20=EB=84=A4=EC=9D=B4=ED=8B=B0?= =?UTF-8?q?=EB=B8=8C=20=EC=BF=BC=EB=A6=AC=20jpql=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/CommentLikeJpaRepository.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentLikeJpaRepository.java b/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentLikeJpaRepository.java index 42c5bb326..a79283150 100644 --- a/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentLikeJpaRepository.java +++ b/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentLikeJpaRepository.java @@ -9,18 +9,22 @@ import java.util.List; public interface CommentLikeJpaRepository extends JpaRepository { - @Query(value = "SELECT * FROM comment_likes WHERE user_id = :userId", nativeQuery = true) - List findAllByUserId(Long userId); + + @Query("SELECT cl FROM CommentLikeJpaEntity cl WHERE cl.userJpaEntity.userId = :userId") + List findAllByUserId(@Param("userId") Long userId); + @Modifying - @Query(value = "DELETE FROM comment_likes WHERE user_id = :userId AND comment_id = :commentId", nativeQuery = true) + @Query("DELETE FROM CommentLikeJpaEntity cl WHERE cl.userJpaEntity.userId = :userId AND cl.commentJpaEntity.commentId = :commentId") void deleteByUserIdAndCommentId(@Param("userId") Long userId, @Param("commentId") Long commentId); - @Query(value = "SELECT EXISTS(SELECT 1 FROM comment_likes WHERE user_id = :userId AND comment_id = :commentId)", nativeQuery = true) - Object existsByUserIdAndCommentId(@Param("userId") Long userId, @Param("commentId") Long commentId); + @Query("SELECT CASE WHEN COUNT(cl) > 0 THEN true ELSE false END " + + "FROM CommentLikeJpaEntity cl " + + "WHERE cl.userJpaEntity.userId = :userId AND cl.commentJpaEntity.commentId = :commentId") + boolean existsByUserIdAndCommentId(@Param("userId") Long userId, @Param("commentId") Long commentId); @Modifying - @Query(value = "DELETE FROM comment_likes WHERE comment_id = :commentId", nativeQuery = true) - int deleteAllByCommentId(@Param("commentId") Long commentId); + @Query("DELETE FROM CommentLikeJpaEntity cl WHERE cl.commentJpaEntity.commentId = :commentId") + void deleteAllByCommentId(@Param("commentId") Long commentId); } \ No newline at end of file From eed90c728cd415ea4a9b1f45d038ac53748658e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 4 Aug 2025 18:33:57 +0900 Subject: [PATCH 46/52] =?UTF-8?q?[refactor]=20=EB=84=A4=EC=9D=B4=ED=8B=B0?= =?UTF-8?q?=EB=B8=8C=20=EC=BF=BC=EB=A6=AC=20jpql=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommentLikeQueryPersistenceAdapter.java | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentLikeQueryPersistenceAdapter.java b/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentLikeQueryPersistenceAdapter.java index 7dd52bd8a..ce173d259 100644 --- a/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentLikeQueryPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentLikeQueryPersistenceAdapter.java @@ -19,15 +19,6 @@ public class CommentLikeQueryPersistenceAdapter implements CommentLikeQueryPort @Override public boolean isLikedCommentByUser(Long userId, Long commentId) { - Object result = commentLikeJpaRepository.existsByUserIdAndCommentId(userId, commentId); - - // 테스트 환경 H2에서의 반환값 - if (result instanceof Boolean) { - return (Boolean) result; - } // 로컬, 배포 환경 Mysql에서의 반환값 - else if (result instanceof Number) { - return ((Number) result).longValue() > 0; - } - return false; + return commentLikeJpaRepository.existsByUserIdAndCommentId(userId, commentId); } } From 2896725915deb6abfd531b18efd3a0d0dc21df9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 4 Aug 2025 18:34:39 +0900 Subject: [PATCH 47/52] =?UTF-8?q?[rename]=20=ED=97=AC=ED=8D=BC=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EB=B0=8F=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CommentCreateService.java | 8 ++++---- .../service/CommentDeleteService.java | 18 ++++++++++-------- .../service/CommentLikeService.java | 6 +++--- ...{PostQueryService.java => PostHandler.java} | 6 +++--- 4 files changed, 20 insertions(+), 18 deletions(-) rename src/main/java/konkuk/thip/common/post/service/{PostQueryService.java => PostHandler.java} (93%) diff --git a/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java b/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java index 82bd52d1e..f779766a7 100644 --- a/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java +++ b/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java @@ -7,7 +7,7 @@ import konkuk.thip.comment.domain.Comment; import konkuk.thip.common.exception.InvalidStateException; import konkuk.thip.common.post.CommentCountUpdatable; -import konkuk.thip.common.post.service.PostQueryService; +import konkuk.thip.common.post.service.PostHandler; import konkuk.thip.common.post.PostType; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -22,7 +22,7 @@ public class CommentCreateService implements CommentCreateUseCase { private final CommentCommandPort commentCommandPort; - private final PostQueryService postQueryService; + private final PostHandler postHandler; private final CommentAuthorizationValidator commentAuthorizationValidator; @Override @@ -34,7 +34,7 @@ public Long createComment(CommentCreateCommand command) { PostType type = PostType.from(command.postType()); // 2. 게시물 타입에 맞게 조회 - CommentCountUpdatable post = postQueryService.findPost(type, command.postId()); + CommentCountUpdatable post = postHandler.findPost(type, command.postId()); // 2-1. 게시글 타입에 따른 댓글 생성 권한 검증 commentAuthorizationValidator.validateUserCanAccessPostForComment(type, post, command.userId()); @@ -50,7 +50,7 @@ public Long createComment(CommentCreateCommand command) { // 4-1. 도메인 게시물 댓글 수 증가 post.increaseCommentCount(); // 4-2 Jpa엔티티 게시물 댓글 수 증가 - postQueryService.updatePost(type, post); + postHandler.updatePost(type, post); return commentId; } diff --git a/src/main/java/konkuk/thip/comment/application/service/CommentDeleteService.java b/src/main/java/konkuk/thip/comment/application/service/CommentDeleteService.java index 2de272ee8..410f80d57 100644 --- a/src/main/java/konkuk/thip/comment/application/service/CommentDeleteService.java +++ b/src/main/java/konkuk/thip/comment/application/service/CommentDeleteService.java @@ -7,7 +7,7 @@ import konkuk.thip.comment.application.service.validator.CommentAuthorizationValidator; import konkuk.thip.comment.domain.Comment; import konkuk.thip.common.post.CommentCountUpdatable; -import konkuk.thip.common.post.service.PostQueryService; +import konkuk.thip.common.post.service.PostHandler; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -18,22 +18,23 @@ public class CommentDeleteService implements CommentDeleteUseCase { private final CommentCommandPort commentCommandPort; private final CommentLikeCommandPort commentLikeCommandPort; - private final PostQueryService postQueryService; + private final PostHandler postHandler; private final CommentAuthorizationValidator commentAuthorizationValidator; @Override @Transactional - public void deleteComment(Long commentId, Long userId) { + public Long deleteComment(Long commentId, Long userId) { // 1. 댓글 조회 및 권한 검증 Comment comment = commentCommandPort.getByIdOrThrow(commentId); // 1-1. 게시글 타입에 따른 댓글 삭제 권한 검증 - CommentCountUpdatable post = postQueryService.findPost(comment.getPostType(), comment.getTargetPostId()); + CommentCountUpdatable post = postHandler.findPost(comment.getPostType(), comment.getTargetPostId()); commentAuthorizationValidator.validateUserCanAccessPostForComment(comment.getPostType(), post, userId); - // 2. 댓글 Soft Delete 처리 - comment.softDelete(userId); - commentCommandPort.update(comment); + // 2. 댓글 삭제 권한 검증 및 소프트 딜리트 + comment.validateDeletable(userId); + commentCommandPort.delete(comment); + //TODO 게시물의 댓글 수 증가/감소 동시성 제어 로직 추가해야됨 @@ -41,10 +42,11 @@ public void deleteComment(Long commentId, Long userId) { // 3-1. 도메인 게시물 댓글 수 감소 post.decreaseCommentCount(); // 3-2 Jpa엔티티 게시물 댓글 수 감소 - postQueryService.updatePost(comment.getPostType(), post); + postHandler.updatePost(comment.getPostType(), post); // 4. 댓글 좋아요 삭제 commentLikeCommandPort.deleteAllByCommentId(commentId); + return post.getId(); } } diff --git a/src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java b/src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java index 69533ce9c..cf601a84f 100644 --- a/src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java +++ b/src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java @@ -10,7 +10,7 @@ import konkuk.thip.comment.application.service.validator.CommentAuthorizationValidator; import konkuk.thip.comment.domain.Comment; import konkuk.thip.common.post.CommentCountUpdatable; -import konkuk.thip.common.post.service.PostQueryService; +import konkuk.thip.common.post.service.PostHandler; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -22,7 +22,7 @@ public class CommentLikeService implements CommentLikeUseCase { private final CommentLikeQueryPort commentLikeQueryPort; private final CommentLikeCommandPort commentLikeCommandPort; - private final PostQueryService postQueryService; + private final PostHandler postHandler; private final CommentAuthorizationValidator commentAuthorizationValidator; @Override @@ -32,7 +32,7 @@ public CommentIsLikeResult changeLikeStatusComment(CommentIsLikeCommand command) // 1. 댓글 조회 및 검증 (존재 여부) Comment comment = commentCommandPort.getByIdOrThrow(command.commentId()); // 1-1. 게시글 타입에 따른 댓글 좋아요 권한 검증 - CommentCountUpdatable post = postQueryService.findPost(comment.getPostType(), comment.getTargetPostId()); + CommentCountUpdatable post = postHandler.findPost(comment.getPostType(), comment.getTargetPostId()); commentAuthorizationValidator.validateUserCanAccessPostForComment(comment.getPostType(), post, command.userId()); // 2. 유저가 해당 댓글에 대해 좋아요 했는지 조회 diff --git a/src/main/java/konkuk/thip/common/post/service/PostQueryService.java b/src/main/java/konkuk/thip/common/post/service/PostHandler.java similarity index 93% rename from src/main/java/konkuk/thip/common/post/service/PostQueryService.java rename to src/main/java/konkuk/thip/common/post/service/PostHandler.java index ee6956554..06428b418 100644 --- a/src/main/java/konkuk/thip/common/post/service/PostQueryService.java +++ b/src/main/java/konkuk/thip/common/post/service/PostHandler.java @@ -1,5 +1,6 @@ package konkuk.thip.common.post.service; +import konkuk.thip.common.annotation.HelperService; import konkuk.thip.common.post.CommentCountUpdatable; import konkuk.thip.common.post.PostType; import konkuk.thip.feed.application.port.out.FeedCommandPort; @@ -9,11 +10,10 @@ import konkuk.thip.vote.application.port.out.VoteCommandPort; import konkuk.thip.vote.domain.Vote; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -@Service +@HelperService @RequiredArgsConstructor -public class PostQueryService { +public class PostHandler { private final FeedCommandPort feedCommandPort; private final RecordCommandPort recordCommandPort; From 7fcdb6b8f0365606a9d86f3483bf42a7931c9de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 4 Aug 2025 18:34:55 +0900 Subject: [PATCH 48/52] =?UTF-8?q?[refactor]=20=EC=95=88=EC=93=B0=EB=8A=94?= =?UTF-8?q?=20=EC=97=90=EB=9F=AC=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/common/exception/code/ErrorCode.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java index b21ca541a..029e85f65 100644 --- a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java +++ b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java @@ -176,7 +176,6 @@ public enum ErrorCode implements ResponseCode { COMMENT_ALREADY_LIKED(HttpStatus.BAD_REQUEST, 190003, "사용자가 이미 좋아요한 댓글입니다."), COMMENT_NOT_LIKED_CANNOT_CANCEL(HttpStatus.BAD_REQUEST, 190004, "사용자가 좋아요하지 않은 댓글은 좋아요 취소 할 수 없습니다."), COMMENT_DELETE_FORBIDDEN(HttpStatus.FORBIDDEN, 190005, "댓글 삭제 권한이 없습니다."), - COMMENT_ALREADY_DELETED(HttpStatus.BAD_REQUEST, 190006, "이미 삭제된 댓글 입니다."), COMMENT_COUNT_UNDERFLOW(HttpStatus.BAD_REQUEST, 190007, "댓글 수는 0 이하로 감소할 수 없습니다."), ; From b6af8aea1a2f581431fa8cbe24572290dc7476a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 4 Aug 2025 18:35:00 +0900 Subject: [PATCH 49/52] =?UTF-8?q?[refactor]=20=EC=95=88=EC=93=B0=EB=8A=94?= =?UTF-8?q?=20=EC=97=90=EB=9F=AC=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/common/swagger/SwaggerResponseDescription.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java b/src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java index 831a2f4ac..1cdb32032 100644 --- a/src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java +++ b/src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java @@ -201,7 +201,6 @@ public enum SwaggerResponseDescription { RECORD_NOT_FOUND, VOTE_NOT_FOUND, COMMENT_DELETE_FORBIDDEN, - COMMENT_ALREADY_DELETED, COMMENT_COUNT_UNDERFLOW, FEED_ACCESS_FORBIDDEN, ROOM_ACCESS_FORBIDDEN From 2d8ed709ff350c337c86432bf07938ec7d74ac85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 4 Aug 2025 18:35:23 +0900 Subject: [PATCH 50/52] =?UTF-8?q?[test]=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/CommentDeleteAPITest.java | 12 +++---- .../thip/comment/domain/CommentTest.java | 36 ------------------- 2 files changed, 4 insertions(+), 44 deletions(-) diff --git a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentDeleteAPITest.java b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentDeleteAPITest.java index 70c12c643..2d1d75468 100644 --- a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentDeleteAPITest.java +++ b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentDeleteAPITest.java @@ -32,7 +32,6 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.annotation.Transactional; -import static konkuk.thip.common.entity.StatusType.INACTIVE; import static konkuk.thip.common.post.PostType.*; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; @@ -100,8 +99,7 @@ void deleteRootComment_success() throws Exception { .andExpect(status().isOk()); // then - CommentJpaEntity found = commentJpaRepository.findById(commentId).get(); - assertThat(found.getStatus()).isEqualTo(INACTIVE); + assertThat(commentJpaRepository.findById(commentId).isEmpty()); } @Test @@ -121,8 +119,7 @@ void deleteReplyComment_success() throws Exception { .andExpect(status().isOk()); // then - CommentJpaEntity found = commentJpaRepository.findById(replyId).get(); - assertThat(found.getStatus()).isEqualTo(INACTIVE); + assertThat(commentJpaRepository.findById(replyId)).isEmpty(); } @Test @@ -143,15 +140,14 @@ void deleteComment_success() throws Exception { .andExpect(status().isOk()); // then - CommentJpaEntity found = commentJpaRepository.findById(commentId).get(); - assertThat(found.getStatus()).isEqualTo(INACTIVE); + assertThat(commentJpaRepository.findById(commentId).isEmpty()); // Feed 댓글수 감소 확인 FeedJpaEntity updatedFeed = feedJpaRepository.findById(feed.getPostId()).get(); assertThat(updatedFeed.getCommentCount()).isEqualTo(beforeCount - 1); // 댓글 좋아요가 모두 삭제됐는지 확인 - boolean like = (boolean) commentLikeJpaRepository.existsByUserIdAndCommentId(user.getUserId(), commentId); + boolean like = commentLikeJpaRepository.existsByUserIdAndCommentId(user.getUserId(), commentId); assertThat(like).isFalse(); } diff --git a/src/test/java/konkuk/thip/comment/domain/CommentTest.java b/src/test/java/konkuk/thip/comment/domain/CommentTest.java index 0fa17c2e7..99425f8e7 100644 --- a/src/test/java/konkuk/thip/comment/domain/CommentTest.java +++ b/src/test/java/konkuk/thip/comment/domain/CommentTest.java @@ -193,40 +193,4 @@ void updateLikeCount_likeFalse_underflow_throws() { assertEquals(COMMENT_LIKE_COUNT_UNDERFLOW, ex.getErrorCode()); } - - @Test - @DisplayName("softDelete: 정상적인 작성자가 호출하면 상태가 INACTIVE로 변경된다") - void softDelete_byCreator_changesStatusToInactive() { - Comment comment = createParentComment(POST_ID); - - comment.softDelete(CREATOR_ID); - - assertEquals(INACTIVE, comment.getStatus()); - } - - @Test - @DisplayName("softDelete: 작성자가 아니면 InvalidStateException 예외가 발생한다") - void softDelete_byNonCreator_throwsInvalidStateException() { - Comment comment = createParentComment(POST_ID); - - InvalidStateException ex = assertThrows(InvalidStateException.class, () -> { - comment.softDelete(OTHER_USER_ID); - }); - - assertEquals(COMMENT_DELETE_FORBIDDEN, ex.getErrorCode()); - } - - @Test - @DisplayName("softDelete: 이미 삭제된 댓글에 다시 호출하면 InvalidStateException 예외가 발생한다") - void softDelete_alreadyInactive_throwsInvalidStateException() { - - Comment comment = createInactiveComment(POST_ID); - - InvalidStateException ex = assertThrows(InvalidStateException.class, () -> { - comment.softDelete(CREATOR_ID); - }); - - assertEquals(COMMENT_ALREADY_DELETED, ex.getErrorCode()); - } - } From 4f50afe33c2e80728df0461cdad11d8ff0a70369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 4 Aug 2025 23:04:07 +0900 Subject: [PATCH 51/52] =?UTF-8?q?[refactor]=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=82=AD=EC=A0=9C=20=EB=A8=BC?= =?UTF-8?q?=EC=A0=80=20=ED=9B=84=20=EB=8C=93=EA=B8=80=20=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/out/jpa/CommentLikeJpaEntity.java | 2 +- .../application/service/CommentDeleteService.java | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/konkuk/thip/comment/adapter/out/jpa/CommentLikeJpaEntity.java b/src/main/java/konkuk/thip/comment/adapter/out/jpa/CommentLikeJpaEntity.java index 880aa231d..15f46203c 100644 --- a/src/main/java/konkuk/thip/comment/adapter/out/jpa/CommentLikeJpaEntity.java +++ b/src/main/java/konkuk/thip/comment/adapter/out/jpa/CommentLikeJpaEntity.java @@ -22,7 +22,7 @@ public class CommentLikeJpaEntity extends BaseJpaEntity { @JoinColumn(name = "user_id") private UserJpaEntity userJpaEntity; - @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "comment_id") private CommentJpaEntity commentJpaEntity; } \ No newline at end of file diff --git a/src/main/java/konkuk/thip/comment/application/service/CommentDeleteService.java b/src/main/java/konkuk/thip/comment/application/service/CommentDeleteService.java index 410f80d57..c85e522e7 100644 --- a/src/main/java/konkuk/thip/comment/application/service/CommentDeleteService.java +++ b/src/main/java/konkuk/thip/comment/application/service/CommentDeleteService.java @@ -33,19 +33,20 @@ public Long deleteComment(Long commentId, Long userId) { // 2. 댓글 삭제 권한 검증 및 소프트 딜리트 comment.validateDeletable(userId); - commentCommandPort.delete(comment); + // 2-1. 댓글 좋아요 삭제 + commentLikeCommandPort.deleteAllByCommentId(commentId); + // 3. 댓글 삭제 + commentCommandPort.delete(comment); //TODO 게시물의 댓글 수 증가/감소 동시성 제어 로직 추가해야됨 - // 3. 게시글 댓글 수 감소 - // 3-1. 도메인 게시물 댓글 수 감소 + // 4. 게시글 댓글 수 감소 + // 4-1. 도메인 게시물 댓글 수 감소 post.decreaseCommentCount(); - // 3-2 Jpa엔티티 게시물 댓글 수 감소 + // 4-2 Jpa엔티티 게시물 댓글 수 감소 postHandler.updatePost(comment.getPostType(), post); - // 4. 댓글 좋아요 삭제 - commentLikeCommandPort.deleteAllByCommentId(commentId); return post.getId(); } From a59f1d16ae255213bd7e8e9f2b4e0f3ca10b2806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 4 Aug 2025 23:05:12 +0900 Subject: [PATCH 52/52] =?UTF-8?q?[test]=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/CommentDeleteAPITest.java | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentDeleteAPITest.java b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentDeleteAPITest.java index 2d1d75468..358b92b01 100644 --- a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentDeleteAPITest.java +++ b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentDeleteAPITest.java @@ -22,6 +22,7 @@ import konkuk.thip.user.adapter.out.persistence.repository.alias.AliasJpaRepository; import konkuk.thip.vote.adapter.out.jpa.VoteJpaEntity; import konkuk.thip.vote.adapter.out.persistence.repository.VoteJpaRepository; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -30,8 +31,8 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.transaction.annotation.Transactional; +import static konkuk.thip.common.entity.StatusType.INACTIVE; import static konkuk.thip.common.post.PostType.*; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; @@ -40,7 +41,6 @@ @SpringBootTest @ActiveProfiles("test") @AutoConfigureMockMvc(addFilters = false) -@Transactional @DisplayName("[통합] 댓글 삭제 api 통합 테스트") class CommentDeleteAPITest { @@ -59,8 +59,6 @@ class CommentDeleteAPITest { @Autowired private RoomParticipantJpaRepository roomParticipantJpaRepository; @Autowired private CommentLikeJpaRepository commentLikeJpaRepository; - - private AliasJpaEntity alias; private UserJpaEntity user; private CategoryJpaEntity category; @@ -83,6 +81,20 @@ record = recordJpaRepository.save(TestEntityFactory.createRecord(user,room)); roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room, user, RoomParticipantRole.HOST, 0.0)); } + @AfterEach + void tearDown() { + recordJpaRepository.deleteAllInBatch(); + voteJpaRepository.deleteAllInBatch(); + commentLikeJpaRepository.deleteAll(); + commentJpaRepository.deleteAllInBatch(); + feedJpaRepository.deleteAllInBatch(); + roomParticipantJpaRepository.deleteAllInBatch(); + roomJpaRepository.deleteAllInBatch(); + bookJpaRepository.deleteAll(); + userJpaRepository.deleteAllInBatch(); + categoryJpaRepository.deleteAll(); + aliasJpaRepository.deleteAll(); + } @Test @DisplayName("루트댓글을 삭제하면 [soft delete 처리]된다") @@ -91,6 +103,7 @@ void deleteRootComment_success() throws Exception { // given CommentJpaEntity comment = commentJpaRepository.save(TestEntityFactory.createComment(feed, user, FEED)); feed.updateCommentCount(1); + feedJpaRepository.save(feed); Long commentId = comment.getCommentId(); // when @@ -99,7 +112,8 @@ void deleteRootComment_success() throws Exception { .andExpect(status().isOk()); // then - assertThat(commentJpaRepository.findById(commentId).isEmpty()); + assertThat(commentJpaRepository.findById(commentId)).isPresent(); + assertThat(commentJpaRepository.findById(commentId).get().getStatus()).isEqualTo(INACTIVE); } @Test @@ -110,16 +124,18 @@ void deleteReplyComment_success() throws Exception { CommentJpaEntity parent = commentJpaRepository.save(TestEntityFactory.createComment(feed, user, FEED)); CommentJpaEntity reply = commentJpaRepository.save(TestEntityFactory.createReplyComment(feed, user, FEED, parent)); feed.updateCommentCount(2); + feedJpaRepository.save(feed); Long replyId = reply.getCommentId(); - // when mockMvc.perform(delete("/comments/{commentId}", replyId) .requestAttr("userId", user.getUserId())) .andExpect(status().isOk()); // then - assertThat(commentJpaRepository.findById(replyId)).isEmpty(); + assertThat(commentJpaRepository.findById(replyId)).isPresent(); + assertThat(commentJpaRepository.findById(replyId).get().getStatus()).isEqualTo(INACTIVE); + } @Test @@ -131,6 +147,7 @@ void deleteComment_success() throws Exception { CommentJpaEntity comment = commentJpaRepository.save(TestEntityFactory.createComment(feed, user, FEED)); commentLikeJpaRepository.save(TestEntityFactory.createCommentLike(comment, user)); feed.updateCommentCount(1); + feedJpaRepository.save(feed); Long commentId = comment.getCommentId(); int beforeCount = feed.getCommentCount(); @@ -140,7 +157,8 @@ void deleteComment_success() throws Exception { .andExpect(status().isOk()); // then - assertThat(commentJpaRepository.findById(commentId).isEmpty()); + assertThat(commentJpaRepository.findById(commentId)).isPresent(); + assertThat(commentJpaRepository.findById(commentId).get().getStatus()).isEqualTo(INACTIVE); // Feed 댓글수 감소 확인 FeedJpaEntity updatedFeed = feedJpaRepository.findById(feed.getPostId()).get(); @@ -151,5 +169,4 @@ void deleteComment_success() throws Exception { assertThat(like).isFalse(); } - }