From 760c2d6db5e80b00a45b81287001e68110efa277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=99=ED=98=B8?= <66300965+kdkdhoho@users.noreply.github.com> Date: Sun, 13 Oct 2024 15:46:28 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=A9=98=EC=85=98=20CRUD=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84=20(#309)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * rename: CommentRepository 패키지 추가 (#308) * feat: 댓글/답글 작성 시 다른 유저를 멘션하는 기능 구현 (#308) * feat: 댓글/답글 조회 시 멘션도 함께 응답하는 로직 구현 (#308) * refactor: RequestDto 필드명을 API 명세에 맞게 변경 (#308) * feat: 멘션 수정 기능 구현 (#308) * feat: 멘션 삭제 기능 구현 (#308) * refactor: 데이터 삭제 및 변경 리팩터링 (#308) * refactor: MentionService 추가 (#308) * refactor: 멘션 데이터를 응답할 때 userId를 기준으로 오름차순 정렬하여 반환 (#308) * refactor: LinkedHashSet에서 HashSet로 수정 (#308) * refactor: 코드 컨벤션 준수 (#308) * test: 멘션 아이디를 given 절로 추출 (#308) * refactor: 변수명 mentionIds로 통일 (#308) --- .../common/config/QuerydslConfig.java | 2 +- .../listywave/common/config/WebConfig.java | 2 +- .../common/exception/CustomException.java | 2 +- .../common/exception/ErrorResponse.java | 2 +- .../common/util/DataUpdateUtils.java | 18 ++ .../application/domain/comment/Comment.java | 25 +- .../list/application/domain/reply/Reply.java | 22 +- .../application/dto/ReplyUpdateCommand.java | 5 +- .../dto/response/CommentFindResponse.java | 51 ++- .../dto/response/ListRecentResponse.java | 1 - .../application/service/CommentService.java | 33 +- .../list/application/service/ListService.java | 2 +- .../application/service/ReplyService.java | 37 ++- .../controller/CommentController.java | 14 +- .../controller/ReplyController.java | 12 +- .../dto/request/ReplyCreateRequest.java | 5 +- .../dto/request/ReplyUpdateRequest.java | 5 +- .../request/comment/CommentCreateRequest.java | 5 +- .../request/comment/CommentUpdateRequest.java | 5 +- .../{ => comment}/CommentRepository.java | 2 +- .../CustomCommentRepository.java | 2 +- .../CustomCommentRepositoryImpl.java | 5 +- .../custom/impl/CustomListRepositoryImpl.java | 1 - .../java/com/listywave/mention/Mention.java | 60 ++++ .../listywave/mention/MentionRepository.java | 13 + .../com/listywave/mention/MentionService.java | 24 ++ .../user/presentation/UserController.java | 1 - .../acceptance/alarm/AlarmAcceptanceTest.java | 2 +- .../comment/CommentAcceptanceTest.java | 9 +- .../comment/CommentAcceptanceTestHelper.java | 2 +- .../acceptance/list/ListAcceptanceTest.java | 13 +- .../acceptance/reply/ReplyAcceptanceTest.java | 15 +- .../com/listywave/common/IntegrationTest.java | 67 ++++ .../domain/comment/CommentTest.java | 5 +- .../application/domain/reply/ReplyTest.java | 5 +- .../listywave/mention/MentionServiceTest.java | 301 ++++++++++++++++++ .../listywave/user/fixture/UserFixture.java | 16 + 37 files changed, 693 insertions(+), 98 deletions(-) create mode 100644 src/main/java/com/listywave/common/util/DataUpdateUtils.java rename src/main/java/com/listywave/list/repository/{ => comment}/CommentRepository.java (95%) rename src/main/java/com/listywave/list/repository/{ => comment}/CustomCommentRepository.java (86%) rename src/main/java/com/listywave/list/repository/{ => comment}/CustomCommentRepositoryImpl.java (94%) create mode 100644 src/main/java/com/listywave/mention/Mention.java create mode 100644 src/main/java/com/listywave/mention/MentionRepository.java create mode 100644 src/main/java/com/listywave/mention/MentionService.java create mode 100644 src/test/java/com/listywave/common/IntegrationTest.java create mode 100644 src/test/java/com/listywave/mention/MentionServiceTest.java diff --git a/src/main/java/com/listywave/common/config/QuerydslConfig.java b/src/main/java/com/listywave/common/config/QuerydslConfig.java index 1967c990..ae83b630 100644 --- a/src/main/java/com/listywave/common/config/QuerydslConfig.java +++ b/src/main/java/com/listywave/common/config/QuerydslConfig.java @@ -9,7 +9,7 @@ public class QuerydslConfig { @Bean - JPAQueryFactory jpaQueryFactory(EntityManager em){ + JPAQueryFactory jpaQueryFactory(EntityManager em) { return new JPAQueryFactory(em); } } diff --git a/src/main/java/com/listywave/common/config/WebConfig.java b/src/main/java/com/listywave/common/config/WebConfig.java index 689a2cb8..fa4f34a7 100644 --- a/src/main/java/com/listywave/common/config/WebConfig.java +++ b/src/main/java/com/listywave/common/config/WebConfig.java @@ -13,7 +13,7 @@ public class WebConfig implements WebMvcConfigurer { @Value("${cors.allowedOrigins}") private String[] allowedOrigins; - + @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") diff --git a/src/main/java/com/listywave/common/exception/CustomException.java b/src/main/java/com/listywave/common/exception/CustomException.java index c64a1bf2..3d72a463 100644 --- a/src/main/java/com/listywave/common/exception/CustomException.java +++ b/src/main/java/com/listywave/common/exception/CustomException.java @@ -5,7 +5,7 @@ @Getter @AllArgsConstructor -public class CustomException extends RuntimeException{ +public class CustomException extends RuntimeException { private final ErrorCode errorCode; diff --git a/src/main/java/com/listywave/common/exception/ErrorResponse.java b/src/main/java/com/listywave/common/exception/ErrorResponse.java index 20a42459..b67a04c8 100644 --- a/src/main/java/com/listywave/common/exception/ErrorResponse.java +++ b/src/main/java/com/listywave/common/exception/ErrorResponse.java @@ -19,7 +19,7 @@ public static ResponseEntity toResponseEntity(CustomException e) errorCode.getDetail(), e.getMessage() ); - + return ResponseEntity.status(errorCode.getStatus()).body(errorResponse); } } diff --git a/src/main/java/com/listywave/common/util/DataUpdateUtils.java b/src/main/java/com/listywave/common/util/DataUpdateUtils.java new file mode 100644 index 00000000..12fa1e68 --- /dev/null +++ b/src/main/java/com/listywave/common/util/DataUpdateUtils.java @@ -0,0 +1,18 @@ +package com.listywave.common.util; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public abstract class DataUpdateUtils { + + public static void update(List before, List after) { + Set removable = new HashSet<>(before); + after.forEach(removable::remove); + before.removeAll(removable); + + Set addable = new HashSet<>(after); + before.forEach(addable::remove); + before.addAll(addable); + } +} diff --git a/src/main/java/com/listywave/list/application/domain/comment/Comment.java b/src/main/java/com/listywave/list/application/domain/comment/Comment.java index 2611c5e9..dd8171ab 100644 --- a/src/main/java/com/listywave/list/application/domain/comment/Comment.java +++ b/src/main/java/com/listywave/list/application/domain/comment/Comment.java @@ -1,23 +1,27 @@ package com.listywave.list.application.domain.comment; +import static jakarta.persistence.CascadeType.ALL; import static jakarta.persistence.FetchType.LAZY; import static lombok.AccessLevel.PROTECTED; import com.listywave.common.BaseEntity; +import com.listywave.common.util.DataUpdateUtils; import com.listywave.list.application.domain.list.ListEntity; +import com.listywave.mention.Mention; import com.listywave.user.application.domain.User; import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; -import lombok.AllArgsConstructor; +import jakarta.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @Entity -@AllArgsConstructor @NoArgsConstructor(access = PROTECTED) public class Comment extends BaseEntity { @@ -32,11 +36,21 @@ public class Comment extends BaseEntity { @Embedded private CommentContent commentContent; + @OneToMany(fetch = LAZY, cascade = ALL, orphanRemoval = true, mappedBy = "comment") + private final List mentions = new ArrayList<>(); + @Column(nullable = false, length = 5) private boolean isDeleted; - public static Comment create(ListEntity list, User user, CommentContent content) { - return new Comment(list, user, content, false); + public Comment(ListEntity list, User user, CommentContent content, List mentions) { + this.list = list; + this.user = user; + this.commentContent = content; + mentions.forEach(it -> { + this.mentions.add(it); + it.setComment(this); + }); + this.isDeleted = false; } public boolean isOwner(User user) { @@ -47,8 +61,9 @@ public void softDelete() { this.isDeleted = true; } - public void update(CommentContent content) { + public void update(CommentContent content, List mentions) { this.commentContent = content; + DataUpdateUtils.update(this.mentions, mentions); } public boolean isDeleted() { diff --git a/src/main/java/com/listywave/list/application/domain/reply/Reply.java b/src/main/java/com/listywave/list/application/domain/reply/Reply.java index 45a3d33c..0032b37e 100644 --- a/src/main/java/com/listywave/list/application/domain/reply/Reply.java +++ b/src/main/java/com/listywave/list/application/domain/reply/Reply.java @@ -1,16 +1,22 @@ package com.listywave.list.application.domain.reply; +import static jakarta.persistence.CascadeType.ALL; import static jakarta.persistence.FetchType.LAZY; import static lombok.AccessLevel.PROTECTED; import com.listywave.common.BaseEntity; +import com.listywave.common.util.DataUpdateUtils; import com.listywave.list.application.domain.comment.Comment; import com.listywave.list.application.domain.comment.CommentContent; +import com.listywave.mention.Mention; import com.listywave.user.application.domain.User; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -32,12 +38,26 @@ public class Reply extends BaseEntity { @Embedded private CommentContent commentContent; + @OneToMany(fetch = LAZY, cascade = ALL, orphanRemoval = true, mappedBy = "reply") + private final List mentions = new ArrayList<>(); + + public Reply(Comment comment, User user, CommentContent commentContent, List mentions) { + this.comment = comment; + this.user = user; + this.commentContent = commentContent; + mentions.forEach(it -> { + this.mentions.add(it); + it.setReply(this); + }); + } + public boolean isOwner(User user) { return this.user.equals(user); } - public void update(CommentContent content) { + public void update(CommentContent content, List mentions) { this.commentContent = content; + DataUpdateUtils.update(this.mentions, mentions); } public Long getCommentId() { diff --git a/src/main/java/com/listywave/list/application/dto/ReplyUpdateCommand.java b/src/main/java/com/listywave/list/application/dto/ReplyUpdateCommand.java index dc00714b..d6748877 100644 --- a/src/main/java/com/listywave/list/application/dto/ReplyUpdateCommand.java +++ b/src/main/java/com/listywave/list/application/dto/ReplyUpdateCommand.java @@ -1,9 +1,12 @@ package com.listywave.list.application.dto; +import java.util.List; + public record ReplyUpdateCommand( Long listId, Long commentId, Long replyId, - String content + String content, + List mentionIds ) { } diff --git a/src/main/java/com/listywave/list/application/dto/response/CommentFindResponse.java b/src/main/java/com/listywave/list/application/dto/response/CommentFindResponse.java index d46c21d4..183144db 100644 --- a/src/main/java/com/listywave/list/application/dto/response/CommentFindResponse.java +++ b/src/main/java/com/listywave/list/application/dto/response/CommentFindResponse.java @@ -1,9 +1,11 @@ package com.listywave.list.application.dto.response; import static java.util.Collections.emptyList; +import static java.util.Comparator.comparingLong; import com.listywave.list.application.domain.comment.Comment; import com.listywave.list.application.domain.reply.Reply; +import com.listywave.mention.Mention; import java.time.LocalDateTime; import java.util.List; import java.util.Map; @@ -14,7 +16,7 @@ public record CommentFindResponse( Long totalCount, Long cursorId, boolean hasNext, - List comments + List comments ) { public static CommentFindResponse emptyResponse() { @@ -36,12 +38,12 @@ public static CommentFindResponse from( .totalCount(totalCount) .cursorId(cursorId) .hasNext(hasNext) - .comments(CommentResponse.toList(comments)) + .comments(CommentDto.toList(comments)) .build(); } @Builder - public record CommentResponse( + public record CommentDto( Long id, Long userId, String userNickname, @@ -50,17 +52,18 @@ public record CommentResponse( LocalDateTime createdDate, LocalDateTime updatedDate, boolean isDeleted, - List replies + List replies, + List mentions ) { - public static List toList(Map> comments) { + public static List toList(Map> comments) { return comments.keySet().stream() - .map(comment -> CommentResponse.of(comment, comments.get(comment))) + .map(comment -> CommentDto.of(comment, comments.get(comment))) .toList(); } - public static CommentResponse of(Comment comment, List replies) { - return CommentResponse.builder() + public static CommentDto of(Comment comment, List replies) { + return CommentDto.builder() .id(comment.getId()) .userId(comment.getUserId()) .userNickname(comment.getUserNickname()) @@ -69,13 +72,14 @@ public static CommentResponse of(Comment comment, List replies) { .createdDate(comment.getCreatedDate()) .updatedDate(comment.getUpdatedDate()) .isDeleted(comment.isDeleted()) - .replies(ReplyResponse.toList(replies)) + .replies(ReplyDto.toList(replies)) + .mentions(MentionDto.toList(comment.getMentions())) .build(); } } @Builder - public record ReplyResponse( + public record ReplyDto( Long id, Long commentId, Long userId, @@ -83,17 +87,18 @@ public record ReplyResponse( String userProfileImageUrl, String content, LocalDateTime createdDate, - LocalDateTime updatedDate + LocalDateTime updatedDate, + List mentions ) { - public static List toList(List replies) { + public static List toList(List replies) { return replies.stream() - .map(ReplyResponse::of) + .map(ReplyDto::of) .toList(); } - public static ReplyResponse of(Reply reply) { - return ReplyResponse.builder() + public static ReplyDto of(Reply reply) { + return ReplyDto.builder() .id(reply.getId()) .commentId(reply.getCommentId()) .userId(reply.getUserId()) @@ -102,7 +107,23 @@ public static ReplyResponse of(Reply reply) { .content(reply.getCommentContent()) .createdDate(reply.getCreatedDate()) .updatedDate(reply.getUpdatedDate()) + .mentions(MentionDto.toList(reply.getMentions())) .build(); } } + + public record MentionDto( + Long userId, + String userNickname + ) { + + public static List toList(List mentions) { + return mentions.stream() + .map(Mention::getUser) + .filter(user -> !user.isDelete()) + .map(user -> new MentionDto(user.getId(), user.getNickname())) + .sorted(comparingLong(MentionDto::userId)) + .toList(); + } + } } diff --git a/src/main/java/com/listywave/list/application/dto/response/ListRecentResponse.java b/src/main/java/com/listywave/list/application/dto/response/ListRecentResponse.java index 34df3a3b..af163499 100644 --- a/src/main/java/com/listywave/list/application/dto/response/ListRecentResponse.java +++ b/src/main/java/com/listywave/list/application/dto/response/ListRecentResponse.java @@ -3,7 +3,6 @@ import com.listywave.list.application.domain.item.Item; import com.listywave.list.application.domain.list.ListEntity; import java.time.LocalDateTime; -import java.util.Comparator; import java.util.List; import lombok.Builder; diff --git a/src/main/java/com/listywave/list/application/service/CommentService.java b/src/main/java/com/listywave/list/application/service/CommentService.java index 82e9a002..32fb8309 100644 --- a/src/main/java/com/listywave/list/application/service/CommentService.java +++ b/src/main/java/com/listywave/list/application/service/CommentService.java @@ -12,9 +12,11 @@ import com.listywave.list.application.domain.reply.Reply; import com.listywave.list.application.dto.response.CommentCreateResponse; import com.listywave.list.application.dto.response.CommentFindResponse; -import com.listywave.list.repository.CommentRepository; +import com.listywave.list.repository.comment.CommentRepository; import com.listywave.list.repository.list.ListRepository; import com.listywave.list.repository.reply.ReplyRepository; +import com.listywave.mention.Mention; +import com.listywave.mention.MentionService; import com.listywave.user.application.domain.User; import com.listywave.user.repository.user.UserRepository; import jakarta.transaction.Transactional; @@ -32,22 +34,23 @@ public class CommentService { private final ListRepository listRepository; private final UserRepository userRepository; + private final MentionService mentionService; private final ReplyRepository replyRepository; private final CommentRepository commentRepository; private final ApplicationEventPublisher applicationEventPublisher; - public CommentCreateResponse create(Long listId, String content, Long loginUserId) { - User user = userRepository.getById(loginUserId); + public CommentCreateResponse create(Long listId, Long writerId, String content, List mentionIds) { + User writer = userRepository.getById(writerId); ListEntity list = listRepository.getById(listId); + List mentions = mentionService.toMentions(mentionIds); - Comment comment = Comment.create(list, user, new CommentContent(content)); - Comment saved = commentRepository.save(comment); + Comment comment = commentRepository.save(new Comment(list, writer, new CommentContent(content), mentions)); - applicationEventPublisher.publishEvent(AlarmEvent.comment(list, saved)); - return CommentCreateResponse.of(saved, user); + applicationEventPublisher.publishEvent(AlarmEvent.comment(list, comment)); + return CommentCreateResponse.of(comment, writer); } - public CommentFindResponse getComments(Long listId, int size, Long cursorId) { + public CommentFindResponse findCommentBy(Long listId, int size, Long cursorId) { ListEntity list = listRepository.getById(listId); List comments = commentRepository.getComments(list, size, cursorId); @@ -73,9 +76,9 @@ public CommentFindResponse getComments(Long listId, int size, Long cursorId) { return CommentFindResponse.from(totalCount, newCursorId, hasNext, result); } - public void delete(Long listId, Long commentId, Long loginUserId) { + public void delete(Long listId, Long commentId, Long userId) { listRepository.getById(listId); - User user = userRepository.getById(loginUserId); + User user = userRepository.getById(userId); Comment comment = commentRepository.getById(commentId); if (!comment.isOwner(user)) { @@ -89,14 +92,16 @@ public void delete(Long listId, Long commentId, Long loginUserId) { commentRepository.delete(comment); } - public void update(Long listId, Long commentId, Long loginUserId, String content) { + public void update(Long listId, Long writerId, Long commentId, String content, List mentionIds) { listRepository.getById(listId); - User user = userRepository.getById(loginUserId); + User writer = userRepository.getById(writerId); Comment comment = commentRepository.getById(commentId); - if (!comment.isOwner(user)) { + if (!comment.isOwner(writer)) { throw new CustomException(INVALID_ACCESS, "댓글은 작성자만 수정할 수 있습니다."); } - comment.update(new CommentContent(content)); + + List mentions = mentionService.toMentions(mentionIds); + comment.update(new CommentContent(content), mentions); } } diff --git a/src/main/java/com/listywave/list/application/service/ListService.java b/src/main/java/com/listywave/list/application/service/ListService.java index 850b2056..d7fd7f4a 100644 --- a/src/main/java/com/listywave/list/application/service/ListService.java +++ b/src/main/java/com/listywave/list/application/service/ListService.java @@ -37,8 +37,8 @@ import com.listywave.list.presentation.dto.request.ItemCreateRequest; import com.listywave.list.presentation.dto.request.ListCreateRequest; import com.listywave.list.presentation.dto.request.ListUpdateRequest; -import com.listywave.list.repository.CommentRepository; import com.listywave.list.repository.ItemRepository; +import com.listywave.list.repository.comment.CommentRepository; import com.listywave.list.repository.label.LabelRepository; import com.listywave.list.repository.list.ListRepository; import com.listywave.list.repository.reply.ReplyRepository; diff --git a/src/main/java/com/listywave/list/application/service/ReplyService.java b/src/main/java/com/listywave/list/application/service/ReplyService.java index b5364409..e5043011 100644 --- a/src/main/java/com/listywave/list/application/service/ReplyService.java +++ b/src/main/java/com/listywave/list/application/service/ReplyService.java @@ -9,11 +9,14 @@ import com.listywave.list.application.dto.ReplyDeleteCommand; import com.listywave.list.application.dto.ReplyUpdateCommand; import com.listywave.list.application.dto.response.ReplyCreateResponse; -import com.listywave.list.repository.CommentRepository; +import com.listywave.list.repository.comment.CommentRepository; import com.listywave.list.repository.list.ListRepository; import com.listywave.list.repository.reply.ReplyRepository; +import com.listywave.mention.Mention; +import com.listywave.mention.MentionService; import com.listywave.user.application.domain.User; import com.listywave.user.repository.user.UserRepository; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; @@ -26,25 +29,32 @@ public class ReplyService { private final ListRepository listRepository; private final UserRepository userRepository; + private final MentionService mentionService; private final ReplyRepository replyRepository; private final CommentRepository commentRepository; private final ApplicationEventPublisher applicationEventPublisher; - public ReplyCreateResponse createReply(Long listId, Long commentId, String content, Long loginUserId) { + public ReplyCreateResponse create( + Long listId, + Long targetCommentId, + Long writerId, + String content, + List mentionIds + ) { listRepository.getById(listId); - User user = userRepository.getById(loginUserId); - Comment comment = commentRepository.getById(commentId); + User user = userRepository.getById(writerId); + Comment comment = commentRepository.getById(targetCommentId); - Reply reply = new Reply(comment, user, new CommentContent(content)); - Reply saved = replyRepository.save(reply); + List mentions = mentionService.toMentions(mentionIds); + Reply reply = replyRepository.save(new Reply(comment, user, new CommentContent(content), mentions)); - applicationEventPublisher.publishEvent(AlarmEvent.reply(comment, saved)); - return ReplyCreateResponse.of(saved, comment, user); + applicationEventPublisher.publishEvent(AlarmEvent.reply(comment, reply)); + return ReplyCreateResponse.of(reply, comment, user); } - public void delete(ReplyDeleteCommand command, Long loginUserId) { + public void delete(ReplyDeleteCommand command, Long userId) { listRepository.getById(command.listId()); - User user = userRepository.getById(loginUserId); + User user = userRepository.getById(userId); Comment comment = commentRepository.getById(command.commentId()); Reply reply = replyRepository.getById(command.replyId()); @@ -58,15 +68,16 @@ public void delete(ReplyDeleteCommand command, Long loginUserId) { } } - public void update(ReplyUpdateCommand command, Long loginUserId) { + public void update(ReplyUpdateCommand command, Long writerId) { listRepository.getById(command.listId()); - User user = userRepository.getById(loginUserId); + User user = userRepository.getById(writerId); commentRepository.getById(command.commentId()); Reply reply = replyRepository.getById(command.replyId()); if (!reply.isOwner(user)) { throw new CustomException(ErrorCode.INVALID_ACCESS, "답글은 작성자만 수정할 수 있습니다."); } - reply.update(new CommentContent(command.content())); + List mentions = mentionService.toMentions(command.mentionIds()); + reply.update(new CommentContent(command.content()), mentions); } } diff --git a/src/main/java/com/listywave/list/presentation/controller/CommentController.java b/src/main/java/com/listywave/list/presentation/controller/CommentController.java index 32193798..c62275f2 100644 --- a/src/main/java/com/listywave/list/presentation/controller/CommentController.java +++ b/src/main/java/com/listywave/list/presentation/controller/CommentController.java @@ -28,10 +28,10 @@ public class CommentController { @PostMapping("/lists/{listId}/comments") ResponseEntity create( @PathVariable("listId") Long listId, - @Auth Long loginUserId, - @RequestBody CommentCreateRequest commentCreateRequest + @Auth Long writerId, + @RequestBody CommentCreateRequest request ) { - CommentCreateResponse response = commentService.create(listId, commentCreateRequest.content(), loginUserId); + CommentCreateResponse response = commentService.create(listId, writerId, request.content(), request.mentionIds()); return ResponseEntity.status(CREATED).body(response); } @@ -41,7 +41,7 @@ ResponseEntity getAllCommentsByList( @RequestParam(value = "size", defaultValue = "5") int size, @RequestParam(value = "cursorId", required = false) Long cursorId ) { - CommentFindResponse response = commentService.getComments(listId, size, cursorId); + CommentFindResponse response = commentService.findCommentBy(listId, size, cursorId); return ResponseEntity.ok(response); } @@ -58,11 +58,11 @@ ResponseEntity delete( @PatchMapping("/lists/{listId}/comments/{commentId}") ResponseEntity update( @PathVariable("listId") Long listId, - @Auth Long loginUserId, + @Auth Long writerId, @PathVariable("commentId") Long commentId, - @RequestBody CommentUpdateRequest commentUpdateRequest + @RequestBody CommentUpdateRequest request ) { - commentService.update(listId, commentId, loginUserId, commentUpdateRequest.content()); + commentService.update(listId, writerId, commentId, request.content(), request.mentionIds()); return ResponseEntity.noContent().build(); } } diff --git a/src/main/java/com/listywave/list/presentation/controller/ReplyController.java b/src/main/java/com/listywave/list/presentation/controller/ReplyController.java index bdddd7aa..5dfdd25d 100644 --- a/src/main/java/com/listywave/list/presentation/controller/ReplyController.java +++ b/src/main/java/com/listywave/list/presentation/controller/ReplyController.java @@ -28,10 +28,16 @@ public class ReplyController { ResponseEntity create( @PathVariable("listId") Long listId, @PathVariable("commentId") Long commentId, - @Auth Long loginUserId, + @Auth Long writerId, @RequestBody ReplyCreateRequest request ) { - ReplyCreateResponse response = replyService.createReply(listId, commentId, request.content(), loginUserId); + ReplyCreateResponse response = replyService.create( + listId, + commentId, + writerId, + request.content(), + request.mentionIds() + ); return ResponseEntity.status(CREATED).body(response); } @@ -55,7 +61,7 @@ ResponseEntity updateReply( @Auth Long loginUserId, @RequestBody ReplyUpdateRequest request ) { - ReplyUpdateCommand replyUpdateCommand = new ReplyUpdateCommand(listId, commentId, replyId, request.content()); + ReplyUpdateCommand replyUpdateCommand = new ReplyUpdateCommand(listId, commentId, replyId, request.content(), request.mentionIds()); replyService.update(replyUpdateCommand, loginUserId); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/com/listywave/list/presentation/dto/request/ReplyCreateRequest.java b/src/main/java/com/listywave/list/presentation/dto/request/ReplyCreateRequest.java index 3a47dfe4..d6cf23fa 100644 --- a/src/main/java/com/listywave/list/presentation/dto/request/ReplyCreateRequest.java +++ b/src/main/java/com/listywave/list/presentation/dto/request/ReplyCreateRequest.java @@ -1,6 +1,9 @@ package com.listywave.list.presentation.dto.request; +import java.util.List; + public record ReplyCreateRequest( - String content + String content, + List mentionIds ) { } diff --git a/src/main/java/com/listywave/list/presentation/dto/request/ReplyUpdateRequest.java b/src/main/java/com/listywave/list/presentation/dto/request/ReplyUpdateRequest.java index e645847a..cf9e8295 100644 --- a/src/main/java/com/listywave/list/presentation/dto/request/ReplyUpdateRequest.java +++ b/src/main/java/com/listywave/list/presentation/dto/request/ReplyUpdateRequest.java @@ -1,6 +1,9 @@ package com.listywave.list.presentation.dto.request; +import java.util.List; + public record ReplyUpdateRequest( - String content + String content, + List mentionIds ) { } diff --git a/src/main/java/com/listywave/list/presentation/dto/request/comment/CommentCreateRequest.java b/src/main/java/com/listywave/list/presentation/dto/request/comment/CommentCreateRequest.java index 44633ab1..a2510725 100644 --- a/src/main/java/com/listywave/list/presentation/dto/request/comment/CommentCreateRequest.java +++ b/src/main/java/com/listywave/list/presentation/dto/request/comment/CommentCreateRequest.java @@ -1,6 +1,9 @@ package com.listywave.list.presentation.dto.request.comment; +import java.util.List; + public record CommentCreateRequest( - String content + String content, + List mentionIds ) { } diff --git a/src/main/java/com/listywave/list/presentation/dto/request/comment/CommentUpdateRequest.java b/src/main/java/com/listywave/list/presentation/dto/request/comment/CommentUpdateRequest.java index 2f243c42..0b360cc4 100644 --- a/src/main/java/com/listywave/list/presentation/dto/request/comment/CommentUpdateRequest.java +++ b/src/main/java/com/listywave/list/presentation/dto/request/comment/CommentUpdateRequest.java @@ -1,6 +1,9 @@ package com.listywave.list.presentation.dto.request.comment; +import java.util.List; + public record CommentUpdateRequest( - String content + String content, + List mentionIds ) { } diff --git a/src/main/java/com/listywave/list/repository/CommentRepository.java b/src/main/java/com/listywave/list/repository/comment/CommentRepository.java similarity index 95% rename from src/main/java/com/listywave/list/repository/CommentRepository.java rename to src/main/java/com/listywave/list/repository/comment/CommentRepository.java index 2b909d3d..7e2da5b6 100644 --- a/src/main/java/com/listywave/list/repository/CommentRepository.java +++ b/src/main/java/com/listywave/list/repository/comment/CommentRepository.java @@ -1,4 +1,4 @@ -package com.listywave.list.repository; +package com.listywave.list.repository.comment; import static com.listywave.common.exception.ErrorCode.RESOURCE_NOT_FOUND; diff --git a/src/main/java/com/listywave/list/repository/CustomCommentRepository.java b/src/main/java/com/listywave/list/repository/comment/CustomCommentRepository.java similarity index 86% rename from src/main/java/com/listywave/list/repository/CustomCommentRepository.java rename to src/main/java/com/listywave/list/repository/comment/CustomCommentRepository.java index 997c072f..ff29cde3 100644 --- a/src/main/java/com/listywave/list/repository/CustomCommentRepository.java +++ b/src/main/java/com/listywave/list/repository/comment/CustomCommentRepository.java @@ -1,4 +1,4 @@ -package com.listywave.list.repository; +package com.listywave.list.repository.comment; import com.listywave.list.application.domain.comment.Comment; import com.listywave.list.application.domain.list.ListEntity; diff --git a/src/main/java/com/listywave/list/repository/CustomCommentRepositoryImpl.java b/src/main/java/com/listywave/list/repository/comment/CustomCommentRepositoryImpl.java similarity index 94% rename from src/main/java/com/listywave/list/repository/CustomCommentRepositoryImpl.java rename to src/main/java/com/listywave/list/repository/comment/CustomCommentRepositoryImpl.java index b9911695..292003f7 100644 --- a/src/main/java/com/listywave/list/repository/CustomCommentRepositoryImpl.java +++ b/src/main/java/com/listywave/list/repository/comment/CustomCommentRepositoryImpl.java @@ -1,4 +1,4 @@ -package com.listywave.list.repository; +package com.listywave.list.repository.comment; import static com.listywave.list.application.domain.comment.QComment.comment; @@ -18,7 +18,8 @@ public class CustomCommentRepositoryImpl implements CustomCommentRepository { @Override public List getComments(ListEntity list, int size, Long cursorId) { - return queryFactory.selectFrom(comment) + return queryFactory + .selectFrom(comment) .leftJoin(listEntity).on(comment.list.id.eq(listEntity.id)).fetchJoin() .where( comment.list.id.eq(list.getId()), diff --git a/src/main/java/com/listywave/list/repository/list/custom/impl/CustomListRepositoryImpl.java b/src/main/java/com/listywave/list/repository/list/custom/impl/CustomListRepositoryImpl.java index 2f5ec2af..0d3c1562 100644 --- a/src/main/java/com/listywave/list/repository/list/custom/impl/CustomListRepositoryImpl.java +++ b/src/main/java/com/listywave/list/repository/list/custom/impl/CustomListRepositoryImpl.java @@ -7,7 +7,6 @@ import static com.listywave.list.application.domain.category.CategoryType.ENTIRE; import static com.listywave.list.application.domain.comment.QComment.comment; import static com.listywave.list.application.domain.item.QItem.item; -import static com.listywave.list.application.domain.label.QLabel.label; import static com.listywave.list.application.domain.list.QListEntity.listEntity; import static com.listywave.list.application.domain.reply.QReply.reply; import static com.listywave.user.application.domain.QUser.user; diff --git a/src/main/java/com/listywave/mention/Mention.java b/src/main/java/com/listywave/mention/Mention.java new file mode 100644 index 00000000..fdd16423 --- /dev/null +++ b/src/main/java/com/listywave/mention/Mention.java @@ -0,0 +1,60 @@ +package com.listywave.mention; + +import static jakarta.persistence.FetchType.LAZY; +import static jakarta.persistence.GenerationType.IDENTITY; +import static lombok.AccessLevel.PROTECTED; + +import com.listywave.list.application.domain.comment.Comment; +import com.listywave.list.application.domain.reply.Reply; +import com.listywave.user.application.domain.User; +import jakarta.persistence.Entity; +import jakarta.persistence.ForeignKey; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import java.util.Objects; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Getter +@NoArgsConstructor(access = PROTECTED) +public class Mention { + + @Id + @GeneratedValue(strategy = IDENTITY) + private Long id; + + @ManyToOne(fetch = LAZY) + @JoinColumn(name = "mentioned_user_id") + private User user; + + @Setter + @ManyToOne(fetch = LAZY) + @JoinColumn(name = "comment_id", nullable = true, foreignKey = @ForeignKey(name = "mention_comment_fk")) + private Comment comment; + + @Setter + @ManyToOne(fetch = LAZY) + @JoinColumn(name = "reply_id", nullable = true, foreignKey = @ForeignKey(name = "mention_reply_fk")) + private Reply reply; + + public Mention(User user) { + this.user = user; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Mention mention = (Mention) o; + return Objects.equals(getUser(), mention.getUser()); + } + + @Override + public int hashCode() { + return Objects.hashCode(getUser()); + } +} diff --git a/src/main/java/com/listywave/mention/MentionRepository.java b/src/main/java/com/listywave/mention/MentionRepository.java new file mode 100644 index 00000000..31ce706f --- /dev/null +++ b/src/main/java/com/listywave/mention/MentionRepository.java @@ -0,0 +1,13 @@ +package com.listywave.mention; + +import com.listywave.list.application.domain.comment.Comment; +import com.listywave.list.application.domain.reply.Reply; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MentionRepository extends JpaRepository { + + List findAllByComment(Comment comment); + + List findAllByReply(Reply reply); +} diff --git a/src/main/java/com/listywave/mention/MentionService.java b/src/main/java/com/listywave/mention/MentionService.java new file mode 100644 index 00000000..88d51e35 --- /dev/null +++ b/src/main/java/com/listywave/mention/MentionService.java @@ -0,0 +1,24 @@ +package com.listywave.mention; + +import com.listywave.user.application.domain.User; +import com.listywave.user.repository.user.UserRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +@RequiredArgsConstructor +public class MentionService { + + private final UserRepository userRepository; + + @Transactional(readOnly = true) + public List toMentions(List mentionIds) { + List mentionedUsers = userRepository.findAllById(mentionIds); + return mentionedUsers.stream() + .map(Mention::new) + .toList(); + } +} diff --git a/src/main/java/com/listywave/user/presentation/UserController.java b/src/main/java/com/listywave/user/presentation/UserController.java index f7f2e8b2..41d285f0 100644 --- a/src/main/java/com/listywave/user/presentation/UserController.java +++ b/src/main/java/com/listywave/user/presentation/UserController.java @@ -9,7 +9,6 @@ import com.listywave.user.application.dto.search.UserElasticSearchResponse; import com.listywave.user.application.dto.search.UserSearchResponse; import com.listywave.user.application.service.UserService; -import com.listywave.user.presentation.dto.ListVisibilityUpdateRequest; import com.listywave.user.presentation.dto.UserProfileUpdateRequest; import java.util.List; import lombok.RequiredArgsConstructor; diff --git a/src/test/java/com/listywave/acceptance/alarm/AlarmAcceptanceTest.java b/src/test/java/com/listywave/acceptance/alarm/AlarmAcceptanceTest.java index 9100578e..a0238f37 100644 --- a/src/test/java/com/listywave/acceptance/alarm/AlarmAcceptanceTest.java +++ b/src/test/java/com/listywave/acceptance/alarm/AlarmAcceptanceTest.java @@ -132,7 +132,7 @@ public class AlarmAcceptanceTest extends AcceptanceTest { var 댓글_생성_요청들 = n개의_댓글_생성_요청(1); 댓글_생성_요청들.forEach(댓글_생성요청 -> 댓글_저장_API_호출(동호_엑세스_토큰, 정수_리스트_ID, 댓글_생성요청)); - var 답글_생성_요청_데이터 = new ReplyCreateRequest("답글 달아요! 😀 "); + var 답글_생성_요청_데이터 = new ReplyCreateRequest("답글 달아요! 😀 ", List.of()); 답글_등록_API_호출(유진_엑세스_토큰, 답글_생성_요청_데이터, 정수_리스트_ID, 1L); // when diff --git a/src/test/java/com/listywave/acceptance/comment/CommentAcceptanceTest.java b/src/test/java/com/listywave/acceptance/comment/CommentAcceptanceTest.java index 55753731..5ba5e1d6 100644 --- a/src/test/java/com/listywave/acceptance/comment/CommentAcceptanceTest.java +++ b/src/test/java/com/listywave/acceptance/comment/CommentAcceptanceTest.java @@ -11,6 +11,7 @@ import static com.listywave.acceptance.reply.ReplyAcceptanceTestHelper.답글_등록_API_호출; import static com.listywave.user.fixture.UserFixture.동호; import static com.listywave.user.fixture.UserFixture.정수; +import static java.util.Collections.EMPTY_LIST; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.http.HttpStatus.FORBIDDEN; import static org.springframework.http.HttpStatus.NOT_FOUND; @@ -64,7 +65,7 @@ class 댓글_수정 { 댓글_생성_요청들.forEach(댓글_생성요청 -> 댓글_저장_API_호출(동호_액세스_토큰, 동호_리스트_ID, 댓글_생성요청)); // when - var 댓글_수정_요청 = new CommentUpdateRequest("수정할게요!"); + var 댓글_수정_요청 = new CommentUpdateRequest("수정할게요!", EMPTY_LIST); 댓글_수정_API_호출(동호_액세스_토큰, 댓글_수정_요청, 동호_리스트_ID, 5L); // then @@ -85,7 +86,7 @@ class 댓글_수정 { 댓글_생성_요청들.forEach(댓글_생성요청 -> 댓글_저장_API_호출(동호_액세스_토큰, 동호_리스트_ID, 댓글_생성요청)); // when - var 댓글_수정_요청 = new CommentUpdateRequest("수정할게요!"); + var 댓글_수정_요청 = new CommentUpdateRequest("수정할게요!", EMPTY_LIST); var 댓글_수정_응답 = 댓글_수정_API_호출(동호_액세스_토큰, 댓글_수정_요청, 동호_리스트_ID, 101L); // then @@ -107,7 +108,7 @@ class 댓글_수정 { 댓글_생성_요청들.forEach(댓글_생성요청 -> 댓글_저장_API_호출(정수_액세스_토큰, 동호_리스트_ID, 댓글_생성요청)); // when - var 댓글_수정_요청 = new CommentUpdateRequest("수정할게요!"); + var 댓글_수정_요청 = new CommentUpdateRequest("수정할게요!", EMPTY_LIST); var 댓글_수정_응답 = 댓글_수정_API_호출(동호_액세스_토큰, 댓글_수정_요청, 동호_리스트_ID, 1L); // then @@ -171,7 +172,7 @@ class 댓글_삭제 { var 댓글_생성_요청들 = n개의_댓글_생성_요청(3); 댓글_생성_요청들.forEach(댓글_생성요청 -> 댓글_저장_API_호출(동호_액세스_토큰, 동호_리스트_ID, 댓글_생성요청)); - var 답글_수정_요청 = new ReplyCreateRequest("답글 달아요! 😀 "); + var 답글_수정_요청 = new ReplyCreateRequest("답글 달아요! 😀 ", List.of()); 답글_등록_API_호출(동호_액세스_토큰, 답글_수정_요청, 동호_리스트_ID, 2L); // when diff --git a/src/test/java/com/listywave/acceptance/comment/CommentAcceptanceTestHelper.java b/src/test/java/com/listywave/acceptance/comment/CommentAcceptanceTestHelper.java index 4308ebc6..584a82d9 100644 --- a/src/test/java/com/listywave/acceptance/comment/CommentAcceptanceTestHelper.java +++ b/src/test/java/com/listywave/acceptance/comment/CommentAcceptanceTestHelper.java @@ -14,7 +14,7 @@ public abstract class CommentAcceptanceTestHelper { public static List n개의_댓글_생성_요청(int size) { return IntStream.range(0, size) - .mapToObj(i -> new CommentCreateRequest((i + 1) + "번 째 댓글")) + .mapToObj(i -> new CommentCreateRequest((i + 1) + "번 째 댓글", List.of())) .toList(); } diff --git a/src/test/java/com/listywave/acceptance/list/ListAcceptanceTest.java b/src/test/java/com/listywave/acceptance/list/ListAcceptanceTest.java index 8decd56d..2cb9545c 100644 --- a/src/test/java/com/listywave/acceptance/list/ListAcceptanceTest.java +++ b/src/test/java/com/listywave/acceptance/list/ListAcceptanceTest.java @@ -42,6 +42,7 @@ import static com.listywave.user.fixture.UserFixture.유진; import static com.listywave.user.fixture.UserFixture.정수; import static java.time.temporal.ChronoUnit.MILLIS; +import static java.util.Collections.EMPTY_LIST; import static java.util.Comparator.comparing; import static java.util.Comparator.reverseOrder; import static org.assertj.core.api.Assertions.assertThat; @@ -301,15 +302,15 @@ class 리스트_상세_조회 { .listId(); var 정수 = 회원을_저장한다(정수()); - 댓글_저장_API_호출(액세스_토큰을_발급한다(정수), 리스트_ID, new CommentCreateRequest("댓글 1빠!")); - var 댓글_ID = 댓글_저장_API_호출(액세스_토큰을_발급한다(정수), 리스트_ID, new CommentCreateRequest("댓글 2빠!")) + 댓글_저장_API_호출(액세스_토큰을_발급한다(정수), 리스트_ID, new CommentCreateRequest("댓글 1빠!", EMPTY_LIST)); + var 댓글_ID = 댓글_저장_API_호출(액세스_토큰을_발급한다(정수), 리스트_ID, new CommentCreateRequest("댓글 2빠!", EMPTY_LIST)) .as(CommentCreateResponse.class) .id(); var 유진 = 회원을_저장한다(유진()); - 답글_등록_API_호출(액세스_토큰을_발급한다(유진), new ReplyCreateRequest("답글 1빠!"), 리스트_ID, 댓글_ID); - 답글_등록_API_호출(액세스_토큰을_발급한다(유진), new ReplyCreateRequest("답글 2빠!"), 리스트_ID, 댓글_ID); - 답글_등록_API_호출(액세스_토큰을_발급한다(유진), new ReplyCreateRequest("답글 3빠!"), 리스트_ID, 댓글_ID); + 답글_등록_API_호출(액세스_토큰을_발급한다(유진), new ReplyCreateRequest("답글 1빠!", EMPTY_LIST), 리스트_ID, 댓글_ID); + 답글_등록_API_호출(액세스_토큰을_발급한다(유진), new ReplyCreateRequest("답글 2빠!", EMPTY_LIST), 리스트_ID, 댓글_ID); + 답글_등록_API_호출(액세스_토큰을_발급한다(유진), new ReplyCreateRequest("답글 3빠!", EMPTY_LIST), 리스트_ID, 댓글_ID); // when var 동호_리스트 = 비회원_리스트_상세_조회_API_호출(1L).as(ListDetailResponse.class); @@ -644,7 +645,7 @@ class 리스트_팀섹 { 댓글_생성_요청들.forEach(댓글_생성요청 -> 댓글_저장_API_호출(동호_액세스_토큰, 2L, 댓글_생성요청)); 댓글_생성_요청들2.forEach(댓글_생성요청 -> 댓글_저장_API_호출(동호_액세스_토큰, 4L, 댓글_생성요청)); - var 답글_생성_요청들 = Arrays.asList(new ReplyCreateRequest("답글1"), new ReplyCreateRequest("답글2")); + var 답글_생성_요청들 = Arrays.asList(new ReplyCreateRequest("답글1", List.of()), new ReplyCreateRequest("답글2", List.of())); 답글_생성_요청들.forEach(답글_생성요청 -> 답글_등록_API_호출(동호_액세스_토큰, 답글_생성요청, 2L, 2L)); var 폴더_생성_요청_데이터 = 폴더_생성_요청_데이터("맛집"); diff --git a/src/test/java/com/listywave/acceptance/reply/ReplyAcceptanceTest.java b/src/test/java/com/listywave/acceptance/reply/ReplyAcceptanceTest.java index a17e8548..f2ce02ac 100644 --- a/src/test/java/com/listywave/acceptance/reply/ReplyAcceptanceTest.java +++ b/src/test/java/com/listywave/acceptance/reply/ReplyAcceptanceTest.java @@ -11,6 +11,7 @@ import static com.listywave.list.fixture.ListFixture.가장_좋아하는_견종_TOP3; import static com.listywave.user.fixture.UserFixture.동호; import static com.listywave.user.fixture.UserFixture.정수; +import static java.util.Collections.EMPTY_LIST; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.http.HttpStatus.FORBIDDEN; import static org.springframework.http.HttpStatus.NO_CONTENT; @@ -38,7 +39,7 @@ public class ReplyAcceptanceTest extends AcceptanceTest { 댓글_생성_요청들.forEach(댓글_생성요청 -> 댓글_저장_API_호출(동호_액세스_토큰, 동호_리스트.getId(), 댓글_생성요청)); // when - var 답글_생성_요청 = new ReplyCreateRequest("답글 달아요! 😀 "); + var 답글_생성_요청 = new ReplyCreateRequest("답글 달아요! 😀 ", List.of()); 답글_등록_API_호출(동호_액세스_토큰, 답글_생성_요청, 동호_리스트.getId(), 2L); // then @@ -59,11 +60,11 @@ class 답글_수정 { var 댓글_생성_요청들 = n개의_댓글_생성_요청(3); 댓글_생성_요청들.forEach(댓글_생성요청 -> 댓글_저장_API_호출(동호_액세스_토큰, 동호_리스트.getId(), 댓글_생성요청)); - var 답글_생성_요청_데이터 = new ReplyCreateRequest("답글 달아요! 😀 "); + var 답글_생성_요청_데이터 = new ReplyCreateRequest("답글 달아요! 😀 ", List.of()); 답글_등록_API_호출(동호_액세스_토큰, 답글_생성_요청_데이터, 동호_리스트.getId(), 2L); // when - var 답글_수정_요청_데이터 = new ReplyUpdateRequest("답글 수정입니다!~!@#!#"); + var 답글_수정_요청_데이터 = new ReplyUpdateRequest("답글 수정입니다!~!@#!#", EMPTY_LIST); 답글_수정_API_호출(동호_액세스_토큰, 답글_수정_요청_데이터, 동호_리스트.getId(), 2L, 1L); // then @@ -83,7 +84,7 @@ class 답글_수정 { var 댓글_생성_요청들 = n개의_댓글_생성_요청(3); 댓글_생성_요청들.forEach(댓글_생성요청 -> 댓글_저장_API_호출(동호_액세스_토큰, 동호_리스트.getId(), 댓글_생성요청)); - var 답글_생성_요청_데이터 = new ReplyCreateRequest("답글 달아요! 😀 "); + var 답글_생성_요청_데이터 = new ReplyCreateRequest("답글 달아요! 😀 ", List.of()); 답글_등록_API_호출(정수_액세스_토큰, 답글_생성_요청_데이터, 동호_리스트.getId(), 2L); // when @@ -107,7 +108,7 @@ class 답글_삭제 { var 댓글_생성_요청들 = n개의_댓글_생성_요청(3); 댓글_생성_요청들.forEach(댓글_생성요청 -> 댓글_저장_API_호출(동호_액세스_토큰, 동호_리스트.getId(), 댓글_생성요청)); - var 답글_수정_요청 = new ReplyCreateRequest("답글 달아요! 😀 "); + var 답글_수정_요청 = new ReplyCreateRequest("답글 달아요! 😀 ", List.of()); 답글_등록_API_호출(동호_액세스_토큰, 답글_수정_요청, 동호_리스트.getId(), 2L); // when @@ -133,7 +134,7 @@ class 답글_삭제 { var 댓글_생성_요청들 = n개의_댓글_생성_요청(3); 댓글_생성_요청들.forEach(댓글_생성요청 -> 댓글_저장_API_호출(동호_액세스_토큰, 동호_리스트.getId(), 댓글_생성요청)); - var 답글_생성_요청_데이터 = new ReplyCreateRequest("답글 달아요! 😀 "); + var 답글_생성_요청_데이터 = new ReplyCreateRequest("답글 달아요! 😀 ", List.of()); 답글_등록_API_호출(정수_액세스_토큰, 답글_생성_요청_데이터, 동호_리스트.getId(), 2L); // when @@ -153,7 +154,7 @@ class 답글_삭제 { var 댓글_생성_요청들 = n개의_댓글_생성_요청(3); 댓글_생성_요청들.forEach(댓글_생성요청 -> 댓글_저장_API_호출(동호_액세스_토큰, 동호_리스트.getId(), 댓글_생성요청)); - var 답글_수정_요청 = new ReplyCreateRequest("답글 달아요! 😀 "); + var 답글_수정_요청 = new ReplyCreateRequest("답글 달아요! 😀 ", List.of()); 답글_등록_API_호출(동호_액세스_토큰, 답글_수정_요청, 동호_리스트.getId(), 2L); 댓글_삭제_API_호출(동호_액세스_토큰, 동호_리스트.getId(), 2L); diff --git a/src/test/java/com/listywave/common/IntegrationTest.java b/src/test/java/com/listywave/common/IntegrationTest.java new file mode 100644 index 00000000..d3bdd9a5 --- /dev/null +++ b/src/test/java/com/listywave/common/IntegrationTest.java @@ -0,0 +1,67 @@ +package com.listywave.common; + +import static com.listywave.user.fixture.UserFixture.동호; +import static com.listywave.user.fixture.UserFixture.유진; +import static com.listywave.user.fixture.UserFixture.정수; + +import com.listywave.auth.application.domain.kakao.KakaoOauthClient; +import com.listywave.auth.application.service.AuthService; +import com.listywave.list.application.domain.list.ListEntity; +import com.listywave.list.application.service.CommentService; +import com.listywave.list.application.service.ReplyService; +import com.listywave.list.fixture.ListFixture; +import com.listywave.list.repository.comment.CommentRepository; +import com.listywave.list.repository.list.ListRepository; +import com.listywave.list.repository.reply.ReplyRepository; +import com.listywave.mention.MentionRepository; +import com.listywave.user.application.domain.User; +import com.listywave.user.application.service.UserService; +import com.listywave.user.repository.user.UserRepository; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +@SpringBootTest +public abstract class IntegrationTest { + + private static final Logger log = LoggerFactory.getLogger(IntegrationTest.class); + + @MockBean + protected KakaoOauthClient kakaoOauthClient; + @Autowired + protected AuthService authService; + @Autowired + protected UserService userService; + @Autowired + protected UserRepository userRepository; + @Autowired + protected ListRepository listRepository; + @Autowired + protected CommentService commentService; + @Autowired + protected CommentRepository commentRepository; + @Autowired + protected MentionRepository mentionRepository; + @Autowired + protected ReplyService replyService; + @Autowired + protected ReplyRepository replyRepository; + + protected User dh, js, ej; + protected ListEntity list; + + @BeforeEach + void setUp() { + dh = userRepository.save(동호()); + js = userRepository.save(정수()); + ej = userRepository.save(유진()); + list = listRepository.save(ListFixture.가장_좋아하는_견종_TOP3(dh, List.of())); + log.info("=============================테스트 데이터 셋 생성============================="); + } +} diff --git a/src/test/java/com/listywave/list/application/domain/comment/CommentTest.java b/src/test/java/com/listywave/list/application/domain/comment/CommentTest.java index 9ab380db..3efd50e1 100644 --- a/src/test/java/com/listywave/list/application/domain/comment/CommentTest.java +++ b/src/test/java/com/listywave/list/application/domain/comment/CommentTest.java @@ -3,6 +3,7 @@ import static com.listywave.list.fixture.ListFixture.가장_좋아하는_견종_TOP3; import static com.listywave.user.fixture.UserFixture.동호; import static com.listywave.user.fixture.UserFixture.정수; +import static java.util.Collections.EMPTY_LIST; import static org.assertj.core.api.Assertions.assertThat; import com.listywave.list.application.domain.list.ListEntity; @@ -17,7 +18,7 @@ class CommentTest { private final User user = 동호(); private final ListEntity list = 가장_좋아하는_견종_TOP3(user, List.of()); private final CommentContent content = new CommentContent("댓글"); - private final Comment comment = Comment.create(list, user, content); + private final Comment comment = new Comment(list, user, content, EMPTY_LIST); @Test void 초기화하면_isDelete는_false이다() { @@ -43,7 +44,7 @@ class CommentTest { void 댓글_내용을_수정할_수_있다() { CommentContent newContent = new CommentContent("댓글 수정"); - comment.update(newContent); + comment.update(newContent, EMPTY_LIST); assertThat(comment.getCommentContent()).isEqualTo("댓글 수정"); } diff --git a/src/test/java/com/listywave/list/application/domain/reply/ReplyTest.java b/src/test/java/com/listywave/list/application/domain/reply/ReplyTest.java index d8f66b67..ee0b6280 100644 --- a/src/test/java/com/listywave/list/application/domain/reply/ReplyTest.java +++ b/src/test/java/com/listywave/list/application/domain/reply/ReplyTest.java @@ -3,6 +3,7 @@ import static com.listywave.list.fixture.ListFixture.가장_좋아하는_견종_TOP3; import static com.listywave.user.fixture.UserFixture.동호; import static com.listywave.user.fixture.UserFixture.정수; +import static java.util.Collections.EMPTY_LIST; import static org.assertj.core.api.Assertions.assertThat; import com.listywave.list.application.domain.comment.Comment; @@ -19,7 +20,7 @@ class ReplyTest { private final User user = 동호(); private final ListEntity list = 가장_좋아하는_견종_TOP3(user, List.of()); private final CommentContent content = new CommentContent("댓글"); - private final Comment comment = Comment.create(list, user, content); + private final Comment comment = new Comment(list, user, content, EMPTY_LIST); private final Reply reply = new Reply(comment, user, content); @Test @@ -35,7 +36,7 @@ class ReplyTest { String newContent = "수정!"; CommentContent newCommentContent = new CommentContent(newContent); - reply.update(newCommentContent); + reply.update(newCommentContent, EMPTY_LIST); assertThat(reply.getCommentContent()).isEqualTo(newContent); } diff --git a/src/test/java/com/listywave/mention/MentionServiceTest.java b/src/test/java/com/listywave/mention/MentionServiceTest.java new file mode 100644 index 00000000..6a3efa5c --- /dev/null +++ b/src/test/java/com/listywave/mention/MentionServiceTest.java @@ -0,0 +1,301 @@ +package com.listywave.mention; + +import static java.util.Collections.EMPTY_LIST; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.listywave.common.IntegrationTest; +import com.listywave.list.application.domain.comment.Comment; +import com.listywave.list.application.domain.reply.Reply; +import com.listywave.list.application.dto.ReplyDeleteCommand; +import com.listywave.list.application.dto.ReplyUpdateCommand; +import com.listywave.list.application.dto.response.CommentFindResponse; +import com.listywave.list.application.dto.response.CommentFindResponse.CommentDto; +import com.listywave.list.application.dto.response.CommentFindResponse.MentionDto; +import com.listywave.list.application.dto.response.CommentFindResponse.ReplyDto; +import java.util.List; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class MentionServiceTest extends IntegrationTest { + + @Nested + class 멘션_생성 { + + @Test + void 댓글_작성_시_다른_유저를_멘션할_경우_저장된다() { + // given + List mentionIds = List.of(js.getId(), ej.getId()); + + // when + Long savedCommentId = commentService.create(list.getId(), dh.getId(), "댓글 남겨요!", mentionIds).id(); + Comment comment = commentRepository.getById(savedCommentId); + + // then + List result = mentionRepository.findAllByComment(comment); + assertAll( + () -> assertThat(result).hasSize(2), + () -> assertThat(result.get(0).getUser().getId()).isEqualTo(js.getId()), + () -> assertThat(result.get(1).getUser().getId()).isEqualTo(ej.getId()) + ); + } + + @Test + void 댓글_작성_시_멘션이_없을_경우_아무_값이_저장되지_않는다() { + // when + Long savedCommentId = commentService.create(list.getId(), dh.getId(), "댓글 남겨요!", EMPTY_LIST).id(); + Comment comment = commentRepository.getById(savedCommentId); + + // then + List result = mentionRepository.findAllByComment(comment); + assertThat(result).isEmpty(); + } + + @Test + void 답글_작성_시_다른_유저를_멘션할_경우_저장된다() { + // given + Long savedCommentId = commentService.create(list.getId(), dh.getId(), "댓글 남겨요!", EMPTY_LIST).id(); + + // when + List mentionIds = List.of(dh.getId(), ej.getId()); + Long savedMentionId = replyService.create(list.getId(), savedCommentId, js.getId(), "답글 남깁니당", mentionIds).id(); + Reply reply = replyRepository.getById(savedMentionId); + + // then + List result = mentionRepository.findAllByReply(reply); + assertAll( + () -> assertThat(result).hasSize(2), + () -> assertThat(result.get(0).getUser().getId()).isEqualTo(dh.getId()), + () -> assertThat(result.get(1).getUser().getId()).isEqualTo(ej.getId()) + ); + } + + @Test + void 답글_작성_시_멘션이_없을_경우_아무_값이_저장되지_않는다() { + // given + Long savedCommentId = commentService.create(list.getId(), dh.getId(), "댓글 남겨요!", EMPTY_LIST).id(); + + // when + Long savedMentionId = replyService.create(list.getId(), savedCommentId, js.getId(), "답글 남깁니당", EMPTY_LIST).id(); + Reply reply = replyRepository.getById(savedMentionId); + + // then + List result = mentionRepository.findAllByReply(reply); + assertThat(result).isEmpty(); + } + } + + @Nested + class 멘션_조회 { + + @Test + void 멘션을_포함한_댓글을_조회한다() { + // given + List mentionIds = List.of(js.getId(), ej.getId()); + Long savedCommentId = commentService.create(list.getId(), dh.getId(), "댓글이용", mentionIds).id(); + commentRepository.getById(savedCommentId); + + // when + CommentFindResponse response = commentService.findCommentBy(list.getId(), 5, null); + CommentDto commentDto = response.comments().get(0); + + // then + assertAll( + () -> assertThat(commentDto.mentions()).hasSize(2), + () -> assertThat(commentDto.mentions().get(0).userId()).isEqualTo(js.getId()), + () -> assertThat(commentDto.mentions().get(1).userId()).isEqualTo(ej.getId()) + ); + } + + @Test + void 멘션이_없는_댓글을_조회한다() { + // given + Long savedCommentId = commentService.create(list.getId(), dh.getId(), "댓글이용", EMPTY_LIST).id(); + commentRepository.getById(savedCommentId); + + // when + CommentFindResponse response = commentService.findCommentBy(list.getId(), 5, null); + CommentDto commentDto = response.comments().get(0); + + // then + assertThat(commentDto.mentions()).isEmpty(); + } + + @Test + void 댓글에서_멘션을_당한_사용자가_탈퇴한_사용자인_경우_조회하지_않는다() { + // given + List mentionIds = List.of(js.getId(), ej.getId()); + commentService.create(list.getId(), dh.getId(), "댓글이용", mentionIds); + + // when + authService.withdraw(js.getId()); + + // then + CommentFindResponse response = commentService.findCommentBy(list.getId(), 5, null); + CommentDto commentDto = response.comments().get(0); + + assertThat(commentDto.mentions()).hasSize(1); + assertThat(commentDto.mentions().get(0).userId()).isEqualTo(ej.getId()); + } + + @Test + void 멘션을_포함한_답글을_조회한다() { + // given + Long commentId = commentService.create(list.getId(), dh.getId(), "댓글이용", EMPTY_LIST).id(); + List mentionIds = List.of(dh.getId()); + replyService.create(list.getId(), commentId, js.getId(), "답글이용", mentionIds); + + // when + List comments = commentService.findCommentBy(list.getId(), 5, null).comments(); + ReplyDto reply = comments.get(0).replies().get(0); + + // then + assertThat(reply.mentions()).hasSize(1); + assertThat(reply.mentions().get(0).userId()).isEqualTo(dh.getId()); + } + + @Test + void 멘션이_없는_답글을_조회한다() { + // given + Long commentId = commentService.create(list.getId(), dh.getId(), "댓글이용", EMPTY_LIST).id(); + replyService.create(list.getId(), commentId, js.getId(), "답글이용", EMPTY_LIST); + + // when + List comments = commentService.findCommentBy(list.getId(), 5, null).comments(); + ReplyDto reply = comments.get(0).replies().get(0); + + // then + assertThat(reply.mentions()).isEmpty(); + } + + @Test + void 답글에서_멘션을_당한_사용자가_탈퇴한_사용자인_경우_조회하지_않는다() { + // given + Long commentId = commentService.create(list.getId(), dh.getId(), "댓글이용", EMPTY_LIST).id(); + List mentionIds = List.of(dh.getId(), ej.getId()); + replyService.create(list.getId(), commentId, js.getId(), "답글이용", mentionIds); + + authService.withdraw(ej.getId()); + + // when + List comments = commentService.findCommentBy(list.getId(), 5, null).comments(); + ReplyDto reply = comments.get(0).replies().get(0); + + // then + assertAll( + () -> assertThat(reply.mentions()).hasSize(1), + () -> assertThat(reply.mentions().get(0).userId()).isEqualTo(dh.getId()) + ); + } + } + + @Nested + class 멘션_수정 { + + @Test + void 댓글을_수정해_새로운_멘션을_추가한다() { + // given + List firstMentionIds = List.of(js.getId()); + Long commentId = commentService.create(list.getId(), dh.getId(), "댓글이요", firstMentionIds).id(); + + // when + List newMentionIds = List.of(js.getId(), ej.getId()); + commentService.update(list.getId(), dh.getId(), commentId, "댓글 수정이요", newMentionIds); + + // then + CommentFindResponse response = commentService.findCommentBy(list.getId(), 5, null); + List result = response.comments().get(0).mentions(); + assertAll( + () -> assertThat(result).hasSize(2), + () -> assertThat(result.get(0).userId()).isEqualTo(js.getId()), + () -> assertThat(result.get(1).userId()).isEqualTo(ej.getId()) + ); + } + + @Test + void 댓글을_수정해_멘션을_제거한다() { + // given + List mentionIds = List.of(js.getId()); + Long commentId = commentService.create(list.getId(), dh.getId(), "댓글이요", mentionIds).id(); + + // when + commentService.update(list.getId(), dh.getId(), commentId, "댓글 수정이요", EMPTY_LIST); + + // then + CommentFindResponse response = commentService.findCommentBy(list.getId(), 5, null); + List result = response.comments().get(0).mentions(); + assertThat(result).isEmpty(); + } + + @Test + void 답글을_수정해_새로운_멘션을_추가한다() { + // given + Long commentId = commentService.create(list.getId(), dh.getId(), "댓글이요", EMPTY_LIST).id(); + List firstMentionIds = List.of(dh.getId()); + Long replyId = replyService.create(list.getId(), commentId, js.getId(), "답글이요", firstMentionIds).id(); + + // when + List newMentionIds = List.of(dh.getId(), ej.getId()); + ReplyUpdateCommand replyUpdateCommand = new ReplyUpdateCommand(list.getId(), commentId, replyId, "답글 수정이요", newMentionIds); + replyService.update(replyUpdateCommand, js.getId()); + + // then + CommentFindResponse response = commentService.findCommentBy(list.getId(), 5, null); + List result = response.comments().get(0).replies().get(0).mentions(); + assertAll( + () -> assertThat(result).hasSize(2), + () -> assertThat(result.get(0).userId()).isEqualTo(dh.getId()), + () -> assertThat(result.get(1).userId()).isEqualTo(ej.getId()) + ); + } + + @Test + void 답글을_수정해_멘션을_제거한다() { + // given + Long commentId = commentService.create(list.getId(), dh.getId(), "댓글이요", EMPTY_LIST).id(); + List firstMentionIds = List.of(dh.getId()); + Long replyId = replyService.create(list.getId(), commentId, js.getId(), "답글이요", firstMentionIds).id(); + + // when + ReplyUpdateCommand replyUpdateCommand = new ReplyUpdateCommand(list.getId(), commentId, replyId, "답글 수정이요", EMPTY_LIST); + replyService.update(replyUpdateCommand, js.getId()); + + // then + CommentFindResponse response = commentService.findCommentBy(list.getId(), 5, null); + List result = response.comments().get(0).replies().get(0).mentions(); + assertThat(result).isEmpty(); + } + } + + @Nested + class 멘션_삭제 { + + @Test + void 멘션을_한_댓글_삭제_시_함께_삭제한다() { + // given + List mentionIds = List.of(js.getId(), ej.getId()); + Long commentId = commentService.create(list.getId(), dh.getId(), "댓글이요", mentionIds).id(); + + // when + commentService.delete(list.getId(), commentId, dh.getId()); + + // then + assertThat(mentionRepository.findAll()).isEmpty(); + } + + @Test + void 멘션을_한_답글_삭제_시_함께_삭제한다() { + // given + Long commentId = commentService.create(list.getId(), dh.getId(), "댓글이요", EMPTY_LIST).id(); + List mentionIds = List.of(dh.getId()); + Long replyId = replyService.create(list.getId(), commentId, js.getId(), "답글이요", mentionIds).id(); + + // when + ReplyDeleteCommand command = new ReplyDeleteCommand(list.getId(), commentId, replyId); + replyService.delete(command, js.getId()); + + // then + assertThat(mentionRepository.findAll()).isEmpty(); + } + } +} diff --git a/src/test/java/com/listywave/user/fixture/UserFixture.java b/src/test/java/com/listywave/user/fixture/UserFixture.java index 24f2e123..abe1e789 100644 --- a/src/test/java/com/listywave/user/fixture/UserFixture.java +++ b/src/test/java/com/listywave/user/fixture/UserFixture.java @@ -56,4 +56,20 @@ public class UserFixture { .isDelete(false) .build(); } + + public static User 서영() { + return User.builder() + .oauthId(5L) + .oauthEmail("seyoung@github.com") + .nickname(Nickname.of("seyoung")) + .backgroundImageUrl(new BackgroundImageUrl(DefaultBackgroundImages.getRandomImageUrl())) + .profileImageUrl(new ProfileImageUrl(DefaultBackgroundImages.getRandomImageUrl())) + .description(new Description("서영서영")) + .followerCount(120) + .followingCount(240) + .allPrivate(false) + .kakaoAccessToken("KAKAO_ACCESS_TOKEN") + .isDelete(false) + .build(); + } }