diff --git a/src/main/java/life/offonoff/ab/application/event/topic/CommentLikedEvent.java b/src/main/java/life/offonoff/ab/application/event/topic/CommentLikedEvent.java new file mode 100644 index 00000000..d27a38a9 --- /dev/null +++ b/src/main/java/life/offonoff/ab/application/event/topic/CommentLikedEvent.java @@ -0,0 +1,15 @@ +package life.offonoff.ab.application.event.topic; + +import life.offonoff.ab.domain.comment.Comment; +import life.offonoff.ab.domain.comment.LikedComment; +import life.offonoff.ab.domain.member.Member; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEvent; + +@Getter +@RequiredArgsConstructor +public class CommentLikedEvent { + + private final LikedComment likedComment; +} diff --git a/src/main/java/life/offonoff/ab/application/event/topic/TopicEventHandler.java b/src/main/java/life/offonoff/ab/application/event/topic/TopicEventHandler.java index 207347ed..5de22015 100644 --- a/src/main/java/life/offonoff/ab/application/event/topic/TopicEventHandler.java +++ b/src/main/java/life/offonoff/ab/application/event/topic/TopicEventHandler.java @@ -3,6 +3,9 @@ import life.offonoff.ab.application.notification.NotificationService; import life.offonoff.ab.application.service.vote.VotingTopicService; import life.offonoff.ab.application.service.vote.votingtopic.container.VotingTopic; +import life.offonoff.ab.domain.comment.Comment; +import life.offonoff.ab.domain.comment.LikedComment; +import life.offonoff.ab.domain.member.Member; import life.offonoff.ab.domain.topic.Topic; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -42,7 +45,7 @@ public void addTopic(TopicCreateEvent event) { public void voteClosed(VoteClosedEvent event) { log.info("# Topic Vote Closed / topic-id : {}, deadline : {}", event.topic().getId(), event.topic().getDeadline()); - notificationService.notifyVoteResult(event.result()); + notificationService.notifyVoteResult(event.topic()); } /** @@ -63,4 +66,16 @@ public void voted(VotedEvent event) { public void commented(CommentedEvent event) { notificationService.notifyCommentOnTopic(event.getComment()); } + + /** + * 댓글 좋아요 이벤트 + */ + @EventListener + public void commentLiked(CommentLikedEvent event) { + LikedComment likedComment = event.getLikedComment(); + + notificationService.notifyLikeInComment(likedComment); + } + + } diff --git a/src/main/java/life/offonoff/ab/application/event/topic/VoteClosedEvent.java b/src/main/java/life/offonoff/ab/application/event/topic/VoteClosedEvent.java index bf17d819..d8833dd5 100644 --- a/src/main/java/life/offonoff/ab/application/event/topic/VoteClosedEvent.java +++ b/src/main/java/life/offonoff/ab/application/event/topic/VoteClosedEvent.java @@ -1,10 +1,8 @@ package life.offonoff.ab.application.event.topic; import life.offonoff.ab.domain.topic.Topic; -import life.offonoff.ab.domain.vote.VoteResult; public record VoteClosedEvent( - Topic topic, - VoteResult result + Topic topic ) { } diff --git a/src/main/java/life/offonoff/ab/application/notification/NotificationService.java b/src/main/java/life/offonoff/ab/application/notification/NotificationService.java index dfd566e6..016541e7 100644 --- a/src/main/java/life/offonoff/ab/application/notification/NotificationService.java +++ b/src/main/java/life/offonoff/ab/application/notification/NotificationService.java @@ -1,12 +1,13 @@ package life.offonoff.ab.application.notification; import life.offonoff.ab.domain.comment.Comment; +import life.offonoff.ab.domain.comment.LikedComment; import life.offonoff.ab.domain.member.Member; import life.offonoff.ab.domain.notification.CommentOnTopicNotification; +import life.offonoff.ab.domain.notification.LikeInCommentNotification; import life.offonoff.ab.domain.notification.VoteCountOnTopicNotification; import life.offonoff.ab.domain.notification.VoteResultNotification; import life.offonoff.ab.domain.topic.Topic; -import life.offonoff.ab.domain.vote.VoteResult; import life.offonoff.ab.repository.member.MemberRepository; import life.offonoff.ab.repository.notfication.NotificationRepository; import life.offonoff.ab.web.response.notification.NotificationResponse; @@ -32,41 +33,56 @@ public class NotificationService { private final NotificationRepository notificationRepository; @Transactional - public void notifyVoteResult(VoteResult result) { + public void notifyVoteResult(Topic topic) { // voters' notifications - List voters = memberRepository.findAllListeningVoteResultAndVotedTopicId(result.getTopicId()); - List notifications = createVotersNotifications(result, voters); + List voters = memberRepository.findAllListeningVoteResultAndVotedTopicId(topic.getId()); + List notifications = createVotersNotifications(topic, voters); // author's notification - addAuthorsNotificationIfAuthorListeningVoteResult(result, notifications); + addAuthorsNotificationIfAuthorListeningVoteResult(topic, notifications); notificationRepository.saveVoteResultNotificationsInBatch(notifications); } - private List createVotersNotifications(VoteResult result, List voters) { + private List createVotersNotifications(Topic topic, List voters) { return voters.stream() .filter(Member::listenVoteResult) .map(receiver -> { log.info("# Notification send / Topic(id = {}, total_vote_count = {}) Member(id = {})", - result.getTopicId(), result.getTotalVoteCount(), receiver.getId()); - return new VoteResultNotification(receiver, result); + topic.getId(), topic.getVoteCount(), receiver.getId()); + return new VoteResultNotification(receiver, topic); } ).collect(Collectors.toList()); } - private void addAuthorsNotificationIfAuthorListeningVoteResult(VoteResult result, List notifications) { - Member author = result.getTopic() - .getAuthor(); + private void addAuthorsNotificationIfAuthorListeningVoteResult(Topic topic, List notifications) { + Member author = topic.getAuthor(); if (author.listenVoteResult()) { - VoteResultNotification authorsNotification = new VoteResultNotification(result.getTopic().getAuthor(), result); + VoteResultNotification authorsNotification = new VoteResultNotification(author, topic); notifications.add(authorsNotification); } } @Transactional - public void notifyLikeInComment() { + public void notifyLikeInComment(LikedComment likedComment) { + if (shouldNotifyLikeInComment(likedComment)) { + LikeInCommentNotification notification = new LikeInCommentNotification(likedComment.getComment()); + notificationRepository.save(notification); + } + } + + private boolean shouldNotifyLikeInComment(LikedComment likedComment) { + Member liker = likedComment.getLiker(); + + Comment comment = likedComment.getComment(); + Member writer = comment.getWriter(); + + boolean likerIsWriter = comment.isWrittenBy(liker); + boolean writerListenLikeInComment = writer.listenLikeInComment(); + + return !likerIsWriter && writerListenLikeInComment; } @Transactional diff --git a/src/main/java/life/offonoff/ab/application/service/CommentService.java b/src/main/java/life/offonoff/ab/application/service/CommentService.java index 08e6b119..c42e6f86 100644 --- a/src/main/java/life/offonoff/ab/application/service/CommentService.java +++ b/src/main/java/life/offonoff/ab/application/service/CommentService.java @@ -1,9 +1,11 @@ package life.offonoff.ab.application.service; +import life.offonoff.ab.application.event.topic.CommentLikedEvent; import life.offonoff.ab.application.event.topic.CommentedEvent; import life.offonoff.ab.application.service.common.TextUtils; import life.offonoff.ab.application.service.request.CommentRequest; import life.offonoff.ab.domain.comment.Comment; +import life.offonoff.ab.domain.comment.LikedComment; import life.offonoff.ab.domain.member.Member; import life.offonoff.ab.domain.topic.Topic; import life.offonoff.ab.domain.vote.Vote; @@ -22,6 +24,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Optional; + import static life.offonoff.ab.application.service.common.LengthInfo.COMMENT_CONTENT; @RequiredArgsConstructor @@ -136,10 +140,21 @@ public CommentReactionResponse likeCommentForMember(final Long memberId, final L private CommentReactionResponse doLike(Member liker, Comment comment) { liker.cancelHateIfExists(comment); - liker.likeCommentIfNew(comment); + Optional optionalLikedComment = liker.likeCommentIfNew(comment); + + publishCommentLikedEventIfPresent(optionalLikedComment); + return new CommentReactionResponse(comment.getLikeCount(), comment.getHateCount(), true, false); } + private void publishCommentLikedEventIfPresent(Optional optional) { + if (optional.isPresent()) { + LikedComment likedComment = optional.get(); + + eventPublisher.publishEvent(new CommentLikedEvent(likedComment)); + } + } + private CommentReactionResponse cancelLike(Member liker, Comment comment) { liker.cancelLikeIfExists(comment); diff --git a/src/main/java/life/offonoff/ab/application/service/vote/VotingTopicReadableService.java b/src/main/java/life/offonoff/ab/application/service/vote/VotingTopicReadableService.java index 571d600d..716b2524 100644 --- a/src/main/java/life/offonoff/ab/application/service/vote/VotingTopicReadableService.java +++ b/src/main/java/life/offonoff/ab/application/service/vote/VotingTopicReadableService.java @@ -6,7 +6,6 @@ import life.offonoff.ab.application.service.vote.votingtopic.container.VotingTopic; import life.offonoff.ab.domain.topic.Topic; import life.offonoff.ab.domain.topic.TopicStatus; -import life.offonoff.ab.domain.vote.VoteResult; import life.offonoff.ab.repository.topic.TopicRepository; import life.offonoff.ab.repository.topic.TopicSearchCond; import lombok.RequiredArgsConstructor; @@ -40,10 +39,9 @@ public void endVote(VoteClosingCriteria criteria) { ended.forEach( topic -> { topic.closeVote(); - VoteResult result = aggregateVote(topic); // 투표 종료 이벤트 발행 - eventPublisher.publishEvent(new VoteClosedEvent(topic, result)); + eventPublisher.publishEvent(new VoteClosedEvent(topic)); } ); } diff --git a/src/main/java/life/offonoff/ab/application/service/vote/VotingTopicService.java b/src/main/java/life/offonoff/ab/application/service/vote/VotingTopicService.java index 8b52eb8f..ba97a9f7 100644 --- a/src/main/java/life/offonoff/ab/application/service/vote/VotingTopicService.java +++ b/src/main/java/life/offonoff/ab/application/service/vote/VotingTopicService.java @@ -3,16 +3,9 @@ import life.offonoff.ab.application.service.vote.criteria.VoteClosingCriteria; import life.offonoff.ab.application.service.vote.votingtopic.container.VotingTopic; import life.offonoff.ab.domain.topic.Topic; -import life.offonoff.ab.domain.vote.VoteResult; public interface VotingTopicService { - default VoteResult aggregateVote(Topic topic) { - VoteResult result = new VoteResult(); - result.setTopic(topic); - return result; - } - void startVote(VotingTopic votingTopic); void endVote(VoteClosingCriteria criteria); diff --git a/src/main/java/life/offonoff/ab/application/service/vote/votingtopic/container/VotingTopicContainerService.java b/src/main/java/life/offonoff/ab/application/service/vote/votingtopic/container/VotingTopicContainerService.java index b21b0695..a32374dc 100644 --- a/src/main/java/life/offonoff/ab/application/service/vote/votingtopic/container/VotingTopicContainerService.java +++ b/src/main/java/life/offonoff/ab/application/service/vote/votingtopic/container/VotingTopicContainerService.java @@ -5,7 +5,6 @@ import life.offonoff.ab.application.service.vote.criteria.VoteClosingCriteria; import life.offonoff.ab.domain.topic.Topic; import life.offonoff.ab.domain.topic.TopicStatus; -import life.offonoff.ab.domain.vote.VoteResult; import life.offonoff.ab.repository.topic.TopicRepository; import life.offonoff.ab.repository.topic.TopicSearchCond; import lombok.RequiredArgsConstructor; @@ -56,10 +55,8 @@ public void endVote(VoteClosingCriteria criteria) { Topic topic = vt.getTopic(); topic.closeVote(); - VoteResult result = aggregateVote(topic); - // 투표 종료 이벤트 발행 - eventPublisher.publishEvent(new VoteClosedEvent(topic, result)); + eventPublisher.publishEvent(new VoteClosedEvent(topic)); }); } } diff --git a/src/main/java/life/offonoff/ab/domain/member/Member.java b/src/main/java/life/offonoff/ab/domain/member/Member.java index 0fadd016..80e85a7c 100644 --- a/src/main/java/life/offonoff/ab/domain/member/Member.java +++ b/src/main/java/life/offonoff/ab/domain/member/Member.java @@ -17,6 +17,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -113,15 +114,17 @@ public void addComment(Comment comment) { comments.add(comment); } - public void likeCommentIfNew(Comment comment) { + public Optional likeCommentIfNew(Comment comment) { if (likeAlready(comment)) { - return; + return Optional.empty(); } LikedComment likedComment = new LikedComment(this, comment); comment.increaseLikeCount(); likedComments.add(likedComment); + + return Optional.of(likedComment); } public void hateCommentIfNew(Comment comment) { diff --git a/src/main/java/life/offonoff/ab/domain/notification/CommentOnTopicNotification.java b/src/main/java/life/offonoff/ab/domain/notification/CommentOnTopicNotification.java index 7a329713..82c805ef 100644 --- a/src/main/java/life/offonoff/ab/domain/notification/CommentOnTopicNotification.java +++ b/src/main/java/life/offonoff/ab/domain/notification/CommentOnTopicNotification.java @@ -1,9 +1,6 @@ package life.offonoff.ab.domain.notification; -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.OneToOne; +import jakarta.persistence.*; import life.offonoff.ab.domain.comment.Comment; import life.offonoff.ab.domain.member.Member; import life.offonoff.ab.domain.topic.Topic; @@ -21,7 +18,10 @@ @DiscriminatorValue(COMMENT_ON_TOPIC_NOTIFICATION) public class CommentOnTopicNotification extends Notification { - @OneToOne + /** + * @OneToOne 관계이지만 {@link LikeInCommentNotification}가 {@link Comment}와 @ManyToOne 이기에 @ManyToOne 설정 ({@link Notification} 의 상속 전략 : SINGLE_TABLE) + */ + @ManyToOne @JoinColumn(name = "comment_id") @OnDelete(action = OnDeleteAction.CASCADE) private Comment comment; diff --git a/src/main/java/life/offonoff/ab/domain/notification/LikeInCommentNotification.java b/src/main/java/life/offonoff/ab/domain/notification/LikeInCommentNotification.java index f9102c4b..bae6eae8 100644 --- a/src/main/java/life/offonoff/ab/domain/notification/LikeInCommentNotification.java +++ b/src/main/java/life/offonoff/ab/domain/notification/LikeInCommentNotification.java @@ -1,10 +1,12 @@ package life.offonoff.ab.domain.notification; -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; +import jakarta.persistence.*; +import life.offonoff.ab.domain.comment.Comment; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; import static life.offonoff.ab.domain.notification.NotificationType.LIKE_IN_COMMENT_NOTIFICATION; // TODO:수정대상 @@ -14,6 +16,17 @@ @DiscriminatorValue(LIKE_IN_COMMENT_NOTIFICATION) public class LikeInCommentNotification extends Notification { + @ManyToOne + @JoinColumn(name = "comment_id") + @OnDelete(action = OnDeleteAction.CASCADE) + private Comment comment; + + public LikeInCommentNotification(Comment comment) { + super(comment.getWriter()); + + this.comment = comment; + } + @Override public String getType() { return LIKE_IN_COMMENT_NOTIFICATION; diff --git a/src/main/java/life/offonoff/ab/domain/notification/VoteResultNotification.java b/src/main/java/life/offonoff/ab/domain/notification/VoteResultNotification.java index 165d91d3..4bd5270a 100644 --- a/src/main/java/life/offonoff/ab/domain/notification/VoteResultNotification.java +++ b/src/main/java/life/offonoff/ab/domain/notification/VoteResultNotification.java @@ -2,7 +2,7 @@ import jakarta.persistence.*; import life.offonoff.ab.domain.member.Member; -import life.offonoff.ab.domain.vote.VoteResult; +import life.offonoff.ab.domain.topic.Topic; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -18,13 +18,13 @@ public class VoteResultNotification extends Notification { @ManyToOne(fetch = FetchType.EAGER) - @JoinColumn(name = "vote_result_id") + @JoinColumn(name = "topic_id") @OnDelete(action = OnDeleteAction.CASCADE) - private VoteResult voteResult; + private Topic topic; - public VoteResultNotification(Member member, VoteResult voteResult) { + public VoteResultNotification(Member member, Topic topic) { super(member); - this.voteResult = voteResult; + this.topic = topic; } @Override diff --git a/src/main/java/life/offonoff/ab/domain/topic/Topic.java b/src/main/java/life/offonoff/ab/domain/topic/Topic.java index 85ca6386..83fbd591 100644 --- a/src/main/java/life/offonoff/ab/domain/topic/Topic.java +++ b/src/main/java/life/offonoff/ab/domain/topic/Topic.java @@ -9,7 +9,6 @@ import life.offonoff.ab.domain.topic.content.TopicContent; import life.offonoff.ab.domain.topic.hide.HiddenTopic; import life.offonoff.ab.domain.vote.Vote; -import life.offonoff.ab.domain.vote.VoteResult; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -58,9 +57,6 @@ public class Topic extends BaseEntity { @OneToMany(mappedBy = "topic", cascade = CascadeType.REMOVE, orphanRemoval = true) private List reports = new ArrayList<>(); - @OneToOne(mappedBy = "topic", cascade = CascadeType.ALL) - private VoteResult voteResult; - @Enumerated(EnumType.STRING) private TopicStatus status = TopicStatus.VOTING; @@ -143,10 +139,6 @@ public void addChoice(Choice choice) { this.choices.add(choice); } - public void setVoteResult(VoteResult voteResult) { - this.voteResult = voteResult; - } - public boolean isBeforeDeadline(LocalDateTime requestTime) { if (deadline == null) { return true; diff --git a/src/main/java/life/offonoff/ab/domain/vote/VoteResult.java b/src/main/java/life/offonoff/ab/domain/vote/VoteResult.java deleted file mode 100644 index 5a61669d..00000000 --- a/src/main/java/life/offonoff/ab/domain/vote/VoteResult.java +++ /dev/null @@ -1,39 +0,0 @@ -package life.offonoff.ab.domain.vote; - -import jakarta.persistence.*; -import life.offonoff.ab.domain.BaseEntity; -import life.offonoff.ab.domain.notification.Notification; -import life.offonoff.ab.domain.notification.VoteResultNotification; -import life.offonoff.ab.domain.topic.Topic; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.util.ArrayList; -import java.util.List; - -@Getter -@NoArgsConstructor -@Entity -public class VoteResult extends BaseEntity { - - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @OneToOne - @JoinColumn(name = "topic_id") - private Topic topic; - - private int totalVoteCount; - - //== 연관관계 매핑 ==// - public void setTopic(Topic topic) { - this.topic = topic; - topic.setVoteResult(this); - this.totalVoteCount = topic.getVoteCount(); - } - - // Getter - public Long getTopicId() { - return topic.getId(); - } -} diff --git a/src/main/java/life/offonoff/ab/repository/notfication/NotificationJdbcRepositoryImpl.java b/src/main/java/life/offonoff/ab/repository/notfication/NotificationJdbcRepositoryImpl.java index bf1f1005..caab9028 100644 --- a/src/main/java/life/offonoff/ab/repository/notfication/NotificationJdbcRepositoryImpl.java +++ b/src/main/java/life/offonoff/ab/repository/notfication/NotificationJdbcRepositoryImpl.java @@ -25,8 +25,8 @@ public void saveVoteResultNotificationsInBatch(List noti Timestamp curTimeStamp = Timestamp.valueOf(LocalDateTime.now()); - String query = "insert into notification (member_id, notification_type, topic_id, vote_result_id, created_at, updated_at)" + - "values (?, ?, ?, ?, ?, ?);"; + String query = "insert into notification (member_id, notification_type, topic_id, created_at, updated_at)" + + "values (?, ?, ?, ?, ?);"; jdbcTemplate.batchUpdate(query, notifications, @@ -34,10 +34,9 @@ public void saveVoteResultNotificationsInBatch(List noti (pstmt, n) -> { pstmt.setLong(1, n.getReceiver().getId()); pstmt.setString(2, VOTE_RESULT_NOTIFICATION); - pstmt.setLong(3, n.getVoteResult().getTopicId()); - pstmt.setLong(4, n.getVoteResult().getId()); + pstmt.setLong(3, n.getTopic().getId()); + pstmt.setTimestamp(4, curTimeStamp); pstmt.setTimestamp(5, curTimeStamp); - pstmt.setTimestamp(6, curTimeStamp); }); } } diff --git a/src/main/java/life/offonoff/ab/web/response/notification/message/LikeInCommentNotificationMessage.java b/src/main/java/life/offonoff/ab/web/response/notification/message/LikeInCommentNotificationMessage.java new file mode 100644 index 00000000..3ec86628 --- /dev/null +++ b/src/main/java/life/offonoff/ab/web/response/notification/message/LikeInCommentNotificationMessage.java @@ -0,0 +1,32 @@ +package life.offonoff.ab.web.response.notification.message; + +import life.offonoff.ab.domain.comment.Comment; +import life.offonoff.ab.domain.notification.LikeInCommentNotification; +import lombok.Getter; + +import static life.offonoff.ab.web.response.notification.message.NotificationMessageTemplate.LIKE_ON_COMMENT_TITLE; + +@Getter +public class LikeInCommentNotificationMessage extends NotificationMessage { + + private final Long topicId; + private final Long commentId; + + public LikeInCommentNotificationMessage(String title, String content, Long topicId, Long commentId) { + super(title, content); + this.topicId = topicId; + this.commentId = commentId; + } + + public LikeInCommentNotificationMessage(LikeInCommentNotification notification) { + super(LIKE_ON_COMMENT_TITLE, notification.getComment() + .getTopic() + .getTitle()); + + Comment comment = notification.getComment(); + + this.commentId = comment.getId(); + this.topicId = comment.getTopic() + .getId(); + } +} diff --git a/src/main/java/life/offonoff/ab/web/response/notification/message/NotificationMessageFactory.java b/src/main/java/life/offonoff/ab/web/response/notification/message/NotificationMessageFactory.java index e47587dc..d8b59fd4 100644 --- a/src/main/java/life/offonoff/ab/web/response/notification/message/NotificationMessageFactory.java +++ b/src/main/java/life/offonoff/ab/web/response/notification/message/NotificationMessageFactory.java @@ -2,25 +2,33 @@ import life.offonoff.ab.domain.notification.*; +import static life.offonoff.ab.domain.notification.NotificationType.*; + public class NotificationMessageFactory { public static NotificationMessage createNoticeMessage(Notification notification) { - if (notification instanceof DefaultNotification) { + String type = notification.getType(); + + if (type.equals(DEFAULT)) { return new DefaultNotificationMessage((DefaultNotification) notification); } - if (notification instanceof VoteResultNotification) { + if (type.equals(VOTE_RESULT_NOTIFICATION)) { return new VoteResultNotificationMessage((VoteResultNotification) notification); } - if (notification instanceof VoteCountOnTopicNotification) { + if (type.equals(VOTE_COUNT_ON_TOPIC_NOTIFICATION)) { return new VoteCountOnTopicNotificationMessage((VoteCountOnTopicNotification) notification); } - if (notification instanceof CommentOnTopicNotification) { + if (type.equals(COMMENT_ON_TOPIC_NOTIFICATION)) { return new CommentOnTopicNotificationMessage((CommentOnTopicNotification) notification); } + if (type.equals(LIKE_IN_COMMENT_NOTIFICATION)) { + return new LikeInCommentNotificationMessage((LikeInCommentNotification) notification); + } + throw new RuntimeException(); } } diff --git a/src/main/java/life/offonoff/ab/web/response/notification/message/VoteResultNotificationMessage.java b/src/main/java/life/offonoff/ab/web/response/notification/message/VoteResultNotificationMessage.java index b8eca8b1..d4534c11 100644 --- a/src/main/java/life/offonoff/ab/web/response/notification/message/VoteResultNotificationMessage.java +++ b/src/main/java/life/offonoff/ab/web/response/notification/message/VoteResultNotificationMessage.java @@ -16,11 +16,9 @@ public VoteResultNotificationMessage(String content, Long topicId) { } public VoteResultNotificationMessage(VoteResultNotification notification) { - super(VOTE_RESULT_TITLE, notification.getVoteResult() - .getTopic() + super(VOTE_RESULT_TITLE, notification.getTopic() .getTitle()); - this.topicId = notification.getVoteResult() - .getTopic() + this.topicId = notification.getTopic() .getId(); } } diff --git a/src/test/java/life/offonoff/ab/application/notification/NotificationServiceIntegrationTest.java b/src/test/java/life/offonoff/ab/application/notification/NotificationServiceIntegrationTest.java index a7835045..d9773f5a 100644 --- a/src/test/java/life/offonoff/ab/application/notification/NotificationServiceIntegrationTest.java +++ b/src/test/java/life/offonoff/ab/application/notification/NotificationServiceIntegrationTest.java @@ -1,10 +1,9 @@ package life.offonoff.ab.application.notification; import jakarta.persistence.EntityManager; -import life.offonoff.ab.application.service.CommentService; -import life.offonoff.ab.application.service.TopicService; import life.offonoff.ab.domain.TestEntityUtil.TestMember; import life.offonoff.ab.domain.comment.Comment; +import life.offonoff.ab.domain.comment.LikedComment; import life.offonoff.ab.domain.member.Member; import life.offonoff.ab.domain.member.Role; import life.offonoff.ab.domain.notification.VoteCountOnTopicNotification; @@ -12,7 +11,6 @@ import life.offonoff.ab.domain.topic.Topic; import life.offonoff.ab.domain.topic.TopicSide; import life.offonoff.ab.domain.topic.choice.ChoiceOption; -import life.offonoff.ab.domain.vote.VoteResult; import life.offonoff.ab.repository.topic.TopicRepository; import life.offonoff.ab.web.response.notification.NotificationResponse; import life.offonoff.ab.web.response.notification.message.CommentOnTopicNotificationMessage; @@ -83,13 +81,8 @@ void delete_VoteResultNotification_when_topic_deleted() { Topic topic = createRandomTopic(); em.persist(topic); - // vote result - VoteResult voteResult = new VoteResult(); - voteResult.setTopic(topic); - em.persist(voteResult); - // notification - VoteResultNotification voteResultNotification = new VoteResultNotification(author, voteResult); + VoteResultNotification voteResultNotification = new VoteResultNotification(author, topic); em.persist(voteResultNotification); // when @@ -253,4 +246,180 @@ void delete_CommentOnTopicNotification_when_topic_deleted(){ List responses = notificationService.findAllByReceiverId(author.getId()); assertThat(responses).isEmpty(); } + + + @Test + @DisplayName("댓글 좋아요 시 댓글 작성자에게 댓글 좋아요 알림을 생성한다.") + void create_LikeInCommentNotification_when_comment_liked_by_liker() { + // given + + // member + Member liker = TestMember.builder() + .nickname("liker") + .build().buildMember(); + em.persist(liker); + + Member author = TestMember.builder() + .nickname("author") + .build().buildMember(); + em.persist(author); + + Member commenter = TestMember.builder() + .nickname("commenter") + .role(Role.USER) + .build().buildMember(); + em.persist(commenter); + + // topic + Topic topic = TestTopic.builder() + .author(author) + .side(TopicSide.TOPIC_B) + .build().buildTopic(); + em.persist(topic); + + // comment + Comment comment = new Comment(commenter, topic, ChoiceOption.CHOICE_A, "content"); + em.persist(comment); + + LikedComment likedComment = new LikedComment(liker, comment); + + notificationService.notifyLikeInComment(likedComment); + + // when + List responses = notificationService.findAllByReceiverId(commenter.getId()); + + // then + assertThat(responses).isNotEmpty(); + } + + @Test + @DisplayName("Liker == Writer 면 알림을 생성하지 않는다.") + void does_not_create_LikeInCommentNotification_when_liker_is_writer() { + // given + + // member + Member author = TestMember.builder() + .nickname("author") + .build().buildMember(); + em.persist(author); + + Member commenter = TestMember.builder() + .nickname("commenter") + .role(Role.USER) + .build().buildMember(); + em.persist(commenter); + + // topic + Topic topic = TestTopic.builder() + .author(author) + .side(TopicSide.TOPIC_B) + .build().buildTopic(); + em.persist(topic); + + // comment + Comment comment = new Comment(commenter, topic, ChoiceOption.CHOICE_A, "content"); + em.persist(comment); + + LikedComment likedComment = new LikedComment(commenter, comment); + + notificationService.notifyLikeInComment(likedComment); + + // when + List responses = notificationService.findAllByReceiverId(commenter.getId()); + + // then + assertThat(responses).isEmpty(); + } + + + @Test + @DisplayName("댓글이 삭제되면 댓글 좋아요 알림은 삭제된다.") + void delete_LikeInCommentNotification_when_comment_deleted() { + // given + + // member + Member liker = TestMember.builder() + .nickname("liker") + .build().buildMember(); + em.persist(liker); + + Member author = TestMember.builder() + .nickname("author") + .build().buildMember(); + em.persist(author); + + Member commenter = TestMember.builder() + .nickname("commenter") + .role(Role.USER) + .build().buildMember(); + em.persist(commenter); + + // topic + Topic topic = TestTopic.builder() + .author(author) + .side(TopicSide.TOPIC_B) + .build().buildTopic(); + em.persist(topic); + + // comment + Comment comment = new Comment(commenter, topic, ChoiceOption.CHOICE_A, "content"); + em.persist(comment); + + LikedComment likedComment = new LikedComment(commenter, comment); + + notificationService.notifyLikeInComment(likedComment); + + // when + comment.remove(); + em.remove(comment); + + // then + List responses = notificationService.findAllByReceiverId(commenter.getId()); + assertThat(responses).isEmpty(); + } + + @Test + @DisplayName("토픽이 삭제되면 토픽에 달린 댓글들의 좋아요 알림은 삭제된다.") + void delete_LikeInCommentNotification_when_topic_deleted() { + // given + + // member + Member liker = TestMember.builder() + .nickname("liker") + .build().buildMember(); + em.persist(liker); + + Member author = TestMember.builder() + .nickname("author") + .build().buildMember(); + em.persist(author); + + Member commenter = TestMember.builder() + .nickname("commenter") + .role(Role.USER) + .build().buildMember(); + em.persist(commenter); + + // topic + Topic topic = TestTopic.builder() + .author(author) + .side(TopicSide.TOPIC_B) + .build().buildTopic(); + em.persist(topic); + + // comment + Comment comment = new Comment(commenter, topic, ChoiceOption.CHOICE_A, "content"); + em.persist(comment); + + LikedComment likedComment = new LikedComment(commenter, comment); + + notificationService.notifyLikeInComment(likedComment); + + // when + em.remove(topic); + + // then + List responses = notificationService.findAllByReceiverId(commenter.getId()); + assertThat(responses).isEmpty(); + } } diff --git a/src/test/java/life/offonoff/ab/application/notification/NotificationServiceTest.java b/src/test/java/life/offonoff/ab/application/notification/NotificationServiceTest.java index d62370ee..84ba5416 100644 --- a/src/test/java/life/offonoff/ab/application/notification/NotificationServiceTest.java +++ b/src/test/java/life/offonoff/ab/application/notification/NotificationServiceTest.java @@ -3,7 +3,6 @@ import life.offonoff.ab.domain.member.Member; import life.offonoff.ab.domain.notification.DefaultNotification; import life.offonoff.ab.domain.topic.Topic; -import life.offonoff.ab.domain.vote.VoteResult; import life.offonoff.ab.repository.member.MemberRepository; import life.offonoff.ab.repository.notfication.NotificationRepository; import life.offonoff.ab.web.response.notification.NotificationResponse; @@ -44,15 +43,12 @@ void notice_VoteResult_to_voter() { .voteCount(1000) .build().buildTopic(); - VoteResult result = new VoteResult(); - result.setTopic(topic); - List voteMembers = List.of(voter); when(memberRepository.findAllListeningVoteResultAndVotedTopicId(anyLong())).thenReturn(voteMembers); // when - notificationService.notifyVoteResult(result); + notificationService.notifyVoteResult(topic); // then assertThat(voter.getNotifications().size()).isGreaterThan(0); @@ -72,13 +68,10 @@ void notice_VoteResult_to_author() { .voteCount(1000) .build().buildTopic(); - VoteResult result = new VoteResult(); - result.setTopic(topic); - when(memberRepository.findAllListeningVoteResultAndVotedTopicId(anyLong())).thenReturn(Collections.emptyList()); // when - notificationService.notifyVoteResult(result); + notificationService.notifyVoteResult(topic); // then assertThat(author.getNotifications().size()).isGreaterThan(0); diff --git a/src/test/java/life/offonoff/ab/application/service/event/topic/TopicEventHandlerTest.java b/src/test/java/life/offonoff/ab/application/service/event/topic/TopicEventHandlerTest.java index 9bd6fc29..2cc8841d 100644 --- a/src/test/java/life/offonoff/ab/application/service/event/topic/TopicEventHandlerTest.java +++ b/src/test/java/life/offonoff/ab/application/service/event/topic/TopicEventHandlerTest.java @@ -3,7 +3,6 @@ import life.offonoff.ab.application.event.topic.*; import life.offonoff.ab.application.notification.NotificationService; import life.offonoff.ab.domain.topic.Topic; -import life.offonoff.ab.domain.vote.VoteResult; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -28,16 +27,14 @@ void invoke_noticeService_when_voting_ended() { Topic topic = TestTopic.builder() .id(1L) .build().buildTopic(); - VoteResult result = new VoteResult(); - result.setTopic(topic); - doNothing().when(notificationService).notifyVoteResult(any(VoteResult.class)); + doNothing().when(notificationService).notifyVoteResult(any()); // when - topicEventHandler.voteClosed(new VoteClosedEvent(topic, result)); + topicEventHandler.voteClosed(new VoteClosedEvent(topic)); // then - verify(notificationService).notifyVoteResult(any(VoteResult.class)); + verify(notificationService).notifyVoteResult(any()); } } \ No newline at end of file diff --git a/src/test/java/life/offonoff/ab/application/service/vote/VotingTopicContainerServiceIntegrationTest.java b/src/test/java/life/offonoff/ab/application/service/vote/VotingTopicContainerServiceIntegrationTest.java index 1ffba16a..f280fadd 100644 --- a/src/test/java/life/offonoff/ab/application/service/vote/VotingTopicContainerServiceIntegrationTest.java +++ b/src/test/java/life/offonoff/ab/application/service/vote/VotingTopicContainerServiceIntegrationTest.java @@ -62,10 +62,7 @@ void endVote_then_status_voting_result() { votingTopicContainerService.endVote(criteria); // then - assertAll( - () -> assertThat(topic.getStatus()).isEqualTo(CLOSED), - () -> assertThat(topic.getVoteResult()).isNotNull() - ); + assertThat(topic.getStatus()).isEqualTo(CLOSED); } @Test diff --git a/src/test/java/life/offonoff/ab/application/service/vote/VotingTopicContainerServiceTest.java b/src/test/java/life/offonoff/ab/application/service/vote/VotingTopicContainerServiceTest.java index 21995c25..afe36a15 100644 --- a/src/test/java/life/offonoff/ab/application/service/vote/VotingTopicContainerServiceTest.java +++ b/src/test/java/life/offonoff/ab/application/service/vote/VotingTopicContainerServiceTest.java @@ -4,7 +4,6 @@ import life.offonoff.ab.application.service.vote.criteria.VoteClosingCriteria; import life.offonoff.ab.application.service.vote.votingtopic.container.VotingTopicContainerService; import life.offonoff.ab.domain.topic.Topic; -import life.offonoff.ab.domain.vote.VoteResult; import life.offonoff.ab.repository.topic.TopicRepository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -15,7 +14,6 @@ import org.springframework.context.ApplicationEventPublisher; import org.springframework.test.util.ReflectionTestUtils; import static life.offonoff.ab.domain.TestEntityUtil.*; -import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*; @ExtendWith(MockitoExtension.class) @@ -32,28 +30,6 @@ class VotingTopicContainerServiceTest { @Mock VoteClosingCriteria criteria; - @Test - @DisplayName("투표가 끝나면 투표 결과를 집계해 저장") - void aggregateVote() { - // given - int totalVoteCount = 1000; - - Topic topic = TestTopic.builder() - .id(1L) - .voteCount(totalVoteCount) - .build().buildTopic(); - - // when - VoteResult voteResult = votingTopicContainerService.aggregateVote(topic); - - // then - assertAll( - () -> assertThat(topic.getVoteResult()).isNotNull(), - () -> assertThat(topic.getVoteResult()).isEqualTo(voteResult), - () -> assertThat(voteResult.getTotalVoteCount()).isEqualTo(totalVoteCount) - ); - } - // @Test // @DisplayName("투표가 끝난 토픽 수 만큼 이벤트 발행") // void event_publish() { diff --git a/src/test/java/life/offonoff/ab/repository/topic/TopicRepositoryTest.java b/src/test/java/life/offonoff/ab/repository/topic/TopicRepositoryTest.java index 46ba8a3d..76ed4e76 100644 --- a/src/test/java/life/offonoff/ab/repository/topic/TopicRepositoryTest.java +++ b/src/test/java/life/offonoff/ab/repository/topic/TopicRepositoryTest.java @@ -9,7 +9,6 @@ import life.offonoff.ab.domain.topic.TopicSide; import life.offonoff.ab.domain.topic.TopicStatus; import life.offonoff.ab.domain.topic.hide.HiddenTopic; -import life.offonoff.ab.domain.vote.VoteResult; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -306,18 +305,11 @@ void delete_topic() { topicRepository.save(topic); - VoteResult voteResult = new VoteResult(); - voteResult.setTopic(topic); - em.persist(voteResult); - // when topicRepository.delete(topic); // then - assertAll( - () -> assertThat(topicRepository.findById(topic.getId())).isEmpty(), - () -> assertThat(em.find(VoteResult.class, voteResult.getId())).isNull() - ); + assertThat(topicRepository.findById(topic.getId())).isEmpty(); } Pageable createVoteCountDescPageable(int page, int size) { diff --git a/src/test/java/life/offonoff/ab/web/NotificationControllerTest.java b/src/test/java/life/offonoff/ab/web/NotificationControllerTest.java index d364e94a..a5cedc58 100644 --- a/src/test/java/life/offonoff/ab/web/NotificationControllerTest.java +++ b/src/test/java/life/offonoff/ab/web/NotificationControllerTest.java @@ -2,11 +2,13 @@ import life.offonoff.ab.application.notification.NotificationService; import life.offonoff.ab.config.WebConfig; +import life.offonoff.ab.domain.notification.LikeInCommentNotification; import life.offonoff.ab.restdocs.RestDocsTest; import life.offonoff.ab.util.token.JwtProvider; import life.offonoff.ab.web.common.aspect.auth.AuthorizedArgumentResolver; import life.offonoff.ab.web.response.notification.NotificationResponse; import life.offonoff.ab.web.response.notification.message.CommentOnTopicNotificationMessage; +import life.offonoff.ab.web.response.notification.message.LikeInCommentNotificationMessage; import life.offonoff.ab.web.response.notification.message.VoteCountOnTopicNotificationMessage; import life.offonoff.ab.web.response.notification.message.VoteResultNotificationMessage; import org.junit.jupiter.api.Test; @@ -18,8 +20,7 @@ import java.util.List; import static life.offonoff.ab.domain.notification.NotificationType.*; -import static life.offonoff.ab.web.response.notification.message.NotificationMessageTemplate.COMMENT_ON_TOPIC_TITLE; -import static life.offonoff.ab.web.response.notification.message.NotificationMessageTemplate.VOTE_COUNT_ON_TOPIC_TITLE; +import static life.offonoff.ab.web.response.notification.message.NotificationMessageTemplate.*; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; @@ -55,8 +56,14 @@ void get_members_notifications() throws Exception { new CommentOnTopicNotificationMessage(COMMENT_ON_TOPIC_TITLE, "test_title3", 3L, 1L) ); + NotificationResponse likeInCommentResponse = new NotificationResponse( + LIKE_IN_COMMENT_NOTIFICATION, + false, + new LikeInCommentNotificationMessage(LIKE_ON_COMMENT_TITLE, "test_title4", 4L, 2L) + ); + when(notificationService.findAllByReceiverId(nullable(Long.class))) - .thenReturn(List.of(voteResultResponse, voteCountResponse, commentOnTopicResponse)); + .thenReturn(List.of(voteResultResponse, voteCountResponse, commentOnTopicResponse, likeInCommentResponse)); // then mvc.perform(get(NoticeUri.BASE)