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 6f9977727..ccc5555ee 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 @@ -7,7 +7,7 @@ import konkuk.thip.comment.adapter.in.web.request.CommentCreateRequest; import konkuk.thip.comment.adapter.in.web.request.CommentIsLikeRequest; import konkuk.thip.comment.adapter.in.web.response.CommentDeleteResponse; -import konkuk.thip.comment.adapter.in.web.response.CommentIdResponse; +import konkuk.thip.comment.adapter.in.web.response.CommentCreateResponse; import konkuk.thip.comment.adapter.in.web.response.CommentIsLikeResponse; import konkuk.thip.comment.application.port.in.CommentCreateUseCase; import konkuk.thip.comment.application.port.in.CommentDeleteUseCase; @@ -42,11 +42,11 @@ public class CommentCommandController { ) @ExceptionDescription(COMMENT_CREATE) @PostMapping("/comments/{postId}") - public BaseResponse createComment( + public BaseResponse createComment( @RequestBody @Valid final CommentCreateRequest request, @Parameter(description = "댓글을 작성하려는 게시물 ID", example = "1") @PathVariable("postId") final Long postId, @Parameter(hidden = true) @UserId final Long userId) { - return BaseResponse.ok(CommentIdResponse.of(commentCreateUseCase.createComment(request.toCommand(userId,postId)))); + return BaseResponse.ok(commentCreateUseCase.createComment(request.toCommand(userId,postId))); } @Operation( diff --git a/src/main/java/konkuk/thip/comment/adapter/in/web/response/CommentCreateResponse.java b/src/main/java/konkuk/thip/comment/adapter/in/web/response/CommentCreateResponse.java new file mode 100644 index 000000000..d67ee2821 --- /dev/null +++ b/src/main/java/konkuk/thip/comment/adapter/in/web/response/CommentCreateResponse.java @@ -0,0 +1,35 @@ +package konkuk.thip.comment.adapter.in.web.response; + +import java.util.List; + +public record CommentCreateResponse( + Long commentId, + Long creatorId, + String creatorProfileImageUrl, + String creatorNickname, + String aliasName, + String aliasColor, + String postDate, // 댓글 작성 시각 (~ 전 형식) + String content, + int likeCount, + boolean isLike, + boolean isDeleted, // 삭제된 댓글인지 아닌지 + boolean isWriter, + List replyList +) { + public record ReplyCommentCreateDto( + Long commentId, + String parentCommentCreatorNickname, + Long creatorId, + String creatorProfileImageUrl, + String creatorNickname, + String aliasName, + String aliasColor, + String postDate, // 댓글 작성 시각 (~ 전 형식) + String content, + int likeCount, + boolean isLike, + boolean isWriter + ) {} + +} 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 deleted file mode 100644 index c774e8fa1..000000000 --- a/src/main/java/konkuk/thip/comment/adapter/in/web/response/CommentIdResponse.java +++ /dev/null @@ -1,7 +0,0 @@ -package konkuk.thip.comment.adapter.in.web.response; - -public record CommentIdResponse(Long commentId) { - public static CommentIdResponse of(Long commentId) { - return new CommentIdResponse(commentId); - } -} 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 a4549ad20..3c10850db 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 @@ -43,4 +43,14 @@ public List findAllActiveChildCommentsOldestFirst(Long rootComm public Map> findAllActiveChildCommentsOldestFirst(Set rootCommentIds) { return commentJpaRepository.findAllActiveChildCommentsByCreatedAtAsc(rootCommentIds); } + + @Override + public CommentQueryDto findRootCommentById(Long rootCommentId) { + return commentJpaRepository.findRootCommentId(rootCommentId); + } + + @Override + public CommentQueryDto findChildCommentById(Long rootCommentId, Long replyCommentId) { + return commentJpaRepository.findChildCommentId(rootCommentId, replyCommentId); + } } diff --git a/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentQueryRepository.java b/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentQueryRepository.java index fe8b9b492..f10f7af76 100644 --- a/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentQueryRepository.java +++ b/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentQueryRepository.java @@ -14,4 +14,8 @@ public interface CommentQueryRepository { List findAllActiveChildCommentsByCreatedAtAsc(Long rootCommentId); Map> findAllActiveChildCommentsByCreatedAtAsc(Set rootCommentIds); + + CommentQueryDto findRootCommentId(Long commentId); + + CommentQueryDto findChildCommentId(Long rootCommentId, Long commentId); } diff --git a/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentQueryRepositoryImpl.java b/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentQueryRepositoryImpl.java index b49144e73..e0029ec58 100644 --- a/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentQueryRepositoryImpl.java +++ b/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentQueryRepositoryImpl.java @@ -186,4 +186,66 @@ public Map> findAllActiveChildCommentsByCreatedAtAsc return resultMap; } + + @Override + public CommentQueryDto findRootCommentId(Long rootCommentId) { + + QCommentQueryDto proj = new QCommentQueryDto( + comment.commentId, + commentCreator.userId, + aliasOfCommentCreator.imageUrl, + commentCreator.nickname, + aliasOfCommentCreator.value, + aliasOfCommentCreator.color, + comment.createdAt, + comment.content, + comment.likeCount, + comment.status.eq(StatusType.INACTIVE) + ); + + return queryFactory + .select(proj) + .from(comment) + .join(comment.userJpaEntity, commentCreator) + .join(commentCreator.aliasForUserJpaEntity, aliasOfCommentCreator) + .where( + comment.commentId.eq(rootCommentId), + comment.status.eq(StatusType.ACTIVE) + ) + .fetchOne(); + } + + @Override + public CommentQueryDto findChildCommentId(Long rootCommentId, Long replyCommentId) { + + QCommentQueryDto proj = new QCommentQueryDto( + comment.commentId, + comment.parent.commentId, + parentCommentCreator.nickname, + commentCreator.userId, + aliasOfCommentCreator.imageUrl, + commentCreator.nickname, + aliasOfCommentCreator.value, + aliasOfCommentCreator.color, + comment.createdAt, + comment.content, + comment.likeCount, + comment.status.eq(StatusType.INACTIVE) + ); + + return queryFactory + .select(proj) + .from(comment) + .join(comment.parent, parentComment) + .join(parentComment.userJpaEntity, parentCommentCreator) + .join(comment.userJpaEntity, commentCreator) + .join(commentCreator.aliasForUserJpaEntity, aliasOfCommentCreator) + .where( + comment.parent.commentId.eq(rootCommentId), + parentComment.status.eq(StatusType.ACTIVE), + comment.status.eq(StatusType.ACTIVE), + comment.commentId.eq(replyCommentId) + ) + .fetchOne(); + } } diff --git a/src/main/java/konkuk/thip/comment/application/mapper/CommentQueryMapper.java b/src/main/java/konkuk/thip/comment/application/mapper/CommentQueryMapper.java index 5f3f36577..3b2544781 100644 --- a/src/main/java/konkuk/thip/comment/application/mapper/CommentQueryMapper.java +++ b/src/main/java/konkuk/thip/comment/application/mapper/CommentQueryMapper.java @@ -1,5 +1,6 @@ package konkuk.thip.comment.application.mapper; +import konkuk.thip.comment.adapter.in.web.response.CommentCreateResponse; import konkuk.thip.comment.adapter.in.web.response.CommentForSinglePostResponse; import konkuk.thip.comment.application.port.out.dto.CommentQueryDto; import konkuk.thip.common.util.DateUtil; @@ -27,6 +28,15 @@ public interface CommentQueryMapper { @Mapping(target = "isWriter", source = "root.creatorId", qualifiedByName = "isWriter") CommentForSinglePostResponse.RootCommentDto toRoot(CommentQueryDto root, @Context Set likedCommentIds, @Context Long userId); + // 댓글/답글 생성시 루트 댓글 매핑 + @Mapping(target = "replyList", expression = "java(new java.util.ArrayList<>())") + @Mapping(target = "isDeleted", constant = "false") + @Mapping(target = "isLike", expression = "java(isLike)") + @Mapping(target = "postDate", expression = "java(DateUtil.formatBeforeTime(root.createdAt()))") + @Mapping(target = "aliasName", source = "root.alias") + @Mapping(target = "isWriter", source = "root.creatorId", qualifiedByName = "isWriter") + CommentCreateResponse toRoot(CommentQueryDto root, boolean isLike, @Context Long userId); + /** * 개별 답글 매핑 */ @@ -36,6 +46,13 @@ public interface CommentQueryMapper { @Mapping(target = "isWriter", source = "child.creatorId", qualifiedByName = "isWriter") CommentForSinglePostResponse.RootCommentDto.ReplyDto toReply(CommentQueryDto child, @Context Set likedCommentIds, @Context Long userId); + // 답글 생성시 답글 매핑 + @Mapping(target = "isLike", constant = "false") + @Mapping(target = "postDate", expression = "java(DateUtil.formatBeforeTime(child.createdAt()))") + @Mapping(target = "aliasName", source = "child.alias") + @Mapping(target = "isWriter", source = "child.creatorId", qualifiedByName = "isWriter") + CommentCreateResponse.ReplyCommentCreateDto toReply(CommentQueryDto child, @Context Long userId); + /** * 답글 리스트 헬퍼 */ @@ -61,6 +78,16 @@ default CommentForSinglePostResponse.RootCommentDto toRootCommentResponseWithChi return rootDto; } + default CommentCreateResponse toRootCommentResponseWithChildren( + CommentQueryDto root, CommentQueryDto children, boolean isLikedParentComment, @Context Long userId) { + CommentCreateResponse.ReplyCommentCreateDto replyDto = toReply(children,userId); + + CommentCreateResponse rootDto = toRoot(root, isLikedParentComment, userId); + rootDto.replyList().add(replyDto); + return rootDto; + } + + @Named("isWriter") default boolean isWriter(Long creatorId, @Context Long userId) { return creatorId != null && creatorId.equals(userId); 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 index 658d4713b..8bcda9582 100644 --- a/src/main/java/konkuk/thip/comment/application/port/in/CommentCreateUseCase.java +++ b/src/main/java/konkuk/thip/comment/application/port/in/CommentCreateUseCase.java @@ -1,7 +1,8 @@ package konkuk.thip.comment.application.port.in; +import konkuk.thip.comment.adapter.in.web.response.CommentCreateResponse; import konkuk.thip.comment.application.port.in.dto.CommentCreateCommand; public interface CommentCreateUseCase { - Long createComment(CommentCreateCommand command); + CommentCreateResponse createComment(CommentCreateCommand command); } 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 770e92a32..388d303d6 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 @@ -15,4 +15,8 @@ public interface CommentQueryPort { List findAllActiveChildCommentsOldestFirst(Long rootCommentId); Map> findAllActiveChildCommentsOldestFirst(Set rootCommentIds); + + CommentQueryDto findRootCommentById(Long rootCommentId); + + CommentQueryDto findChildCommentById(Long rootCommentId , Long replyCommentId); } 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 2d581aa8b..0a61b2700 100644 --- a/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java +++ b/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java @@ -1,8 +1,13 @@ package konkuk.thip.comment.application.service; +import konkuk.thip.comment.adapter.in.web.response.CommentCreateResponse; +import konkuk.thip.comment.application.mapper.CommentQueryMapper; 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.port.out.CommentLikeQueryPort; +import konkuk.thip.comment.application.port.out.CommentQueryPort; +import konkuk.thip.comment.application.port.out.dto.CommentQueryDto; import konkuk.thip.comment.application.service.validator.CommentAuthorizationValidator; import konkuk.thip.comment.domain.Comment; import konkuk.thip.common.exception.InvalidStateException; @@ -21,13 +26,17 @@ public class CommentCreateService implements CommentCreateUseCase { private final CommentCommandPort commentCommandPort; + private final CommentQueryPort commentQueryPort; + private final CommentLikeQueryPort commentLikeQueryPort; + private final CommentQueryMapper commentQueryMapper; + private final PostHandler postHandler; private final CommentAuthorizationValidator commentAuthorizationValidator; @Override @Transactional - public Long createComment(CommentCreateCommand command) { + public CommentCreateResponse createComment(CommentCreateCommand command) { // 1. 댓글/답글 생성 선행검증 및 작성하려는 게시글 타입 검증 Comment.validateCommentCreate(command.isReplyRequest(), command.parentId()); @@ -42,7 +51,7 @@ public Long createComment(CommentCreateCommand command) { // TODO 기록 및 투표: 모임방의 내 게시글에 대한 댓글, 내 댓글에 대한 답글 알림 전송 // 3. 댓글 생성 - Long commentId = createCommentDomain(command); + Long savedCommentId = createCommentDomain(command); //TODO 게시물의 댓글 수 증가/감소 동시성 제어 로직 추가해야됨 @@ -52,7 +61,20 @@ public Long createComment(CommentCreateCommand command) { // 4-2 Jpa엔티티 게시물 댓글 수 증가 postHandler.updatePost(type, post); - return commentId; + // 5. 매퍼로 DTO 변환 후 반환 + if (command.isReplyRequest()) { + // 부모 댓글 조회 + CommentQueryDto parentCommentDto = commentQueryPort.findRootCommentById(command.parentId()); + // 사용자 부모 댓글 좋아요 여부 조회 + boolean isLikedParentComment = commentLikeQueryPort.isLikedCommentByUser(command.userId(),parentCommentDto.commentId()); + + CommentQueryDto savedReplyCommentDto = commentQueryPort.findChildCommentById(command.parentId(), savedCommentId); + return commentQueryMapper.toRootCommentResponseWithChildren(parentCommentDto, savedReplyCommentDto,isLikedParentComment,command.userId()); + } else { + CommentQueryDto savedCommentDto = commentQueryPort.findRootCommentById(savedCommentId); + return commentQueryMapper.toRoot(savedCommentDto, false, command.userId()); + } + } private Long createCommentDomain(CommentCreateCommand command) {