From 7bdfd23cf6f1217b4a611e89516c89019b0b4282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 01:44:44 +0900 Subject: [PATCH 01/82] =?UTF-8?q?[remove]=20=EB=8D=94=EB=AF=B8=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=82=AD=EC=A0=9C(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/adapter/in/web/request/DummyRequest.java | 7 ------- .../adapter/in/web/response/DummyResponse.java | 7 ------- .../comment/application/port/in/DummyUseCase.java | 5 ----- .../comment/application/port/in/dto/DummyCommand.java | 10 ---------- .../comment/application/service/CommentService.java | 11 ----------- 5 files changed, 40 deletions(-) delete mode 100644 src/main/java/konkuk/thip/comment/adapter/in/web/request/DummyRequest.java delete mode 100644 src/main/java/konkuk/thip/comment/adapter/in/web/response/DummyResponse.java delete mode 100644 src/main/java/konkuk/thip/comment/application/port/in/DummyUseCase.java delete mode 100644 src/main/java/konkuk/thip/comment/application/port/in/dto/DummyCommand.java delete mode 100644 src/main/java/konkuk/thip/comment/application/service/CommentService.java diff --git a/src/main/java/konkuk/thip/comment/adapter/in/web/request/DummyRequest.java b/src/main/java/konkuk/thip/comment/adapter/in/web/request/DummyRequest.java deleted file mode 100644 index 5721af3a3..000000000 --- a/src/main/java/konkuk/thip/comment/adapter/in/web/request/DummyRequest.java +++ /dev/null @@ -1,7 +0,0 @@ -package konkuk.thip.comment.adapter.in.web.request; - -import lombok.Getter; - -@Getter -public class DummyRequest { -} diff --git a/src/main/java/konkuk/thip/comment/adapter/in/web/response/DummyResponse.java b/src/main/java/konkuk/thip/comment/adapter/in/web/response/DummyResponse.java deleted file mode 100644 index 174514f5c..000000000 --- a/src/main/java/konkuk/thip/comment/adapter/in/web/response/DummyResponse.java +++ /dev/null @@ -1,7 +0,0 @@ -package konkuk.thip.comment.adapter.in.web.response; - -import lombok.Getter; - -@Getter -public class DummyResponse { -} diff --git a/src/main/java/konkuk/thip/comment/application/port/in/DummyUseCase.java b/src/main/java/konkuk/thip/comment/application/port/in/DummyUseCase.java deleted file mode 100644 index 69ed901ce..000000000 --- a/src/main/java/konkuk/thip/comment/application/port/in/DummyUseCase.java +++ /dev/null @@ -1,5 +0,0 @@ -package konkuk.thip.comment.application.port.in; - -public interface DummyUseCase { - -} diff --git a/src/main/java/konkuk/thip/comment/application/port/in/dto/DummyCommand.java b/src/main/java/konkuk/thip/comment/application/port/in/dto/DummyCommand.java deleted file mode 100644 index 33c9022fa..000000000 --- a/src/main/java/konkuk/thip/comment/application/port/in/dto/DummyCommand.java +++ /dev/null @@ -1,10 +0,0 @@ -package konkuk.thip.comment.application.port.in.dto; - -import lombok.Builder; -import lombok.Getter; - -@Builder -@Getter -public class DummyCommand { - -} diff --git a/src/main/java/konkuk/thip/comment/application/service/CommentService.java b/src/main/java/konkuk/thip/comment/application/service/CommentService.java deleted file mode 100644 index 114b89ded..000000000 --- a/src/main/java/konkuk/thip/comment/application/service/CommentService.java +++ /dev/null @@ -1,11 +0,0 @@ -package konkuk.thip.comment.application.service; - -import konkuk.thip.comment.application.port.in.DummyUseCase; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class CommentService implements DummyUseCase { - -} From 18fdd7f59367bdcfa3f904925457bfbf23f385e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 01:46:39 +0900 Subject: [PATCH 02/82] =?UTF-8?q?[feat]=20Comment=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EC=83=9D=EC=84=B1=20=EC=B1=85=EC=9E=84=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/comment/domain/Comment.java | 78 ++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/comment/domain/Comment.java b/src/main/java/konkuk/thip/comment/domain/Comment.java index 16d17211f..f5134e766 100644 --- a/src/main/java/konkuk/thip/comment/domain/Comment.java +++ b/src/main/java/konkuk/thip/comment/domain/Comment.java @@ -1,9 +1,14 @@ package konkuk.thip.comment.domain; import konkuk.thip.common.entity.BaseDomainEntity; +import konkuk.thip.common.exception.InvalidStateException; +import konkuk.thip.common.post.PostType; +import lombok.Builder; import lombok.Getter; import lombok.experimental.SuperBuilder; +import static konkuk.thip.common.exception.code.ErrorCode.INVALID_COMMENT_CREATE; + @Getter @SuperBuilder public class Comment extends BaseDomainEntity { @@ -12,7 +17,11 @@ public class Comment extends BaseDomainEntity { private String content; - private int reportCount; + @Builder.Default + private Integer reportCount = 0; + + @Builder.Default + private Integer likeCount = 0; private Long targetPostId; @@ -20,4 +29,71 @@ public class Comment extends BaseDomainEntity { private Long parentCommentId; + private PostType postType; + + public static Comment createComment(String content, Long postId, Long creatorId, String type, + boolean isReplyRequest, Long parentId, Comment parent) { + + // 댓글/답글 생성 검증 + validateCommentCreate(isReplyRequest,parentId); + PostType postType = PostType.from(type); + + if (isReplyRequest) { + // 답글 생성 검증 + validateReplyCommentCreate(postId, parent); + return withoutIdReplyComment(content, postId, creatorId, parent, postType); + } + return withoutIdRootComment(content, postId, creatorId, postType); + } + + + private static Comment withoutIdRootComment(String content, Long targetPostId, Long creatorId, PostType postType) { + return Comment.builder() + .id(null) + .content(content) + .targetPostId(targetPostId) + .creatorId(creatorId) + .parentCommentId(null) + .postType(postType) + .reportCount(0) + .likeCount(0) + .build(); + } + + private static Comment withoutIdReplyComment(String content, Long targetPostId, Long creatorId, Comment parentComment, PostType postType) { + return Comment.builder() + .id(null) + .content(content) + .targetPostId(targetPostId) + .creatorId(creatorId) + .parentCommentId(parentComment.getId()) + .postType(postType) + .reportCount(0) + .likeCount(0) + .build(); + } + + private static void validateReplyCommentCreate(Long targetPostId, Comment parentComment) { + if (parentComment == null) { + throw new InvalidStateException( + INVALID_COMMENT_CREATE,new IllegalArgumentException("parentId에 해당하는 부모 댓글이 존재해야 합니다.")); + } + if (!targetPostId.equals(parentComment.getTargetPostId())) { + throw new InvalidStateException( + INVALID_COMMENT_CREATE,new IllegalArgumentException("댓글과 부모 댓글의 게시글이 일치하지 않습니다.")); + } + } + + public static void validateCommentCreate(boolean isReplyRequest, Long parentId) { + if (isReplyRequest && parentId == null) { + throw new InvalidStateException( + INVALID_COMMENT_CREATE, new IllegalArgumentException("답글 작성 시 parentId는 필수입니다.")); + + } + if (!isReplyRequest && parentId != null) { + throw new InvalidStateException( + INVALID_COMMENT_CREATE, new IllegalArgumentException("일반 댓글에는 parentId가 없어야 합니다.")); + } + } + } From e07ccf68fcfb2836dd6e5756174ea484bfdc7685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 01:46:57 +0900 Subject: [PATCH 03/82] =?UTF-8?q?[feat]=20CommentCommandController.createC?= =?UTF-8?q?omment=20=EC=9E=91=EC=84=B1=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/CommentCommandController.java | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) 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 8c11daf93..4a7058bea 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 @@ -1,10 +1,31 @@ package konkuk.thip.comment.adapter.in.web; +import jakarta.validation.Valid; +import konkuk.thip.comment.adapter.in.web.request.CommentCreateRequest; +import konkuk.thip.comment.adapter.in.web.response.CommentIdResponse; +import konkuk.thip.comment.application.port.in.CommentCreateUseCase; +import konkuk.thip.common.dto.BaseResponse; +import konkuk.thip.common.security.annotation.UserId; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor public class CommentCommandController { + private final CommentCreateUseCase commentCreateUseCase; + + + /** + * 댓글/답글 작성 + * parentId:{Long},isReplyRequest:true 답글 + * parentId:null,isReplyRequest:false 댓글 + */ + @PostMapping("/comments/{postId}") + public BaseResponse createComment(@RequestBody @Valid final CommentCreateRequest request, + @PathVariable("postId") final Long postId, + @UserId final Long userId) { + return BaseResponse.ok(CommentIdResponse.of(commentCreateUseCase.createComment(request.toCommand(userId,postId)))); + } + } From e83274342a80048b206c9175b3269a6d6c082188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 01:47:30 +0900 Subject: [PATCH 04/82] =?UTF-8?q?[feat]=20CommentCommandPersistenceAdapter?= =?UTF-8?q?.save=20/=20findById=20=20=EC=9E=91=EC=84=B1=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommentCommandPersistenceAdapter.java | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) 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 e7e42477d..e824a01ab 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 @@ -1,12 +1,74 @@ package konkuk.thip.comment.adapter.out.persistence; +import konkuk.thip.comment.adapter.out.jpa.CommentJpaEntity; +import konkuk.thip.comment.adapter.out.mapper.CommentMapper; +import konkuk.thip.comment.adapter.out.persistence.repository.CommentJpaRepository; import konkuk.thip.comment.application.port.out.CommentCommandPort; +import konkuk.thip.comment.domain.Comment; +import konkuk.thip.common.exception.EntityNotFoundException; +import konkuk.thip.common.post.PostType; +import konkuk.thip.feed.adapter.out.persistence.repository.FeedJpaRepository; +import konkuk.thip.post.adapter.out.jpa.PostJpaEntity; +import konkuk.thip.record.adapter.out.persistence.repository.RecordJpaRepository; +import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; +import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; +import konkuk.thip.vote.adapter.out.persistence.repository.VoteJpaRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; +import java.util.Optional; + +import static konkuk.thip.common.exception.code.ErrorCode.*; + @Repository @RequiredArgsConstructor public class CommentCommandPersistenceAdapter implements CommentCommandPort { + private final CommentJpaRepository commentJpaRepository; + private final FeedJpaRepository feedJpaRepository; + private final RecordJpaRepository recordJpaRepository; + private final VoteJpaRepository voteJpaRepository; + private final UserJpaRepository userJpaRepository; + private final CommentMapper commentMapper; + + @Override + public Long save(Comment comment) { + + // 1. 작성자(User) 조회 및 존재 검증 + UserJpaEntity userJpaEntity = userJpaRepository.findById(comment.getCreatorId()).orElseThrow( + () -> new EntityNotFoundException(USER_NOT_FOUND) + ); + + // 2. 게시물(Post) 조회 및 존재 검증 + PostJpaEntity postJpaEntity = findPostJpaEntity(comment.getPostType(), comment.getTargetPostId()); + + // 3. 부모 댓글 조회 (있을 경우) + CommentJpaEntity parentCommentJpaEntity = null; + if (comment.getParentCommentId() != null) { + parentCommentJpaEntity = commentJpaRepository.findById(comment.getParentCommentId()) + .orElseThrow(() -> new EntityNotFoundException(COMMENT_NOT_FOUND)); + } + + return commentJpaRepository.save( + commentMapper.toJpaEntity(comment, postJpaEntity, userJpaEntity,parentCommentJpaEntity) + ).getCommentId(); + } + + private PostJpaEntity findPostJpaEntity(PostType postType, Long postId) { + return switch (postType) { + case FEED -> feedJpaRepository.findById(postId) + .orElseThrow(() -> new EntityNotFoundException(FEED_NOT_FOUND)); + case RECORD -> recordJpaRepository.findById(postId) + .orElseThrow(() -> new EntityNotFoundException(RECORD_NOT_FOUND)); + case VOTE -> voteJpaRepository.findById(postId) + .orElseThrow(() -> new EntityNotFoundException(VOTE_NOT_FOUND)); + }; + } + + @Override + public Optional findById(Long id) { + return commentJpaRepository.findById(id) + .map(commentMapper::toDomainEntity); + } } From 5e482df2bcfb69b061fa45cb4dfe95e0ed0587d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 01:47:45 +0900 Subject: [PATCH 05/82] =?UTF-8?q?[feat]=20CommentCommandPort=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/port/out/CommentCommandPort.java | 16 ++++++++++++++++ 1 file changed, 16 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 9293a61d0..a6e2797c0 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 @@ -1,6 +1,22 @@ package konkuk.thip.comment.application.port.out; +import konkuk.thip.comment.domain.Comment; +import konkuk.thip.common.exception.EntityNotFoundException; + +import java.util.Optional; + +import static konkuk.thip.common.exception.code.ErrorCode.COMMENT_NOT_FOUND; + public interface CommentCommandPort { + Long save(Comment comment); + + Optional findById(Long id); + + default Comment getByIdOrThrow(Long id) { + return findById(id) + .orElseThrow(() -> new EntityNotFoundException(COMMENT_NOT_FOUND)); + } + } From 3b9b5d5fb655676540ceae9924dedae75eb6e04a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 01:48:01 +0900 Subject: [PATCH 06/82] =?UTF-8?q?[feat]=20CommentCountUpdatable=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/konkuk/thip/common/post/CommentCountUpdatable.java | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/main/java/konkuk/thip/common/post/CommentCountUpdatable.java diff --git a/src/main/java/konkuk/thip/common/post/CommentCountUpdatable.java b/src/main/java/konkuk/thip/common/post/CommentCountUpdatable.java new file mode 100644 index 000000000..3ac397005 --- /dev/null +++ b/src/main/java/konkuk/thip/common/post/CommentCountUpdatable.java @@ -0,0 +1,5 @@ +package konkuk.thip.common.post; + +public interface CommentCountUpdatable { + void increaseCommentCount(); +} \ No newline at end of file From a264cda2696002b3ca099cd404604260538ef7a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 01:48:19 +0900 Subject: [PATCH 07/82] =?UTF-8?q?[feat]=20=EB=8C=93=EA=B8=80=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20Command=20dto=20=EC=9E=91=EC=84=B1=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../port/in/dto/CommentCreateCommand.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/main/java/konkuk/thip/comment/application/port/in/dto/CommentCreateCommand.java diff --git a/src/main/java/konkuk/thip/comment/application/port/in/dto/CommentCreateCommand.java b/src/main/java/konkuk/thip/comment/application/port/in/dto/CommentCreateCommand.java new file mode 100644 index 000000000..37df203c2 --- /dev/null +++ b/src/main/java/konkuk/thip/comment/application/port/in/dto/CommentCreateCommand.java @@ -0,0 +1,18 @@ +package konkuk.thip.comment.application.port.in.dto; + +public record CommentCreateCommand( + + String content, + + Boolean isReplyRequest, + + Long parentId, + + String postType, + + Long postId, + + Long userId +) +{ +} From 3a55c363441d317fb357e015445df4e00cfce7d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 01:53:12 +0900 Subject: [PATCH 08/82] =?UTF-8?q?[feat]=20=EB=8C=93=EA=B8=80=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20request=20dto=20=EC=9E=91=EC=84=B1=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/request/CommentCreateRequest.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/main/java/konkuk/thip/comment/adapter/in/web/request/CommentCreateRequest.java diff --git a/src/main/java/konkuk/thip/comment/adapter/in/web/request/CommentCreateRequest.java b/src/main/java/konkuk/thip/comment/adapter/in/web/request/CommentCreateRequest.java new file mode 100644 index 000000000..dbc5abc6f --- /dev/null +++ b/src/main/java/konkuk/thip/comment/adapter/in/web/request/CommentCreateRequest.java @@ -0,0 +1,31 @@ +package konkuk.thip.comment.adapter.in.web.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import konkuk.thip.comment.application.port.in.dto.CommentCreateCommand; + +public record CommentCreateRequest( + + @NotBlank(message = "댓글 내용은 필수입니다.") + String content, + + @NotNull(message = "답글 여부는 필수입니다.") + Boolean isReplyRequest, + + Long parentId, + + @NotBlank(message = "게시물 타입은 필수입니다.") + String postType + +) { + public CommentCreateCommand toCommand(Long userId, Long postId) { + return new CommentCreateCommand( + content, + isReplyRequest, + parentId, + postType, + postId, + userId + ); + } +} From 81ff6ca382b5ee5b852a8456b36583d80c261ed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 01:53:38 +0900 Subject: [PATCH 09/82] =?UTF-8?q?[feat]=20=EB=8C=93=EA=B8=80=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=9C=A0=EC=A6=88=EC=BC=80=EC=9D=B4=EC=8A=A4=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=EC=B2=B4=20CommentCreateService=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CommentCreateService.java | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java diff --git a/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java b/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java new file mode 100644 index 000000000..d29c6c98f --- /dev/null +++ b/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java @@ -0,0 +1,97 @@ +package konkuk.thip.comment.application.service; + +import konkuk.thip.comment.application.port.in.CommentCreateUseCase; +import konkuk.thip.comment.application.port.in.dto.CommentCreateCommand; +import konkuk.thip.comment.application.port.out.CommentCommandPort; +import konkuk.thip.comment.domain.Comment; +import konkuk.thip.common.exception.InvalidStateException; +import konkuk.thip.common.post.CommentCountUpdatable; +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; + +import static konkuk.thip.common.exception.code.ErrorCode.INVALID_COMMENT_CREATE; + + +@Service +@RequiredArgsConstructor +public class CommentCreateService implements CommentCreateUseCase { + + private final CommentCommandPort commentCommandPort; + private final FeedCommandPort feedCommandPort; + private final RecordCommandPort recordCommandPort; + private final VoteCommandPort voteCommandPort; + + @Override + @Transactional + public Long createComment(CommentCreateCommand command) { + + // 1. 댓글/답글 생성 선행검증 및 작성하려는 게시글 타입 검증 + Comment.validateCommentCreate(command.isReplyRequest(), command.parentId()); + PostType type = PostType.from(command.postType()); + + // 2. 게시물 타입에 맞게 조회 + CommentCountUpdatable post = findPost(type, command.postId()); + + // TODO 피드: 내 게시글의 댓글, 내 댓글에 대한 답글 + // TODO 기록 및 투표: 모임방의 내 게시글에 대한 댓글, 내 댓글에 대한 답글 + + // 3. 댓글 생성 + Long commentId = createCommentDomain(command); + + // 4. 게시글 댓글 수 증가 + // 4-1. 도메인 게시물 댓글 수 증가 + post.increaseCommentCount(); + // 4-2 Jpa엔티티 게시물 댓글 수 증가 + updatePost(type, post); + + return commentId; + } + + private CommentCountUpdatable findPost(PostType type, Long postId) { + return switch (type) { + case FEED -> feedCommandPort.getByIdOrThrow(postId); + case RECORD -> recordCommandPort.getByIdOrThrow(postId); + case VOTE -> voteCommandPort.getByIdOrThrow(postId); + }; + } + + private Long createCommentDomain(CommentCreateCommand command) { + + // 3-1. (답글일 경우) 부모 댓글 조회 + Comment parentComment = null; + if (command.isReplyRequest()) { + parentComment = commentCommandPort.findById(command.parentId()).orElseThrow(() + -> new InvalidStateException(INVALID_COMMENT_CREATE, new IllegalArgumentException("parentId에 해당하는 부모 댓글이 존재해야 합니다."))); + } + + // 3-2. 도메인 댓글 생성 (유효성 검증 포함됨) + Comment comment = Comment.createComment( + command.content(), + command.postId(), + command.userId(), + command.postType(), + command.isReplyRequest(), + command.parentId(), + parentComment + ); + + 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 7c40758186d37312ac51216354be35cee65b6ced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 01:53:49 +0900 Subject: [PATCH 10/82] =?UTF-8?q?[feat]=20=EB=8C=93=EA=B8=80=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=9C=A0=EC=A6=88=EC=BC=80=EC=9D=B4=EC=8A=A4=20Com?= =?UTF-8?q?mentCreateUseCase=20=EC=9E=91=EC=84=B1=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/application/port/in/CommentCreateUseCase.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/java/konkuk/thip/comment/application/port/in/CommentCreateUseCase.java diff --git a/src/main/java/konkuk/thip/comment/application/port/in/CommentCreateUseCase.java b/src/main/java/konkuk/thip/comment/application/port/in/CommentCreateUseCase.java new file mode 100644 index 000000000..658d4713b --- /dev/null +++ b/src/main/java/konkuk/thip/comment/application/port/in/CommentCreateUseCase.java @@ -0,0 +1,7 @@ +package konkuk.thip.comment.application.port.in; + +import konkuk.thip.comment.application.port.in.dto.CommentCreateCommand; + +public interface CommentCreateUseCase { + Long createComment(CommentCreateCommand command); +} From c189a0fa87cc800e3f7269ceb71cd23f436cfb9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 01:54:01 +0900 Subject: [PATCH 11/82] =?UTF-8?q?[feat]=20CommentIdResponse=20dto=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/adapter/in/web/response/CommentIdResponse.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/java/konkuk/thip/comment/adapter/in/web/response/CommentIdResponse.java diff --git a/src/main/java/konkuk/thip/comment/adapter/in/web/response/CommentIdResponse.java b/src/main/java/konkuk/thip/comment/adapter/in/web/response/CommentIdResponse.java new file mode 100644 index 000000000..c774e8fa1 --- /dev/null +++ b/src/main/java/konkuk/thip/comment/adapter/in/web/response/CommentIdResponse.java @@ -0,0 +1,7 @@ +package konkuk.thip.comment.adapter.in.web.response; + +public record CommentIdResponse(Long commentId) { + public static CommentIdResponse of(Long commentId) { + return new CommentIdResponse(commentId); + } +} From 2fa454a14b41db6028d22f4fc4394cd2ce2b5d29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 01:54:44 +0900 Subject: [PATCH 12/82] =?UTF-8?q?[feat]=20CommentJpaEntity=EC=97=90=20post?= =?UTF-8?q?Type,likeCount=20=EC=B6=94=EA=B0=80=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/comment/adapter/out/jpa/CommentJpaEntity.java | 9 +++++++++ 1 file changed, 9 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 807e18556..e7a6bebd5 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 @@ -2,6 +2,7 @@ import jakarta.persistence.*; import konkuk.thip.common.entity.BaseJpaEntity; +import konkuk.thip.common.post.PostType; import konkuk.thip.post.adapter.out.jpa.PostJpaEntity; import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import lombok.*; @@ -26,10 +27,18 @@ public class CommentJpaEntity extends BaseJpaEntity { @Column(name = "report_count", nullable = false) private int reportCount = 0; + @Builder.Default + @Column(name = "like_count", nullable = false) + private int likeCount = 0; + + //TODO 상속구조 해지하면서 postType만 가질지, postId + postType가질지 논의 필요 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "post_id", nullable = false) private PostJpaEntity postJpaEntity; + @Column(name = "post_type", nullable = false, length = 10) + private PostType postType; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) private UserJpaEntity userJpaEntity; From 03fee5856e993dc26d3cb79b79fa319a20fb2488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 01:55:22 +0900 Subject: [PATCH 13/82] =?UTF-8?q?[feat]=20CommentMapper=20=EC=97=90=20like?= =?UTF-8?q?Count,postType=20=EB=A7=A4=ED=95=91=EC=B6=94=EA=B0=80=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/comment/adapter/out/mapper/CommentMapper.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/konkuk/thip/comment/adapter/out/mapper/CommentMapper.java b/src/main/java/konkuk/thip/comment/adapter/out/mapper/CommentMapper.java index d9ca08cc0..0f37523bc 100644 --- a/src/main/java/konkuk/thip/comment/adapter/out/mapper/CommentMapper.java +++ b/src/main/java/konkuk/thip/comment/adapter/out/mapper/CommentMapper.java @@ -12,8 +12,10 @@ public class CommentMapper { public CommentJpaEntity toJpaEntity(Comment comment, PostJpaEntity postJpaEntity, UserJpaEntity userJpaEntity, CommentJpaEntity commentJpaEntity) { return CommentJpaEntity.builder() .content(comment.getContent()) + .likeCount(comment.getLikeCount()) .reportCount(comment.getReportCount()) .postJpaEntity(postJpaEntity) + .postType(comment.getPostType()) .userJpaEntity(userJpaEntity) .parent(commentJpaEntity) .build(); @@ -24,7 +26,9 @@ public Comment toDomainEntity(CommentJpaEntity commentJpaEntity) { .id(commentJpaEntity.getCommentId()) .content(commentJpaEntity.getContent()) .reportCount(commentJpaEntity.getReportCount()) + .likeCount(commentJpaEntity.getLikeCount()) .targetPostId(commentJpaEntity.getPostJpaEntity().getPostId()) + .postType(commentJpaEntity.getPostType()) .creatorId(commentJpaEntity.getUserJpaEntity().getUserId()) .parentCommentId(commentJpaEntity.getParent() != null ? commentJpaEntity.getParent().getCommentId() : null) .createdAt(commentJpaEntity.getCreatedAt()) From 567c5374ce38b01d9edc3330a5eda84609846890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 01:55:39 +0900 Subject: [PATCH 14/82] =?UTF-8?q?[feat]=20=EA=B4=80=EB=A0=A8=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/common/exception/code/ErrorCode.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 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 0da903803..8032a9a2a 100644 --- a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java +++ b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java @@ -142,7 +142,20 @@ public enum ErrorCode implements ResponseCode { EMPTY_FILE_EXCEPTION(HttpStatus.BAD_REQUEST, 170001, "업로드하려는 이미지가 비어있습니다."), EXCEPTION_ON_IMAGE_UPLOAD(HttpStatus.BAD_REQUEST, 170002, "이미지 업로드에 실패하였습니다."), INVALID_FILE_EXTENSION(HttpStatus.BAD_REQUEST, 170003, "올바르지 않은 파일 형식입니다."), - IO_EXCEPTION_ON_IMAGE_DELETE(HttpStatus.BAD_REQUEST, 170004, "파일 삭제에 실패하였습니다.") + IO_EXCEPTION_ON_IMAGE_DELETE(HttpStatus.BAD_REQUEST, 170004, "파일 삭제에 실패하였습니다."), + + /** + * 180000 : Post error + */ + POST_TYPE_NOT_MATCH(HttpStatus.BAD_REQUEST, 180000, "일치하는 게시물 타입 이름이 없습니다. [feed, record, vote] 중 하나여야 합니다."), + + /** + * 190000 : Comment error + */ + COMMENT_NOT_FOUND(HttpStatus.NOT_FOUND, 190000, "존재하지 않는 COMMENT 입니다."), + INVALID_COMMENT_CREATE(HttpStatus.BAD_REQUEST, 190001, "유효하지 않은 COMMENT 생성 요청 입니다."), + + ; private final HttpStatus httpStatus; From d6cb099defdc27eb67ecde46485efc6dfb72e0fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 01:56:44 +0900 Subject: [PATCH 15/82] =?UTF-8?q?[feat]=20vote,record,=20feed=20CommentCou?= =?UTF-8?q?ntUpdatableincreaseCommentCount=EA=B3=B5=ED=86=B5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/feed/domain/Feed.java | 7 ++++++- src/main/java/konkuk/thip/record/domain/Record.java | 8 +++++++- src/main/java/konkuk/thip/vote/domain/Vote.java | 8 +++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/main/java/konkuk/thip/feed/domain/Feed.java b/src/main/java/konkuk/thip/feed/domain/Feed.java index eb0ab0447..c91c9e473 100644 --- a/src/main/java/konkuk/thip/feed/domain/Feed.java +++ b/src/main/java/konkuk/thip/feed/domain/Feed.java @@ -2,6 +2,7 @@ import konkuk.thip.common.entity.BaseDomainEntity; import konkuk.thip.common.exception.InvalidStateException; +import konkuk.thip.common.post.CommentCountUpdatable; import lombok.Builder; import lombok.Getter; import lombok.experimental.SuperBuilder; @@ -16,7 +17,7 @@ @Getter @SuperBuilder -public class Feed extends BaseDomainEntity { +public class Feed extends BaseDomainEntity implements CommentCountUpdatable { private Long id; @@ -148,4 +149,8 @@ public void validateOwnsImages(List candidateImageUrls) { } } + @Override + public void increaseCommentCount() { + commentCount++; + } } diff --git a/src/main/java/konkuk/thip/record/domain/Record.java b/src/main/java/konkuk/thip/record/domain/Record.java index 4bcf99ef0..e860fc8c9 100644 --- a/src/main/java/konkuk/thip/record/domain/Record.java +++ b/src/main/java/konkuk/thip/record/domain/Record.java @@ -2,6 +2,7 @@ import konkuk.thip.common.entity.BaseDomainEntity; import konkuk.thip.common.exception.InvalidStateException; +import konkuk.thip.common.post.CommentCountUpdatable; import lombok.Builder; import lombok.Getter; import lombok.experimental.SuperBuilder; @@ -11,7 +12,7 @@ @Getter @SuperBuilder -public class Record extends BaseDomainEntity { +public class Record extends BaseDomainEntity implements CommentCountUpdatable { private Long id; @@ -72,4 +73,9 @@ public void validatePage(int totalPageCount) { ); } } + + @Override + public void increaseCommentCount() { + commentCount++; + } } diff --git a/src/main/java/konkuk/thip/vote/domain/Vote.java b/src/main/java/konkuk/thip/vote/domain/Vote.java index 653710ce9..affeb2da6 100644 --- a/src/main/java/konkuk/thip/vote/domain/Vote.java +++ b/src/main/java/konkuk/thip/vote/domain/Vote.java @@ -2,6 +2,7 @@ import konkuk.thip.common.entity.BaseDomainEntity; import konkuk.thip.common.exception.InvalidStateException; +import konkuk.thip.common.post.CommentCountUpdatable; import lombok.Builder; import lombok.Getter; import lombok.experimental.SuperBuilder; @@ -11,7 +12,7 @@ @Getter @SuperBuilder -public class Vote extends BaseDomainEntity { +public class Vote extends BaseDomainEntity implements CommentCountUpdatable { private Long id; @@ -66,4 +67,9 @@ public void validatePage(int totalPageCount) { ); } } + + @Override + public void increaseCommentCount() { + commentCount++; + } } From 954f9d7c1397381db22c0f59cfbc31ac6fb339ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 01:57:16 +0900 Subject: [PATCH 16/82] =?UTF-8?q?[feat]=20=20RecordCommandPersistenceAdapt?= =?UTF-8?q?er.findById=20/=20update=20=EC=9E=91=EC=84=B1=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecordCommandPersistenceAdapter.java | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/main/java/konkuk/thip/record/adapter/out/persistence/RecordCommandPersistenceAdapter.java b/src/main/java/konkuk/thip/record/adapter/out/persistence/RecordCommandPersistenceAdapter.java index 5dfebdc44..d1170074c 100644 --- a/src/main/java/konkuk/thip/record/adapter/out/persistence/RecordCommandPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/record/adapter/out/persistence/RecordCommandPersistenceAdapter.java @@ -1,6 +1,7 @@ package konkuk.thip.record.adapter.out.persistence; import konkuk.thip.common.exception.EntityNotFoundException; +import konkuk.thip.record.adapter.out.jpa.RecordJpaEntity; import konkuk.thip.record.adapter.out.mapper.RecordMapper; import konkuk.thip.record.adapter.out.persistence.repository.RecordJpaRepository; import konkuk.thip.record.application.port.out.RecordCommandPort; @@ -12,8 +13,9 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; -import static konkuk.thip.common.exception.code.ErrorCode.ROOM_NOT_FOUND; -import static konkuk.thip.common.exception.code.ErrorCode.USER_NOT_FOUND; +import java.util.Optional; + +import static konkuk.thip.common.exception.code.ErrorCode.*; @Repository @RequiredArgsConstructor @@ -39,4 +41,19 @@ public Long saveRecord(Record record) { ).getPostId(); } + @Override + public Optional findById(Long id) { + return recordJpaRepository.findById(id) + .map(recordMapper::toDomainEntity); + } + + @Override + public void update(Record record) { + RecordJpaEntity recordJpaEntity = recordJpaRepository.findById(record.getId()).orElseThrow( + () -> new EntityNotFoundException(RECORD_NOT_FOUND) + ); + + recordJpaRepository.save(recordJpaEntity.updateFrom(record)); + } + } From 376be349e912179d3d4a27f28b77b9d3b28388de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 01:57:40 +0900 Subject: [PATCH 17/82] =?UTF-8?q?[feat]=20=20VoteCommandPersistenceAdapter?= =?UTF-8?q?.findById=20/=20updateVote=20=EC=9E=91=EC=84=B1=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../VoteCommandPersistenceAdapter.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteCommandPersistenceAdapter.java b/src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteCommandPersistenceAdapter.java index 0f9ba1bc7..162d51cce 100644 --- a/src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteCommandPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteCommandPersistenceAdapter.java @@ -19,6 +19,7 @@ import org.springframework.stereotype.Repository; import java.util.List; +import java.util.Optional; import static konkuk.thip.common.exception.code.ErrorCode.*; @@ -76,4 +77,20 @@ public List findVoteItemsByVoteId(Long voteId) { return voteItems; } + @Override + public Optional findById(Long id) { + return voteJpaRepository.findById(id) + .map(voteMapper::toDomainEntity); + } + + + @Override + public void updateVote(Vote vote) { + VoteJpaEntity voteJpaEntity = voteJpaRepository.findById(vote.getId()).orElseThrow( + () -> new EntityNotFoundException(VOTE_NOT_FOUND) + ); + + voteJpaRepository.save(voteJpaEntity.updateFrom(vote)); + } + } From ee6addf6e4e27cd23fa900e04f04545882f335fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 01:57:59 +0900 Subject: [PATCH 18/82] =?UTF-8?q?[feat]=20=20RecordCommandPort=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/port/out/RecordCommandPort.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/konkuk/thip/record/application/port/out/RecordCommandPort.java b/src/main/java/konkuk/thip/record/application/port/out/RecordCommandPort.java index d57a5d6c3..a662daf86 100644 --- a/src/main/java/konkuk/thip/record/application/port/out/RecordCommandPort.java +++ b/src/main/java/konkuk/thip/record/application/port/out/RecordCommandPort.java @@ -1,10 +1,23 @@ package konkuk.thip.record.application.port.out; +import konkuk.thip.common.exception.EntityNotFoundException; import konkuk.thip.record.domain.Record; +import java.util.Optional; + +import static konkuk.thip.common.exception.code.ErrorCode.RECORD_NOT_FOUND; + public interface RecordCommandPort { Long saveRecord(Record record); + void update(Record record); + + Optional findById(Long id); + + default Record getByIdOrThrow(Long id) { + return findById(id) + .orElseThrow(() -> new EntityNotFoundException(RECORD_NOT_FOUND)); + } } From 83510561bb5b1e6fe5882a8d42c108c25f869584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 01:58:10 +0900 Subject: [PATCH 19/82] =?UTF-8?q?[feat]=20=20VoteCommandPort=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=ED=95=A8=EC=88=98=20=EC=A0=95=EC=9D=98=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vote/application/port/out/VoteCommandPort.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/konkuk/thip/vote/application/port/out/VoteCommandPort.java b/src/main/java/konkuk/thip/vote/application/port/out/VoteCommandPort.java index 2874b7a1a..9742cddb6 100644 --- a/src/main/java/konkuk/thip/vote/application/port/out/VoteCommandPort.java +++ b/src/main/java/konkuk/thip/vote/application/port/out/VoteCommandPort.java @@ -1,15 +1,28 @@ package konkuk.thip.vote.application.port.out; +import konkuk.thip.common.exception.EntityNotFoundException; import konkuk.thip.vote.domain.Vote; import konkuk.thip.vote.domain.VoteItem; import java.util.List; +import java.util.Optional; + +import static konkuk.thip.common.exception.code.ErrorCode.VOTE_NOT_FOUND; public interface VoteCommandPort { Long saveVote(Vote vote); + void updateVote(Vote vote); + void saveAllVoteItems(List voteItems); List findVoteItemsByVoteId(Long voteId); + + Optional findById(Long id); + + default Vote getByIdOrThrow(Long id) { + return findById(id) + .orElseThrow(() -> new EntityNotFoundException(VOTE_NOT_FOUND)); + } } From 6b6293daca079ba9206cad4e052fe06ff59d7bf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 01:58:38 +0900 Subject: [PATCH 20/82] =?UTF-8?q?[feat]=20=20VoteJpaEntity.updateFrom=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/record/adapter/out/jpa/RecordJpaEntity.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/konkuk/thip/record/adapter/out/jpa/RecordJpaEntity.java b/src/main/java/konkuk/thip/record/adapter/out/jpa/RecordJpaEntity.java index b18654d09..48ce26a0c 100644 --- a/src/main/java/konkuk/thip/record/adapter/out/jpa/RecordJpaEntity.java +++ b/src/main/java/konkuk/thip/record/adapter/out/jpa/RecordJpaEntity.java @@ -1,6 +1,7 @@ package konkuk.thip.record.adapter.out.jpa; import jakarta.persistence.*; +import konkuk.thip.record.domain.Record; import konkuk.thip.post.adapter.out.jpa.PostJpaEntity; import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; @@ -33,5 +34,14 @@ public RecordJpaEntity(String content, Integer likeCount, Integer commentCount, this.roomJpaEntity = roomJpaEntity; } + public RecordJpaEntity updateFrom(Record record) { + this.content = record.getContent(); + this.likeCount = record.getLikeCount(); + this.commentCount = record.getCommentCount(); + this.page = record.getPage(); + this.isOverview = record.isOverview(); + return this; + } + } From 1bde1e63b8b9ab04f0effe7062ec0e9191189964 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 01:58:43 +0900 Subject: [PATCH 21/82] =?UTF-8?q?[feat]=20VoteJpaEntity.updateFrom=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/vote/adapter/out/jpa/VoteJpaEntity.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/konkuk/thip/vote/adapter/out/jpa/VoteJpaEntity.java b/src/main/java/konkuk/thip/vote/adapter/out/jpa/VoteJpaEntity.java index d109c2eee..2d8a28378 100644 --- a/src/main/java/konkuk/thip/vote/adapter/out/jpa/VoteJpaEntity.java +++ b/src/main/java/konkuk/thip/vote/adapter/out/jpa/VoteJpaEntity.java @@ -4,6 +4,7 @@ import konkuk.thip.post.adapter.out.jpa.PostJpaEntity; import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; +import konkuk.thip.vote.domain.Vote; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -32,4 +33,13 @@ public VoteJpaEntity(String content, Integer likeCount, Integer commentCount, Us this.isOverview = isOverview; this.roomJpaEntity = roomJpaEntity; } + + public VoteJpaEntity updateFrom(Vote vote) { + this.content = vote.getContent(); + this.likeCount = vote.getLikeCount(); + this.commentCount = vote.getCommentCount(); + this.page = vote.getPage(); + this.isOverview = vote.isOverview(); + return this; + } } \ No newline at end of file From be08e43918d7234afa23a14d70d99cf26ddbcb86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 01:59:12 +0900 Subject: [PATCH 22/82] =?UTF-8?q?[feat]=20PostType=20enum=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/common/post/PostType.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/main/java/konkuk/thip/common/post/PostType.java diff --git a/src/main/java/konkuk/thip/common/post/PostType.java b/src/main/java/konkuk/thip/common/post/PostType.java new file mode 100644 index 000000000..05176a47c --- /dev/null +++ b/src/main/java/konkuk/thip/common/post/PostType.java @@ -0,0 +1,41 @@ +package konkuk.thip.common.post; + +import konkuk.thip.common.exception.InvalidStateException; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +import static konkuk.thip.common.exception.code.ErrorCode.POST_TYPE_NOT_MATCH; + +@Getter +@RequiredArgsConstructor +public enum PostType { + + FEED("feed"), + RECORD("record"), + VOTE("vote"); + + private final String type; + + public static PostType from(String type) { + return Arrays.stream(PostType.values()) + .filter(p -> p.getType().equalsIgnoreCase(type)) + .findFirst() + .orElseThrow(() -> + new InvalidStateException(POST_TYPE_NOT_MATCH) + ); + } + + public boolean isFeed() { + return this == FEED; + } + + public boolean isRecord() { + return this == RECORD; + } + + public boolean isVote() { + return this == VOTE; + } +} From c704fe8bb81ac0474a1654611076b1af03a012a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 01:59:37 +0900 Subject: [PATCH 23/82] =?UTF-8?q?[remove]=20=EC=95=88=EC=93=B0=EB=8A=94=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=82=AD=EC=A0=9C=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../out/persistence/CommentQueryPersistenceAdapter.java | 4 ---- .../thip/comment/application/port/out/CommentQueryPort.java | 2 -- 2 files changed, 6 deletions(-) diff --git a/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentQueryPersistenceAdapter.java b/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentQueryPersistenceAdapter.java index 1ba9d5d8a..a40d2e714 100644 --- a/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentQueryPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentQueryPersistenceAdapter.java @@ -13,8 +13,4 @@ public class CommentQueryPersistenceAdapter implements CommentQueryPort { private final CommentJpaRepository jpaRepository; private final CommentMapper userMapper; - @Override - public int countByPostId(Long postId) { - return jpaRepository.countByPostJpaEntity_PostId(postId); - } } diff --git a/src/main/java/konkuk/thip/comment/application/port/out/CommentQueryPort.java b/src/main/java/konkuk/thip/comment/application/port/out/CommentQueryPort.java index bdefba463..cce75f165 100644 --- a/src/main/java/konkuk/thip/comment/application/port/out/CommentQueryPort.java +++ b/src/main/java/konkuk/thip/comment/application/port/out/CommentQueryPort.java @@ -2,6 +2,4 @@ public interface CommentQueryPort { - int countByPostId(Long postId); - } From b1d138796f3865433c4764bb4692b870fa7e14cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 01:59:53 +0900 Subject: [PATCH 24/82] =?UTF-8?q?[test]=20=EB=8C=93=EA=B8=80=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/CommentControllerTest.java | 266 ++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 src/test/java/konkuk/thip/comment/adapter/in/web/CommentControllerTest.java diff --git a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentControllerTest.java b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentControllerTest.java new file mode 100644 index 000000000..05e1a5632 --- /dev/null +++ b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentControllerTest.java @@ -0,0 +1,266 @@ +package konkuk.thip.comment.adapter.in.web; + +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; +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.persistence.repository.RoomJpaRepository; +import konkuk.thip.room.adapter.out.persistence.repository.category.CategoryJpaRepository; +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.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import java.util.HashMap; +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; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@ActiveProfiles("test") +@AutoConfigureMockMvc(addFilters = false) +@Transactional +@DisplayName("[단위] 댓글 생성 api controller 단위 테스트") +class CommentControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired private ObjectMapper objectMapper; + @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; + + + 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)); + } + + + private Map buildValidRequest() { + Map req = new HashMap<>(); + req.put("content", "정상 댓글"); + req.put("isReplyRequest", false); + req.put("parentId", null); + req.put("postType", "feed"); + return req; + } + + private void assertBadRequest(Map req, String expectedMessage) throws Exception { + mockMvc.perform(post("/comments/{postId}", feed.getPostId()) + .requestAttr("userId", user.getUserId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsBytes(req))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(API_INVALID_PARAM.getCode())) + .andExpect(jsonPath("$.message", containsString(expectedMessage))); + } + + private void assertBadCommentCreateRequest(Map req, String expectedMessage) throws Exception { + mockMvc.perform(post("/comments/{postId}", feed.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(expectedMessage))); + } + + @Nested + @DisplayName("댓글 내용(content) 검증") + class ContentValidation { + @Test + @DisplayName("빈 문자열일 때 400 error") + void blankContent() throws Exception { + Map req = buildValidRequest(); + req.put("content", ""); + assertBadRequest(req, "댓글 내용은 필수입니다."); + } + } + + @Nested + @DisplayName("isReplyRequest(답글 여부) 검증") + class IsReplyRequestValidation { + @Test + @DisplayName("누락될 경우 400 error") + void missingIsReplyRequest() throws Exception { + Map req = buildValidRequest(); + req.remove("isReplyRequest"); + assertBadRequest(req, "답글 여부는 필수입니다."); + } + } + + @Nested + @DisplayName("postType(게시물 타입) 검증") + class PostTypeValidation { + + @Test + @DisplayName("빈 문자열일 때 400 error") + void blankPostType() throws Exception { + Map req = buildValidRequest(); + req.put("postType", ""); + assertBadRequest(req, "게시물 타입은 필수입니다."); + } + + @Test + @DisplayName("지원하지 않는 게시물 타입 입력 시 400 반환") + void invalidPostType() throws Exception { + Map req = buildValidRequest(); + req.put("postType", "invalidType"); + mockMvc.perform(post("/comments/{postId}", 1) // 없는 ID + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsBytes(req)) + .requestAttr("userId", user.getUserId())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(POST_TYPE_NOT_MATCH.getCode())) + .andExpect(jsonPath("$.message", containsString("일치하는 게시물 타입 이름이 없습니다."))); + } + } + + @Nested + @DisplayName("예외 상황 검증") + class CommentExceptionValidation { + + @Test + @DisplayName("존재하지 않는 FEED일 경우 404 반환") + void feedNotFound() throws Exception { + Map req = buildValidRequest(); + mockMvc.perform(post("/comments/{postId}", 99999L) // 없는 ID + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsBytes(req)) + .requestAttr("userId", user.getUserId())) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(FEED_NOT_FOUND.getCode())) + .andExpect(jsonPath("$.message", containsString("존재하지 않는 FEED 입니다."))); + } + + @Test + @DisplayName("존재하지 않는 RECORD일 경우 404 반환") + void recordNotFound() throws Exception { + Map req = buildValidRequest(); + req.put("postType", "record"); + mockMvc.perform(post("/comments/{postId}", 99999L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsBytes(req)) + .requestAttr("userId", user.getUserId())) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(RECORD_NOT_FOUND.getCode())) + .andExpect(jsonPath("$.message", containsString("존재하지 않는 RECORD 입니다."))); + } + + @Test + @DisplayName("존재하지 않는 VOTE일 경우 404 반환") + void voteNotFound() throws Exception { + Map req = buildValidRequest(); + req.put("postType", "vote"); + mockMvc.perform(post("/comments/{postId}", 99999L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsBytes(req)) + .requestAttr("userId", user.getUserId())) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(VOTE_NOT_FOUND.getCode())) + .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 0f90c975592e3edfbc71ae7b13169742ed08b146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 02:00:00 +0900 Subject: [PATCH 25/82] =?UTF-8?q?[test]=20=EB=8C=93=EA=B8=80=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/CommentCreateAPITest.java | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 src/test/java/konkuk/thip/comment/adapter/in/web/CommentCreateAPITest.java diff --git a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentCreateAPITest.java b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentCreateAPITest.java new file mode 100644 index 000000000..3e529079f --- /dev/null +++ b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentCreateAPITest.java @@ -0,0 +1,143 @@ +package konkuk.thip.comment.adapter.in.web; + +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.persistence.repository.CommentJpaRepository; +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.persistence.repository.RoomJpaRepository; +import konkuk.thip.room.adapter.out.persistence.repository.category.CategoryJpaRepository; +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.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import java.util.HashMap; +import java.util.Map; + +import static konkuk.thip.common.post.PostType.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@ActiveProfiles("test") +@AutoConfigureMockMvc(addFilters = false) +@Transactional +@DisplayName("[통합] 댓글 생성 api 통합 테스트") +class CommentCreateAPITest { + + @Autowired + private MockMvc mockMvc; + + @Autowired private ObjectMapper objectMapper; + @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; + + + 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)); + } + + // 공통 JSON 생성 함수 + private String toJson(String content, boolean isReply, Long parentId, String postType) throws Exception { + Map req = new HashMap<>(); + req.put("content", content); + req.put("isReplyRequest", isReply); + req.put("parentId", parentId); + req.put("postType", postType); + return objectMapper.writeValueAsString(req); + } + + @Test + @DisplayName("각 게시물 타입별로 존재하는 게시물에 대해 (루트)댓글 생성을 할 수 있다.") + void createRootCommentEachPostType() throws Exception { + + // given + String[] postTypes = {"feed", "record", "vote"}; + Long[] postIds = {feed.getPostId(), record.getPostId(), vote.getPostId()}; + + // when & then + for (int i = 0; i < postTypes.length; i++) { + mockMvc.perform(post("/comments/{postId}", postIds[i]) + .contentType(MediaType.APPLICATION_JSON) + .content(toJson("루트 댓글입니다", false, null, postTypes[i])) + .requestAttr("userId", user.getUserId())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.commentId").exists()); + } + } + + + @Test + @DisplayName("각 게시물 타입별로 존재하는 게시물 및 댓글에 대해 답글 생성을 할 수 있다.") + void createReplyCommentEachPostType() throws Exception { + + // given + //부모 댓글 생성 + Long feedParentId = commentJpaRepository.save(TestEntityFactory.createComment(feed,user,FEED)).getCommentId(); + Long recordParentId = commentJpaRepository.save(TestEntityFactory.createComment(record,user,RECORD)).getCommentId(); + Long voteParentId = commentJpaRepository.save(TestEntityFactory.createComment(vote,user,VOTE)).getCommentId(); + + // 답글 생성 요청 + Map[] replyRequests = new Map[]{ + Map.of("content", "Feed 답글", "isReplyRequest", true, "parentId", feedParentId, "postType", "feed"), + Map.of("content", "Record 답글", "isReplyRequest", true, "parentId", recordParentId, "postType", "record"), + Map.of("content", "Vote 답글", "isReplyRequest", true, "parentId", voteParentId, "postType", "vote") + }; + + Long[] postIds = {feed.getPostId(), record.getPostId(), vote.getPostId()}; + + for (int i = 0; i < replyRequests.length; i++) { + mockMvc.perform(post("/comments/{postId}", postIds[i]) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(replyRequests[i])) + .requestAttr("userId", user.getUserId())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.commentId").exists()); + } + } + +} From 5c5e2e9fcec51f1758e8bf248a8aa60ccb55fd75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 02:00:35 +0900 Subject: [PATCH 26/82] =?UTF-8?q?[test]=20Comment=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=8B=A8=EC=9C=84=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(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/comment/domain/CommentTest.java | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 src/test/java/konkuk/thip/comment/domain/CommentTest.java diff --git a/src/test/java/konkuk/thip/comment/domain/CommentTest.java b/src/test/java/konkuk/thip/comment/domain/CommentTest.java new file mode 100644 index 000000000..cb880a8d3 --- /dev/null +++ b/src/test/java/konkuk/thip/comment/domain/CommentTest.java @@ -0,0 +1,136 @@ +package konkuk.thip.comment.domain; + +import konkuk.thip.common.exception.InvalidStateException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static konkuk.thip.common.post.PostType.FEED; +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("[단위] Comment 단위 테스트") +class CommentTest { + + private final String CONTENT = "댓글 본문"; + private final Long CREATOR_ID = 1L; + private final Long POST_ID = 100L; + + private Comment createParentComment(Long postId) { + return Comment.builder() + .id(123L) //ID 임의 주입 + .content(CONTENT) + .targetPostId(postId) + .creatorId(CREATOR_ID) + .postType(FEED) + .parentCommentId(null) + .reportCount(0) + .likeCount(0) + .build(); + } + + @Test + @DisplayName("createComment: 일반 댓글 생성 시 parentId는 null이면 정상적으로 Comment가 생성된다.") + void createRootComment_valid() { + Comment comment = Comment.createComment( + CONTENT, + POST_ID, + CREATOR_ID, + "feed", + false, + null, + null + ); + + assertNotNull(comment); + assertNull(comment.getParentCommentId()); + assertEquals(FEED, comment.getPostType()); + assertEquals(POST_ID, comment.getTargetPostId()); + assertEquals(CONTENT, comment.getContent()); + } + + @Test + @DisplayName("createComment: 답글 생성 시 parentComment 존재 + 게시글 ID 일치하면 정상적으로 Comment가 생성된다.") + void createReplyComment_valid() { + Comment parent = createParentComment(POST_ID); + + Comment reply = Comment.createComment( + "답글입니다.", + POST_ID, + CREATOR_ID, + "feed", + true, + parent.getId(), + parent + ); + + assertNotNull(reply); + assertEquals(parent.getId(), reply.getParentCommentId()); + assertEquals(FEED, reply.getPostType()); + } + + @Test + @DisplayName("createComment: 일반 댓글 생성 시 parentId가 존재하면 InvalidStateException 이 발생한다.") + void createRootComment_withParentId_shouldFail() { + InvalidStateException ex = assertThrows(InvalidStateException.class, () -> Comment.createComment( + CONTENT, + POST_ID, + CREATOR_ID, + "feed", + false, + 99L, + null + )); + + assertEquals("일반 댓글에는 parentId가 없어야 합니다.", ex.getCause().getMessage()); + } + + @Test + @DisplayName("createComment: 답글 생성 시 parentId가 null이면 InvalidStateException 이 발생한다.") + void createReplyComment_missingParentId_shouldFail() { + InvalidStateException ex = assertThrows(InvalidStateException.class, () -> Comment.createComment( + CONTENT, + POST_ID, + CREATOR_ID, + "feed", + true, + null, + null + )); + + assertEquals("답글 작성 시 parentId는 필수입니다.", ex.getCause().getMessage()); + } + + @Test + @DisplayName("createComment: 답글 생성 시 parentComment 가 null 이면 InvalidStateException 이 발생한다.") + void createReplyComment_missingParentComment_shouldFail() { + InvalidStateException ex = assertThrows(InvalidStateException.class, () -> Comment.createComment( + CONTENT, + POST_ID, + CREATOR_ID, + "feed", + true, + 1L, + null // parentComment 누락 + )); + + assertEquals("parentId에 해당하는 부모 댓글이 존재해야 합니다.", ex.getCause().getMessage()); + } + + @Test + @DisplayName("createComment: 답글 생성 시 parentComment 가 null 이면 InvalidStateException 이 발생한다.") + void createReplyComment_parentPostMismatch_shouldFail() { + Comment parent = createParentComment(999L); // 다른 postId + + InvalidStateException ex = assertThrows(InvalidStateException.class, () -> Comment.createComment( + CONTENT, + POST_ID, + CREATOR_ID, + "feed", + true, + parent.getId(), + parent + )); + + assertEquals("댓글과 부모 댓글의 게시글이 일치하지 않습니다.", ex.getCause().getMessage()); + } + +} From f09f7945a1065e116f495d7e3db1303ba39b7bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 02:00:52 +0900 Subject: [PATCH 27/82] =?UTF-8?q?[test]=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=ED=8E=99=ED=86=A0=EB=A6=AC=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EB=88=84=EB=9D=BD=20=EA=B0=92=20=EC=B6=94=EA=B0=80=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/common/util/TestEntityFactory.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/test/java/konkuk/thip/common/util/TestEntityFactory.java b/src/test/java/konkuk/thip/common/util/TestEntityFactory.java index 240df2cd5..0e12f6435 100644 --- a/src/test/java/konkuk/thip/common/util/TestEntityFactory.java +++ b/src/test/java/konkuk/thip/common/util/TestEntityFactory.java @@ -2,6 +2,7 @@ import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; import konkuk.thip.comment.adapter.out.jpa.CommentJpaEntity; +import konkuk.thip.common.post.PostType; import konkuk.thip.feed.adapter.out.jpa.ContentJpaEntity; import konkuk.thip.feed.adapter.out.jpa.FeedJpaEntity; import konkuk.thip.feed.adapter.out.jpa.FeedTagJpaEntity; @@ -140,6 +141,8 @@ public static RecordJpaEntity createRecord(UserJpaEntity user, RoomJpaEntity roo .userJpaEntity(user) .page(22) .isOverview(false) + .commentCount(0) + .likeCount(0) .roomJpaEntity(room) .build(); } @@ -150,15 +153,20 @@ public static VoteJpaEntity createVote(UserJpaEntity user, RoomJpaEntity room) { .userJpaEntity(user) .page(33) .isOverview(true) + .commentCount(0) + .likeCount(0) .roomJpaEntity(room) .build(); } - public static CommentJpaEntity createComment(PostJpaEntity post, UserJpaEntity user) { + public static CommentJpaEntity createComment(PostJpaEntity post, UserJpaEntity user,PostType postType) { return CommentJpaEntity.builder() .content("댓글 내용") .postJpaEntity(post) .userJpaEntity(user) + .likeCount(0) + .reportCount(0) + .postType(postType) .build(); } From 2c013776829261e6b0b4c2cb1b7de85ea96df05a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 02:41:58 +0900 Subject: [PATCH 28/82] =?UTF-8?q?[test]=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/konkuk/thip/comment/domain/CommentTest.java | 2 +- 1 file changed, 1 insertion(+), 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 cb880a8d3..cbd213c95 100644 --- a/src/test/java/konkuk/thip/comment/domain/CommentTest.java +++ b/src/test/java/konkuk/thip/comment/domain/CommentTest.java @@ -116,7 +116,7 @@ void createReplyComment_missingParentComment_shouldFail() { } @Test - @DisplayName("createComment: 답글 생성 시 parentComment 가 null 이면 InvalidStateException 이 발생한다.") + @DisplayName("createComment: 답글 생성 시 부모 댓글과 게시글 ID가 일치하지 않으면 InvalidStateException 이 발생한다.") void createReplyComment_parentPostMismatch_shouldFail() { Comment parent = createParentComment(999L); // 다른 postId From a0c62cb51f757fcae6da6bffa893b99ce4e55976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 02:42:31 +0900 Subject: [PATCH 29/82] =?UTF-8?q?[test]=20postId=20=ED=95=98=EB=93=9C=20?= =?UTF-8?q?=EC=BD=94=EB=94=A9=20=EC=88=98=EC=A0=95=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/comment/adapter/in/web/CommentControllerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentControllerTest.java b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentControllerTest.java index 05e1a5632..0c23c14a9 100644 --- a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentControllerTest.java +++ b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentControllerTest.java @@ -156,7 +156,7 @@ void blankPostType() throws Exception { void invalidPostType() throws Exception { Map req = buildValidRequest(); req.put("postType", "invalidType"); - mockMvc.perform(post("/comments/{postId}", 1) // 없는 ID + mockMvc.perform(post("/comments/{postId}", feed.getPostId()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsBytes(req)) .requestAttr("userId", user.getUserId())) From 9dfe2d06798f1985ef1a2d831b2d9000e6495cb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 02:50:25 +0900 Subject: [PATCH 30/82] =?UTF-8?q?[refactor]=20todo=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/application/service/CommentCreateService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 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 d29c6c98f..f39e27050 100644 --- a/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java +++ b/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java @@ -40,8 +40,8 @@ public Long createComment(CommentCreateCommand command) { // 2. 게시물 타입에 맞게 조회 CommentCountUpdatable post = findPost(type, command.postId()); - // TODO 피드: 내 게시글의 댓글, 내 댓글에 대한 답글 - // TODO 기록 및 투표: 모임방의 내 게시글에 대한 댓글, 내 댓글에 대한 답글 + // TODO 피드: 내 게시글의 댓글, 내 댓글에 대한 답글 알림 전송 + // TODO 기록 및 투표: 모임방의 내 게시글에 대한 댓글, 내 댓글에 대한 답글 알림 전송 // 3. 댓글 생성 Long commentId = createCommentDomain(command); From ce6a11a4a11389277029ab602d3b7864979c0bc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 21:33:24 +0900 Subject: [PATCH 31/82] =?UTF-8?q?[feat]=20Google=20Guava=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index c007b4385..a908a53bd 100644 --- a/build.gradle +++ b/build.gradle @@ -68,6 +68,9 @@ dependencies { // MapStruct implementation 'org.mapstruct:mapstruct:1.5.5.Final' annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final' + + //Google Guava + implementation 'com.google.guava:guava:33.2.0-jre' } def querydslDir = layout.buildDirectory.dir("generated/querydsl").get().asFile From 1e03d25855b2f5be169809fee9256e57ec6a62af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 21:33:56 +0900 Subject: [PATCH 32/82] =?UTF-8?q?[feat]=20Comment=20=EC=A2=8B=EC=95=84?= =?UTF-8?q?=EC=9A=94=20=EA=B4=80=EB=A0=A8=20=EB=8F=84=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EC=B1=85=EC=9E=84=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/comment/domain/Comment.java | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/main/java/konkuk/thip/comment/domain/Comment.java b/src/main/java/konkuk/thip/comment/domain/Comment.java index f5134e766..076e3aeb7 100644 --- a/src/main/java/konkuk/thip/comment/domain/Comment.java +++ b/src/main/java/konkuk/thip/comment/domain/Comment.java @@ -7,6 +7,9 @@ import lombok.Getter; import lombok.experimental.SuperBuilder; +import java.util.Objects; + +import static konkuk.thip.common.exception.code.ErrorCode.COMMENT_LIKE_COUNT_UNDERFLOW; import static konkuk.thip.common.exception.code.ErrorCode.INVALID_COMMENT_CREATE; @Getter @@ -18,10 +21,10 @@ public class Comment extends BaseDomainEntity { private String content; @Builder.Default - private Integer reportCount = 0; + private int reportCount = 0; @Builder.Default - private Integer likeCount = 0; + private int likeCount = 0; private Long targetPostId; @@ -31,6 +34,20 @@ public class Comment extends BaseDomainEntity { private PostType postType; + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Comment comment = (Comment) o; + return Objects.equals(id, comment.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + public static Comment createComment(String content, Long postId, Long creatorId, String type, boolean isReplyRequest, Long parentId, Comment parent) { @@ -96,4 +113,18 @@ public static void validateCommentCreate(boolean isReplyRequest, Long parentId) } } + public void updateLikeCount(Boolean like) { + if (like) { + likeCount++; + } else { + checkLikeCountNotUnderflow(); + likeCount--; + } + } + + private void checkLikeCountNotUnderflow() { + if (likeCount <= 0) { + throw new InvalidStateException(COMMENT_LIKE_COUNT_UNDERFLOW); + } + } } From 35732e7a1e107064bf4034cc4938293d463a6df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 21:34:07 +0900 Subject: [PATCH 33/82] =?UTF-8?q?[feat]=20=EB=8C=93=EA=B8=80=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20=EC=83=81=ED=83=9C=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EC=9E=91=EC=84=B1=20(#9?= =?UTF-8?q?9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/CommentCommandController.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) 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 4a7058bea..131b84c44 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 @@ -2,8 +2,11 @@ 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.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.CommentLikeUseCase; import konkuk.thip.common.dto.BaseResponse; import konkuk.thip.common.security.annotation.UserId; import lombok.RequiredArgsConstructor; @@ -14,7 +17,7 @@ public class CommentCommandController { private final CommentCreateUseCase commentCreateUseCase; - + private final CommentLikeUseCase commentLikeUseCase; /** * 댓글/답글 작성 @@ -28,4 +31,12 @@ public BaseResponse createComment(@RequestBody @Valid final C return BaseResponse.ok(CommentIdResponse.of(commentCreateUseCase.createComment(request.toCommand(userId,postId)))); } + //댓글 좋아요 상태 변경: true -> 좋아요, false -> 좋아요 취소 + @PostMapping("/comments/{commentId}/likes") + public BaseResponse likeComment(@RequestBody @Valid final CommentIsLikeRequest request, + @PathVariable("commentId") final Long commentId, + @UserId final Long userId) { + return BaseResponse.ok(CommentIsLikeResponse.of(commentLikeUseCase.changeLikeStatusComment(CommentIsLikeRequest.toCommand(userId, commentId,request.type())))); + } + } From 2d1534ede49112ea5a96a4f171b8e0cd233ac60d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 21:38:54 +0900 Subject: [PATCH 34/82] =?UTF-8?q?[feat]=20CommentCommandPort.update=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98=20(#99)?= 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 a6e2797c0..22bff6d06 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 @@ -19,4 +19,6 @@ default Comment getByIdOrThrow(Long id) { .orElseThrow(() -> new EntityNotFoundException(COMMENT_NOT_FOUND)); } + void update(Comment comment); + } From 240bbb3f024fc5ce2141a6b7b9b1f60415af9a65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 21:39:11 +0900 Subject: [PATCH 35/82] =?UTF-8?q?[feat]=20CommentCommandPersistenceAdapter?= =?UTF-8?q?.update=20=EA=B5=AC=ED=98=84=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/CommentCommandPersistenceAdapter.java | 10 ++++++++++ 1 file changed, 10 insertions(+) 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 e824a01ab..e9a44a1bd 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 @@ -71,4 +71,14 @@ public Optional findById(Long id) { .map(commentMapper::toDomainEntity); } + @Override + public void update(Comment comment) { + + CommentJpaEntity commentJpaEntity = commentJpaRepository.findById(comment.getId()).orElseThrow( + () -> new EntityNotFoundException(ROOM_NOT_FOUND) + ); + + commentJpaRepository.save(commentJpaEntity.updateFrom(comment)); + } + } From c5bf4fe1aa3f367d604576274c364b06ea758cb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 21:39:35 +0900 Subject: [PATCH 36/82] =?UTF-8?q?[feat]=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EB=B3=80=EA=B2=BD=20request=20dto=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/request/CommentIsLikeRequest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/main/java/konkuk/thip/comment/adapter/in/web/request/CommentIsLikeRequest.java diff --git a/src/main/java/konkuk/thip/comment/adapter/in/web/request/CommentIsLikeRequest.java b/src/main/java/konkuk/thip/comment/adapter/in/web/request/CommentIsLikeRequest.java new file mode 100644 index 000000000..8034f4657 --- /dev/null +++ b/src/main/java/konkuk/thip/comment/adapter/in/web/request/CommentIsLikeRequest.java @@ -0,0 +1,13 @@ +package konkuk.thip.comment.adapter.in.web.request; + +import jakarta.validation.constraints.NotNull; +import konkuk.thip.comment.application.port.in.dto.CommentIsLikeCommand; + +public record CommentIsLikeRequest( + @NotNull(message = "좋아요 여부는 필수입니다.") + Boolean type +) { + public static CommentIsLikeCommand toCommand(Long userId, Long commentId, Boolean type) { + return new CommentIsLikeCommand(userId, commentId, type); + } +} From 57dac46934875d973525667e4a97386f1dbe84a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 21:39:46 +0900 Subject: [PATCH 37/82] =?UTF-8?q?[feat]=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EB=B3=80=EA=B2=BD=20command=20dto=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../port/in/dto/CommentIsLikeCommand.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/main/java/konkuk/thip/comment/application/port/in/dto/CommentIsLikeCommand.java diff --git a/src/main/java/konkuk/thip/comment/application/port/in/dto/CommentIsLikeCommand.java b/src/main/java/konkuk/thip/comment/application/port/in/dto/CommentIsLikeCommand.java new file mode 100644 index 000000000..fcbb2c617 --- /dev/null +++ b/src/main/java/konkuk/thip/comment/application/port/in/dto/CommentIsLikeCommand.java @@ -0,0 +1,12 @@ +package konkuk.thip.comment.application.port.in.dto; + +public record CommentIsLikeCommand( + + Long userId, + + Long commentId, + + Boolean isLike +) +{ +} From bbb9d1024d171e095be8cd864a63aaa8ec843cdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 21:39:54 +0900 Subject: [PATCH 38/82] =?UTF-8?q?[feat]=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EB=B3=80=EA=B2=BD=20response=20dto=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/response/CommentIsLikeResponse.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/main/java/konkuk/thip/comment/adapter/in/web/response/CommentIsLikeResponse.java diff --git a/src/main/java/konkuk/thip/comment/adapter/in/web/response/CommentIsLikeResponse.java b/src/main/java/konkuk/thip/comment/adapter/in/web/response/CommentIsLikeResponse.java new file mode 100644 index 000000000..8a57ce4e1 --- /dev/null +++ b/src/main/java/konkuk/thip/comment/adapter/in/web/response/CommentIsLikeResponse.java @@ -0,0 +1,12 @@ +package konkuk.thip.comment.adapter.in.web.response; + +import konkuk.thip.comment.application.port.in.dto.CommentIsLikeResult; + +public record CommentIsLikeResponse( + Long commentId, + boolean isLiked +) { + public static CommentIsLikeResponse of(CommentIsLikeResult commentIsLikeResult) { + return new CommentIsLikeResponse(commentIsLikeResult.commentId(), commentIsLikeResult.isLiked()); + } +} From 1374d14bc23df2bc754a0ab90a8047fe9adac215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 21:40:02 +0900 Subject: [PATCH 39/82] =?UTF-8?q?[feat]=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EB=B3=80=EA=B2=BD=20result=20dto=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/port/in/dto/CommentIsLikeResult.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/java/konkuk/thip/comment/application/port/in/dto/CommentIsLikeResult.java diff --git a/src/main/java/konkuk/thip/comment/application/port/in/dto/CommentIsLikeResult.java b/src/main/java/konkuk/thip/comment/application/port/in/dto/CommentIsLikeResult.java new file mode 100644 index 000000000..ba2f06cbb --- /dev/null +++ b/src/main/java/konkuk/thip/comment/application/port/in/dto/CommentIsLikeResult.java @@ -0,0 +1,11 @@ +package konkuk.thip.comment.application.port.in.dto; + +public record CommentIsLikeResult( + Long commentId, + boolean isLiked +) +{ + public static CommentIsLikeResult of(Long commentId, boolean isLiked) { + return new CommentIsLikeResult(commentId, isLiked); + } +} \ No newline at end of file From 3a8f508d5ba2496be0100687b1b5b855a583882d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 21:40:34 +0900 Subject: [PATCH 40/82] =?UTF-8?q?[feat]=20CommentJpaEntity.updateFrom=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=9A=A9=20updateLikeCount=EC=9E=91=EC=84=B1=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/adapter/out/jpa/CommentJpaEntity.java | 14 ++++++++++++++ 1 file changed, 14 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 e7a6bebd5..997285e0d 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 @@ -1,6 +1,8 @@ package konkuk.thip.comment.adapter.out.jpa; +import com.google.common.annotations.VisibleForTesting; import jakarta.persistence.*; +import konkuk.thip.comment.domain.Comment; import konkuk.thip.common.entity.BaseJpaEntity; import konkuk.thip.common.post.PostType; import konkuk.thip.post.adapter.out.jpa.PostJpaEntity; @@ -36,6 +38,7 @@ public class CommentJpaEntity extends BaseJpaEntity { @JoinColumn(name = "post_id", nullable = false) private PostJpaEntity postJpaEntity; + @Enumerated(EnumType.STRING) @Column(name = "post_type", nullable = false, length = 10) private PostType postType; @@ -47,4 +50,15 @@ public class CommentJpaEntity extends BaseJpaEntity { @JoinColumn(name = "parent_id") private CommentJpaEntity parent; + public CommentJpaEntity updateFrom(Comment comment) { + this.reportCount = comment.getReportCount(); + this.likeCount = comment.getLikeCount(); + return this; + } + + @VisibleForTesting + public void updateLikeCount(int likeCount) { + this.likeCount = likeCount; + } + } \ No newline at end of file From 94d998fb9785dffa21620b0c88c46e611ce4065a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 21:40:49 +0900 Subject: [PATCH 41/82] =?UTF-8?q?[feat]=20CommentLikeCommandPort=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/port/out/CommentLikeCommandPort.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/java/konkuk/thip/comment/application/port/out/CommentLikeCommandPort.java 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 new file mode 100644 index 000000000..bc8b2800e --- /dev/null +++ b/src/main/java/konkuk/thip/comment/application/port/out/CommentLikeCommandPort.java @@ -0,0 +1,7 @@ +package konkuk.thip.comment.application.port.out; + + +public interface CommentLikeCommandPort { + void save(Long userId, Long commentId); + void delete(Long userId, Long commentId); +} From a8e2832b99aac5a391790bd5512fd528a9c58d27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 21:41:00 +0900 Subject: [PATCH 42/82] =?UTF-8?q?[feat]=20CommentLikeJpaRepository=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=95=A8=EC=88=98=20=EC=9E=91=EC=84=B1=20?= =?UTF-8?q?(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/CommentLikeJpaRepository.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentLikeJpaRepository.java 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 new file mode 100644 index 000000000..2df1577be --- /dev/null +++ b/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentLikeJpaRepository.java @@ -0,0 +1,18 @@ +package konkuk.thip.comment.adapter.out.persistence.repository; + +import konkuk.thip.comment.adapter.out.jpa.CommentLikeJpaEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +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); + + @Modifying + @Query(value = "DELETE FROM comment_likes WHERE user_id = :userId AND comment_id = :commentId", nativeQuery = true) + void deleteByUserIdAndCommentId(@Param("userId") Long userId, @Param("commentId") Long commentId); +} \ No newline at end of file From e82067aa2985037b171a719f1e8fd51ea9c04b00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 21:41:10 +0900 Subject: [PATCH 43/82] =?UTF-8?q?[feat]=20CommentLikeCommandPersistenceAda?= =?UTF-8?q?pter=20=EC=9E=91=EC=84=B1=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommentLikeCommandPersistenceAdapter.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentLikeCommandPersistenceAdapter.java 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 new file mode 100644 index 000000000..528b3aa4d --- /dev/null +++ b/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentLikeCommandPersistenceAdapter.java @@ -0,0 +1,44 @@ +package konkuk.thip.comment.adapter.out.persistence; + +import konkuk.thip.comment.adapter.out.jpa.CommentJpaEntity; +import konkuk.thip.comment.adapter.out.jpa.CommentLikeJpaEntity; +import konkuk.thip.comment.adapter.out.persistence.repository.CommentJpaRepository; +import konkuk.thip.comment.adapter.out.persistence.repository.CommentLikeJpaRepository; +import konkuk.thip.comment.application.port.out.CommentLikeCommandPort; +import konkuk.thip.common.exception.EntityNotFoundException; +import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; +import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import static konkuk.thip.common.exception.code.ErrorCode.*; + +@Repository +@RequiredArgsConstructor +public class CommentLikeCommandPersistenceAdapter implements CommentLikeCommandPort { + + private final CommentLikeJpaRepository commentLikeJpaRepository; + private final CommentJpaRepository commentJpaRepository; + private final UserJpaRepository userJpaRepository; + + @Override + public void save(Long userId, Long commentId) { + + UserJpaEntity user = userJpaRepository.findById(userId) + .orElseThrow(() -> new EntityNotFoundException(USER_NOT_FOUND)); + CommentJpaEntity comment = commentJpaRepository.findById(commentId) + .orElseThrow(() -> new EntityNotFoundException(COMMENT_NOT_FOUND)); + + CommentLikeJpaEntity commentLike = CommentLikeJpaEntity.builder() + .userJpaEntity(user) + .commentJpaEntity(comment) + .build(); + + commentLikeJpaRepository.save(commentLike); + } + + @Override + public void delete(Long userId, Long commentId) { + commentLikeJpaRepository.deleteByUserIdAndCommentId(userId, commentId); + } +} From eb4d6e72f0e14c3a49f7d68701895d8169b4f177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 21:41:27 +0900 Subject: [PATCH 44/82] =?UTF-8?q?[feat]=20CommentLikeQueryPersistenceAdapt?= =?UTF-8?q?er=20=EC=9E=91=EC=84=B1=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommentLikeQueryPersistenceAdapter.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentLikeQueryPersistenceAdapter.java 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 new file mode 100644 index 000000000..02fb9d2b6 --- /dev/null +++ b/src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentLikeQueryPersistenceAdapter.java @@ -0,0 +1,44 @@ +package konkuk.thip.comment.adapter.out.persistence; + +import konkuk.thip.comment.adapter.out.jpa.CommentLikeJpaEntity; +import konkuk.thip.comment.adapter.out.mapper.CommentLikeMapper; +import konkuk.thip.comment.adapter.out.mapper.CommentMapper; +import konkuk.thip.comment.adapter.out.persistence.repository.CommentLikeJpaRepository; +import konkuk.thip.comment.application.port.out.CommentLikeQueryPort; +import konkuk.thip.comment.domain.Comment; +import konkuk.thip.comment.domain.LikedComments; +import konkuk.thip.common.exception.EntityNotFoundException; +import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; +import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.stream.Collectors; + +import static konkuk.thip.common.exception.code.ErrorCode.USER_NOT_FOUND; + +@Repository +@RequiredArgsConstructor +public class CommentLikeQueryPersistenceAdapter implements CommentLikeQueryPort { + + private final CommentLikeJpaRepository commentLikeJpaRepository; + private final UserJpaRepository userJpaRepository; + private final CommentMapper commentMapper; + private final CommentLikeMapper commentLikeMapper; + + @Override + public LikedComments findLikedCommentsByUserId(Long userId) { + + UserJpaEntity user = userJpaRepository.findById(userId) + .orElseThrow(() -> new EntityNotFoundException(USER_NOT_FOUND)); + + List likedCommentEntities = commentLikeJpaRepository.findAllByUserId((user.getUserId())); + + List comments = likedCommentEntities.stream() + .map(entity -> commentMapper.toDomainEntity(entity.getCommentJpaEntity())) + .collect(Collectors.toList()); + + return new LikedComments(comments); + } +} From 42d4870384c3f9383807c6fec967397aab8d109c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 21:41:35 +0900 Subject: [PATCH 45/82] =?UTF-8?q?[feat]=20CommentLikeQueryPort=20=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/application/port/out/CommentLikeQueryPort.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/java/konkuk/thip/comment/application/port/out/CommentLikeQueryPort.java diff --git a/src/main/java/konkuk/thip/comment/application/port/out/CommentLikeQueryPort.java b/src/main/java/konkuk/thip/comment/application/port/out/CommentLikeQueryPort.java new file mode 100644 index 000000000..f20c0df07 --- /dev/null +++ b/src/main/java/konkuk/thip/comment/application/port/out/CommentLikeQueryPort.java @@ -0,0 +1,7 @@ +package konkuk.thip.comment.application.port.out; + +import konkuk.thip.comment.domain.LikedComments; + +public interface CommentLikeQueryPort { + LikedComments findLikedCommentsByUserId(Long userId); +} From aea45e0bd75e6f85373370f3dcd9c549346a60da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 21:42:56 +0900 Subject: [PATCH 46/82] =?UTF-8?q?[feat]=20LikedComments=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=EA=B0=80=20=EC=A0=80=EC=9E=A5=ED=95=9C=20?= =?UTF-8?q?=EB=8C=93=EA=B8=80=20=EC=9D=BC=EA=B8=89=20=EC=BB=AC=EB=A0=89?= =?UTF-8?q?=EC=85=98=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/comment/domain/LikedComments.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/main/java/konkuk/thip/comment/domain/LikedComments.java diff --git a/src/main/java/konkuk/thip/comment/domain/LikedComments.java b/src/main/java/konkuk/thip/comment/domain/LikedComments.java new file mode 100644 index 000000000..b1fb1846a --- /dev/null +++ b/src/main/java/konkuk/thip/comment/domain/LikedComments.java @@ -0,0 +1,42 @@ +package konkuk.thip.comment.domain; + +import konkuk.thip.common.exception.InvalidStateException; +import lombok.Getter; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static konkuk.thip.common.exception.code.ErrorCode.*; + +@Getter +public class LikedComments { + + private final Set comments; + + public LikedComments(List comments) { + Set commentSet = new HashSet<>(comments); + if (commentSet.size() != comments.size()) { + throw new InvalidStateException(DUPLICATED_COMMENTS_IN_COLLECTION); + } + this.comments = Collections.unmodifiableSet(commentSet); + } + + // 중복 좋아요 검증 + public void validateNotAlreadyLiked(Comment comment) { + if (comments.contains(comment)) { + throw new InvalidStateException(COMMENT_ALREADY_LIKED); + } + } + + // 좋아요 취소 가능 여부 검증 + public void validateCanUnlike(Comment comment) { + if (!comments.contains(comment)) { + throw new InvalidStateException(COMMENT_NOT_LIKED_CANNOT_CANCEL); + } + } + +} + + From 00aaf958b0b63fe90076e0f9cdd7ce1c92bb8f17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 21:43:22 +0900 Subject: [PATCH 47/82] =?UTF-8?q?[feat]=20=EA=B4=80=EB=A0=A8=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/konkuk/thip/common/exception/code/ErrorCode.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 8032a9a2a..f381c2f67 100644 --- a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java +++ b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java @@ -154,8 +154,10 @@ public enum ErrorCode implements ResponseCode { */ COMMENT_NOT_FOUND(HttpStatus.NOT_FOUND, 190000, "존재하지 않는 COMMENT 입니다."), INVALID_COMMENT_CREATE(HttpStatus.BAD_REQUEST, 190001, "유효하지 않은 COMMENT 생성 요청 입니다."), - - + 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, "사용자가 좋아요하지 않은 댓글은 좋아요 취소 할 수 없습니다."), + DUPLICATED_COMMENTS_IN_COLLECTION(HttpStatus.INTERNAL_SERVER_ERROR, 190005, "중복된 댓글이 존재합니다."), ; private final HttpStatus httpStatus; From 3e2b4903adcbecdd2122d0dedc1454c740be4eb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 21:43:45 +0900 Subject: [PATCH 48/82] =?UTF-8?q?[feat]=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EB=B3=80=EA=B2=BD=20changeLikeStatusComment?= =?UTF-8?q?=20=EC=9C=A0=EC=A6=88=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/application/port/in/CommentLikeUseCase.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/konkuk/thip/comment/application/port/in/CommentLikeUseCase.java diff --git a/src/main/java/konkuk/thip/comment/application/port/in/CommentLikeUseCase.java b/src/main/java/konkuk/thip/comment/application/port/in/CommentLikeUseCase.java new file mode 100644 index 000000000..639728dd3 --- /dev/null +++ b/src/main/java/konkuk/thip/comment/application/port/in/CommentLikeUseCase.java @@ -0,0 +1,8 @@ +package konkuk.thip.comment.application.port.in; + +import konkuk.thip.comment.application.port.in.dto.CommentIsLikeCommand; +import konkuk.thip.comment.application.port.in.dto.CommentIsLikeResult; + +public interface CommentLikeUseCase { + CommentIsLikeResult changeLikeStatusComment(CommentIsLikeCommand commentIsLikeCommand); +} From 332426fad8896c0f27a375f120e2af279198b89e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 21:45:10 +0900 Subject: [PATCH 49/82] =?UTF-8?q?[feat]=20changeLikeStatusComment=20?= =?UTF-8?q?=EC=9C=A0=EC=A6=88=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=EC=B2=B4=20CommentLikeService=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EC=9E=91=EC=84=B1=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CommentLikeService.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java diff --git a/src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java b/src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java new file mode 100644 index 000000000..1cf1170f2 --- /dev/null +++ b/src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java @@ -0,0 +1,50 @@ +package konkuk.thip.comment.application.service; + +import jakarta.transaction.Transactional; +import konkuk.thip.comment.application.port.in.CommentLikeUseCase; +import konkuk.thip.comment.application.port.in.dto.CommentIsLikeCommand; +import konkuk.thip.comment.application.port.in.dto.CommentIsLikeResult; +import konkuk.thip.comment.application.port.out.CommentCommandPort; +import konkuk.thip.comment.application.port.out.CommentLikeCommandPort; +import konkuk.thip.comment.application.port.out.CommentLikeQueryPort; +import konkuk.thip.comment.domain.Comment; +import konkuk.thip.comment.domain.LikedComments; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CommentLikeService implements CommentLikeUseCase { + + private final CommentCommandPort commentCommandPort; + private final CommentLikeQueryPort commentLikeQueryPort; + private final CommentLikeCommandPort commentLikeCommandPort; + + @Override + @Transactional + public CommentIsLikeResult changeLikeStatusComment(CommentIsLikeCommand command) { + + // 1. 댓글 조회 및 검증 (존재 여부) + Comment comment = commentCommandPort.getByIdOrThrow(command.commentId()); + + // 2. 유저가 좋아요 한 댓글 목록 조회 + LikedComments likedComments = commentLikeQueryPort.findLikedCommentsByUserId(command.userId()); + + // 3. 좋아요 상태변경 + if (command.isLike()) { + // 좋아요 요청 시 이미 좋아요되어 있으면 예외 발생 + likedComments.validateNotAlreadyLiked(comment); + commentLikeCommandPort.save(command.userId(),comment.getId()); + } else { + // 좋아요 취소 요청 시 좋아요되어 있지 않으면 예외 발생 + likedComments.validateCanUnlike(comment); + commentLikeCommandPort.delete(command.userId(),comment.getId()); + } + + // 4. 댓글 좋아요 수 업데이트 + comment.updateLikeCount(command.isLike()); + commentCommandPort.update(comment); + + return CommentIsLikeResult.of(comment.getId(), command.isLike()); + } +} From 0016a7dbbef7bbdddc8b4df5212d9d491c0f2d42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 21:45:25 +0900 Subject: [PATCH 50/82] =?UTF-8?q?[remove]=20=EB=8D=94=EB=AF=B8=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=82=AD=EC=A0=9C=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/application/port/in/dto/DummyResult.java | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 src/main/java/konkuk/thip/comment/application/port/in/dto/DummyResult.java diff --git a/src/main/java/konkuk/thip/comment/application/port/in/dto/DummyResult.java b/src/main/java/konkuk/thip/comment/application/port/in/dto/DummyResult.java deleted file mode 100644 index 792cae3fe..000000000 --- a/src/main/java/konkuk/thip/comment/application/port/in/dto/DummyResult.java +++ /dev/null @@ -1,10 +0,0 @@ -package konkuk.thip.comment.application.port.in.dto; - -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class DummyResult { - -} From 8aa574f04a1e47bc945593be41905a63b997e670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 21:45:52 +0900 Subject: [PATCH 51/82] =?UTF-8?q?[refactor]=20request=20boolean=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EC=88=98=EC=A0=95=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/feed/adapter/in/web/request/FeedIsSavedRequest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/feed/adapter/in/web/request/FeedIsSavedRequest.java b/src/main/java/konkuk/thip/feed/adapter/in/web/request/FeedIsSavedRequest.java index efb6c0f50..fb0f290e0 100644 --- a/src/main/java/konkuk/thip/feed/adapter/in/web/request/FeedIsSavedRequest.java +++ b/src/main/java/konkuk/thip/feed/adapter/in/web/request/FeedIsSavedRequest.java @@ -5,7 +5,7 @@ public record FeedIsSavedRequest( @NotNull(message = "type은 필수입니다.") - boolean type + Boolean type ) { public static FeedIsSavedCommand toCommand(Long userId, Long feedId, Boolean type) { return new FeedIsSavedCommand(userId, feedId, type); From bd1a31f4a67e1467b844fed33a5b7692d67984a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 21:46:12 +0900 Subject: [PATCH 52/82] =?UTF-8?q?[test]=20createCommentLike=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=9E=91=EC=84=B1=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/konkuk/thip/common/util/TestEntityFactory.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/test/java/konkuk/thip/common/util/TestEntityFactory.java b/src/test/java/konkuk/thip/common/util/TestEntityFactory.java index 0e12f6435..dee138ec5 100644 --- a/src/test/java/konkuk/thip/common/util/TestEntityFactory.java +++ b/src/test/java/konkuk/thip/common/util/TestEntityFactory.java @@ -2,6 +2,7 @@ import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; import konkuk.thip.comment.adapter.out.jpa.CommentJpaEntity; +import konkuk.thip.comment.adapter.out.jpa.CommentLikeJpaEntity; import konkuk.thip.common.post.PostType; import konkuk.thip.feed.adapter.out.jpa.ContentJpaEntity; import konkuk.thip.feed.adapter.out.jpa.FeedJpaEntity; @@ -170,6 +171,13 @@ public static CommentJpaEntity createComment(PostJpaEntity post, UserJpaEntity u .build(); } + public static CommentLikeJpaEntity createCommentLike(CommentJpaEntity comment, UserJpaEntity user) { + return CommentLikeJpaEntity.builder() + .userJpaEntity(user) + .commentJpaEntity(comment) + .build(); + } + public static FollowingJpaEntity createFollowing(UserJpaEntity followerUser,UserJpaEntity followingUser) { return FollowingJpaEntity.builder() .userJpaEntity(followerUser) From 91b34f2a8c38c09b6b9e56923bb3676f4eedbf72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 21:46:29 +0900 Subject: [PATCH 53/82] =?UTF-8?q?[test]=20Comment=20=EC=A2=8B=EC=95=84?= =?UTF-8?q?=EC=9A=94=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/comment/domain/CommentTest.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/test/java/konkuk/thip/comment/domain/CommentTest.java b/src/test/java/konkuk/thip/comment/domain/CommentTest.java index cbd213c95..586ae0051 100644 --- a/src/test/java/konkuk/thip/comment/domain/CommentTest.java +++ b/src/test/java/konkuk/thip/comment/domain/CommentTest.java @@ -4,6 +4,7 @@ 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.post.PostType.FEED; import static org.junit.jupiter.api.Assertions.*; @@ -133,4 +134,45 @@ void createReplyComment_parentPostMismatch_shouldFail() { assertEquals("댓글과 부모 댓글의 게시글이 일치하지 않습니다.", ex.getCause().getMessage()); } + @Test + @DisplayName("updateLikeCount: like == true 면 likeCount 가 1씩 증가한다.") + void updateLikeCount_likeTrue_increments() { + Comment comment = createParentComment(POST_ID); + + comment.updateLikeCount(true); + assertEquals(1, comment.getLikeCount()); + + comment.updateLikeCount(true); + assertEquals(2, comment.getLikeCount()); + } + + @Test + @DisplayName("updateLikeCount: like == false 면 likeCount 가 1씩 감소한다.") + void updateLikeCount_likeFalse_decrements() { + Comment comment = createParentComment(POST_ID); + // 먼저 likeCount 증가 셋업 + comment.updateLikeCount(true); + comment.updateLikeCount(true); + assertEquals(2, comment.getLikeCount()); + + comment.updateLikeCount(false); + assertEquals(1, comment.getLikeCount()); + + comment.updateLikeCount(false); + assertEquals(0, comment.getLikeCount()); + } + + @Test + @DisplayName("updateLikeCount: like == false 면 likeCount 가 0 이하로 내려가면 InvalidStateException이 발생한다.") + void updateLikeCount_likeFalse_underflow_throws() { + Comment comment = createParentComment(POST_ID); + assertEquals(0, comment.getLikeCount()); + + InvalidStateException ex = assertThrows(InvalidStateException.class, () -> { + comment.updateLikeCount(false); + }); + + assertEquals(COMMENT_LIKE_COUNT_UNDERFLOW, ex.getErrorCode()); + } + } From ec1c60c270f63ca5d8c595ff21d64fdea23e1f55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 21:46:40 +0900 Subject: [PATCH 54/82] =?UTF-8?q?[test]=20=EB=8C=93=EA=B8=80=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20=EC=83=81=ED=83=9C=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/CommentChangeLikeStatusAPITest.java | 193 ++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 src/test/java/konkuk/thip/comment/adapter/in/web/CommentChangeLikeStatusAPITest.java diff --git a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentChangeLikeStatusAPITest.java b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentChangeLikeStatusAPITest.java new file mode 100644 index 000000000..d38ba405c --- /dev/null +++ b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentChangeLikeStatusAPITest.java @@ -0,0 +1,193 @@ +package konkuk.thip.comment.adapter.in.web; + +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.in.web.request.CommentIsLikeRequest; +import konkuk.thip.comment.adapter.out.jpa.CommentJpaEntity; +import konkuk.thip.comment.adapter.out.jpa.CommentLikeJpaEntity; +import konkuk.thip.comment.adapter.out.persistence.repository.CommentJpaRepository; +import konkuk.thip.comment.adapter.out.persistence.repository.CommentLikeJpaRepository; +import konkuk.thip.common.exception.code.ErrorCode; +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.room.adapter.out.jpa.CategoryJpaEntity; +import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; +import konkuk.thip.room.adapter.out.persistence.repository.RoomJpaRepository; +import konkuk.thip.room.adapter.out.persistence.repository.category.CategoryJpaRepository; +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 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.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static konkuk.thip.common.post.PostType.FEED; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@ActiveProfiles("test") +@Transactional +@AutoConfigureMockMvc(addFilters = false) +@DisplayName("[통합] 댓글 좋아요 상태 변경 api 통합 테스트") +class CommentChangeLikeStatusAPITest { + + @Autowired + private MockMvc mockMvc; + + @Autowired private ObjectMapper objectMapper; + @Autowired private AliasJpaRepository aliasJpaRepository; + @Autowired private UserJpaRepository userJpaRepository; + @Autowired private CategoryJpaRepository categoryJpaRepository; + @Autowired private BookJpaRepository bookJpaRepository; + @Autowired private FeedJpaRepository feedJpaRepository; + @Autowired private CommentJpaRepository commentJpaRepository; + @Autowired private CommentLikeJpaRepository commentLikeJpaRepository; + @Autowired private RoomJpaRepository roomJpaRepository; + + + private AliasJpaEntity alias; + private UserJpaEntity user; + private CategoryJpaEntity category; + private FeedJpaEntity feed; + private BookJpaEntity book; + private RoomJpaEntity room; + private CommentJpaEntity comment; + + @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)); + comment = commentJpaRepository.save(TestEntityFactory.createComment(feed,user, FEED)); + } + + private static final String COMMENT_LIKE_API_PATH = "/comments/{commentId}/likes"; + + + @Test + @DisplayName("댓글을 처음 좋아요하면 [좋아요 성공]") + void likeComment_success() throws Exception { + + // given + CommentIsLikeRequest request = new CommentIsLikeRequest(true); + + // when + mockMvc.perform(post(COMMENT_LIKE_API_PATH, comment.getCommentId()) + .requestAttr("userId", user.getUserId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.commentId").value(comment.getCommentId())) + .andExpect(jsonPath("$.data.isLiked").value(true)); + + // then + // 1. 좋아요 저장 여부 확인 + List likedComments = commentLikeJpaRepository.findAllByUserId(user.getUserId()); + boolean exists = likedComments.stream() + .anyMatch(entity -> entity.getCommentJpaEntity().getCommentId().equals(comment.getCommentId())); + assertThat(exists).isTrue(); + + // 2. 댓글 좋아요 수가 1 증가했는지 확인 + CommentJpaEntity updatedComment = commentJpaRepository.findById(comment.getCommentId()).orElse(null); + assertThat(updatedComment.getLikeCount()).isEqualTo(1); + } + + @Test + @DisplayName("이미 좋아요한 댓글을 다시 좋아요하려면 [400 에러 발생]") + void likeComment_alreadyLiked_fail() throws Exception { + + // given + commentLikeJpaRepository.save(TestEntityFactory.createCommentLike(comment,user)); + CommentIsLikeRequest request = new CommentIsLikeRequest(true); + + // when & then + mockMvc.perform(post(COMMENT_LIKE_API_PATH, comment.getCommentId()) + .requestAttr("userId", user.getUserId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(ErrorCode.COMMENT_ALREADY_LIKED.getCode())); + } + + @Test + @DisplayName("댓글을 좋아요한 이후 좋아요 취소 요청하면 [좋아요 취소 성공]") + void unlikeComment_success() throws Exception { + + // given + commentLikeJpaRepository.save(TestEntityFactory.createCommentLike(comment,user)); + comment.updateLikeCount(1); + CommentIsLikeRequest request = new CommentIsLikeRequest(false); + + mockMvc.perform(post(COMMENT_LIKE_API_PATH, comment.getCommentId()) + .requestAttr("userId", user.getUserId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.commentId").value(comment.getCommentId())) + .andExpect(jsonPath("$.data.isLiked").value(false)); + + // then + // 1. 좋아요 삭제 여부 확인 + List likedComments = commentLikeJpaRepository.findAllByUserId(user.getUserId()); + boolean exists = likedComments.stream() + .anyMatch(entity -> entity.getCommentJpaEntity().getCommentId().equals(comment.getCommentId())); + assertThat(exists).isFalse(); + + // 2. 댓글 좋아요 수가 0으로 감소했는지 확인 + CommentJpaEntity updatedComment = commentJpaRepository.findById(comment.getCommentId()).orElse(null); + assertThat(updatedComment.getLikeCount()).isEqualTo(0); + } + + @Test + @DisplayName("좋아요하지 않은 댓글을 좋아요 취소하려고 하면 [400 에러 발생]") + void unlikeComment_notLiked_fail() throws Exception { + // given + CommentIsLikeRequest request = new CommentIsLikeRequest(false); + + // when & then + mockMvc.perform(post(COMMENT_LIKE_API_PATH, comment.getCommentId()) + .requestAttr("userId", user.getUserId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(ErrorCode.COMMENT_NOT_LIKED_CANNOT_CANCEL.getCode())); + } + + + @Test + @DisplayName("존재하지 않는 댓글 좋아요 요청 시 [404 에러 발생]") + void likeComment_commentNotFound_fail() throws Exception { + + // given + Long invalidCommentId = 999999L; + CommentIsLikeRequest request = new CommentIsLikeRequest(true); + + // when + mockMvc.perform(post(COMMENT_LIKE_API_PATH, invalidCommentId) + .requestAttr("userId", user.getUserId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(ErrorCode.COMMENT_NOT_FOUND.getCode())); + } + + +} From 4e77647445f77f6c0c2fbe7615f9bd8dfa9e9403 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 21:47:40 +0900 Subject: [PATCH 55/82] =?UTF-8?q?[fix]=20=EB=88=84=EB=9D=BD=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80=20(#9?= =?UTF-8?q?7)?= 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 e7a6bebd5..165d79d53 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 @@ -36,6 +36,7 @@ public class CommentJpaEntity extends BaseJpaEntity { @JoinColumn(name = "post_id", nullable = false) private PostJpaEntity postJpaEntity; + @Enumerated(EnumType.STRING) @Column(name = "post_type", nullable = false, length = 10) private PostType postType; From 39cb5766e3136b5bbae0c8d2ed4b397586647a23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Thu, 24 Jul 2025 22:08:25 +0900 Subject: [PATCH 56/82] =?UTF-8?q?[refactor]=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=B0=98=EC=98=81=20=EB=A6=AC=ED=8E=99=ED=86=A0=EB=A7=81=20(#9?= =?UTF-8?q?9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/comment/adapter/in/web/CommentCommandController.java | 2 +- .../comment/adapter/in/web/request/CommentIsLikeRequest.java | 2 +- .../out/persistence/CommentCommandPersistenceAdapter.java | 2 +- .../comment/application/port/in/dto/CommentIsLikeCommand.java | 2 +- 4 files changed, 4 insertions(+), 4 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 131b84c44..8898571f1 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 @@ -36,7 +36,7 @@ public BaseResponse createComment(@RequestBody @Valid final C public BaseResponse likeComment(@RequestBody @Valid final CommentIsLikeRequest request, @PathVariable("commentId") final Long commentId, @UserId final Long userId) { - return BaseResponse.ok(CommentIsLikeResponse.of(commentLikeUseCase.changeLikeStatusComment(CommentIsLikeRequest.toCommand(userId, commentId,request.type())))); + return BaseResponse.ok(CommentIsLikeResponse.of(commentLikeUseCase.changeLikeStatusComment(request.toCommand(userId, commentId)))); } } diff --git a/src/main/java/konkuk/thip/comment/adapter/in/web/request/CommentIsLikeRequest.java b/src/main/java/konkuk/thip/comment/adapter/in/web/request/CommentIsLikeRequest.java index 8034f4657..72da84898 100644 --- a/src/main/java/konkuk/thip/comment/adapter/in/web/request/CommentIsLikeRequest.java +++ b/src/main/java/konkuk/thip/comment/adapter/in/web/request/CommentIsLikeRequest.java @@ -7,7 +7,7 @@ public record CommentIsLikeRequest( @NotNull(message = "좋아요 여부는 필수입니다.") Boolean type ) { - public static CommentIsLikeCommand toCommand(Long userId, Long commentId, Boolean type) { + public CommentIsLikeCommand toCommand(Long userId, Long commentId) { return new CommentIsLikeCommand(userId, commentId, type); } } 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 e9a44a1bd..d19de9d45 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 @@ -75,7 +75,7 @@ public Optional findById(Long id) { public void update(Comment comment) { CommentJpaEntity commentJpaEntity = commentJpaRepository.findById(comment.getId()).orElseThrow( - () -> new EntityNotFoundException(ROOM_NOT_FOUND) + () -> new EntityNotFoundException(COMMENT_NOT_FOUND) ); commentJpaRepository.save(commentJpaEntity.updateFrom(comment)); diff --git a/src/main/java/konkuk/thip/comment/application/port/in/dto/CommentIsLikeCommand.java b/src/main/java/konkuk/thip/comment/application/port/in/dto/CommentIsLikeCommand.java index fcbb2c617..17e279f57 100644 --- a/src/main/java/konkuk/thip/comment/application/port/in/dto/CommentIsLikeCommand.java +++ b/src/main/java/konkuk/thip/comment/application/port/in/dto/CommentIsLikeCommand.java @@ -6,7 +6,7 @@ public record CommentIsLikeCommand( Long commentId, - Boolean isLike + boolean isLike ) { } From 04e17d15ad278bb471cb67e91fe0c91c25c20da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Fri, 25 Jul 2025 18:47:24 +0900 Subject: [PATCH 57/82] =?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=20CommentLike=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=EC=9D=B4=20=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/comment/domain/CommentLike.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/main/java/konkuk/thip/comment/domain/CommentLike.java b/src/main/java/konkuk/thip/comment/domain/CommentLike.java index 141e022bf..ea3487821 100644 --- a/src/main/java/konkuk/thip/comment/domain/CommentLike.java +++ b/src/main/java/konkuk/thip/comment/domain/CommentLike.java @@ -1,9 +1,13 @@ package konkuk.thip.comment.domain; import konkuk.thip.common.entity.BaseDomainEntity; +import konkuk.thip.common.exception.InvalidStateException; import lombok.Getter; import lombok.experimental.SuperBuilder; +import static konkuk.thip.common.exception.code.ErrorCode.COMMENT_ALREADY_LIKED; +import static konkuk.thip.common.exception.code.ErrorCode.COMMENT_NOT_LIKED_CANNOT_CANCEL; + @Getter @SuperBuilder public class CommentLike extends BaseDomainEntity { @@ -13,4 +17,19 @@ public class CommentLike extends BaseDomainEntity { private Long userId; private Long targetCommentId; + + // 좋아요 생성 가능 여부 검증 (이미 좋아요한 상태면 예외) + public static void validateCanLike(boolean alreadyLiked) { + if (alreadyLiked) { + throw new InvalidStateException(COMMENT_ALREADY_LIKED); + } + } + + // 좋아요 취소 가능 여부 검증 (좋아요 안 한 상태면 예외) + public static void validateCanUnlike(boolean alreadyLiked) { + if (!alreadyLiked) { + throw new InvalidStateException(COMMENT_NOT_LIKED_CANNOT_CANCEL); + } + } + } From 343b12c362b1fe85028ec770eba211b5857c5449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Fri, 25 Jul 2025 18:47:43 +0900 Subject: [PATCH 58/82] [refactor] CommentLikeJpaRepository.existsByUserIdAndCommentId (#99) --- .../out/persistence/repository/CommentLikeJpaRepository.java | 3 +++ 1 file changed, 3 insertions(+) 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 2df1577be..1ec42c080 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 @@ -15,4 +15,7 @@ public interface CommentLikeJpaRepository extends JpaRepository Date: Fri, 25 Jul 2025 18:49:13 +0900 Subject: [PATCH 59/82] =?UTF-8?q?[refactor]=20=EC=9D=BC=EA=B8=89=EC=BB=AC?= =?UTF-8?q?=EB=A0=89=EC=85=98=20=EC=A1=B0=ED=9A=8C=20=ED=95=98=EC=A7=80?= =?UTF-8?q?=EC=95=8A=EA=B3=A0=20=EB=8B=A8=EC=88=9C=20=ED=95=B4=EB=8B=B9?= =?UTF-8?q?=EB=8C=93=EA=B8=80=EC=97=90=EB=8C=80=ED=95=B4=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=EC=97=AC=EB=B6=80=EB=A7=8C=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=ED=95=9C=EB=92=A4=20CommentLike=EA=B0=80=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=A6=AC=ED=8E=99=20(#9?= =?UTF-8?q?9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommentLikeQueryPersistenceAdapter.java | 24 +---------- .../port/out/CommentLikeQueryPort.java | 4 +- .../service/CommentLikeService.java | 18 ++++---- .../thip/comment/domain/LikedComments.java | 42 ------------------- .../thip/common/exception/code/ErrorCode.java | 1 - 5 files changed, 11 insertions(+), 78 deletions(-) delete mode 100644 src/main/java/konkuk/thip/comment/domain/LikedComments.java 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 02fb9d2b6..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 @@ -1,23 +1,13 @@ package konkuk.thip.comment.adapter.out.persistence; -import konkuk.thip.comment.adapter.out.jpa.CommentLikeJpaEntity; import konkuk.thip.comment.adapter.out.mapper.CommentLikeMapper; import konkuk.thip.comment.adapter.out.mapper.CommentMapper; import konkuk.thip.comment.adapter.out.persistence.repository.CommentLikeJpaRepository; import konkuk.thip.comment.application.port.out.CommentLikeQueryPort; -import konkuk.thip.comment.domain.Comment; -import konkuk.thip.comment.domain.LikedComments; -import konkuk.thip.common.exception.EntityNotFoundException; -import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; -import java.util.List; -import java.util.stream.Collectors; - -import static konkuk.thip.common.exception.code.ErrorCode.USER_NOT_FOUND; - @Repository @RequiredArgsConstructor public class CommentLikeQueryPersistenceAdapter implements CommentLikeQueryPort { @@ -28,17 +18,7 @@ public class CommentLikeQueryPersistenceAdapter implements CommentLikeQueryPort private final CommentLikeMapper commentLikeMapper; @Override - public LikedComments findLikedCommentsByUserId(Long userId) { - - UserJpaEntity user = userJpaRepository.findById(userId) - .orElseThrow(() -> new EntityNotFoundException(USER_NOT_FOUND)); - - List likedCommentEntities = commentLikeJpaRepository.findAllByUserId((user.getUserId())); - - List comments = likedCommentEntities.stream() - .map(entity -> commentMapper.toDomainEntity(entity.getCommentJpaEntity())) - .collect(Collectors.toList()); - - return new LikedComments(comments); + public boolean isLikedCommentByUser(Long userId, Long commentId) { + return commentLikeJpaRepository.existsByUserIdAndCommentId(userId, commentId); } } diff --git a/src/main/java/konkuk/thip/comment/application/port/out/CommentLikeQueryPort.java b/src/main/java/konkuk/thip/comment/application/port/out/CommentLikeQueryPort.java index f20c0df07..9de640d96 100644 --- a/src/main/java/konkuk/thip/comment/application/port/out/CommentLikeQueryPort.java +++ b/src/main/java/konkuk/thip/comment/application/port/out/CommentLikeQueryPort.java @@ -1,7 +1,5 @@ package konkuk.thip.comment.application.port.out; -import konkuk.thip.comment.domain.LikedComments; - public interface CommentLikeQueryPort { - LikedComments findLikedCommentsByUserId(Long userId); + boolean isLikedCommentByUser(Long userId, Long commentId); } 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 1cf1170f2..56ceefb69 100644 --- a/src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java +++ b/src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java @@ -8,7 +8,7 @@ import konkuk.thip.comment.application.port.out.CommentLikeCommandPort; import konkuk.thip.comment.application.port.out.CommentLikeQueryPort; import konkuk.thip.comment.domain.Comment; -import konkuk.thip.comment.domain.LikedComments; +import konkuk.thip.comment.domain.CommentLike; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -27,21 +27,19 @@ public CommentIsLikeResult changeLikeStatusComment(CommentIsLikeCommand command) // 1. 댓글 조회 및 검증 (존재 여부) Comment comment = commentCommandPort.getByIdOrThrow(command.commentId()); - // 2. 유저가 좋아요 한 댓글 목록 조회 - LikedComments likedComments = commentLikeQueryPort.findLikedCommentsByUserId(command.userId()); + // 2. 유저가 해당 댓글에 대해 좋아요 했는지 조회 + boolean alreadyLiked = commentLikeQueryPort.isLikedCommentByUser(command.userId(), command.commentId()); // 3. 좋아요 상태변경 if (command.isLike()) { - // 좋아요 요청 시 이미 좋아요되어 있으면 예외 발생 - likedComments.validateNotAlreadyLiked(comment); - commentLikeCommandPort.save(command.userId(),comment.getId()); + CommentLike.validateCanLike(alreadyLiked); // 좋아요 가능 여부 검증 + commentLikeCommandPort.save(command.userId(), command.commentId()); } else { - // 좋아요 취소 요청 시 좋아요되어 있지 않으면 예외 발생 - likedComments.validateCanUnlike(comment); - commentLikeCommandPort.delete(command.userId(),comment.getId()); + CommentLike.validateCanUnlike(alreadyLiked); // 좋아요 취소 가능 여부 검증 + commentLikeCommandPort.delete(command.userId(), command.commentId()); } - // 4. 댓글 좋아요 수 업데이트 + // 5. 댓글 좋아요 수 업데이트 comment.updateLikeCount(command.isLike()); commentCommandPort.update(comment); diff --git a/src/main/java/konkuk/thip/comment/domain/LikedComments.java b/src/main/java/konkuk/thip/comment/domain/LikedComments.java deleted file mode 100644 index b1fb1846a..000000000 --- a/src/main/java/konkuk/thip/comment/domain/LikedComments.java +++ /dev/null @@ -1,42 +0,0 @@ -package konkuk.thip.comment.domain; - -import konkuk.thip.common.exception.InvalidStateException; -import lombok.Getter; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import static konkuk.thip.common.exception.code.ErrorCode.*; - -@Getter -public class LikedComments { - - private final Set comments; - - public LikedComments(List comments) { - Set commentSet = new HashSet<>(comments); - if (commentSet.size() != comments.size()) { - throw new InvalidStateException(DUPLICATED_COMMENTS_IN_COLLECTION); - } - this.comments = Collections.unmodifiableSet(commentSet); - } - - // 중복 좋아요 검증 - public void validateNotAlreadyLiked(Comment comment) { - if (comments.contains(comment)) { - throw new InvalidStateException(COMMENT_ALREADY_LIKED); - } - } - - // 좋아요 취소 가능 여부 검증 - public void validateCanUnlike(Comment comment) { - if (!comments.contains(comment)) { - throw new InvalidStateException(COMMENT_NOT_LIKED_CANNOT_CANCEL); - } - } - -} - - 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 f381c2f67..54bab4d80 100644 --- a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java +++ b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java @@ -157,7 +157,6 @@ 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, "사용자가 좋아요하지 않은 댓글은 좋아요 취소 할 수 없습니다."), - DUPLICATED_COMMENTS_IN_COLLECTION(HttpStatus.INTERNAL_SERVER_ERROR, 190005, "중복된 댓글이 존재합니다."), ; private final HttpStatus httpStatus; From 7b824bfdb109387b815e699126c8f2e971ff323e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Fri, 25 Jul 2025 23:01:41 +0900 Subject: [PATCH 60/82] =?UTF-8?q?[refactor]=20comment=EA=B0=80=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=A6=AC=ED=8E=99=20(#9?= =?UTF-8?q?9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CommentLikeService.java | 5 ++--- .../konkuk/thip/comment/domain/Comment.java | 18 ++++++++++++++++-- .../thip/comment/domain/CommentLike.java | 19 ------------------- 3 files changed, 18 insertions(+), 24 deletions(-) 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 56ceefb69..dfc909aac 100644 --- a/src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java +++ b/src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java @@ -8,7 +8,6 @@ import konkuk.thip.comment.application.port.out.CommentLikeCommandPort; import konkuk.thip.comment.application.port.out.CommentLikeQueryPort; import konkuk.thip.comment.domain.Comment; -import konkuk.thip.comment.domain.CommentLike; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -32,10 +31,10 @@ public CommentIsLikeResult changeLikeStatusComment(CommentIsLikeCommand command) // 3. 좋아요 상태변경 if (command.isLike()) { - CommentLike.validateCanLike(alreadyLiked); // 좋아요 가능 여부 검증 + comment.validateCanLike(alreadyLiked); // 좋아요 가능 여부 검증 commentLikeCommandPort.save(command.userId(), command.commentId()); } else { - CommentLike.validateCanUnlike(alreadyLiked); // 좋아요 취소 가능 여부 검증 + comment.validateCanUnlike(alreadyLiked); // 좋아요 취소 가능 여부 검증 commentLikeCommandPort.delete(command.userId(), command.commentId()); } diff --git a/src/main/java/konkuk/thip/comment/domain/Comment.java b/src/main/java/konkuk/thip/comment/domain/Comment.java index 076e3aeb7..865b73292 100644 --- a/src/main/java/konkuk/thip/comment/domain/Comment.java +++ b/src/main/java/konkuk/thip/comment/domain/Comment.java @@ -9,8 +9,8 @@ import java.util.Objects; -import static konkuk.thip.common.exception.code.ErrorCode.COMMENT_LIKE_COUNT_UNDERFLOW; -import static konkuk.thip.common.exception.code.ErrorCode.INVALID_COMMENT_CREATE; +import static konkuk.thip.common.exception.code.ErrorCode.*; +import static konkuk.thip.common.exception.code.ErrorCode.COMMENT_NOT_LIKED_CANNOT_CANCEL; @Getter @SuperBuilder @@ -127,4 +127,18 @@ private void checkLikeCountNotUnderflow() { throw new InvalidStateException(COMMENT_LIKE_COUNT_UNDERFLOW); } } + + // 좋아요 생성 가능 여부 검증 (이미 좋아요한 상태면 예외) + public void validateCanLike(boolean alreadyLiked) { + if (alreadyLiked) { + throw new InvalidStateException(COMMENT_ALREADY_LIKED); + } + } + + // 좋아요 취소 가능 여부 검증 (좋아요 안 한 상태면 예외) + public void validateCanUnlike(boolean alreadyLiked) { + if (!alreadyLiked) { + throw new InvalidStateException(COMMENT_NOT_LIKED_CANNOT_CANCEL); + } + } } diff --git a/src/main/java/konkuk/thip/comment/domain/CommentLike.java b/src/main/java/konkuk/thip/comment/domain/CommentLike.java index ea3487821..141e022bf 100644 --- a/src/main/java/konkuk/thip/comment/domain/CommentLike.java +++ b/src/main/java/konkuk/thip/comment/domain/CommentLike.java @@ -1,13 +1,9 @@ package konkuk.thip.comment.domain; import konkuk.thip.common.entity.BaseDomainEntity; -import konkuk.thip.common.exception.InvalidStateException; import lombok.Getter; import lombok.experimental.SuperBuilder; -import static konkuk.thip.common.exception.code.ErrorCode.COMMENT_ALREADY_LIKED; -import static konkuk.thip.common.exception.code.ErrorCode.COMMENT_NOT_LIKED_CANNOT_CANCEL; - @Getter @SuperBuilder public class CommentLike extends BaseDomainEntity { @@ -17,19 +13,4 @@ public class CommentLike extends BaseDomainEntity { private Long userId; private Long targetCommentId; - - // 좋아요 생성 가능 여부 검증 (이미 좋아요한 상태면 예외) - public static void validateCanLike(boolean alreadyLiked) { - if (alreadyLiked) { - throw new InvalidStateException(COMMENT_ALREADY_LIKED); - } - } - - // 좋아요 취소 가능 여부 검증 (좋아요 안 한 상태면 예외) - public static void validateCanUnlike(boolean alreadyLiked) { - if (!alreadyLiked) { - throw new InvalidStateException(COMMENT_NOT_LIKED_CANNOT_CANCEL); - } - } - } From 2eacfd84960470786bfd5f15c797573d0a5c5170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 26 Jul 2025 18:07:31 +0900 Subject: [PATCH 61/82] =?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(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/CommentControllerTest.java | 4 ++++ .../adapter/in/web/CommentCreateAPITest.java | 5 +++++ .../thip/common/util/TestEntityFactory.java | 2 +- .../adapter/in/web/FeedUpdateControllerTest.java | 4 ++-- .../adapter/in/web/RoomCloseJoinApiTest.java | 4 ++-- .../in/web/RoomGetHomeJoinedRoomsApiTest.java | 16 ++++++++-------- .../adapter/in/web/RoomGetMemberListApiTest.java | 8 ++++---- .../room/adapter/in/web/RoomJoinApiTest.java | 6 +++--- 8 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentControllerTest.java b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentControllerTest.java index 0c23c14a9..25c4e9ed4 100644 --- a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentControllerTest.java +++ b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentControllerTest.java @@ -12,8 +12,10 @@ 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; @@ -62,6 +64,7 @@ class CommentControllerTest { @Autowired private RecordJpaRepository recordJpaRepository; @Autowired private CommentJpaRepository commentJpaRepository; @Autowired private RoomJpaRepository roomJpaRepository; + @Autowired private RoomParticipantJpaRepository roomParticipantJpaRepository; private AliasJpaEntity alias; @@ -83,6 +86,7 @@ void setUp() { 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)); } diff --git a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentCreateAPITest.java b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentCreateAPITest.java index 3e529079f..50130c467 100644 --- a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentCreateAPITest.java +++ b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentCreateAPITest.java @@ -11,8 +11,10 @@ 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; @@ -58,6 +60,8 @@ class CommentCreateAPITest { @Autowired private RecordJpaRepository recordJpaRepository; @Autowired private CommentJpaRepository commentJpaRepository; @Autowired private RoomJpaRepository roomJpaRepository; + @Autowired private RoomParticipantJpaRepository roomParticipantJpaRepository; + private AliasJpaEntity alias; @@ -79,6 +83,7 @@ void setUp() { 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)); } // 공통 JSON 생성 함수 diff --git a/src/test/java/konkuk/thip/common/util/TestEntityFactory.java b/src/test/java/konkuk/thip/common/util/TestEntityFactory.java index 0e12f6435..870c6784c 100644 --- a/src/test/java/konkuk/thip/common/util/TestEntityFactory.java +++ b/src/test/java/konkuk/thip/common/util/TestEntityFactory.java @@ -125,7 +125,7 @@ public static RoomJpaEntity createCustomRoom(BookJpaEntity book, CategoryJpaEnti .build(); } - public static RoomParticipantJpaEntity createUserRoom(RoomJpaEntity room, UserJpaEntity user, RoomParticipantRole roomParticipantRole, double userPercentage) { + public static RoomParticipantJpaEntity createRoomParticipant(RoomJpaEntity room, UserJpaEntity user, RoomParticipantRole roomParticipantRole, double userPercentage) { return RoomParticipantJpaEntity.builder() .userJpaEntity(user) .roomJpaEntity(room) diff --git a/src/test/java/konkuk/thip/feed/adapter/in/web/FeedUpdateControllerTest.java b/src/test/java/konkuk/thip/feed/adapter/in/web/FeedUpdateControllerTest.java index 1b466ed7b..fec5d80e1 100644 --- a/src/test/java/konkuk/thip/feed/adapter/in/web/FeedUpdateControllerTest.java +++ b/src/test/java/konkuk/thip/feed/adapter/in/web/FeedUpdateControllerTest.java @@ -118,8 +118,8 @@ void unauthorizedFeedEdit() throws Exception { .content(objectMapper.writeValueAsBytes(req)) ) .andExpect(status().isForbidden()) - .andExpect(jsonPath("$.code").value(FEED_UPDATE_FORBIDDEN.getCode())) - .andExpect(jsonPath("$.message", containsString("피드 수정 권한이 없습니다."))); + .andExpect(jsonPath("$.code").value(FEED_ACCESS_FORBIDDEN.getCode())) + .andExpect(jsonPath("$.message", containsString("피드 접근 권한이 없습니다."))); } } diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomCloseJoinApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomCloseJoinApiTest.java index e1741a92c..a7bca2dfc 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomCloseJoinApiTest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomCloseJoinApiTest.java @@ -96,8 +96,8 @@ void setup() { .categoryJpaEntity(category) .build()); - roomParticipantJpaRepository.save(TestEntityFactory.createUserRoom(room, host, RoomParticipantRole.HOST, 0.0)); - roomParticipantJpaRepository.save(TestEntityFactory.createUserRoom(room, member, RoomParticipantRole.MEMBER, 0.0)); + roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room, host, RoomParticipantRole.HOST, 0.0)); + roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room, member, RoomParticipantRole.MEMBER, 0.0)); } @AfterEach diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetHomeJoinedRoomsApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetHomeJoinedRoomsApiTest.java index 058cdd1e7..cf5f2ff6f 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetHomeJoinedRoomsApiTest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetHomeJoinedRoomsApiTest.java @@ -84,11 +84,11 @@ void setUp() { room2 = roomJpaRepository.save(TestEntityFactory.createRoom(book, category)); // 1번방에 유저 1이 호스트, 유저2가 멤버 - roomParticipantJpaRepository.save(TestEntityFactory.createUserRoom(room1,user1, RoomParticipantRole.HOST, 80.0)); - roomParticipantJpaRepository.save(TestEntityFactory.createUserRoom(room1,user2, RoomParticipantRole.MEMBER, 60.0)); + roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room1,user1, RoomParticipantRole.HOST, 80.0)); + roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room1,user2, RoomParticipantRole.MEMBER, 60.0)); // 2번방에 유저 1이 호스트 - roomParticipantJpaRepository.save(TestEntityFactory.createUserRoom(room2,user1, RoomParticipantRole.HOST,60.0)); + roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room2,user1, RoomParticipantRole.HOST,60.0)); } @AfterEach @@ -151,9 +151,9 @@ void getHomeJoinedRooms_sortByStartDateWhenUserPercentageEquals() throws Excepti ); // 모두 동일한 진행률(70%)로 참여 - roomParticipantJpaRepository.save(TestEntityFactory.createUserRoom(room1, newUser, RoomParticipantRole.MEMBER, 70.0)); - roomParticipantJpaRepository.save(TestEntityFactory.createUserRoom(room2, newUser, RoomParticipantRole.MEMBER, 70.0)); - roomParticipantJpaRepository.save(TestEntityFactory.createUserRoom(room3, newUser, RoomParticipantRole.MEMBER, 70.0)); + roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room1, newUser, RoomParticipantRole.MEMBER, 70.0)); + roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room2, newUser, RoomParticipantRole.MEMBER, 70.0)); + roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room3, newUser, RoomParticipantRole.MEMBER, 70.0)); Long userId = newUser.getUserId(); @@ -194,8 +194,8 @@ void getHomeJoinedRooms_excludeRecruitingRooms() throws Exception { TestEntityFactory.createCustomRoom(book, category, LocalDate.now().minusDays(1), LocalDate.now().plusDays(2)) ); - roomParticipantJpaRepository.save(TestEntityFactory.createUserRoom(recruitRoom, newUser, RoomParticipantRole.MEMBER, 20.0)); - roomParticipantJpaRepository.save(TestEntityFactory.createUserRoom(activeRoom, newUser, RoomParticipantRole.MEMBER, 50.0)); + roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(recruitRoom, newUser, RoomParticipantRole.MEMBER, 20.0)); + roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(activeRoom, newUser, RoomParticipantRole.MEMBER, 50.0)); // when diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetMemberListApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetMemberListApiTest.java index 8f58228f7..58617147f 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetMemberListApiTest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetMemberListApiTest.java @@ -107,9 +107,9 @@ void setUp() { room1 = roomJpaRepository.save(TestEntityFactory.createRoom(book, category)); // 유저1(호스트), 유저2(멤버), 유저3(멤버)로 참여 - roomParticipantJpaRepository.save(TestEntityFactory.createUserRoom(room1, user1, RoomParticipantRole.HOST, 80.0)); - roomParticipantJpaRepository.save(TestEntityFactory.createUserRoom(room1, user2, RoomParticipantRole.MEMBER, 60.0)); - roomParticipantJpaRepository.save(TestEntityFactory.createUserRoom(room1, user3, RoomParticipantRole.MEMBER, 50.0)); + roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room1, user1, RoomParticipantRole.HOST, 80.0)); + roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room1, user2, RoomParticipantRole.MEMBER, 60.0)); + roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room1, user3, RoomParticipantRole.MEMBER, 50.0)); // 팔로잉 관계 설정 @@ -201,7 +201,7 @@ void getRoomMemberList_subscriberCount() throws Exception { void getRoomMemberList_noSubscriber() throws Exception { //given UserJpaEntity userNoFollower = userJpaRepository.save(TestEntityFactory.createUser(aliasJpaRepository.findAll().get(0))); - roomParticipantJpaRepository.save(TestEntityFactory.createUserRoom(room1, userNoFollower, RoomParticipantRole.MEMBER, 10.0)); + roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room1, userNoFollower, RoomParticipantRole.MEMBER, 10.0)); Long roomId = room1.getRoomId(); //when diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomJoinApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomJoinApiTest.java index 80c3a9d9d..5df681964 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomJoinApiTest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomJoinApiTest.java @@ -63,7 +63,7 @@ private void setUpWithOnlyHost() { BookJpaEntity book = bookJpaRepository.save(TestEntityFactory.createBook()); CategoryJpaEntity category = categoryJpaRepository.save(TestEntityFactory.createLiteratureCategory(alias)); createRoom(book, category,1); // 방장만 포함 - roomParticipantJpaRepository.save(TestEntityFactory.createUserRoom(room, host, RoomParticipantRole.HOST, 0.0)); + roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room, host, RoomParticipantRole.HOST, 0.0)); } private void setUpWithParticipant() { @@ -73,8 +73,8 @@ private void setUpWithParticipant() { BookJpaEntity book = bookJpaRepository.save(TestEntityFactory.createBook()); CategoryJpaEntity category = categoryJpaRepository.save(TestEntityFactory.createLiteratureCategory(alias)); createRoom(book, category,2); // 방장과 참여자 포함 - roomParticipantJpaRepository.save(TestEntityFactory.createUserRoom(room, host, RoomParticipantRole.HOST, 0.0)); - roomParticipantJpaRepository.save(TestEntityFactory.createUserRoom(room, participant, RoomParticipantRole.MEMBER, 0.0)); + roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room, host, RoomParticipantRole.HOST, 0.0)); + roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room, participant, RoomParticipantRole.MEMBER, 0.0)); } private void createRoom(BookJpaEntity book, CategoryJpaEntity category, int memberCount) { From 7c1249fe446130815bf4c6a5dd552c13e61da8ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 26 Jul 2025 18:08:04 +0900 Subject: [PATCH 62/82] =?UTF-8?q?[refactor]RoomParticipantService=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20(#9?= =?UTF-8?q?7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/RoomParticipantService.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/main/java/konkuk/thip/room/domain/service/RoomParticipantService.java diff --git a/src/main/java/konkuk/thip/room/domain/service/RoomParticipantService.java b/src/main/java/konkuk/thip/room/domain/service/RoomParticipantService.java new file mode 100644 index 000000000..d6aa73f09 --- /dev/null +++ b/src/main/java/konkuk/thip/room/domain/service/RoomParticipantService.java @@ -0,0 +1,22 @@ +package konkuk.thip.room.domain.service; + +import konkuk.thip.common.exception.InvalidStateException; +import konkuk.thip.room.application.port.out.RoomParticipantQueryPort; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import static konkuk.thip.common.exception.code.ErrorCode.ROOM_ACCESS_FORBIDDEN; + +@Component +@RequiredArgsConstructor +public class RoomParticipantService { + private final RoomParticipantQueryPort participantPort; + + // 사용자가 방에 속해있는지 검증 + public void validateUserIsRoomMember(Long roomId, Long userId) { + if (!participantPort.existByUserIdAndRoomId(roomId, userId)) { + throw new InvalidStateException(ROOM_ACCESS_FORBIDDEN, + new IllegalArgumentException("사용자가 이 방의 참가자가 아닙니다. roomId=" + roomId + ", userId=" + userId)); + } + } +} \ No newline at end of file From 3d680a55e6d9942d1bb5e36af5c04db78d18cf94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 26 Jul 2025 18:08:17 +0900 Subject: [PATCH 63/82] =?UTF-8?q?[refactor]validateCreateComment=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/feed/domain/Feed.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/java/konkuk/thip/feed/domain/Feed.java b/src/main/java/konkuk/thip/feed/domain/Feed.java index c91c9e473..b5947b597 100644 --- a/src/main/java/konkuk/thip/feed/domain/Feed.java +++ b/src/main/java/konkuk/thip/feed/domain/Feed.java @@ -107,9 +107,16 @@ public static void validateImageCount(int imageSize) { } } - public void validateCreator(Long userId) { + public void validateCreateComment(Long userId){ + if (!this.isPublic && !this.creatorId.equals(userId)) { + validateCreator(userId); + throw new InvalidStateException(FEED_ACCESS_FORBIDDEN, new IllegalArgumentException("비공개 글은 작성자만 댓글을 쓸 수 있습니다.")); + } + } + + private void validateCreator(Long userId) { if (!this.creatorId.equals(userId)) { - throw new InvalidStateException(FEED_UPDATE_FORBIDDEN); + throw new InvalidStateException(FEED_ACCESS_FORBIDDEN); } } @@ -153,4 +160,10 @@ public void validateOwnsImages(List candidateImageUrls) { public void increaseCommentCount() { commentCount++; } + + @Override + //Feed는 RoomId 없음 + public Long getRoomId() { + return null; + } } From 770b6f28cdac88995fe2810434c7ef4f661e2598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 26 Jul 2025 18:08:30 +0900 Subject: [PATCH 64/82] =?UTF-8?q?[refactor]=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EB=B0=8F=20=EC=B6=94=EA=B0=80(#97)?= 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 | 3 ++- 1 file changed, 2 insertions(+), 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 8032a9a2a..8d1617152 100644 --- a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java +++ b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java @@ -117,6 +117,7 @@ public enum ErrorCode implements ResponseCode { USER_NOT_PARTICIPATED_CANNOT_CANCEL(HttpStatus.BAD_REQUEST, 140006, "사용자가 방에 참여하지 않은 상태에서 취소하기는 불가합니다."), HOST_CANNOT_CANCEL(HttpStatus.BAD_REQUEST, 140007, "방장은 참여 취소를 할 수 없습니다."), ROOM_RECRUIT_CANNOT_CLOSED(HttpStatus.BAD_REQUEST, 140008, "방 모집 마감을 할 수 없습니다."), + ROOM_ACCESS_FORBIDDEN(HttpStatus.FORBIDDEN, 140009, "방 접근 권한이 없습니다."), /** * 150000 : Category error @@ -131,7 +132,7 @@ public enum ErrorCode implements ResponseCode { TAG_NAME_NOT_MATCH(HttpStatus.BAD_REQUEST, 160001, "일치하는 태그 이름이 없습니다."), TAG_NOT_FOUND(HttpStatus.NOT_FOUND, 160002, "존재하지 않는 TAG 입니다."), INVALID_FEED_COMMAND(HttpStatus.BAD_REQUEST, 160003, "유효하지 않은 FEED 생성/수정 요청 입니다."), - FEED_UPDATE_FORBIDDEN(HttpStatus.FORBIDDEN, 160004, "피드 수정 권한이 없습니다."), + FEED_ACCESS_FORBIDDEN(HttpStatus.FORBIDDEN, 160004, "피드 접근 권한이 없습니다."), DUPLICATED_FEEDS_IN_COLLECTION(HttpStatus.INTERNAL_SERVER_ERROR, 160005, "중복된 피드가 존재합니다."), FEED_ALREADY_SAVED(HttpStatus.BAD_REQUEST, 160006, "사용자가 이미 저장한 피드입니다."), FEED_NOT_SAVED_CANNOT_DELETE(HttpStatus.BAD_REQUEST, 160007, "사용자가 저장하지 않은 피드는 저장삭제 할 수 없습니다."), From 4f7b632e83907d640a6682121ac10e0f0034dadd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 26 Jul 2025 18:08:46 +0900 Subject: [PATCH 65/82] =?UTF-8?q?[refactor]=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=EC=8B=9C=20=EA=B6=8C=ED=95=9C=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CommentCreateService.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) 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 f39e27050..9b8af4d1e 100644 --- a/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java +++ b/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java @@ -11,6 +11,7 @@ import konkuk.thip.common.post.PostType; import konkuk.thip.record.application.port.out.RecordCommandPort; import konkuk.thip.record.domain.Record; +import konkuk.thip.room.domain.service.RoomParticipantService; import konkuk.thip.vote.application.port.out.VoteCommandPort; import konkuk.thip.vote.domain.Vote; import lombok.RequiredArgsConstructor; @@ -29,6 +30,8 @@ public class CommentCreateService implements CommentCreateUseCase { private final RecordCommandPort recordCommandPort; private final VoteCommandPort voteCommandPort; + private final RoomParticipantService roomParticipantService; + @Override @Transactional public Long createComment(CommentCreateCommand command) { @@ -39,6 +42,8 @@ public Long createComment(CommentCreateCommand command) { // 2. 게시물 타입에 맞게 조회 CommentCountUpdatable post = findPost(type, command.postId()); + // 2-1. 게시글 타입에 따른 댓글 생성 권한 검증 + validateCommentCreateAuthorization(type, post, command.userId()); // TODO 피드: 내 게시글의 댓글, 내 댓글에 대한 답글 알림 전송 // TODO 기록 및 투표: 모임방의 내 게시글에 대한 댓글, 내 댓글에 대한 답글 알림 전송 @@ -63,6 +68,18 @@ private CommentCountUpdatable findPost(PostType type, Long postId) { }; } + private void validateCommentCreateAuthorization(PostType type, CommentCountUpdatable post, Long userId) { + // 2-1. RECORD, VOTE는 방 멤버 자격 검증 필요 + if (type == PostType.RECORD || type == PostType.VOTE) { + roomParticipantService.validateUserIsRoomMember(post.getRoomId(), userId); + } + // 2-2. FEED는 비공개 글 일시, 작성자 자격 검증 필요 + else { + Feed feed = (Feed) post; + feed.validateCreateComment(userId); + } + } + private Long createCommentDomain(CommentCreateCommand command) { // 3-1. (답글일 경우) 부모 댓글 조회 From f438ba62f24deff71293146b72767d468acee19d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 26 Jul 2025 18:10:15 +0900 Subject: [PATCH 66/82] =?UTF-8?q?[refactor]=20CommentCountUpdatable.getRoo?= =?UTF-8?q?mId=20=EC=B6=94=EA=B0=80=20(#97)?= 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..cbc52b22e 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(); + Long getRoomId(); } \ No newline at end of file From 791fee4e0b52a575c8e6748e2dcadc74638806f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 26 Jul 2025 18:22:05 +0900 Subject: [PATCH 67/82] =?UTF-8?q?[test]=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EB=A7=88=EB=8B=A4=20=EB=8F=85=EB=A6=BD=EC=84=B1=20=EB=B3=B4?= =?UTF-8?q?=EC=9E=A5=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/comment/adapter/in/web/CommentControllerTest.java | 2 ++ .../thip/comment/adapter/in/web/CommentCreateAPITest.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentControllerTest.java b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentControllerTest.java index 25c4e9ed4..027d11d13 100644 --- a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentControllerTest.java +++ b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentControllerTest.java @@ -30,6 +30,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.annotation.Transactional; @@ -48,6 +49,7 @@ @ActiveProfiles("test") @AutoConfigureMockMvc(addFilters = false) @Transactional +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) @DisplayName("[단위] 댓글 생성 api controller 단위 테스트") class CommentControllerTest { diff --git a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentCreateAPITest.java b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentCreateAPITest.java index 50130c467..9515f17c6 100644 --- a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentCreateAPITest.java +++ b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentCreateAPITest.java @@ -28,6 +28,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.annotation.Transactional; @@ -44,6 +45,7 @@ @ActiveProfiles("test") @AutoConfigureMockMvc(addFilters = false) @Transactional +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) @DisplayName("[통합] 댓글 생성 api 통합 테스트") class CommentCreateAPITest { From 0ee568deb86b83c9879fc69949a7053d9631e17f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 26 Jul 2025 19:35:12 +0900 Subject: [PATCH 68/82] =?UTF-8?q?[refactor]=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=A0=91=EA=B7=BC=20=EC=A0=95=EC=B1=85=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EB=8F=84?= =?UTF-8?q?=EC=9E=85=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/comment/domain/policy/CommentAccessPolicy.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/konkuk/thip/comment/domain/policy/CommentAccessPolicy.java diff --git a/src/main/java/konkuk/thip/comment/domain/policy/CommentAccessPolicy.java b/src/main/java/konkuk/thip/comment/domain/policy/CommentAccessPolicy.java new file mode 100644 index 000000000..7105700d0 --- /dev/null +++ b/src/main/java/konkuk/thip/comment/domain/policy/CommentAccessPolicy.java @@ -0,0 +1,9 @@ +package konkuk.thip.comment.domain.policy; + +import konkuk.thip.common.post.CommentCountUpdatable; +import konkuk.thip.common.post.PostType; + +public interface CommentAccessPolicy { + boolean supports(PostType type); + void validateCommentAccess(CommentCountUpdatable post, Long userId); +} From bf2955634d5eec9caaf4ecd71940bcbb343bf323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 26 Jul 2025 19:35:29 +0900 Subject: [PATCH 69/82] =?UTF-8?q?[refactor]=20=EA=B2=8C=EC=8B=9C=EB=AC=BC?= =?UTF-8?q?=20=EB=B3=84=20=EB=8C=93=EA=B8=80=20=EC=A0=91=EA=B7=BC=20?= =?UTF-8?q?=EC=A0=95=EC=B1=85=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EA=B5=AC=ED=98=84=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../policy/FeedCommentAccessPolicy.java | 22 +++++++++++++++++ .../policy/RoomPostCommentAccessPolicy.java | 24 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 src/main/java/konkuk/thip/comment/domain/policy/FeedCommentAccessPolicy.java create mode 100644 src/main/java/konkuk/thip/comment/domain/policy/RoomPostCommentAccessPolicy.java diff --git a/src/main/java/konkuk/thip/comment/domain/policy/FeedCommentAccessPolicy.java b/src/main/java/konkuk/thip/comment/domain/policy/FeedCommentAccessPolicy.java new file mode 100644 index 000000000..b6e7644df --- /dev/null +++ b/src/main/java/konkuk/thip/comment/domain/policy/FeedCommentAccessPolicy.java @@ -0,0 +1,22 @@ +package konkuk.thip.comment.domain.policy; + +import konkuk.thip.common.post.CommentCountUpdatable; +import konkuk.thip.common.post.PostType; +import konkuk.thip.feed.domain.Feed; +import org.springframework.stereotype.Component; + +@Component +public class FeedCommentAccessPolicy implements CommentAccessPolicy { + + @Override + public boolean supports(PostType type) { + return type == PostType.FEED; + } + + @Override + public void validateCommentAccess(CommentCountUpdatable post, Long userId) { + Feed feed = (Feed) post; + feed.validateCreateComment(userId); + + } +} diff --git a/src/main/java/konkuk/thip/comment/domain/policy/RoomPostCommentAccessPolicy.java b/src/main/java/konkuk/thip/comment/domain/policy/RoomPostCommentAccessPolicy.java new file mode 100644 index 000000000..39d058b29 --- /dev/null +++ b/src/main/java/konkuk/thip/comment/domain/policy/RoomPostCommentAccessPolicy.java @@ -0,0 +1,24 @@ +package konkuk.thip.comment.domain.policy; + +import konkuk.thip.common.post.CommentCountUpdatable; +import konkuk.thip.common.post.PostType; +import konkuk.thip.room.domain.service.RoomParticipantService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class RoomPostCommentAccessPolicy implements CommentAccessPolicy { + + private final RoomParticipantService roomParticipantService; + + @Override + public boolean supports(PostType type) { + return type == PostType.RECORD || type == PostType.VOTE; + } + + @Override + public void validateCommentAccess(CommentCountUpdatable post, Long userId) { + roomParticipantService.validateUserIsRoomMember(post.getRoomId(), userId); + } +} From 58608394665aa898e76c546e9048824eb2b3eee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 26 Jul 2025 19:35:35 +0900 Subject: [PATCH 70/82] =?UTF-8?q?[refactor]=20=EA=B2=8C=EC=8B=9C=EB=AC=BC?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/post/service/PostQueryService.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/main/java/konkuk/thip/common/post/service/PostQueryService.java diff --git a/src/main/java/konkuk/thip/common/post/service/PostQueryService.java b/src/main/java/konkuk/thip/common/post/service/PostQueryService.java new file mode 100644 index 000000000..758c08c1e --- /dev/null +++ b/src/main/java/konkuk/thip/common/post/service/PostQueryService.java @@ -0,0 +1,26 @@ +package konkuk.thip.common.post.service; + +import konkuk.thip.common.post.CommentCountUpdatable; +import konkuk.thip.common.post.PostType; +import konkuk.thip.feed.application.port.out.FeedCommandPort; +import konkuk.thip.record.application.port.out.RecordCommandPort; +import konkuk.thip.vote.application.port.out.VoteCommandPort; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class PostQueryService { + + private final FeedCommandPort feedCommandPort; + private final RecordCommandPort recordCommandPort; + private final VoteCommandPort voteCommandPort; + + public CommentCountUpdatable findPost(PostType type, Long postId) { + return switch (type) { + case FEED -> feedCommandPort.getByIdOrThrow(postId); + case RECORD -> recordCommandPort.getByIdOrThrow(postId); + case VOTE -> voteCommandPort.getByIdOrThrow(postId); + }; + } +} From 0208ed6e034d760ae119ab84db45d2c57aea2bb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 26 Jul 2025 19:35:56 +0900 Subject: [PATCH 71/82] =?UTF-8?q?[refactor]=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=A0=91=EA=B7=BC=20=EA=B4=80=EB=A0=A8=20=EA=B6=8C=ED=95=9C=20?= =?UTF-8?q?=EB=8C=93=EA=B8=80=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EC=9E=91=EC=84=B1=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CommentAuthorizationService.java | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/main/java/konkuk/thip/comment/domain/service/CommentAuthorizationService.java diff --git a/src/main/java/konkuk/thip/comment/domain/service/CommentAuthorizationService.java b/src/main/java/konkuk/thip/comment/domain/service/CommentAuthorizationService.java new file mode 100644 index 000000000..ce309f8ab --- /dev/null +++ b/src/main/java/konkuk/thip/comment/domain/service/CommentAuthorizationService.java @@ -0,0 +1,56 @@ +package konkuk.thip.comment.domain.service; + +import konkuk.thip.comment.domain.Comment; +import konkuk.thip.comment.domain.policy.CommentAccessPolicy; +import konkuk.thip.common.exception.InvalidStateException; +import konkuk.thip.common.post.CommentCountUpdatable; +import konkuk.thip.common.post.PostType; +import konkuk.thip.common.post.service.PostQueryService; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static konkuk.thip.common.exception.code.ErrorCode.POST_TYPE_NOT_MATCH; + +@Component +public class CommentAuthorizationService { + + private final PostQueryService postQueryService; + private final Map policyMap; + + public CommentAuthorizationService(PostQueryService postQueryService, List policies) { + this.postQueryService = postQueryService; + this.policyMap = new HashMap<>(); + for (CommentAccessPolicy policy : policies) { + for (PostType type : PostType.values()) { + if (policy.supports(type)) { + policyMap.put(type, policy); + } + } + } + } + + private void validateUserCanAccessPostForComment(PostType type, Long postId, Long userId) { + CommentCountUpdatable post = postQueryService.findPost(type, postId); + getPolicy(type).validateCommentAccess(post, userId); + } + + public void validateUserCanAccessPostForComment(PostType type, CommentCountUpdatable post, Long userId) { + getPolicy(type).validateCommentAccess(post, userId); + } + + public void validateUserCanAccessPostForComment(Comment comment, Long userId) { + validateUserCanAccessPostForComment(comment.getPostType(), comment.getTargetPostId(), userId); + } + + + private CommentAccessPolicy getPolicy(PostType type) { + CommentAccessPolicy policy = policyMap.get(type); + if (policy == null) { + throw new InvalidStateException(POST_TYPE_NOT_MATCH); + } + return policy; + } +} From 1dcead4b9ea0594167b3c04f8ef2c15a39ec5745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 26 Jul 2025 19:36:15 +0900 Subject: [PATCH 72/82] =?UTF-8?q?[refactor]=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1/=EC=A2=8B=EC=95=84=EC=9A=94=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EB=A1=9C=EC=A7=81=20=EC=A0=95=EC=B1=85=EB=8F=84?= =?UTF-8?q?=EC=9E=85=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CommentCreateService.java | 30 ++++--------------- .../service/CommentLikeService.java | 5 ++++ 2 files changed, 11 insertions(+), 24 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 9b8af4d1e..9cdf36442 100644 --- a/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java +++ b/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java @@ -4,14 +4,15 @@ import konkuk.thip.comment.application.port.in.dto.CommentCreateCommand; import konkuk.thip.comment.application.port.out.CommentCommandPort; import konkuk.thip.comment.domain.Comment; +import konkuk.thip.comment.domain.service.CommentAuthorizationService; 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.room.domain.service.RoomParticipantService; import konkuk.thip.vote.application.port.out.VoteCommandPort; import konkuk.thip.vote.domain.Vote; import lombok.RequiredArgsConstructor; @@ -30,7 +31,8 @@ public class CommentCreateService implements CommentCreateUseCase { private final RecordCommandPort recordCommandPort; private final VoteCommandPort voteCommandPort; - private final RoomParticipantService roomParticipantService; + private final PostQueryService postQueryService; + private final CommentAuthorizationService commentAuthorizationService; @Override @Transactional @@ -41,9 +43,9 @@ public Long createComment(CommentCreateCommand command) { PostType type = PostType.from(command.postType()); // 2. 게시물 타입에 맞게 조회 - CommentCountUpdatable post = findPost(type, command.postId()); + CommentCountUpdatable post = postQueryService.findPost(type, command.postId()); // 2-1. 게시글 타입에 따른 댓글 생성 권한 검증 - validateCommentCreateAuthorization(type, post, command.userId()); + commentAuthorizationService.validateUserCanAccessPostForComment(type, post, command.userId()); // TODO 피드: 내 게시글의 댓글, 내 댓글에 대한 답글 알림 전송 // TODO 기록 및 투표: 모임방의 내 게시글에 대한 댓글, 내 댓글에 대한 답글 알림 전송 @@ -60,26 +62,6 @@ public Long createComment(CommentCreateCommand command) { return commentId; } - private CommentCountUpdatable findPost(PostType type, Long postId) { - return switch (type) { - case FEED -> feedCommandPort.getByIdOrThrow(postId); - case RECORD -> recordCommandPort.getByIdOrThrow(postId); - case VOTE -> voteCommandPort.getByIdOrThrow(postId); - }; - } - - private void validateCommentCreateAuthorization(PostType type, CommentCountUpdatable post, Long userId) { - // 2-1. RECORD, VOTE는 방 멤버 자격 검증 필요 - if (type == PostType.RECORD || type == PostType.VOTE) { - roomParticipantService.validateUserIsRoomMember(post.getRoomId(), userId); - } - // 2-2. FEED는 비공개 글 일시, 작성자 자격 검증 필요 - else { - Feed feed = (Feed) post; - feed.validateCreateComment(userId); - } - } - private Long createCommentDomain(CommentCreateCommand command) { // 3-1. (답글일 경우) 부모 댓글 조회 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 dfc909aac..0f4e22e6b 100644 --- a/src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java +++ b/src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java @@ -8,6 +8,7 @@ import konkuk.thip.comment.application.port.out.CommentLikeCommandPort; import konkuk.thip.comment.application.port.out.CommentLikeQueryPort; import konkuk.thip.comment.domain.Comment; +import konkuk.thip.comment.domain.service.CommentAuthorizationService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -19,12 +20,16 @@ public class CommentLikeService implements CommentLikeUseCase { private final CommentLikeQueryPort commentLikeQueryPort; private final CommentLikeCommandPort commentLikeCommandPort; + private final CommentAuthorizationService commentAuthorizationService; + @Override @Transactional public CommentIsLikeResult changeLikeStatusComment(CommentIsLikeCommand command) { // 1. 댓글 조회 및 검증 (존재 여부) Comment comment = commentCommandPort.getByIdOrThrow(command.commentId()); + // 1-1. 게시글 타입에 따른 댓글 좋아요 권한 검증 + commentAuthorizationService.validateUserCanAccessPostForComment(comment, command.userId()); // 2. 유저가 해당 댓글에 대해 좋아요 했는지 조회 boolean alreadyLiked = commentLikeQueryPort.isLikedCommentByUser(command.userId(), command.commentId()); From b76b43ff8a91beeac4bdedbcfb35c5be00ed1d6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 27 Jul 2025 15:02:20 +0900 Subject: [PATCH 73/82] =?UTF-8?q?[refactor]=20=EA=B4=80=EB=A0=A8=ED=97=AC?= =?UTF-8?q?=ED=8D=BC=20=EC=96=B4=ED=94=8C=EB=A6=AC=EC=BC=80=EC=9D=B4?= =?UTF-8?q?=EC=85=98=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EB=8F=99(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommentAuthorizationValidator.java | 31 +++++++++++++++++++ .../service}/policy/CommentAccessPolicy.java | 2 +- .../policy/FeedCommentAccessPolicy.java | 2 +- .../policy/RoomPostCommentAccessPolicy.java | 30 ++++++++++++++++++ .../policy/RoomPostCommentAccessPolicy.java | 24 -------------- .../service/RoomParticipantValidator.java} | 4 +-- 6 files changed, 65 insertions(+), 28 deletions(-) create mode 100644 src/main/java/konkuk/thip/comment/application/service/CommentAuthorizationValidator.java rename src/main/java/konkuk/thip/comment/{domain => application/service}/policy/CommentAccessPolicy.java (81%) rename src/main/java/konkuk/thip/comment/{domain => application/service}/policy/FeedCommentAccessPolicy.java (90%) create mode 100644 src/main/java/konkuk/thip/comment/application/service/policy/RoomPostCommentAccessPolicy.java delete mode 100644 src/main/java/konkuk/thip/comment/domain/policy/RoomPostCommentAccessPolicy.java rename src/main/java/konkuk/thip/room/{domain/service/RoomParticipantService.java => application/service/RoomParticipantValidator.java} (90%) diff --git a/src/main/java/konkuk/thip/comment/application/service/CommentAuthorizationValidator.java b/src/main/java/konkuk/thip/comment/application/service/CommentAuthorizationValidator.java new file mode 100644 index 000000000..3fe3750e4 --- /dev/null +++ b/src/main/java/konkuk/thip/comment/application/service/CommentAuthorizationValidator.java @@ -0,0 +1,31 @@ +package konkuk.thip.comment.application.service; + +import konkuk.thip.comment.application.service.policy.CommentAccessPolicy; +import konkuk.thip.common.exception.InvalidStateException; +import konkuk.thip.common.post.CommentCountUpdatable; +import konkuk.thip.common.post.PostType; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.Map; + +import static konkuk.thip.common.exception.code.ErrorCode.POST_TYPE_NOT_MATCH; + +@Component +@RequiredArgsConstructor +public class CommentAuthorizationValidator { + + private final Map policyMap; + + public void validateUserCanAccessPostForComment(PostType type, CommentCountUpdatable post, Long userId) { + getPolicy(type).validateCommentAccess(post, userId); + } + + private CommentAccessPolicy getPolicy(PostType type) { + CommentAccessPolicy policy = policyMap.get(type); + if (policy == null) { + throw new InvalidStateException(POST_TYPE_NOT_MATCH); + } + return policy; + } +} diff --git a/src/main/java/konkuk/thip/comment/domain/policy/CommentAccessPolicy.java b/src/main/java/konkuk/thip/comment/application/service/policy/CommentAccessPolicy.java similarity index 81% rename from src/main/java/konkuk/thip/comment/domain/policy/CommentAccessPolicy.java rename to src/main/java/konkuk/thip/comment/application/service/policy/CommentAccessPolicy.java index 7105700d0..39304dded 100644 --- a/src/main/java/konkuk/thip/comment/domain/policy/CommentAccessPolicy.java +++ b/src/main/java/konkuk/thip/comment/application/service/policy/CommentAccessPolicy.java @@ -1,4 +1,4 @@ -package konkuk.thip.comment.domain.policy; +package konkuk.thip.comment.application.service.policy; import konkuk.thip.common.post.CommentCountUpdatable; import konkuk.thip.common.post.PostType; diff --git a/src/main/java/konkuk/thip/comment/domain/policy/FeedCommentAccessPolicy.java b/src/main/java/konkuk/thip/comment/application/service/policy/FeedCommentAccessPolicy.java similarity index 90% rename from src/main/java/konkuk/thip/comment/domain/policy/FeedCommentAccessPolicy.java rename to src/main/java/konkuk/thip/comment/application/service/policy/FeedCommentAccessPolicy.java index b6e7644df..f8dfbfe59 100644 --- a/src/main/java/konkuk/thip/comment/domain/policy/FeedCommentAccessPolicy.java +++ b/src/main/java/konkuk/thip/comment/application/service/policy/FeedCommentAccessPolicy.java @@ -1,4 +1,4 @@ -package konkuk.thip.comment.domain.policy; +package konkuk.thip.comment.application.service.policy; import konkuk.thip.common.post.CommentCountUpdatable; import konkuk.thip.common.post.PostType; diff --git a/src/main/java/konkuk/thip/comment/application/service/policy/RoomPostCommentAccessPolicy.java b/src/main/java/konkuk/thip/comment/application/service/policy/RoomPostCommentAccessPolicy.java new file mode 100644 index 000000000..d47e6e8ac --- /dev/null +++ b/src/main/java/konkuk/thip/comment/application/service/policy/RoomPostCommentAccessPolicy.java @@ -0,0 +1,30 @@ +package konkuk.thip.comment.application.service.policy; + +import konkuk.thip.common.exception.InvalidStateException; +import konkuk.thip.common.post.CommentCountUpdatable; +import konkuk.thip.common.post.PostType; +import konkuk.thip.room.application.port.out.RoomParticipantQueryPort; +import konkuk.thip.room.application.service.RoomParticipantValidator; +import konkuk.thip.room.domain.RoomPost; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import static konkuk.thip.common.exception.code.ErrorCode.ROOM_ACCESS_FORBIDDEN; + +@Component +@RequiredArgsConstructor +public class RoomPostCommentAccessPolicy implements CommentAccessPolicy { + + private final RoomParticipantValidator roomParticipantValidator; + + @Override + public boolean supports(PostType type) { + return type == PostType.RECORD || type == PostType.VOTE; + } + + @Override + public void validateCommentAccess(CommentCountUpdatable post, Long userId) { + RoomPost roomPost = (RoomPost) post; + roomParticipantValidator.validateUserIsRoomMember(roomPost.getRoomId(), userId); + } +} diff --git a/src/main/java/konkuk/thip/comment/domain/policy/RoomPostCommentAccessPolicy.java b/src/main/java/konkuk/thip/comment/domain/policy/RoomPostCommentAccessPolicy.java deleted file mode 100644 index 39d058b29..000000000 --- a/src/main/java/konkuk/thip/comment/domain/policy/RoomPostCommentAccessPolicy.java +++ /dev/null @@ -1,24 +0,0 @@ -package konkuk.thip.comment.domain.policy; - -import konkuk.thip.common.post.CommentCountUpdatable; -import konkuk.thip.common.post.PostType; -import konkuk.thip.room.domain.service.RoomParticipantService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; - -@Component -@RequiredArgsConstructor -public class RoomPostCommentAccessPolicy implements CommentAccessPolicy { - - private final RoomParticipantService roomParticipantService; - - @Override - public boolean supports(PostType type) { - return type == PostType.RECORD || type == PostType.VOTE; - } - - @Override - public void validateCommentAccess(CommentCountUpdatable post, Long userId) { - roomParticipantService.validateUserIsRoomMember(post.getRoomId(), userId); - } -} diff --git a/src/main/java/konkuk/thip/room/domain/service/RoomParticipantService.java b/src/main/java/konkuk/thip/room/application/service/RoomParticipantValidator.java similarity index 90% rename from src/main/java/konkuk/thip/room/domain/service/RoomParticipantService.java rename to src/main/java/konkuk/thip/room/application/service/RoomParticipantValidator.java index d6aa73f09..fc1440e98 100644 --- a/src/main/java/konkuk/thip/room/domain/service/RoomParticipantService.java +++ b/src/main/java/konkuk/thip/room/application/service/RoomParticipantValidator.java @@ -1,4 +1,4 @@ -package konkuk.thip.room.domain.service; +package konkuk.thip.room.application.service; import konkuk.thip.common.exception.InvalidStateException; import konkuk.thip.room.application.port.out.RoomParticipantQueryPort; @@ -9,7 +9,7 @@ @Component @RequiredArgsConstructor -public class RoomParticipantService { +public class RoomParticipantValidator{ private final RoomParticipantQueryPort participantPort; // 사용자가 방에 속해있는지 검증 From e0c0aac22b3e9c27c23cc6dbe2ddcde27945f8fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 27 Jul 2025 15:25:42 +0900 Subject: [PATCH 74/82] =?UTF-8?q?[refactor]=20RoomPost=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/feed/domain/Feed.java | 5 ----- src/main/java/konkuk/thip/vote/domain/Vote.java | 3 ++- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/konkuk/thip/feed/domain/Feed.java b/src/main/java/konkuk/thip/feed/domain/Feed.java index b5947b597..943eeddfb 100644 --- a/src/main/java/konkuk/thip/feed/domain/Feed.java +++ b/src/main/java/konkuk/thip/feed/domain/Feed.java @@ -161,9 +161,4 @@ public void increaseCommentCount() { commentCount++; } - @Override - //Feed는 RoomId 없음 - public Long getRoomId() { - return null; - } } diff --git a/src/main/java/konkuk/thip/vote/domain/Vote.java b/src/main/java/konkuk/thip/vote/domain/Vote.java index affeb2da6..700f94704 100644 --- a/src/main/java/konkuk/thip/vote/domain/Vote.java +++ b/src/main/java/konkuk/thip/vote/domain/Vote.java @@ -3,6 +3,7 @@ import konkuk.thip.common.entity.BaseDomainEntity; import konkuk.thip.common.exception.InvalidStateException; import konkuk.thip.common.post.CommentCountUpdatable; +import konkuk.thip.room.domain.RoomPost; import lombok.Builder; import lombok.Getter; import lombok.experimental.SuperBuilder; @@ -12,7 +13,7 @@ @Getter @SuperBuilder -public class Vote extends BaseDomainEntity implements CommentCountUpdatable { +public class Vote extends BaseDomainEntity implements CommentCountUpdatable, RoomPost { private Long id; From a7a41bf3ee2287aa7de36cbbc6eed263e4c3a156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 27 Jul 2025 15:25:56 +0900 Subject: [PATCH 75/82] =?UTF-8?q?[refactor]=20=EB=8C=93=EA=B8=80=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=EA=B4=80=EB=A0=A8=20=EC=A0=95=EC=B1=85=20Config=20(#9?= =?UTF-8?q?9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/CommentAccessPolicyConfig.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/main/java/konkuk/thip/config/CommentAccessPolicyConfig.java diff --git a/src/main/java/konkuk/thip/config/CommentAccessPolicyConfig.java b/src/main/java/konkuk/thip/config/CommentAccessPolicyConfig.java new file mode 100644 index 000000000..ddf53ceed --- /dev/null +++ b/src/main/java/konkuk/thip/config/CommentAccessPolicyConfig.java @@ -0,0 +1,28 @@ +package konkuk.thip.config; + +import konkuk.thip.comment.application.service.policy.CommentAccessPolicy; +import konkuk.thip.comment.application.service.policy.FeedCommentAccessPolicy; +import konkuk.thip.comment.application.service.policy.RoomPostCommentAccessPolicy; +import konkuk.thip.common.post.PostType; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Map; + +@Configuration +@RequiredArgsConstructor +public class CommentAccessPolicyConfig { + + private final FeedCommentAccessPolicy feedCommentPolicy; + private final RoomPostCommentAccessPolicy roomCommentPolicy; + + @Bean + public Map commentAccessPolicyMap() { + return Map.of( + PostType.FEED, feedCommentPolicy, + PostType.RECORD, roomCommentPolicy, + PostType.VOTE, roomCommentPolicy + ); + } +} \ No newline at end of file From e2fd0e8723ebf049c614e00242a9d489bee5734d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 27 Jul 2025 15:26:17 +0900 Subject: [PATCH 76/82] =?UTF-8?q?[refactor]=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1/=EC=A2=8B=EC=95=84=EC=9A=94=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/service/CommentCreateService.java | 5 ++--- .../comment/application/service/CommentLikeService.java | 9 ++++++--- 2 files changed, 8 insertions(+), 6 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 9cdf36442..27c03ce46 100644 --- a/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java +++ b/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java @@ -4,7 +4,6 @@ import konkuk.thip.comment.application.port.in.dto.CommentCreateCommand; import konkuk.thip.comment.application.port.out.CommentCommandPort; import konkuk.thip.comment.domain.Comment; -import konkuk.thip.comment.domain.service.CommentAuthorizationService; import konkuk.thip.common.exception.InvalidStateException; import konkuk.thip.common.post.CommentCountUpdatable; import konkuk.thip.common.post.service.PostQueryService; @@ -32,7 +31,7 @@ public class CommentCreateService implements CommentCreateUseCase { private final VoteCommandPort voteCommandPort; private final PostQueryService postQueryService; - private final CommentAuthorizationService commentAuthorizationService; + private final CommentAuthorizationValidator commentAuthorizationValidator; @Override @Transactional @@ -45,7 +44,7 @@ public Long createComment(CommentCreateCommand command) { // 2. 게시물 타입에 맞게 조회 CommentCountUpdatable post = postQueryService.findPost(type, command.postId()); // 2-1. 게시글 타입에 따른 댓글 생성 권한 검증 - commentAuthorizationService.validateUserCanAccessPostForComment(type, post, command.userId()); + commentAuthorizationValidator.validateUserCanAccessPostForComment(type, post, command.userId()); // TODO 피드: 내 게시글의 댓글, 내 댓글에 대한 답글 알림 전송 // TODO 기록 및 투표: 모임방의 내 게시글에 대한 댓글, 내 댓글에 대한 답글 알림 전송 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 0f4e22e6b..cdc7d189c 100644 --- a/src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java +++ b/src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java @@ -8,7 +8,8 @@ import konkuk.thip.comment.application.port.out.CommentLikeCommandPort; import konkuk.thip.comment.application.port.out.CommentLikeQueryPort; import konkuk.thip.comment.domain.Comment; -import konkuk.thip.comment.domain.service.CommentAuthorizationService; +import konkuk.thip.common.post.CommentCountUpdatable; +import konkuk.thip.common.post.service.PostQueryService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -20,7 +21,8 @@ public class CommentLikeService implements CommentLikeUseCase { private final CommentLikeQueryPort commentLikeQueryPort; private final CommentLikeCommandPort commentLikeCommandPort; - private final CommentAuthorizationService commentAuthorizationService; + private final PostQueryService postQueryService; + private final CommentAuthorizationValidator commentAuthorizationValidator; @Override @Transactional @@ -29,7 +31,8 @@ public CommentIsLikeResult changeLikeStatusComment(CommentIsLikeCommand command) // 1. 댓글 조회 및 검증 (존재 여부) Comment comment = commentCommandPort.getByIdOrThrow(command.commentId()); // 1-1. 게시글 타입에 따른 댓글 좋아요 권한 검증 - commentAuthorizationService.validateUserCanAccessPostForComment(comment, command.userId()); + CommentCountUpdatable post = postQueryService.findPost(comment.getPostType(), comment.getTargetPostId()); + commentAuthorizationValidator.validateUserCanAccessPostForComment(comment.getPostType(), post, command.userId()); // 2. 유저가 해당 댓글에 대해 좋아요 했는지 조회 boolean alreadyLiked = commentLikeQueryPort.isLikedCommentByUser(command.userId(), command.commentId()); From a0b31f9a3bd82774604be0ecf3a7ae19b6703b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 27 Jul 2025 15:26:24 +0900 Subject: [PATCH 77/82] =?UTF-8?q?[refactor]=20RoomPost=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/record/domain/Record.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/record/domain/Record.java b/src/main/java/konkuk/thip/record/domain/Record.java index e860fc8c9..f014765dc 100644 --- a/src/main/java/konkuk/thip/record/domain/Record.java +++ b/src/main/java/konkuk/thip/record/domain/Record.java @@ -3,6 +3,7 @@ import konkuk.thip.common.entity.BaseDomainEntity; import konkuk.thip.common.exception.InvalidStateException; import konkuk.thip.common.post.CommentCountUpdatable; +import konkuk.thip.room.domain.RoomPost; import lombok.Builder; import lombok.Getter; import lombok.experimental.SuperBuilder; @@ -12,7 +13,7 @@ @Getter @SuperBuilder -public class Record extends BaseDomainEntity implements CommentCountUpdatable { +public class Record extends BaseDomainEntity implements CommentCountUpdatable, RoomPost { private Long id; From 0638934ebc6300247b8eedb83cc819a812d19709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 27 Jul 2025 15:26:41 +0900 Subject: [PATCH 78/82] =?UTF-8?q?[refactor]=20RoomPost=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/room/domain/RoomPost.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/java/konkuk/thip/room/domain/RoomPost.java diff --git a/src/main/java/konkuk/thip/room/domain/RoomPost.java b/src/main/java/konkuk/thip/room/domain/RoomPost.java new file mode 100644 index 000000000..59124104d --- /dev/null +++ b/src/main/java/konkuk/thip/room/domain/RoomPost.java @@ -0,0 +1,7 @@ +package konkuk.thip.room.domain; + +import konkuk.thip.common.post.CommentCountUpdatable; + +public interface RoomPost extends CommentCountUpdatable { + Long getRoomId(); +} \ No newline at end of file From e5a2507913cf827d95a4db82e19b20b853a63958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 27 Jul 2025 15:27:20 +0900 Subject: [PATCH 79/82] [refactor] RoomPostCommentAccessPolicy (#99) --- .../policy/RoomPostCommentAccessPolicy.java | 7 +-- .../service/CommentAuthorizationService.java | 56 ------------------- 2 files changed, 2 insertions(+), 61 deletions(-) delete mode 100644 src/main/java/konkuk/thip/comment/domain/service/CommentAuthorizationService.java diff --git a/src/main/java/konkuk/thip/comment/application/service/policy/RoomPostCommentAccessPolicy.java b/src/main/java/konkuk/thip/comment/application/service/policy/RoomPostCommentAccessPolicy.java index d47e6e8ac..f37985d92 100644 --- a/src/main/java/konkuk/thip/comment/application/service/policy/RoomPostCommentAccessPolicy.java +++ b/src/main/java/konkuk/thip/comment/application/service/policy/RoomPostCommentAccessPolicy.java @@ -1,16 +1,12 @@ package konkuk.thip.comment.application.service.policy; -import konkuk.thip.common.exception.InvalidStateException; import konkuk.thip.common.post.CommentCountUpdatable; import konkuk.thip.common.post.PostType; -import konkuk.thip.room.application.port.out.RoomParticipantQueryPort; import konkuk.thip.room.application.service.RoomParticipantValidator; import konkuk.thip.room.domain.RoomPost; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import static konkuk.thip.common.exception.code.ErrorCode.ROOM_ACCESS_FORBIDDEN; - @Component @RequiredArgsConstructor public class RoomPostCommentAccessPolicy implements CommentAccessPolicy { @@ -27,4 +23,5 @@ public void validateCommentAccess(CommentCountUpdatable post, Long userId) { RoomPost roomPost = (RoomPost) post; roomParticipantValidator.validateUserIsRoomMember(roomPost.getRoomId(), userId); } -} + +} \ No newline at end of file diff --git a/src/main/java/konkuk/thip/comment/domain/service/CommentAuthorizationService.java b/src/main/java/konkuk/thip/comment/domain/service/CommentAuthorizationService.java deleted file mode 100644 index ce309f8ab..000000000 --- a/src/main/java/konkuk/thip/comment/domain/service/CommentAuthorizationService.java +++ /dev/null @@ -1,56 +0,0 @@ -package konkuk.thip.comment.domain.service; - -import konkuk.thip.comment.domain.Comment; -import konkuk.thip.comment.domain.policy.CommentAccessPolicy; -import konkuk.thip.common.exception.InvalidStateException; -import konkuk.thip.common.post.CommentCountUpdatable; -import konkuk.thip.common.post.PostType; -import konkuk.thip.common.post.service.PostQueryService; -import org.springframework.stereotype.Component; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static konkuk.thip.common.exception.code.ErrorCode.POST_TYPE_NOT_MATCH; - -@Component -public class CommentAuthorizationService { - - private final PostQueryService postQueryService; - private final Map policyMap; - - public CommentAuthorizationService(PostQueryService postQueryService, List policies) { - this.postQueryService = postQueryService; - this.policyMap = new HashMap<>(); - for (CommentAccessPolicy policy : policies) { - for (PostType type : PostType.values()) { - if (policy.supports(type)) { - policyMap.put(type, policy); - } - } - } - } - - private void validateUserCanAccessPostForComment(PostType type, Long postId, Long userId) { - CommentCountUpdatable post = postQueryService.findPost(type, postId); - getPolicy(type).validateCommentAccess(post, userId); - } - - public void validateUserCanAccessPostForComment(PostType type, CommentCountUpdatable post, Long userId) { - getPolicy(type).validateCommentAccess(post, userId); - } - - public void validateUserCanAccessPostForComment(Comment comment, Long userId) { - validateUserCanAccessPostForComment(comment.getPostType(), comment.getTargetPostId(), userId); - } - - - private CommentAccessPolicy getPolicy(PostType type) { - CommentAccessPolicy policy = policyMap.get(type); - if (policy == null) { - throw new InvalidStateException(POST_TYPE_NOT_MATCH); - } - return policy; - } -} From c1cc63fce0db109cbb37808f2d12ff5d10a63c29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 27 Jul 2025 15:27:36 +0900 Subject: [PATCH 80/82] =?UTF-8?q?[refactor]=20getRoomId()=EB=8A=94=20Roomp?= =?UTF-8?q?ost=EB=A7=8C=20=EA=B5=AC=ED=98=84=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?(#99)?= 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 deletion(-) diff --git a/src/main/java/konkuk/thip/common/post/CommentCountUpdatable.java b/src/main/java/konkuk/thip/common/post/CommentCountUpdatable.java index cbc52b22e..3ac397005 100644 --- a/src/main/java/konkuk/thip/common/post/CommentCountUpdatable.java +++ b/src/main/java/konkuk/thip/common/post/CommentCountUpdatable.java @@ -2,5 +2,4 @@ public interface CommentCountUpdatable { void increaseCommentCount(); - Long getRoomId(); } \ No newline at end of file From 2e5c3aa7460230523daac1d786af0bbbfe4047a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 27 Jul 2025 15:59:27 +0900 Subject: [PATCH 81/82] =?UTF-8?q?[refactor]=20=EC=95=88=EC=93=B0=EB=8A=94?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=82=AD=EC=A0=9C=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/service/policy/CommentAccessPolicy.java | 2 -- .../application/service/policy/FeedCommentAccessPolicy.java | 6 ------ .../service/policy/RoomPostCommentAccessPolicy.java | 6 ------ 3 files changed, 14 deletions(-) diff --git a/src/main/java/konkuk/thip/comment/application/service/policy/CommentAccessPolicy.java b/src/main/java/konkuk/thip/comment/application/service/policy/CommentAccessPolicy.java index 39304dded..c9a5b8e96 100644 --- a/src/main/java/konkuk/thip/comment/application/service/policy/CommentAccessPolicy.java +++ b/src/main/java/konkuk/thip/comment/application/service/policy/CommentAccessPolicy.java @@ -1,9 +1,7 @@ package konkuk.thip.comment.application.service.policy; import konkuk.thip.common.post.CommentCountUpdatable; -import konkuk.thip.common.post.PostType; public interface CommentAccessPolicy { - boolean supports(PostType type); void validateCommentAccess(CommentCountUpdatable post, Long userId); } diff --git a/src/main/java/konkuk/thip/comment/application/service/policy/FeedCommentAccessPolicy.java b/src/main/java/konkuk/thip/comment/application/service/policy/FeedCommentAccessPolicy.java index f8dfbfe59..c53badbd0 100644 --- a/src/main/java/konkuk/thip/comment/application/service/policy/FeedCommentAccessPolicy.java +++ b/src/main/java/konkuk/thip/comment/application/service/policy/FeedCommentAccessPolicy.java @@ -1,18 +1,12 @@ package konkuk.thip.comment.application.service.policy; import konkuk.thip.common.post.CommentCountUpdatable; -import konkuk.thip.common.post.PostType; import konkuk.thip.feed.domain.Feed; import org.springframework.stereotype.Component; @Component public class FeedCommentAccessPolicy implements CommentAccessPolicy { - @Override - public boolean supports(PostType type) { - return type == PostType.FEED; - } - @Override public void validateCommentAccess(CommentCountUpdatable post, Long userId) { Feed feed = (Feed) post; diff --git a/src/main/java/konkuk/thip/comment/application/service/policy/RoomPostCommentAccessPolicy.java b/src/main/java/konkuk/thip/comment/application/service/policy/RoomPostCommentAccessPolicy.java index f37985d92..4d8ba78e8 100644 --- a/src/main/java/konkuk/thip/comment/application/service/policy/RoomPostCommentAccessPolicy.java +++ b/src/main/java/konkuk/thip/comment/application/service/policy/RoomPostCommentAccessPolicy.java @@ -1,7 +1,6 @@ package konkuk.thip.comment.application.service.policy; import konkuk.thip.common.post.CommentCountUpdatable; -import konkuk.thip.common.post.PostType; import konkuk.thip.room.application.service.RoomParticipantValidator; import konkuk.thip.room.domain.RoomPost; import lombok.RequiredArgsConstructor; @@ -13,11 +12,6 @@ public class RoomPostCommentAccessPolicy implements CommentAccessPolicy { private final RoomParticipantValidator roomParticipantValidator; - @Override - public boolean supports(PostType type) { - return type == PostType.RECORD || type == PostType.VOTE; - } - @Override public void validateCommentAccess(CommentCountUpdatable post, Long userId) { RoomPost roomPost = (RoomPost) post; From 1931a1ae495fd5b67c22b576ff6c21a6d9e8eab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 27 Jul 2025 16:14:14 +0900 Subject: [PATCH 82/82] =?UTF-8?q?[rename]=20=ED=8C=8C=EC=9D=BC=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EB=B3=80=EA=B2=BD=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/comment/application/service/CommentCreateService.java | 1 + .../thip/comment/application/service/CommentLikeService.java | 1 + .../application/service/policy/RoomPostCommentAccessPolicy.java | 2 +- .../service/{ => validator}/CommentAuthorizationValidator.java | 2 +- .../service/{ => validator}/RoomParticipantValidator.java | 2 +- 5 files changed, 5 insertions(+), 3 deletions(-) rename src/main/java/konkuk/thip/comment/application/service/{ => validator}/CommentAuthorizationValidator.java (94%) rename src/main/java/konkuk/thip/room/application/service/{ => validator}/RoomParticipantValidator.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 27c03ce46..c15ab1345 100644 --- a/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java +++ b/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java @@ -3,6 +3,7 @@ import konkuk.thip.comment.application.port.in.CommentCreateUseCase; import konkuk.thip.comment.application.port.in.dto.CommentCreateCommand; import konkuk.thip.comment.application.port.out.CommentCommandPort; +import konkuk.thip.comment.application.service.validator.CommentAuthorizationValidator; import konkuk.thip.comment.domain.Comment; import konkuk.thip.common.exception.InvalidStateException; import konkuk.thip.common.post.CommentCountUpdatable; 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 cdc7d189c..69533ce9c 100644 --- a/src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java +++ b/src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java @@ -7,6 +7,7 @@ import konkuk.thip.comment.application.port.out.CommentCommandPort; import konkuk.thip.comment.application.port.out.CommentLikeCommandPort; import konkuk.thip.comment.application.port.out.CommentLikeQueryPort; +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; diff --git a/src/main/java/konkuk/thip/comment/application/service/policy/RoomPostCommentAccessPolicy.java b/src/main/java/konkuk/thip/comment/application/service/policy/RoomPostCommentAccessPolicy.java index 4d8ba78e8..125c9b7e6 100644 --- a/src/main/java/konkuk/thip/comment/application/service/policy/RoomPostCommentAccessPolicy.java +++ b/src/main/java/konkuk/thip/comment/application/service/policy/RoomPostCommentAccessPolicy.java @@ -1,7 +1,7 @@ package konkuk.thip.comment.application.service.policy; import konkuk.thip.common.post.CommentCountUpdatable; -import konkuk.thip.room.application.service.RoomParticipantValidator; +import konkuk.thip.room.application.service.validator.RoomParticipantValidator; import konkuk.thip.room.domain.RoomPost; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; diff --git a/src/main/java/konkuk/thip/comment/application/service/CommentAuthorizationValidator.java b/src/main/java/konkuk/thip/comment/application/service/validator/CommentAuthorizationValidator.java similarity index 94% rename from src/main/java/konkuk/thip/comment/application/service/CommentAuthorizationValidator.java rename to src/main/java/konkuk/thip/comment/application/service/validator/CommentAuthorizationValidator.java index 3fe3750e4..6d6be7c9d 100644 --- a/src/main/java/konkuk/thip/comment/application/service/CommentAuthorizationValidator.java +++ b/src/main/java/konkuk/thip/comment/application/service/validator/CommentAuthorizationValidator.java @@ -1,4 +1,4 @@ -package konkuk.thip.comment.application.service; +package konkuk.thip.comment.application.service.validator; import konkuk.thip.comment.application.service.policy.CommentAccessPolicy; import konkuk.thip.common.exception.InvalidStateException; diff --git a/src/main/java/konkuk/thip/room/application/service/RoomParticipantValidator.java b/src/main/java/konkuk/thip/room/application/service/validator/RoomParticipantValidator.java similarity index 93% rename from src/main/java/konkuk/thip/room/application/service/RoomParticipantValidator.java rename to src/main/java/konkuk/thip/room/application/service/validator/RoomParticipantValidator.java index fc1440e98..637949dd7 100644 --- a/src/main/java/konkuk/thip/room/application/service/RoomParticipantValidator.java +++ b/src/main/java/konkuk/thip/room/application/service/validator/RoomParticipantValidator.java @@ -1,4 +1,4 @@ -package konkuk.thip.room.application.service; +package konkuk.thip.room.application.service.validator; import konkuk.thip.common.exception.InvalidStateException; import konkuk.thip.room.application.port.out.RoomParticipantQueryPort;