diff --git a/src/main/java/com/chooz/comment/application/CommentCommandService.java b/src/main/java/com/chooz/comment/application/CommentCommandService.java index 981f8138..3308bcb1 100644 --- a/src/main/java/com/chooz/comment/application/CommentCommandService.java +++ b/src/main/java/com/chooz/comment/application/CommentCommandService.java @@ -6,6 +6,8 @@ import com.chooz.comment.presentation.dto.CommentRequest; import com.chooz.comment.support.CommentValidator; import com.chooz.commentLike.application.CommentLikeCommandService; +import com.chooz.common.event.DeleteEvent; +import com.chooz.common.event.EventPublisher; import com.chooz.common.exception.BadRequestException; import com.chooz.common.exception.ErrorCode; import com.chooz.post.domain.PostRepository; @@ -26,6 +28,7 @@ public class CommentCommandService { private final UserRepository userRepository; private final CommentValidator commentValidator; private final CommentLikeCommandService commentLikeCommandService; + private final EventPublisher eventPublisher; public CommentIdResponse createComment(Long postId, CommentRequest commentRequest, Long userId) { commentValidator.validateContentLength(commentRequest.content()); @@ -51,9 +54,10 @@ public CommentIdResponse updateComment(Long postId, Long commentId, CommentReque public void deleteComment(Long postId, Long commentId, Long userId) { commentLikeCommandService.deleteCommentLikeByCommentId(commentId); - Comment commentForDelete = commentRepository.findById(commentId) + Comment comment = commentRepository.findById(commentId) .orElseThrow(() -> new BadRequestException(ErrorCode.COMMENT_NOT_FOUND)); - commentValidator.validateCommentAccess(commentForDelete, postId, userId); - commentRepository.delete(commentForDelete); + commentValidator.validateCommentAccess(comment, postId, userId); + commentRepository.delete(comment); + eventPublisher.publish(DeleteEvent.of(comment.getId(), comment.getClass().getSimpleName().toUpperCase())); } } diff --git a/src/main/java/com/chooz/common/event/DeleteEvent.java b/src/main/java/com/chooz/common/event/DeleteEvent.java new file mode 100644 index 00000000..aef62bce --- /dev/null +++ b/src/main/java/com/chooz/common/event/DeleteEvent.java @@ -0,0 +1,7 @@ +package com.chooz.common.event; + +public record DeleteEvent(Long id, String domain) { + public static DeleteEvent of(Long id, String domain) { + return new DeleteEvent(id, domain); + } +} diff --git a/src/main/java/com/chooz/notification/application/NotificationService.java b/src/main/java/com/chooz/notification/application/NotificationService.java index bd8d2c12..d868109b 100644 --- a/src/main/java/com/chooz/notification/application/NotificationService.java +++ b/src/main/java/com/chooz/notification/application/NotificationService.java @@ -6,6 +6,7 @@ import com.chooz.notification.application.service.NotificationCommandService; import com.chooz.notification.application.service.NotificationQueryService; import com.chooz.notification.domain.Notification; +import com.chooz.notification.domain.TargetType; import com.chooz.notification.presentation.dto.NotificationPresentResponse; import com.chooz.notification.presentation.dto.NotificationResponse; import lombok.RequiredArgsConstructor; @@ -53,4 +54,7 @@ public void markRead(Long notificationId) { public NotificationPresentResponse present(Long userId) { return notificationQueryService.present(userId); } + public List findByTargetIdAndType(Long id, TargetType targetType){ + return notificationQueryService.findByTargetIdAndType(id, targetType); + } } diff --git a/src/main/java/com/chooz/notification/application/listener/NotificationInvalidListener.java b/src/main/java/com/chooz/notification/application/listener/NotificationInvalidListener.java new file mode 100644 index 00000000..0a481e12 --- /dev/null +++ b/src/main/java/com/chooz/notification/application/listener/NotificationInvalidListener.java @@ -0,0 +1,23 @@ +package com.chooz.notification.application.listener; + +import com.chooz.common.event.DeleteEvent; +import com.chooz.notification.application.NotificationService; +import com.chooz.notification.domain.Notification; +import com.chooz.notification.domain.TargetType; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Component +@RequiredArgsConstructor +public class NotificationInvalidListener { + + private final NotificationService notificationService; + + @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) + public void inValid(DeleteEvent deleteEvent) { + notificationService.findByTargetIdAndType(deleteEvent.id(), TargetType.valueOf(deleteEvent.domain())) + .forEach(Notification::invalidate); + } +} diff --git a/src/main/java/com/chooz/notification/application/service/NotificationQueryService.java b/src/main/java/com/chooz/notification/application/service/NotificationQueryService.java index ad54d88e..e68243f4 100644 --- a/src/main/java/com/chooz/notification/application/service/NotificationQueryService.java +++ b/src/main/java/com/chooz/notification/application/service/NotificationQueryService.java @@ -6,8 +6,10 @@ import com.chooz.notification.application.dto.TargetPostDto; import com.chooz.notification.application.dto.TargetUserDto; import com.chooz.notification.application.web.dto.NotificationDto; +import com.chooz.notification.domain.Notification; import com.chooz.notification.domain.NotificationQueryRepository; import com.chooz.notification.domain.NotificationRepository; +import com.chooz.notification.domain.TargetType; import com.chooz.notification.presentation.dto.NotificationPresentResponse; import com.chooz.notification.presentation.dto.NotificationResponse; import lombok.RequiredArgsConstructor; @@ -56,5 +58,7 @@ public List findVoteUsersByPostId(Long postId) { public NotificationPresentResponse present(Long userId) { return NotificationPresentResponse.of(notificationRepository.existsByReceiverIdAndIsReadFalseAndDeletedFalse(userId)); } - + public List findByTargetIdAndType(Long id, TargetType targetType) { + return notificationRepository.findByTargetIdAndType(id, targetType); + } } diff --git a/src/main/java/com/chooz/notification/domain/NotificationRepository.java b/src/main/java/com/chooz/notification/domain/NotificationRepository.java index 70388298..968ca92d 100644 --- a/src/main/java/com/chooz/notification/domain/NotificationRepository.java +++ b/src/main/java/com/chooz/notification/domain/NotificationRepository.java @@ -8,4 +8,5 @@ public interface NotificationRepository { void saveAll(List notifications); Optional findNotificationById(Long id); boolean existsByReceiverIdAndIsReadFalseAndDeletedFalse(Long userId); + List findByTargetIdAndType(Long targetId, TargetType targetType); } diff --git a/src/main/java/com/chooz/notification/persistence/NotificationJpaRepository.java b/src/main/java/com/chooz/notification/persistence/NotificationJpaRepository.java index fe2b5544..42d24a75 100644 --- a/src/main/java/com/chooz/notification/persistence/NotificationJpaRepository.java +++ b/src/main/java/com/chooz/notification/persistence/NotificationJpaRepository.java @@ -1,10 +1,26 @@ package com.chooz.notification.persistence; import com.chooz.notification.domain.Notification; +import com.chooz.notification.domain.TargetType; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public interface NotificationJpaRepository extends JpaRepository { boolean existsByReceiverIdAndIsReadFalseAndDeletedFalse(Long userId); + @Query(""" + SELECT distinct n + FROM Notification n + join n.targets t + where t.id = :targetId + and t.type = :targetType + and n.isValid = true + order by n.id desc + """ + ) + List findByTargetIdAndType(@Param("targetId") Long targetId, @Param("targetType") TargetType targetType); } diff --git a/src/main/java/com/chooz/notification/persistence/NotificationRepositoryImpl.java b/src/main/java/com/chooz/notification/persistence/NotificationRepositoryImpl.java index ea066d2f..9fcbbffc 100644 --- a/src/main/java/com/chooz/notification/persistence/NotificationRepositoryImpl.java +++ b/src/main/java/com/chooz/notification/persistence/NotificationRepositoryImpl.java @@ -2,6 +2,7 @@ import com.chooz.notification.domain.Notification; import com.chooz.notification.domain.NotificationRepository; +import com.chooz.notification.domain.TargetType; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -35,4 +36,8 @@ public boolean existsByReceiverIdAndIsReadFalseAndDeletedFalse(Long userId) { return notificationJpaRepository.existsByReceiverIdAndIsReadFalseAndDeletedFalse(userId); } + @Override + public List findByTargetIdAndType(Long targetId, TargetType targetType) { + return notificationJpaRepository.findByTargetIdAndType(targetId, targetType); + } } \ No newline at end of file diff --git a/src/main/java/com/chooz/post/application/PostCommandService.java b/src/main/java/com/chooz/post/application/PostCommandService.java index 3fceb3b9..9c0469c0 100644 --- a/src/main/java/com/chooz/post/application/PostCommandService.java +++ b/src/main/java/com/chooz/post/application/PostCommandService.java @@ -1,5 +1,6 @@ package com.chooz.post.application; +import com.chooz.common.event.DeleteEvent; import com.chooz.common.event.EventPublisher; import com.chooz.common.exception.BadRequestException; import com.chooz.common.exception.ErrorCode; @@ -84,6 +85,7 @@ public void delete(Long userId, Long postId) { Post post = postRepository.findById(postId) .orElseThrow(() -> new BadRequestException(ErrorCode.POST_NOT_FOUND)); post.delete(userId); + eventPublisher.publish(DeleteEvent.of(post.getId(), post.getClass().getSimpleName().toUpperCase())); } @Transactional diff --git a/src/test/java/com/chooz/notification/application/CommentLikeNotificationListenerTest.java b/src/test/java/com/chooz/notification/application/CommentLikeNotificationListenerTest.java index 3545ae53..ecd4d68a 100644 --- a/src/test/java/com/chooz/notification/application/CommentLikeNotificationListenerTest.java +++ b/src/test/java/com/chooz/notification/application/CommentLikeNotificationListenerTest.java @@ -8,7 +8,6 @@ import com.chooz.notification.domain.TargetType; import com.chooz.post.domain.PollChoiceRepository; import com.chooz.post.domain.Post; -import com.chooz.post.domain.PostRepository; import com.chooz.post.persistence.PostJpaRepository; import com.chooz.support.IntegrationTest; import com.chooz.support.fixture.CommentFixture; diff --git a/src/test/java/com/chooz/notification/application/MyPostClosedNotificationListenerTest.java b/src/test/java/com/chooz/notification/application/MyPostClosedNotificationListenerTest.java index a4ed8d5c..1d3e4764 100644 --- a/src/test/java/com/chooz/notification/application/MyPostClosedNotificationListenerTest.java +++ b/src/test/java/com/chooz/notification/application/MyPostClosedNotificationListenerTest.java @@ -10,7 +10,6 @@ import com.chooz.post.domain.CloseType; import com.chooz.post.domain.PollChoiceRepository; import com.chooz.post.domain.Post; -import com.chooz.post.domain.PostRepository; import com.chooz.post.persistence.PostJpaRepository; import com.chooz.support.IntegrationTest; import com.chooz.support.fixture.PostFixture; @@ -19,7 +18,6 @@ import com.chooz.user.domain.User; import com.chooz.user.domain.UserRepository; import com.chooz.vote.application.VotedEvent; -import com.chooz.vote.domain.VoteRepository; import com.chooz.vote.persistence.VoteJpaRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; diff --git a/src/test/java/com/chooz/notification/application/NotificationInvalidListenerTest.java b/src/test/java/com/chooz/notification/application/NotificationInvalidListenerTest.java new file mode 100644 index 00000000..ac8627ed --- /dev/null +++ b/src/test/java/com/chooz/notification/application/NotificationInvalidListenerTest.java @@ -0,0 +1,136 @@ +package com.chooz.notification.application; + +import com.chooz.comment.application.CommentService; +import com.chooz.comment.domain.Comment; +import com.chooz.comment.domain.CommentRepository; +import com.chooz.commentLike.application.CommentLikeService; +import com.chooz.notification.application.web.dto.NotificationDto; +import com.chooz.notification.domain.NotificationQueryRepository; +import com.chooz.post.application.PostCommandService; +import com.chooz.post.domain.PollChoiceRepository; +import com.chooz.post.domain.Post; +import com.chooz.post.persistence.PostJpaRepository; +import com.chooz.support.IntegrationTest; +import com.chooz.support.fixture.CommentFixture; +import com.chooz.support.fixture.PostFixture; +import com.chooz.support.fixture.UserFixture; +import com.chooz.support.fixture.VoteFixture; +import com.chooz.user.domain.User; +import com.chooz.user.domain.UserRepository; +import com.chooz.vote.persistence.VoteJpaRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.context.transaction.TestTransaction; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +class NotificationInvalidListenerTest extends IntegrationTest { + + @Autowired + UserRepository userRepository; + + @Autowired + PostJpaRepository postRepository; + + @Autowired + VoteJpaRepository voteRepository; + + @Autowired + PollChoiceRepository pollChoiceRepository; + + @Autowired + CommentRepository commentRepository; + + @Autowired + NotificationQueryRepository notificationQueryRepository; + + @Autowired + CommentLikeService commentLikeService; + + @Autowired + CommentService commentService; + + @Autowired + PostCommandService postCommandService; + + @AfterEach + void tearDown() { + voteRepository.deleteAllInBatch(); + pollChoiceRepository.deleteAllInBatch(); + postRepository.deleteAllInBatch(); + userRepository.deleteAllInBatch(); + } + + private void commit(Runnable work) { + if(!TestTransaction.isActive()){ + TestTransaction.start(); + } + work.run(); + TestTransaction.flagForCommit(); + TestTransaction.end(); + } + + @Test + @DisplayName("댓글좋아요 원 댓글 삭제 시 알림 Invalid 처리") + void InvalidNotificationByDeleteComment() throws Exception { + //given + User receiver = userRepository.save(UserFixture.createDefaultUser()); + User actor = userRepository.save(UserFixture.createDefaultUser()); + Post post = postRepository.save(PostFixture.createPostBuilder().userId(receiver.getId()).build()); + Comment comment = commentRepository.save(CommentFixture.createCommentBuilder() + .postId(post.getId()) + .userId(receiver.getId()) + .build()); + commit(() -> commentLikeService.createCommentLike(comment.getId(), actor.getId())); + + //when + commit(() -> commentService.deleteComment(post.getId(), comment.getId(), receiver.getId())); + + //then + List notifications = notificationQueryRepository.findNotifications( + receiver.getId(), + null, + PageRequest.ofSize(10) + ).getContent(); + assertAll( + () -> assertThat(notifications.size()).isZero() + ); + } + @Test + @DisplayName("원 게시물 삭제 시 알림 Invalid 처리") + void InvalidNotificationByDeletePost() throws Exception { + // given + User user = userRepository.save(UserFixture.createDefaultUser()); + Post post = postRepository.save(PostFixture.createDefaultPost(user.getId())); + + // when + List users = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + User voteUser = userRepository.save(UserFixture.createDefaultUser()); + users.add(voteUser); + voteRepository.save(VoteFixture.createDefaultVote(voteUser.getId(), post.getId(), post.getPollChoices().get(0).getId())); + } + commit(() -> postCommandService.close(user.getId(), post.getId())); + + //when + commit(() -> postCommandService.delete(user.getId(), post.getId())); + + //then + List notifications = notificationQueryRepository.findNotifications( + users.get(0).getId(), + null, + PageRequest.ofSize(10) + ).getContent(); + + assertAll( + () -> assertThat(notifications.size()).isZero() + ); + } +} diff --git a/src/test/java/com/chooz/notification/application/PostClosedNotificationListenerTest.java b/src/test/java/com/chooz/notification/application/PostClosedNotificationListenerTest.java index 36215bb0..4361b327 100644 --- a/src/test/java/com/chooz/notification/application/PostClosedNotificationListenerTest.java +++ b/src/test/java/com/chooz/notification/application/PostClosedNotificationListenerTest.java @@ -9,7 +9,6 @@ import com.chooz.post.domain.CloseType; import com.chooz.post.domain.PollChoiceRepository; import com.chooz.post.domain.Post; -import com.chooz.post.domain.PostRepository; import com.chooz.post.persistence.PostJpaRepository; import com.chooz.support.IntegrationTest; import com.chooz.support.fixture.PostFixture; @@ -18,7 +17,6 @@ import com.chooz.user.domain.User; import com.chooz.user.domain.UserRepository; import com.chooz.vote.application.VotedEvent; -import com.chooz.vote.domain.VoteRepository; import com.chooz.vote.persistence.VoteJpaRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; diff --git a/src/test/java/com/chooz/notification/application/VotedNotificationListenerTest.java b/src/test/java/com/chooz/notification/application/VotedNotificationListenerTest.java index bdc913d7..3ccd7610 100644 --- a/src/test/java/com/chooz/notification/application/VotedNotificationListenerTest.java +++ b/src/test/java/com/chooz/notification/application/VotedNotificationListenerTest.java @@ -6,7 +6,6 @@ import com.chooz.post.domain.PollChoice; import com.chooz.post.domain.PollChoiceRepository; import com.chooz.post.domain.Post; -import com.chooz.post.domain.PostRepository; import com.chooz.post.persistence.PostJpaRepository; import com.chooz.support.IntegrationTest; import com.chooz.support.fixture.PostFixture;