diff --git a/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java b/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java index b959bbfb9..af9a0d330 100644 --- a/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java +++ b/src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java @@ -11,8 +11,8 @@ import konkuk.thip.comment.application.service.validator.CommentAuthorizationValidator; import konkuk.thip.comment.domain.Comment; import konkuk.thip.common.exception.InvalidStateException; -import konkuk.thip.message.application.port.out.FeedEventCommandPort; -import konkuk.thip.message.application.port.out.RoomEventCommandPort; +import konkuk.thip.notification.application.port.in.FeedNotificationOrchestrator; +import konkuk.thip.notification.application.port.in.RoomNotificationOrchestrator; import konkuk.thip.post.application.port.out.dto.PostQueryDto; import konkuk.thip.post.domain.CountUpdatable; import konkuk.thip.post.application.service.handler.PostHandler; @@ -26,7 +26,6 @@ import static konkuk.thip.common.exception.code.ErrorCode.INVALID_COMMENT_CREATE; import static konkuk.thip.post.domain.PostType.*; - @Service @RequiredArgsConstructor public class CommentCreateService implements CommentCreateUseCase { @@ -40,8 +39,8 @@ public class CommentCreateService implements CommentCreateUseCase { private final PostHandler postHandler; private final CommentAuthorizationValidator commentAuthorizationValidator; - private final FeedEventCommandPort feedEventCommandPort; - private final RoomEventCommandPort roomEventCommandPort; + private final FeedNotificationOrchestrator feedNotificationOrchestrator; + private final RoomNotificationOrchestrator roomNotificationOrchestrator; @Override @Transactional @@ -96,10 +95,10 @@ private void sendNotificationsToPostWriter(PostQueryDto postQueryDto, User actor if (postQueryDto.postType().equals(FEED.getType())) { // 피드 댓글 알림 이벤트 발행 - feedEventCommandPort.publishFeedCommentedEvent(postQueryDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.postId()); + feedNotificationOrchestrator.notifyFeedCommented(postQueryDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.postId()); } else if (postQueryDto.postType().equals(RECORD.getType()) || postQueryDto.postType().equals(VOTE.getType())) { // 모임방 게시글 댓글 알림 이벤트 발행 - roomEventCommandPort.publishRoomPostCommentedEvent(postQueryDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.roomId(), postQueryDto.page(), postQueryDto.postId(), postQueryDto.postType()); + roomNotificationOrchestrator.notifyRoomPostCommented(postQueryDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.roomId(), postQueryDto.page(), postQueryDto.postId(), postQueryDto.postType()); } } @@ -108,10 +107,10 @@ private void sendNotificationsToParentCommentWriter(PostQueryDto postQueryDto, C if (postQueryDto.postType().equals(FEED.getType())) { // 피드 답글 알림 이벤트 발행 - feedEventCommandPort.publishFeedRepliedEvent(parentCommentDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.postId()); + feedNotificationOrchestrator.notifyFeedReplied(parentCommentDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.postId()); } else if (postQueryDto.postType().equals(RECORD.getType()) || postQueryDto.postType().equals(VOTE.getType())) { // 모임방 게시글 답글 알림 이벤트 발행 - roomEventCommandPort.publishRoomPostCommentRepliedEvent(parentCommentDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.roomId(), postQueryDto.page(), postQueryDto.postId(), postQueryDto.postType()); + roomNotificationOrchestrator.notifyRoomPostCommentReplied(parentCommentDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.roomId(), postQueryDto.page(), postQueryDto.postId(), postQueryDto.postType()); } } diff --git a/src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java b/src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java index 4fc0c4725..40b90d99d 100644 --- a/src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java +++ b/src/main/java/konkuk/thip/comment/application/service/CommentLikeService.java @@ -8,8 +8,8 @@ import konkuk.thip.comment.application.port.out.CommentLikeQueryPort; import konkuk.thip.comment.application.service.validator.CommentAuthorizationValidator; import konkuk.thip.comment.domain.Comment; -import konkuk.thip.message.application.port.out.FeedEventCommandPort; -import konkuk.thip.message.application.port.out.RoomEventCommandPort; +import konkuk.thip.notification.application.port.in.FeedNotificationOrchestrator; +import konkuk.thip.notification.application.port.in.RoomNotificationOrchestrator; import konkuk.thip.post.application.port.out.dto.PostQueryDto; import konkuk.thip.post.application.service.handler.PostHandler; import konkuk.thip.post.domain.CountUpdatable; @@ -32,8 +32,8 @@ public class CommentLikeService implements CommentLikeUseCase { private final PostHandler postHandler; private final CommentAuthorizationValidator commentAuthorizationValidator; - private final FeedEventCommandPort feedEventCommandPort; - private final RoomEventCommandPort roomEventCommandPort; + private final FeedNotificationOrchestrator feedNotificationOrchestrator; + private final RoomNotificationOrchestrator roomNotificationOrchestrator; @Override @Transactional @@ -73,11 +73,11 @@ private void sendNotifications(CommentIsLikeCommand command, Comment comment) { User actorUser = userCommandPort.findById(command.userId()); // 좋아요 푸쉬알림 전송 if (comment.getPostType() == PostType.FEED) { - feedEventCommandPort.publishFeedCommentLikedEvent(comment.getCreatorId(), actorUser.getId(), actorUser.getNickname(), comment.getTargetPostId()); + feedNotificationOrchestrator.notifyFeedCommentLiked(comment.getCreatorId(), actorUser.getId(), actorUser.getNickname(), comment.getTargetPostId()); } if (comment.getPostType() == PostType.RECORD || comment.getPostType() == PostType.VOTE) { PostQueryDto postQueryDto = postHandler.getPostQueryDto(comment.getPostType(), comment.getTargetPostId()); - roomEventCommandPort.publishRoomCommentLikedEvent(comment.getCreatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.roomId(), postQueryDto.page(), postQueryDto.postId(), postQueryDto.postType()); + roomNotificationOrchestrator.notifyRoomCommentLiked(comment.getCreatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.roomId(), postQueryDto.page(), postQueryDto.postId(), postQueryDto.postType()); } } } diff --git a/src/main/java/konkuk/thip/common/security/constant/SecurityWhitelist.java b/src/main/java/konkuk/thip/common/security/constant/SecurityWhitelist.java new file mode 100644 index 000000000..35d417829 --- /dev/null +++ b/src/main/java/konkuk/thip/common/security/constant/SecurityWhitelist.java @@ -0,0 +1,39 @@ +package konkuk.thip.common.security.constant; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.List; + +@RequiredArgsConstructor +@Getter +public enum SecurityWhitelist { + + SWAGGER_UI("/swagger-ui/**"), + API_DOCS("/api-docs/**"), + SWAGGER_UI_HTML("/swagger-ui.html"), + V3_API_DOCS("/v3/api-docs/**"), + OAUTH2_AUTHORIZATION("/oauth2/authorization/**"), + LOGIN_OAUTH2_CODE("/login/oauth2/code/**"), + ACTUATOR_HEALTH("/actuator/health"), + AUTH_USERS("/auth/users"), + AUTH_TOKEN("/auth/token"), + API_TEST("/api/test/**"), + AUTH_EXCHANGE_TEMP_TOKEN("/auth/exchange-temp-token"), + AUTH_SET_COOKIE("/auth/set-cookie"); + + private final String pattern; + + // SecurityConfig 용 전체 리스트 + public static String[] patterns() { + return Arrays.stream(values()) + .map(SecurityWhitelist::getPattern) + .toArray(String[]::new); + } + + // JwtAuthenticationFilter.shouldNotFilter() 용 편의 메서드 + public static List patternsList() { + return Arrays.asList(patterns()); + } +} diff --git a/src/main/java/konkuk/thip/common/security/filter/JwtAuthenticationFilter.java b/src/main/java/konkuk/thip/common/security/filter/JwtAuthenticationFilter.java index 47cabf33b..51070cf47 100644 --- a/src/main/java/konkuk/thip/common/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/konkuk/thip/common/security/filter/JwtAuthenticationFilter.java @@ -1,23 +1,29 @@ package konkuk.thip.common.security.filter; +import jakarta.annotation.PostConstruct; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import konkuk.thip.common.exception.AuthException; +import konkuk.thip.common.security.constant.SecurityWhitelist; import konkuk.thip.common.security.oauth2.CustomOAuth2User; import konkuk.thip.common.security.oauth2.LoginUser; import konkuk.thip.common.security.util.JwtUtil; import konkuk.thip.user.application.port.UserTokenBlacklistQueryPort; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.server.PathContainer; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.util.pattern.PathPattern; +import org.springframework.web.util.pattern.PathPatternParser; import java.io.IOException; +import java.util.List; import static konkuk.thip.common.exception.code.ErrorCode.*; import static konkuk.thip.common.security.constant.AuthParameters.*; @@ -30,6 +36,31 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtUtil jwtUtil; private final UserTokenBlacklistQueryPort userTokenBlacklistQueryPort; + private List whitelistPatterns; + private final PathPatternParser pathPatternParser = new PathPatternParser(); + + @PostConstruct + void initWhitelistPatterns() { + this.whitelistPatterns = SecurityWhitelist.patternsList().stream() + .map(pathPatternParser::parse) // 애플리케이션 시작 시 1회 컴파일 + .toList(); + } + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + // 컨텍스트 패스 고려한 실제 경로(= path) 추출 + String requestUri = request.getRequestURI(); + String contextPath = request.getContextPath(); + String path = (contextPath != null && !contextPath.isEmpty() && requestUri.startsWith(contextPath)) + ? requestUri.substring(contextPath.length()) + : requestUri; + + PathContainer container = PathContainer.parsePath(path); + + // PathPattern으로 세그먼트 기반 매칭 + return whitelistPatterns.stream().anyMatch(p -> p.matches(container)); + } + @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, @@ -92,24 +123,4 @@ private String extractToken(HttpServletRequest request) { log.info("토큰이 없습니다."); return null; } - - @Override - protected boolean shouldNotFilter(HttpServletRequest request) { - String path = request.getRequestURI(); - - // 화이트리스트 경로에 대해서는 JWT 필터 제외 - return path.startsWith("/swagger-ui") - || path.startsWith("/v3/api-docs") - || path.startsWith("/api-docs") - || path.startsWith("/actuator/health") - || path.startsWith("/oauth2/authorization") - || path.startsWith("/login/oauth2/code") - || path.startsWith("/auth/users") - || path.equals("/auth/token") - -// || path.equals("/auth/set-cookie") -// || path.equals("/auth/exchange-temp-token") - ; - } - } diff --git a/src/main/java/konkuk/thip/config/SecurityConfig.java b/src/main/java/konkuk/thip/config/SecurityConfig.java index fa4cbbe98..9ee8472fe 100644 --- a/src/main/java/konkuk/thip/config/SecurityConfig.java +++ b/src/main/java/konkuk/thip/config/SecurityConfig.java @@ -1,5 +1,6 @@ package konkuk.thip.config; +import konkuk.thip.common.security.constant.SecurityWhitelist; import konkuk.thip.common.security.filter.JwtAuthenticationEntryPoint; import konkuk.thip.common.security.filter.JwtAuthenticationFilter; import konkuk.thip.common.security.oauth2.CustomOAuth2UserService; @@ -48,17 +49,6 @@ public class SecurityConfig { private final CustomOAuth2UserService customOAuth2UserService; private final CustomSuccessHandler customSuccessHandler; - private static final String[] WHITELIST = { - "/swagger-ui/**", "/api-docs/**", "/swagger-ui.html", - "/v3/api-docs/**","/oauth2/authorization/**", - "/login/oauth2/code/**", "/actuator/health", - "/auth/users", "/auth/token", - - "/api/test/**", // for test - - "/auth/exchange-temp-token", "/auth/set-cookie", // deprecated - }; - @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -75,7 +65,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .successHandler(customSuccessHandler) // OAuth2 로그인 성공 시 처리 ) .authorizeHttpRequests(auth -> auth - .requestMatchers(WHITELIST).permitAll() + .requestMatchers(SecurityWhitelist.patterns()).permitAll() .anyRequest().authenticated() ) .exceptionHandling(handler -> handler.authenticationEntryPoint(jwtAuthenticationEntryPoint)) diff --git a/src/main/java/konkuk/thip/feed/application/service/FeedCreateService.java b/src/main/java/konkuk/thip/feed/application/service/FeedCreateService.java index a1aea4ac4..4d61ce21d 100644 --- a/src/main/java/konkuk/thip/feed/application/service/FeedCreateService.java +++ b/src/main/java/konkuk/thip/feed/application/service/FeedCreateService.java @@ -12,7 +12,7 @@ import konkuk.thip.feed.domain.value.ContentList; import konkuk.thip.feed.domain.value.Tag; import konkuk.thip.feed.domain.value.TagList; -import konkuk.thip.message.application.port.out.FeedEventCommandPort; +import konkuk.thip.notification.application.port.in.FeedNotificationOrchestrator; import konkuk.thip.user.application.port.out.UserCommandPort; import konkuk.thip.user.application.port.out.UserQueryPort; import konkuk.thip.user.domain.User; @@ -33,7 +33,7 @@ public class FeedCreateService implements FeedCreateUseCase { private final UserCommandPort userCommandPort; private final ImageUrlValidationService imageUrlValidationService; - private final FeedEventCommandPort feedEventCommandPort; + private final FeedNotificationOrchestrator feedNotificationOrchestrator; @Override @Transactional @@ -71,7 +71,7 @@ private void sendNotifications(FeedCreateCommand command, Long savedFeedId) { List targetUsers = userQueryPort.getAllFollowersByUserId(command.userId()); User actorUser = userCommandPort.findById(command.userId()); for (User targetUser : targetUsers) { - feedEventCommandPort.publishFolloweeNewPostEvent(targetUser.getId(), actorUser.getId(), actorUser.getNickname(), savedFeedId); + feedNotificationOrchestrator.notifyFolloweeNewPost(targetUser.getId(), actorUser.getId(), actorUser.getNickname(), savedFeedId); } } diff --git a/src/main/java/konkuk/thip/message/adapter/out/event/FeedEventPublisherAdapter.java b/src/main/java/konkuk/thip/message/adapter/out/event/FeedEventPublisherAdapter.java index b826b181e..f210e480b 100644 --- a/src/main/java/konkuk/thip/message/adapter/out/event/FeedEventPublisherAdapter.java +++ b/src/main/java/konkuk/thip/message/adapter/out/event/FeedEventPublisherAdapter.java @@ -13,8 +13,12 @@ public class FeedEventPublisherAdapter implements FeedEventCommandPort { private final ApplicationEventPublisher publisher; @Override - public void publishFollowEvent(Long targetUserId, Long actorUserId, String actorUsername) { + public void publishFollowEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername) { publisher.publishEvent(FeedEvents.FollowerEvent.builder() + .title(title) + .content(content) .targetUserId(targetUserId) .actorUserId(actorUserId) .actorUsername(actorUsername) @@ -22,9 +26,13 @@ public void publishFollowEvent(Long targetUserId, Long actorUserId, String actor } @Override - public void publishFeedCommentedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long feedId) { + public void publishFeedCommentedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long feedId) { publisher.publishEvent(FeedEvents.FeedCommentedEvent.builder() + .title(title) + .content(content) .targetUserId(targetUserId) .actorUserId(actorUserId) .actorUsername(actorUsername) @@ -33,9 +41,13 @@ public void publishFeedCommentedEvent(Long targetUserId, Long actorUserId, Strin } @Override - public void publishFeedRepliedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long feedId) { + public void publishFeedRepliedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long feedId) { publisher.publishEvent(FeedEvents.FeedCommentRepliedEvent.builder() + .title(title) + .content(content) .targetUserId(targetUserId) .actorUserId(actorUserId) .actorUsername(actorUsername) @@ -44,9 +56,13 @@ public void publishFeedRepliedEvent(Long targetUserId, Long actorUserId, String } @Override - public void publishFolloweeNewPostEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long feedId) { + public void publishFolloweeNewPostEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long feedId) { publisher.publishEvent(FeedEvents.FolloweeNewPostEvent.builder() + .title(title) + .content(content) .targetUserId(targetUserId) .actorUserId(actorUserId) .actorUsername(actorUsername) @@ -55,9 +71,13 @@ public void publishFolloweeNewPostEvent(Long targetUserId, Long actorUserId, Str } @Override - public void publishFeedLikedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long feedId) { + public void publishFeedLikedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long feedId) { publisher.publishEvent(FeedEvents.FeedLikedEvent.builder() + .title(title) + .content(content) .targetUserId(targetUserId) .actorUserId(actorUserId) .actorUsername(actorUsername) @@ -66,9 +86,13 @@ public void publishFeedLikedEvent(Long targetUserId, Long actorUserId, String ac } @Override - public void publishFeedCommentLikedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long feedId) { + public void publishFeedCommentLikedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long feedId) { publisher.publishEvent(FeedEvents.FeedCommentLikedEvent.builder() + .title(title) + .content(content) .targetUserId(targetUserId) .actorUserId(actorUserId) .actorUsername(actorUsername) diff --git a/src/main/java/konkuk/thip/message/adapter/out/event/RoomEventPublisherAdapter.java b/src/main/java/konkuk/thip/message/adapter/out/event/RoomEventPublisherAdapter.java index c8cf0c5d7..873b42131 100644 --- a/src/main/java/konkuk/thip/message/adapter/out/event/RoomEventPublisherAdapter.java +++ b/src/main/java/konkuk/thip/message/adapter/out/event/RoomEventPublisherAdapter.java @@ -13,9 +13,13 @@ public class RoomEventPublisherAdapter implements RoomEventCommandPort { private final ApplicationEventPublisher publisher; @Override - public void publishRoomPostCommentedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long roomId, Integer page, Long postId, String postType) { + public void publishRoomPostCommentedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long roomId, Integer page, Long postId, String postType) { publisher.publishEvent(RoomEvents.RoomPostCommentedEvent.builder() + .title(title) + .content(content) .targetUserId(targetUserId) .actorUserId(actorUserId) .actorUsername(actorUsername) @@ -27,9 +31,13 @@ public void publishRoomPostCommentedEvent(Long targetUserId, Long actorUserId, S } @Override - public void publishRoomVoteStartedEvent(Long targetUserId, Long roomId, String roomTitle, - Integer page, Long postId) { + public void publishRoomVoteStartedEvent( + String title, String content, + Long targetUserId, Long roomId, String roomTitle, + Integer page, Long postId) { publisher.publishEvent(RoomEvents.RoomVoteStartedEvent.builder() + .title(title) + .content(content) .targetUserId(targetUserId) .roomId(roomId) .roomTitle(roomTitle) @@ -39,9 +47,13 @@ public void publishRoomVoteStartedEvent(Long targetUserId, Long roomId, String r } @Override - public void publishRoomRecordCreatedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long roomId, String roomTitle, Integer page, Long postId) { + public void publishRoomRecordCreatedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long roomId, String roomTitle, Integer page, Long postId) { publisher.publishEvent(RoomEvents.RoomRecordCreatedEvent.builder() + .title(title) + .content(content) .targetUserId(targetUserId) .actorUserId(actorUserId) .actorUsername(actorUsername) @@ -53,8 +65,12 @@ public void publishRoomRecordCreatedEvent(Long targetUserId, Long actorUserId, S } @Override - public void publishRoomRecruitClosedEarlyEvent(Long targetUserId, Long roomId, String roomTitle) { + public void publishRoomRecruitClosedEarlyEvent( + String title, String content, + Long targetUserId, Long roomId, String roomTitle) { publisher.publishEvent(RoomEvents.RoomRecruitClosedEarlyEvent.builder() + .title(title) + .content(content) .targetUserId(targetUserId) .roomId(roomId) .roomTitle(roomTitle) @@ -62,8 +78,12 @@ public void publishRoomRecruitClosedEarlyEvent(Long targetUserId, Long roomId, S } @Override - public void publishRoomActivityStartedEvent(Long targetUserId, Long roomId, String roomTitle) { + public void publishRoomActivityStartedEvent( + String title, String content, + Long targetUserId, Long roomId, String roomTitle) { publisher.publishEvent(RoomEvents.RoomActivityStartedEvent.builder() + .title(title) + .content(content) .targetUserId(targetUserId) .roomId(roomId) .roomTitle(roomTitle) @@ -71,9 +91,13 @@ public void publishRoomActivityStartedEvent(Long targetUserId, Long roomId, Stri } @Override - public void publishRoomJoinEventToHost(Long hostUserId, Long roomId, String roomTitle, - Long actorUserId, String actorUsername) { + public void publishRoomJoinEventToHost( + String title, String content, + Long hostUserId, Long roomId, String roomTitle, + Long actorUserId, String actorUsername) { publisher.publishEvent(RoomEvents.RoomJoinRequestedToOwnerEvent.builder() + .title(title) + .content(content) .ownerUserId(hostUserId) .roomId(roomId) .roomTitle(roomTitle) @@ -83,9 +107,13 @@ public void publishRoomJoinEventToHost(Long hostUserId, Long roomId, String room } @Override - public void publishRoomCommentLikedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long roomId, Integer page, Long postId, String postType) { + public void publishRoomCommentLikedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long roomId, Integer page, Long postId, String postType) { publisher.publishEvent(RoomEvents.RoomCommentLikedEvent.builder() + .title(title) + .content(content) .targetUserId(targetUserId) .actorUserId(actorUserId) .actorUsername(actorUsername) @@ -97,9 +125,13 @@ public void publishRoomCommentLikedEvent(Long targetUserId, Long actorUserId, St } @Override - public void publishRoomPostLikedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long roomId, Integer page, Long postId, String postType) { + public void publishRoomPostLikedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long roomId, Integer page, Long postId, String postType) { publisher.publishEvent(RoomEvents.RoomPostLikedEvent.builder() + .title(title) + .content(content) .targetUserId(targetUserId) .actorUserId(actorUserId) .actorUsername(actorUsername) @@ -111,8 +143,12 @@ public void publishRoomPostLikedEvent(Long targetUserId, Long actorUserId, Strin } @Override - public void publishRoomPostCommentRepliedEvent(Long targetUserId, Long actorUserId, String actorUsername, Long roomId, Integer page, Long postId, String postType) { + public void publishRoomPostCommentRepliedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, Long roomId, Integer page, Long postId, String postType) { publisher.publishEvent(RoomEvents.RoomPostCommentRepliedEvent.builder() + .title(title) + .content(content) .targetUserId(targetUserId) .actorUserId(actorUserId) .actorUsername(actorUsername) diff --git a/src/main/java/konkuk/thip/message/adapter/out/event/dto/FeedEvents.java b/src/main/java/konkuk/thip/message/adapter/out/event/dto/FeedEvents.java index 176f1f801..922721d64 100644 --- a/src/main/java/konkuk/thip/message/adapter/out/event/dto/FeedEvents.java +++ b/src/main/java/konkuk/thip/message/adapter/out/event/dto/FeedEvents.java @@ -7,30 +7,42 @@ public class FeedEvents { // 누군가 나를 팔로우하는 경우 @Builder - public record FollowerEvent(Long targetUserId, Long actorUserId, String actorUsername) {} + public record FollowerEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername) {} // 누군가 내 피드에 댓글을 다는 경우 @Builder - public record FeedCommentedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long feedId) {} + public record FeedCommentedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long feedId) {} // 누군가 내 댓글에 대댓글을 다는 경우 @Builder - public record FeedCommentRepliedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long feedId) {} + public record FeedCommentRepliedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long feedId) {} // 내가 팔로우하는 사람이 새 글을 올리는 경우 @Builder - public record FolloweeNewPostEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long feedId) {} + public record FolloweeNewPostEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long feedId) {} // 내 피드가 좋아요를 받는 경우 @Builder - public record FeedLikedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long feedId) {} + public record FeedLikedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long feedId) {} // 내 피드 댓글이 좋아요를 받는 경우 @Builder - public record FeedCommentLikedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long feedId) {} -} \ No newline at end of file + public record FeedCommentLikedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long feedId) {} +} diff --git a/src/main/java/konkuk/thip/message/adapter/out/event/dto/RoomEvents.java b/src/main/java/konkuk/thip/message/adapter/out/event/dto/RoomEvents.java index bdc076f93..e96c8c098 100644 --- a/src/main/java/konkuk/thip/message/adapter/out/event/dto/RoomEvents.java +++ b/src/main/java/konkuk/thip/message/adapter/out/event/dto/RoomEvents.java @@ -8,44 +8,62 @@ public class RoomEvents { // 댓글 대상이 "기록/투표" 모두 가능하므로 통합 스키마 사용 // 내 모임방 기록/투표에 댓글이 달린 경우 @Builder - public record RoomPostCommentedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long roomId, Integer page, Long postId, String postType) {} + public record RoomPostCommentedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long roomId, Integer page, Long postId, String postType) {} // 내가 참여한 모임방에 새로운 투표가 시작된 경우 @Builder - public record RoomVoteStartedEvent(Long targetUserId, Long roomId, String roomTitle, - Integer page, Long postId) {} + public record RoomVoteStartedEvent( + String title, String content, + Long targetUserId, Long roomId, String roomTitle, + Integer page, Long postId) {} // 내가 참여한 모임방에 새로운 기록이 작성된 경우 @Builder - public record RoomRecordCreatedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long roomId, String roomTitle, Integer page, Long postId) {} + public record RoomRecordCreatedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long roomId, String roomTitle, Integer page, Long postId) {} // 내가 참여한 모임방이 조기 종료된 경우 (호스트가 모집 마감 버튼 누른 경우) @Builder - public record RoomRecruitClosedEarlyEvent(Long targetUserId, Long roomId, String roomTitle) {} + public record RoomRecruitClosedEarlyEvent( + String title, String content, + Long targetUserId, Long roomId, String roomTitle) {} // 내가 참여한 모임방 활동이 시작된 경우 (방이 시작 기간이 되어 자동으로 시작된 경우) @Builder - public record RoomActivityStartedEvent(Long targetUserId, Long roomId, String roomTitle) {} + public record RoomActivityStartedEvent( + String title, String content, + Long targetUserId, Long roomId, String roomTitle) {} // 내가 방장일 때, 새로운 사용자가 모임방 참여를 한 경우 @Builder - public record RoomJoinRequestedToOwnerEvent(Long ownerUserId, Long roomId, String roomTitle, - Long applicantUserId, String applicantUsername) {} + public record RoomJoinRequestedToOwnerEvent( + String title, String content, + Long ownerUserId, Long roomId, String roomTitle, + Long applicantUserId, String applicantUsername) {} // 내가 참여한 모임방의 나의 댓글이 좋아요를 받는 경우 @Builder - public record RoomCommentLikedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long roomId, Integer page, Long postId, String postType) {} + public record RoomCommentLikedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long roomId, Integer page, Long postId, String postType) {} // 내가 참여한 모임방의 나의 기록이 좋아요를 받는 경우 @Builder - public record RoomPostLikedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long roomId, Integer page, Long postId, String postType) {} + public record RoomPostLikedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long roomId, Integer page, Long postId, String postType) {} // 내가 참여한 모임방의 나의 댓글에 대댓글이 달린 경우 @Builder - public record RoomPostCommentRepliedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long roomId, Integer page, Long postId, String postType) {} -} \ No newline at end of file + public record RoomPostCommentRepliedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long roomId, Integer page, Long postId, String postType) {} +} diff --git a/src/main/java/konkuk/thip/message/adapter/out/firebase/FirebaseAdapter.java b/src/main/java/konkuk/thip/message/adapter/out/firebase/FirebaseAdapter.java index 318a1d668..f907ceb6a 100644 --- a/src/main/java/konkuk/thip/message/adapter/out/firebase/FirebaseAdapter.java +++ b/src/main/java/konkuk/thip/message/adapter/out/firebase/FirebaseAdapter.java @@ -13,7 +13,7 @@ @Slf4j @Component -@Profile({"!test & !local"}) +@Profile("!test & !local") @RequiredArgsConstructor public class FirebaseAdapter implements FirebaseMessagingPort { diff --git a/src/main/java/konkuk/thip/message/application/port/out/FeedEventCommandPort.java b/src/main/java/konkuk/thip/message/application/port/out/FeedEventCommandPort.java index 0fbc5631d..aa9b0433f 100644 --- a/src/main/java/konkuk/thip/message/application/port/out/FeedEventCommandPort.java +++ b/src/main/java/konkuk/thip/message/application/port/out/FeedEventCommandPort.java @@ -3,25 +3,37 @@ public interface FeedEventCommandPort { // 누군가 나를 팔로우하는 경우 - void publishFollowEvent(Long targetUserId, Long actorUserId, String actorUsername); + void publishFollowEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername); // 누군가 내 피드에 댓글을 다는 경우 - void publishFeedCommentedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long feedId); + void publishFeedCommentedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long feedId); // 누군가 내 댓글에 대댓글을 다는 경우 - void publishFeedRepliedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long feedId); + void publishFeedRepliedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long feedId); // 내가 팔로우하는 사람이 새 글을 올리는 경우 - void publishFolloweeNewPostEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long feedId); + void publishFolloweeNewPostEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long feedId); // 내 피드가 좋아요를 받는 경우 - void publishFeedLikedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long feedId); + void publishFeedLikedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long feedId); // 내 피드 댓글이 좋아요를 받는 경우 - void publishFeedCommentLikedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long feedId); + void publishFeedCommentLikedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long feedId); } \ No newline at end of file diff --git a/src/main/java/konkuk/thip/message/application/port/out/RoomEventCommandPort.java b/src/main/java/konkuk/thip/message/application/port/out/RoomEventCommandPort.java index b840969a1..a52c7c990 100644 --- a/src/main/java/konkuk/thip/message/application/port/out/RoomEventCommandPort.java +++ b/src/main/java/konkuk/thip/message/application/port/out/RoomEventCommandPort.java @@ -3,36 +3,54 @@ public interface RoomEventCommandPort { // 내 모임방 기록/투표에 댓글이 달린 경우 - void publishRoomPostCommentedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long roomId, Integer page, Long postId, String postType); + void publishRoomPostCommentedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long roomId, Integer page, Long postId, String postType); // 내가 참여한 모임방에 새로운 투표가 시작된 경우 - void publishRoomVoteStartedEvent(Long targetUserId, Long roomId, String roomTitle, - Integer page, Long postId); + void publishRoomVoteStartedEvent( + String title, String content, + Long targetUserId, Long roomId, String roomTitle, + Integer page, Long postId); // 내가 참여한 모임방에 새로운 기록이 작성된 경우 - void publishRoomRecordCreatedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long roomId, String roomTitle, Integer page, Long postId); + void publishRoomRecordCreatedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long roomId, String roomTitle, Integer page, Long postId); // 내가 참여한 모임방이 조기 종료된 경우 (호스트가 모집 마감 버튼 누른 경우) - void publishRoomRecruitClosedEarlyEvent(Long targetUserId, Long roomId, String roomTitle); + void publishRoomRecruitClosedEarlyEvent( + String title, String content, + Long targetUserId, Long roomId, String roomTitle); // 내가 참여한 모임방 활동이 시작된 경우 (방이 시작 기간이 되어 자동으로 시작된 경우) - void publishRoomActivityStartedEvent(Long targetUserId, Long roomId, String roomTitle); + void publishRoomActivityStartedEvent( + String title, String content, + Long targetUserId, Long roomId, String roomTitle); // 내가 방장일 때, 새로운 사용자가 모임방 참여를 한 경우 - void publishRoomJoinEventToHost(Long hostUserId, Long roomId, String roomTitle, - Long actorUserId, String actorUsername); + void publishRoomJoinEventToHost( + String title, String content, + Long hostUserId, Long roomId, String roomTitle, + Long actorUserId, String actorUsername); // 내가 참여한 모임방의 나의 댓글이 좋아요를 받는 경우 - void publishRoomCommentLikedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long roomId, Integer page, Long postId, String postType); + void publishRoomCommentLikedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long roomId, Integer page, Long postId, String postType); // 내가 참여한 모임방 안의 나의 기록/투표가 좋아요를 받는 경우 - void publishRoomPostLikedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long roomId, Integer page, Long postId, String postType); + void publishRoomPostLikedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long roomId, Integer page, Long postId, String postType); // 내가 참여한 모임방의 나의 댓글에 대댓글이 달린 경우 - void publishRoomPostCommentRepliedEvent(Long targetUserId, Long actorUserId, String actorUsername, - Long roomId, Integer page, Long postId, String postType); + void publishRoomPostCommentRepliedEvent( + String title, String content, + Long targetUserId, Long actorUserId, String actorUsername, + Long roomId, Integer page, Long postId, String postType); } \ No newline at end of file diff --git a/src/main/java/konkuk/thip/message/application/service/FeedNotificationDispatchService.java b/src/main/java/konkuk/thip/message/application/service/FeedNotificationDispatchService.java index 27ecdcd7b..1941205dd 100644 --- a/src/main/java/konkuk/thip/message/application/service/FeedNotificationDispatchService.java +++ b/src/main/java/konkuk/thip/message/application/service/FeedNotificationDispatchService.java @@ -5,7 +5,7 @@ import konkuk.thip.message.application.port.in.FeedNotificationDispatchUseCase; import konkuk.thip.message.application.port.out.FirebaseMessagingPort; import konkuk.thip.message.adapter.out.event.dto.FeedEvents; -import konkuk.thip.message.domain.NotificationCategory; +import konkuk.thip.notification.domain.value.NotificationCategory; import konkuk.thip.message.domain.MessageRoute; import konkuk.thip.notification.application.port.out.FcmTokenPersistencePort; import konkuk.thip.notification.domain.FcmToken; @@ -26,8 +26,7 @@ public class FeedNotificationDispatchService implements FeedNotificationDispatch @Override public void handleFollower(final FeedEvents.FollowerEvent event) { - Notification n = buildNotification("팔로워 알림", - "@" + event.actorUsername() + " 님이 나를 띱했어요!"); + Notification n = buildNotification(event.title(), event.content()); List tokens = fcmTokenPersistencePort.findEnabledByUserId(event.targetUserId()); @@ -49,40 +48,35 @@ public void handleFollower(final FeedEvents.FollowerEvent event) { @Override public void handleFeedCommented(final FeedEvents.FeedCommentedEvent event) { - Notification notification = buildNotification("새로운 댓글이 달렸어요", - "@" +event.actorUsername() + " 님이 내 글에 댓글을 달았어요!"); + Notification notification = buildNotification(event.title(), event.content()); pushFeedDetail(event.targetUserId(), notification, event.feedId()); } @Override public void handleFeedCommentReplied(final FeedEvents.FeedCommentRepliedEvent event) { - Notification notification = buildNotification("새로운 답글이 달렸어요", - "@" + event.actorUsername() + " 님이 내 댓글에 답글을 달았어요!"); + Notification notification = buildNotification(event.title(), event.content()); pushFeedDetail(event.targetUserId(), notification, event.feedId()); } @Override public void handleFolloweeNewPost(final FeedEvents.FolloweeNewPostEvent event) { - Notification notification = buildNotification("새 글 알림", - "@" + event.actorUsername() + " 님이 새로운 글을 작성했어요!"); + Notification notification = buildNotification(event.title(), event.content()); pushFeedDetail(event.targetUserId(), notification, event.feedId()); } @Override public void handleFeedLiked(final FeedEvents.FeedLikedEvent event) { - Notification notification = buildNotification("내 글을 좋아합니다", - "@" + event.actorUsername() + " 님이 내 글에 좋아요를 눌렀어요!"); + Notification notification = buildNotification(event.title(), event.content()); pushFeedDetail(event.targetUserId(), notification, event.feedId()); } @Override public void handleFeedCommentLiked(final FeedEvents.FeedCommentLikedEvent event) { - Notification notification = buildNotification("좋아요 알림", - "@" + event.actorUsername() + " 님이 내 댓글에 좋아요를 눌렀어요!"); + Notification notification = buildNotification(event.title(), event.content()); pushFeedDetail(event.targetUserId(), notification, event.feedId()); } @@ -109,7 +103,7 @@ private void pushFeedDetail(Long userId, Notification notification, Long feedId) } private Notification buildNotification(final String title, final String body) { - return Notification.builder().setTitle(NotificationCategory.FEED.prefixedTitle(title)).setBody(body).build(); + return Notification.builder().setTitle(title).setBody(body).build(); } private Message buildMessage(final String token, final Notification n, diff --git a/src/main/java/konkuk/thip/message/application/service/RoomNotificationDispatchService.java b/src/main/java/konkuk/thip/message/application/service/RoomNotificationDispatchService.java index 93acf0b81..e705697a2 100644 --- a/src/main/java/konkuk/thip/message/application/service/RoomNotificationDispatchService.java +++ b/src/main/java/konkuk/thip/message/application/service/RoomNotificationDispatchService.java @@ -5,7 +5,7 @@ import konkuk.thip.message.application.port.in.RoomNotificationDispatchUseCase; import konkuk.thip.message.application.port.out.FirebaseMessagingPort; import konkuk.thip.message.adapter.out.event.dto.RoomEvents; -import konkuk.thip.message.domain.NotificationCategory; +import konkuk.thip.notification.domain.value.NotificationCategory; import konkuk.thip.message.domain.MessageRoute; import konkuk.thip.notification.application.port.out.FcmTokenPersistencePort; import konkuk.thip.notification.domain.FcmToken; @@ -26,8 +26,7 @@ public class RoomNotificationDispatchService implements RoomNotificationDispatch @Override public void handleRoomPostCommented(final RoomEvents.RoomPostCommentedEvent event) { - Notification notification = buildNotification("새로운 댓글이 달렸어요", - "@" + event.actorUsername() + " 님이 내 독서기록에 댓글을 달았어요!"); + Notification notification = buildNotification(event.title(), event.content()); List tokens = fcmTokenQueryPort.findEnabledByUserId(event.targetUserId()); if (tokens.isEmpty()) return; @@ -52,8 +51,7 @@ public void handleRoomPostCommented(final RoomEvents.RoomPostCommentedEvent even @Override public void handleRoomVoteStarted(final RoomEvents.RoomVoteStartedEvent event) { - Notification notification = buildNotification(event.roomTitle(), - "새로운 투표가 시작되었어요!"); + Notification notification = buildNotification(event.title(), event.content()); List tokens = fcmTokenQueryPort.findEnabledByUserId(event.targetUserId()); if (tokens.isEmpty()) return; @@ -78,8 +76,7 @@ public void handleRoomVoteStarted(final RoomEvents.RoomVoteStartedEvent event) { @Override public void handleRoomRecordCreated(final RoomEvents.RoomRecordCreatedEvent event) { - Notification notification = buildNotification(event.roomTitle(), - "@" + event.actorUsername() + " 님이 새로운 독서 기록을 작성했어요!"); + Notification notification = buildNotification(event.title(), event.content()); List tokens = fcmTokenQueryPort.findEnabledByUserId(event.targetUserId()); if (tokens.isEmpty()) return; @@ -104,32 +101,28 @@ public void handleRoomRecordCreated(final RoomEvents.RoomRecordCreatedEvent even @Override public void handleRoomRecruitClosedEarly(final RoomEvents.RoomRecruitClosedEarlyEvent event) { - Notification n = buildNotification(event.roomTitle(), - "모임방 활동이 시작되었어요. 모임방에서 독서 기록을 시작해보세요!"); + Notification notification = buildNotification(event.title(), event.content()); - pushRoomMain(event.targetUserId(), event.roomId(), n); + pushRoomMain(event.targetUserId(), event.roomId(), notification); } @Override public void handleRoomActivityStarted(final RoomEvents.RoomActivityStartedEvent event) { - Notification notification = buildNotification(event.roomTitle(), - "모임방 활동이 시작되었어요. 모임방에서 독서 기록을 시작해보세요!"); + Notification notification = buildNotification(event.title(), event.content()); pushRoomMain(event.targetUserId(), event.roomId(), notification); } @Override public void handleRoomJoinRequestedToOwner(final RoomEvents.RoomJoinRequestedToOwnerEvent event) { - Notification n = buildNotification(event.roomTitle(), - "@" + event.applicantUsername() + " 님이 모임에 참여했어요!"); + Notification notification = buildNotification(event.title(), event.content()); - pushRoomDetail(event.ownerUserId(), event.roomId(), n); + pushRoomDetail(event.ownerUserId(), event.roomId(), notification); } @Override public void handleRoomCommentLiked(final RoomEvents.RoomCommentLikedEvent event) { - Notification notification = buildNotification("내 댓글을 좋아합니다", - "@" + event.actorUsername() + " 님이 내 댓글에 좋아요를 눌렀어요!"); + Notification notification = buildNotification(event.title(), event.content()); List tokens = fcmTokenQueryPort.findEnabledByUserId(event.targetUserId()); if (tokens.isEmpty()) return; @@ -153,8 +146,7 @@ public void handleRoomCommentLiked(final RoomEvents.RoomCommentLikedEvent event) @Override public void handleRoomPostLiked(final RoomEvents.RoomPostLikedEvent event) { - Notification notification = buildNotification("좋아요 알림", - "@" + event.actorUsername() + " 님이 내 독서기록에 좋아요를 눌렀어요!"); + Notification notification = buildNotification(event.title(), event.content()); List tokens = fcmTokenQueryPort.findEnabledByUserId(event.targetUserId()); if (tokens.isEmpty()) return; @@ -177,11 +169,10 @@ public void handleRoomPostLiked(final RoomEvents.RoomPostLikedEvent event) { } @Override - public void handleRoomPostCommentReplied(RoomEvents.RoomPostCommentRepliedEvent e) { - Notification notification = buildNotification("새로운 댓글이 달렸어요", - "@" + e.actorUsername() + " 님이 내 댓글에 대댓글을 달았어요!"); + public void handleRoomPostCommentReplied(RoomEvents.RoomPostCommentRepliedEvent event) { + Notification notification = buildNotification(event.title(), event.content()); - List tokens = fcmTokenQueryPort.findEnabledByUserId(e.targetUserId()); + List tokens = fcmTokenQueryPort.findEnabledByUserId(event.targetUserId()); if (tokens.isEmpty()) return; List msgs = new ArrayList<>(tokens.size()); @@ -191,11 +182,11 @@ public void handleRoomPostCommentReplied(RoomEvents.RoomPostCommentRepliedEvent for (FcmToken t : tokens) { Message m = buildMessage(t.getFcmToken(), notification, MessageRoute.ROOM_POST_DETAIL, - "roomId", String.valueOf(e.roomId()), - "page", String.valueOf(e.page()), + "roomId", String.valueOf(event.roomId()), + "page", String.valueOf(event.page()), "type", "group", - "postId", String.valueOf(e.postId()), - "postType", String.valueOf(e.postType())); + "postId", String.valueOf(event.postId()), + "postType", String.valueOf(event.postType())); msgs.add(m); tk.add(t.getFcmToken()); dev.add(t.getDeviceId()); } @@ -243,7 +234,7 @@ private void pushRoomDetail(Long targetUserId, Long roomId, Notification notific } private Notification buildNotification(final String title, final String body) { - return Notification.builder().setTitle(NotificationCategory.ROOM.prefixedTitle(title)).setBody(body).build(); + return Notification.builder().setTitle(title).setBody(body).build(); } private Message buildMessage(final String token, final Notification n, diff --git a/src/main/java/konkuk/thip/notification/adapter/out/persistence/NotificationCommandPersistenceAdapter.java b/src/main/java/konkuk/thip/notification/adapter/out/persistence/NotificationCommandPersistenceAdapter.java index 568af5196..0227e28d9 100644 --- a/src/main/java/konkuk/thip/notification/adapter/out/persistence/NotificationCommandPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/notification/adapter/out/persistence/NotificationCommandPersistenceAdapter.java @@ -1,8 +1,13 @@ package konkuk.thip.notification.adapter.out.persistence; +import konkuk.thip.common.exception.EntityNotFoundException; +import konkuk.thip.common.exception.code.ErrorCode; import konkuk.thip.notification.adapter.out.mapper.NotificationMapper; import konkuk.thip.notification.adapter.out.persistence.repository.NotificationJpaRepository; import konkuk.thip.notification.application.port.out.NotificationCommandPort; +import konkuk.thip.notification.domain.Notification; +import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; +import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -11,6 +16,16 @@ public class NotificationCommandPersistenceAdapter implements NotificationCommandPort { private final NotificationJpaRepository notificationJpaRepository; + private final UserJpaRepository userJpaRepository; + private final NotificationMapper notificationMapper; + @Override + public void save(Notification notification) { + UserJpaEntity userJpaEntity = userJpaRepository.findByUserId(notification.getTargetUserId()).orElseThrow( + () -> new EntityNotFoundException(ErrorCode.USER_NOT_FOUND) + ); + + notificationJpaRepository.save(notificationMapper.toJpaEntity(notification, userJpaEntity)); + } } diff --git a/src/main/java/konkuk/thip/notification/adapter/out/persistence/NotificationQueryPersistenceAdapter.java b/src/main/java/konkuk/thip/notification/adapter/out/persistence/NotificationQueryPersistenceAdapter.java index 1b6163c39..469395bc3 100644 --- a/src/main/java/konkuk/thip/notification/adapter/out/persistence/NotificationQueryPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/notification/adapter/out/persistence/NotificationQueryPersistenceAdapter.java @@ -2,13 +2,12 @@ import konkuk.thip.notification.adapter.out.mapper.NotificationMapper; import konkuk.thip.notification.adapter.out.persistence.repository.NotificationJpaRepository; -import konkuk.thip.notification.application.port.out.NotificationQueryPort; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @Repository @RequiredArgsConstructor -public class NotificationQueryPersistenceAdapter implements NotificationQueryPort { +public class NotificationQueryPersistenceAdapter { private final NotificationJpaRepository jpaRepository; private final NotificationMapper notificationMapper; diff --git a/src/main/java/konkuk/thip/notification/application/port/in/FeedNotificationOrchestrator.java b/src/main/java/konkuk/thip/notification/application/port/in/FeedNotificationOrchestrator.java new file mode 100644 index 000000000..f58a8b845 --- /dev/null +++ b/src/main/java/konkuk/thip/notification/application/port/in/FeedNotificationOrchestrator.java @@ -0,0 +1,22 @@ +package konkuk.thip.notification.application.port.in; + +public interface FeedNotificationOrchestrator { + + /** + * 비즈니스 로직 이후, NotificationOrchestrator 를 호출하여 알림 관련 로직 실행 + * -> DB에 notification data save + 푸시알림 + */ + + // ===== Feed 영역 ===== + void notifyFollowed(Long targetUserId, Long actorUserId, String actorUsername); + + void notifyFeedCommented(Long targetUserId, Long actorUserId, String actorUsername, Long feedId); + + void notifyFeedReplied(Long targetUserId, Long actorUserId, String actorUsername, Long feedId); + + void notifyFolloweeNewPost(Long targetUserId, Long actorUserId, String actorUsername, Long feedId); + + void notifyFeedLiked(Long targetUserId, Long actorUserId, String actorUsername, Long feedId); + + void notifyFeedCommentLiked(Long targetUserId, Long actorUserId, String actorUsername, Long feedId); +} diff --git a/src/main/java/konkuk/thip/notification/application/port/in/RoomNotificationOrchestrator.java b/src/main/java/konkuk/thip/notification/application/port/in/RoomNotificationOrchestrator.java new file mode 100644 index 000000000..436f64e8d --- /dev/null +++ b/src/main/java/konkuk/thip/notification/application/port/in/RoomNotificationOrchestrator.java @@ -0,0 +1,34 @@ +package konkuk.thip.notification.application.port.in; + +public interface RoomNotificationOrchestrator { + + /** + * 비즈니스 로직 이후, NotificationOrchestrator 를 호출하여 알림 관련 로직 실행 + * -> DB에 notification data save + 푸시알림 + */ + + // ===== Room 영역 ===== + void notifyRoomPostCommented(Long targetUserId, Long actorUserId, String actorUsername, + Long roomId, Integer page, Long postId, String postType); + + void notifyRoomVoteStarted(Long targetUserId, Long roomId, String roomTitle, Integer page, Long postId); + + void notifyRoomRecordCreated(Long targetUserId, Long actorUserId, String actorUsername, + Long roomId, String roomTitle, Integer page, Long postId); + + void notifyRoomRecruitClosedEarly(Long targetUserId, Long roomId, String roomTitle); + + void notifyRoomActivityStarted(Long targetUserId, Long roomId, String roomTitle); + + void notifyRoomJoinToHost(Long hostUserId, Long roomId, String roomTitle, + Long actorUserId, String actorUsername); + + void notifyRoomCommentLiked(Long targetUserId, Long actorUserId, String actorUsername, + Long roomId, Integer page, Long postId, String postType); + + void notifyRoomPostLiked(Long targetUserId, Long actorUserId, String actorUsername, + Long roomId, Integer page, Long postId, String postType); + + void notifyRoomPostCommentReplied(Long targetUserId, Long actorUserId, String actorUsername, + Long roomId, Integer page, Long postId, String postType); +} diff --git a/src/main/java/konkuk/thip/notification/application/port/out/NotificationCommandPort.java b/src/main/java/konkuk/thip/notification/application/port/out/NotificationCommandPort.java index e335879cb..e9a122db3 100644 --- a/src/main/java/konkuk/thip/notification/application/port/out/NotificationCommandPort.java +++ b/src/main/java/konkuk/thip/notification/application/port/out/NotificationCommandPort.java @@ -1,6 +1,9 @@ package konkuk.thip.notification.application.port.out; +import konkuk.thip.notification.domain.Notification; + public interface NotificationCommandPort { + void save(Notification notification); } diff --git a/src/main/java/konkuk/thip/notification/application/port/out/NotificationQueryPort.java b/src/main/java/konkuk/thip/notification/application/port/out/NotificationQueryPort.java deleted file mode 100644 index ac161135f..000000000 --- a/src/main/java/konkuk/thip/notification/application/port/out/NotificationQueryPort.java +++ /dev/null @@ -1,5 +0,0 @@ -package konkuk.thip.notification.application.port.out; - -public interface NotificationQueryPort { - -} diff --git a/src/main/java/konkuk/thip/notification/application/service/EventCommandInvoker.java b/src/main/java/konkuk/thip/notification/application/service/EventCommandInvoker.java new file mode 100644 index 000000000..c104814a3 --- /dev/null +++ b/src/main/java/konkuk/thip/notification/application/service/EventCommandInvoker.java @@ -0,0 +1,7 @@ +package konkuk.thip.notification.application.service; + +@FunctionalInterface +public interface EventCommandInvoker { + + void publish(String title, String content); +} diff --git a/src/main/java/konkuk/thip/notification/application/service/FeedNotificationOrchestratorSyncImpl.java b/src/main/java/konkuk/thip/notification/application/service/FeedNotificationOrchestratorSyncImpl.java new file mode 100644 index 000000000..1e3db8503 --- /dev/null +++ b/src/main/java/konkuk/thip/notification/application/service/FeedNotificationOrchestratorSyncImpl.java @@ -0,0 +1,109 @@ +package konkuk.thip.notification.application.service; + +import konkuk.thip.common.annotation.application.HelperService; +import konkuk.thip.message.application.port.out.FeedEventCommandPort; +import konkuk.thip.notification.application.port.in.FeedNotificationOrchestrator; +import konkuk.thip.notification.application.service.template.feed.*; +import lombok.RequiredArgsConstructor; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@HelperService +@RequiredArgsConstructor +public class FeedNotificationOrchestratorSyncImpl implements FeedNotificationOrchestrator { + + /** + * 정책: + * 1) 알림(Notification) DB 저장은 비즈니스 트랜잭션과 동일한 경계 내에서 "동기"로 수행한다. + * -> 비즈니스 로직에서 시작한 상위 트랜잭션에 DB notification 저장이 포함되어야 하므로, Propagation.MANDATORY 강제 + * 2) 푸시 알림은 AFTER_COMMIT 리스너에서 "비동기"로 발송한다. + */ + + private final NotificationSyncExecutor notificationSyncExecutor; + private final FeedEventCommandPort feedEventCommandPort; + + // ========================= Feed 영역 ========================= + @Override + @Transactional(propagation = Propagation.MANDATORY) + public void notifyFollowed(Long targetUserId, Long actorUserId, String actorUsername) { + var args = new FollowedTemplate.Args(actorUsername); + notificationSyncExecutor.execute( + FollowedTemplate.INSTANCE, + args, + targetUserId, + (title, content) -> feedEventCommandPort.publishFollowEvent( + title, content, targetUserId, actorUserId, actorUsername + ) + ); + } + + @Override + @Transactional(propagation = Propagation.MANDATORY) + public void notifyFeedCommented(Long targetUserId, Long actorUserId, String actorUsername, Long feedId) { + var args = new FeedCommentedTemplate.Args(actorUsername); + notificationSyncExecutor.execute( + FeedCommentedTemplate.INSTANCE, + args, + targetUserId, + (title, content) -> feedEventCommandPort.publishFeedCommentedEvent( + title, content, targetUserId, actorUserId, actorUsername, feedId + ) + ); + } + + @Override + @Transactional(propagation = Propagation.MANDATORY) + public void notifyFeedReplied(Long targetUserId, Long actorUserId, String actorUsername, Long feedId) { + var args = new FeedRepliedTemplate.Args(actorUsername); + notificationSyncExecutor.execute( + FeedRepliedTemplate.INSTANCE, + args, + targetUserId, + (title, content) -> feedEventCommandPort.publishFeedRepliedEvent( + title, content, targetUserId, actorUserId, actorUsername, feedId + ) + ); + } + + @Override + @Transactional(propagation = Propagation.MANDATORY) + public void notifyFolloweeNewPost(Long targetUserId, Long actorUserId, String actorUsername, Long feedId) { + var args = new FolloweeNewPostTemplate.Args(actorUsername); + notificationSyncExecutor.execute( + FolloweeNewPostTemplate.INSTANCE, + args, + targetUserId, + (title, content) -> feedEventCommandPort.publishFolloweeNewPostEvent( + title, content, targetUserId, actorUserId, actorUsername, feedId + ) + ); + } + + @Override + @Transactional(propagation = Propagation.MANDATORY) + public void notifyFeedLiked(Long targetUserId, Long actorUserId, String actorUsername, Long feedId) { + var args = new FeedLikedTemplate.Args(actorUsername); + notificationSyncExecutor.execute( + FeedLikedTemplate.INSTANCE, + args, + targetUserId, + (title, content) -> feedEventCommandPort.publishFeedLikedEvent( + title, content, targetUserId, actorUserId, actorUsername, feedId + ) + ); + } + + @Override + @Transactional(propagation = Propagation.MANDATORY) + public void notifyFeedCommentLiked(Long targetUserId, Long actorUserId, String actorUsername, Long feedId) { + var args = new FeedCommentLikedTemplate.Args(actorUsername); + notificationSyncExecutor.execute( + FeedCommentLikedTemplate.INSTANCE, + args, + targetUserId, + (title, content) -> feedEventCommandPort.publishFeedCommentLikedEvent( + title, content, targetUserId, actorUserId, actorUsername, feedId + ) + ); + } +} diff --git a/src/main/java/konkuk/thip/notification/application/service/NotificationService.java b/src/main/java/konkuk/thip/notification/application/service/NotificationService.java deleted file mode 100644 index 586627fbf..000000000 --- a/src/main/java/konkuk/thip/notification/application/service/NotificationService.java +++ /dev/null @@ -1,10 +0,0 @@ -package konkuk.thip.notification.application.service; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class NotificationService { - -} diff --git a/src/main/java/konkuk/thip/notification/application/service/NotificationSyncExecutor.java b/src/main/java/konkuk/thip/notification/application/service/NotificationSyncExecutor.java new file mode 100644 index 000000000..bff1959b9 --- /dev/null +++ b/src/main/java/konkuk/thip/notification/application/service/NotificationSyncExecutor.java @@ -0,0 +1,44 @@ +package konkuk.thip.notification.application.service; + +import konkuk.thip.common.annotation.application.HelperService; +import konkuk.thip.notification.application.port.out.NotificationCommandPort; +import konkuk.thip.notification.application.service.template.NotificationTemplate; +import konkuk.thip.notification.domain.Notification; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@HelperService +@RequiredArgsConstructor +@Slf4j +public class NotificationSyncExecutor { + + private final NotificationCommandPort notificationCommandPort; + + public void execute( + NotificationTemplate template, + T args, + Long targetUserId, + EventCommandInvoker invoker + ) { + String title = template.title(args); + String content = template.content(args); + + // 1. DB 저장 + saveNotification(title, content, targetUserId); + + // 2. 이벤트 퍼블리시 + try { + invoker.publish(title, content); + } catch (Exception e) { + // 이벤트 발행 실패 시, DB에 저장된 알림을 롤백하지는 않음 + // -> 알림 저장은 비즈니스 트랜잭션과 동일한 경계 내에서 수행되므로, 알림 저장은 유지 + // -> 푸시 알림 이벤트 발행이 실패한 경우, 일단 로깅만 추가 + log.error("푸시 알림 이벤트 퍼블리시 실패 targetUserId = {}, title = {}", targetUserId, title, e); + } + } + + private void saveNotification(String title, String content, Long targetUserId) { + Notification notification = Notification.withoutId(title, content, targetUserId); + notificationCommandPort.save(notification); + } +} diff --git a/src/main/java/konkuk/thip/notification/application/service/RoomNotificationOrchestratorSyncImpl.java b/src/main/java/konkuk/thip/notification/application/service/RoomNotificationOrchestratorSyncImpl.java new file mode 100644 index 000000000..9162c65d8 --- /dev/null +++ b/src/main/java/konkuk/thip/notification/application/service/RoomNotificationOrchestratorSyncImpl.java @@ -0,0 +1,156 @@ +package konkuk.thip.notification.application.service; + +import konkuk.thip.common.annotation.application.HelperService; +import konkuk.thip.message.application.port.out.RoomEventCommandPort; +import konkuk.thip.notification.application.port.in.RoomNotificationOrchestrator; +import konkuk.thip.notification.application.service.template.room.*; +import lombok.RequiredArgsConstructor; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@HelperService +@RequiredArgsConstructor +public class RoomNotificationOrchestratorSyncImpl implements RoomNotificationOrchestrator { + + /** + * 정책: + * 1) 알림(Notification) DB 저장은 비즈니스 트랜잭션과 동일한 경계 내에서 "동기"로 수행한다. + * -> 비즈니스 로직에서 시작한 상위 트랜잭션에 DB notification 저장이 포함되어야 하므로, Propagation.MANDATORY 강제 + * 2) 푸시 알림은 AFTER_COMMIT 리스너에서 "비동기"로 발송한다. + */ + + private final NotificationSyncExecutor notificationSyncExecutor; + private final RoomEventCommandPort roomEventCommandPort; + + // ========================= Room 영역 ========================= + @Override + @Transactional(propagation = Propagation.MANDATORY) + public void notifyRoomPostCommented(Long targetUserId, Long actorUserId, String actorUsername, + Long roomId, Integer page, Long postId, String postType) { + var args = new RoomPostCommentedTemplate.Args(actorUsername); + notificationSyncExecutor.execute( + RoomPostCommentedTemplate.INSTANCE, + args, + targetUserId, + (title, content) -> roomEventCommandPort.publishRoomPostCommentedEvent( + title, content, targetUserId, actorUserId, actorUsername, roomId, page, postId, postType + ) + ); + } + + @Override + @Transactional(propagation = Propagation.MANDATORY) + public void notifyRoomVoteStarted(Long targetUserId, Long roomId, String roomTitle, Integer page, Long postId) { + var args = new RoomVoteStartedTemplate.Args(roomTitle); + notificationSyncExecutor.execute( + RoomVoteStartedTemplate.INSTANCE, + args, + targetUserId, + (title, content) -> roomEventCommandPort.publishRoomVoteStartedEvent( + title, content, targetUserId, roomId, roomTitle, page, postId + ) + ); + } + + @Override + @Transactional(propagation = Propagation.MANDATORY) + public void notifyRoomRecordCreated(Long targetUserId, Long actorUserId, String actorUsername, + Long roomId, String roomTitle, Integer page, Long postId) { + var args = new RoomRecordCreatedTemplate.Args(roomTitle, actorUsername); + notificationSyncExecutor.execute( + RoomRecordCreatedTemplate.INSTANCE, + args, + targetUserId, + (title, content) -> roomEventCommandPort.publishRoomRecordCreatedEvent( + title, content, targetUserId, actorUserId, actorUsername, roomId, roomTitle, page, postId + ) + ); + } + + @Override + @Transactional(propagation = Propagation.MANDATORY) + public void notifyRoomRecruitClosedEarly(Long targetUserId, Long roomId, String roomTitle) { + var args = new RoomRecruitClosedEarlyTemplate.Args(roomTitle); + notificationSyncExecutor.execute( + RoomRecruitClosedEarlyTemplate.INSTANCE, + args, + targetUserId, + (title, content) -> roomEventCommandPort.publishRoomRecruitClosedEarlyEvent( + title, content, targetUserId, roomId, roomTitle + ) + ); + } + + @Override + @Transactional(propagation = Propagation.MANDATORY) + public void notifyRoomActivityStarted(Long targetUserId, Long roomId, String roomTitle) { + var args = new RoomActivityStartedTemplate.Args(roomTitle); + notificationSyncExecutor.execute( + RoomActivityStartedTemplate.INSTANCE, + args, + targetUserId, + (title, content) -> roomEventCommandPort.publishRoomActivityStartedEvent( + title, content, targetUserId, roomId, roomTitle + ) + ); + } + + @Override + @Transactional(propagation = Propagation.MANDATORY) + public void notifyRoomJoinToHost(Long hostUserId, Long roomId, String roomTitle, Long actorUserId, String actorUsername) { + var args = new RoomJoinToHostTemplate.Args(roomTitle, actorUsername); + notificationSyncExecutor.execute( + RoomJoinToHostTemplate.INSTANCE, + args, + hostUserId, + (title, content) -> roomEventCommandPort.publishRoomJoinEventToHost( + title, content, hostUserId, roomId, roomTitle, actorUserId, actorUsername + ) + ); + } + + @Override + @Transactional(propagation = Propagation.MANDATORY) + public void notifyRoomCommentLiked(Long targetUserId, Long actorUserId, String actorUsername, + Long roomId, Integer page, Long postId, String postType) { + var args = new RoomCommentLikedTemplate.Args(actorUsername); + notificationSyncExecutor.execute( + RoomCommentLikedTemplate.INSTANCE, + args, + targetUserId, + (title, content) -> roomEventCommandPort.publishRoomCommentLikedEvent( + title, content, targetUserId, actorUserId, actorUsername, roomId, page, postId, postType + ) + ); + } + + @Override + @Transactional(propagation = Propagation.MANDATORY) + public void notifyRoomPostLiked(Long targetUserId, Long actorUserId, String actorUsername, + Long roomId, Integer page, Long postId, String postType) { + var args = new RoomPostLikedTemplate.Args(actorUsername); + notificationSyncExecutor.execute( + RoomPostLikedTemplate.INSTANCE, + args, + targetUserId, + (title, content) -> roomEventCommandPort.publishRoomPostLikedEvent( + title, content, targetUserId, actorUserId, actorUsername, roomId, page, postId, postType + ) + ); + } + + @Override + @Transactional(propagation = Propagation.MANDATORY) + public void notifyRoomPostCommentReplied(Long targetUserId, Long actorUserId, String actorUsername, + Long roomId, Integer page, Long postId, String postType) { + var args = new RoomPostCommentRepliedTemplate.Args(actorUsername); + notificationSyncExecutor.execute( + RoomPostCommentRepliedTemplate.INSTANCE, + args, + targetUserId, + (title, content) -> roomEventCommandPort.publishRoomPostCommentRepliedEvent( + title, content, targetUserId, actorUserId, actorUsername, roomId, page, postId, postType + ) + ); + } +} diff --git a/src/main/java/konkuk/thip/notification/application/service/template/NotificationTemplate.java b/src/main/java/konkuk/thip/notification/application/service/template/NotificationTemplate.java new file mode 100644 index 000000000..8d8e22f95 --- /dev/null +++ b/src/main/java/konkuk/thip/notification/application/service/template/NotificationTemplate.java @@ -0,0 +1,8 @@ +package konkuk.thip.notification.application.service.template; + +public interface NotificationTemplate { + + String title(T args); + + String content(T args); +} diff --git a/src/main/java/konkuk/thip/notification/application/service/template/feed/FeedCommentLikedTemplate.java b/src/main/java/konkuk/thip/notification/application/service/template/feed/FeedCommentLikedTemplate.java new file mode 100644 index 000000000..4c2358115 --- /dev/null +++ b/src/main/java/konkuk/thip/notification/application/service/template/feed/FeedCommentLikedTemplate.java @@ -0,0 +1,20 @@ +package konkuk.thip.notification.application.service.template.feed; + +import konkuk.thip.notification.application.service.template.NotificationTemplate; +import konkuk.thip.notification.domain.value.NotificationCategory; + +public enum FeedCommentLikedTemplate implements NotificationTemplate { + INSTANCE; + + @Override + public String title(Args args) { + return NotificationCategory.FEED.prefixedTitle("좋아요 알림"); + } + + @Override + public String content(Args args) { + return "@" + args.actorUsername() + " 님이 내 댓글에 좋아요를 눌렀어요!"; + } + + public record Args(String actorUsername) {} +} diff --git a/src/main/java/konkuk/thip/notification/application/service/template/feed/FeedCommentedTemplate.java b/src/main/java/konkuk/thip/notification/application/service/template/feed/FeedCommentedTemplate.java new file mode 100644 index 000000000..e58391abe --- /dev/null +++ b/src/main/java/konkuk/thip/notification/application/service/template/feed/FeedCommentedTemplate.java @@ -0,0 +1,20 @@ +package konkuk.thip.notification.application.service.template.feed; + +import konkuk.thip.notification.application.service.template.NotificationTemplate; +import konkuk.thip.notification.domain.value.NotificationCategory; + +public enum FeedCommentedTemplate implements NotificationTemplate { + INSTANCE; + + @Override + public String title(Args args) { + return NotificationCategory.FEED.prefixedTitle("새로운 댓글이 달렸어요"); + } + + @Override + public String content(Args args) { + return "@" + args.actorUsername() + " 님이 내 글에 댓글을 달았어요!"; + } + + public record Args(String actorUsername) {} +} diff --git a/src/main/java/konkuk/thip/notification/application/service/template/feed/FeedLikedTemplate.java b/src/main/java/konkuk/thip/notification/application/service/template/feed/FeedLikedTemplate.java new file mode 100644 index 000000000..15b608c3a --- /dev/null +++ b/src/main/java/konkuk/thip/notification/application/service/template/feed/FeedLikedTemplate.java @@ -0,0 +1,20 @@ +package konkuk.thip.notification.application.service.template.feed; + +import konkuk.thip.notification.application.service.template.NotificationTemplate; +import konkuk.thip.notification.domain.value.NotificationCategory; + +public enum FeedLikedTemplate implements NotificationTemplate { + INSTANCE; + + @Override + public String title(Args args) { + return NotificationCategory.FEED.prefixedTitle("내 글을 좋아합니다"); + } + + @Override + public String content(Args args) { + return "@" + args.actorUsername() + " 님이 내 글에 좋아요를 눌렀어요!"; + } + + public record Args(String actorUsername) {} +} diff --git a/src/main/java/konkuk/thip/notification/application/service/template/feed/FeedRepliedTemplate.java b/src/main/java/konkuk/thip/notification/application/service/template/feed/FeedRepliedTemplate.java new file mode 100644 index 000000000..37a8de487 --- /dev/null +++ b/src/main/java/konkuk/thip/notification/application/service/template/feed/FeedRepliedTemplate.java @@ -0,0 +1,20 @@ +package konkuk.thip.notification.application.service.template.feed; + +import konkuk.thip.notification.application.service.template.NotificationTemplate; +import konkuk.thip.notification.domain.value.NotificationCategory; + +public enum FeedRepliedTemplate implements NotificationTemplate { + INSTANCE; + + @Override + public String title(Args args) { + return NotificationCategory.FEED.prefixedTitle("새로운 답글이 달렸어요"); + } + + @Override + public String content(Args args) { + return "@" + args.actorUsername() + " 님이 내 댓글에 답글을 달았어요!"; + } + + public record Args(String actorUsername) {} +} diff --git a/src/main/java/konkuk/thip/notification/application/service/template/feed/FollowedTemplate.java b/src/main/java/konkuk/thip/notification/application/service/template/feed/FollowedTemplate.java new file mode 100644 index 000000000..f9e8003f4 --- /dev/null +++ b/src/main/java/konkuk/thip/notification/application/service/template/feed/FollowedTemplate.java @@ -0,0 +1,22 @@ +package konkuk.thip.notification.application.service.template.feed; + +import konkuk.thip.notification.application.service.template.NotificationTemplate; +import konkuk.thip.notification.domain.value.NotificationCategory; +import lombok.Getter; + +@Getter +public enum FollowedTemplate implements NotificationTemplate { + INSTANCE; + + @Override + public String title(Args args) { + return NotificationCategory.FEED.prefixedTitle("팔로워 알림"); + } + + @Override + public String content(Args args) { + return "@" + args.actorUsername() + " 님이 나를 띱했어요!"; + } + + public record Args(String actorUsername) {} +} diff --git a/src/main/java/konkuk/thip/notification/application/service/template/feed/FolloweeNewPostTemplate.java b/src/main/java/konkuk/thip/notification/application/service/template/feed/FolloweeNewPostTemplate.java new file mode 100644 index 000000000..af18247fa --- /dev/null +++ b/src/main/java/konkuk/thip/notification/application/service/template/feed/FolloweeNewPostTemplate.java @@ -0,0 +1,20 @@ +package konkuk.thip.notification.application.service.template.feed; + +import konkuk.thip.notification.application.service.template.NotificationTemplate; +import konkuk.thip.notification.domain.value.NotificationCategory; + +public enum FolloweeNewPostTemplate implements NotificationTemplate { + INSTANCE; + + @Override + public String title(Args args) { + return NotificationCategory.FEED.prefixedTitle("새 글 알림"); + } + + @Override + public String content(Args args) { + return "@" + args.actorUsername() + " 님이 새로운 글을 작성했어요!"; + } + + public record Args(String actorUsername) {} +} diff --git a/src/main/java/konkuk/thip/notification/application/service/template/room/RoomActivityStartedTemplate.java b/src/main/java/konkuk/thip/notification/application/service/template/room/RoomActivityStartedTemplate.java new file mode 100644 index 000000000..4ea611682 --- /dev/null +++ b/src/main/java/konkuk/thip/notification/application/service/template/room/RoomActivityStartedTemplate.java @@ -0,0 +1,20 @@ +package konkuk.thip.notification.application.service.template.room; + +import konkuk.thip.notification.application.service.template.NotificationTemplate; +import konkuk.thip.notification.domain.value.NotificationCategory; + +public enum RoomActivityStartedTemplate implements NotificationTemplate { + INSTANCE; + + @Override + public String title(Args args) { + return NotificationCategory.ROOM.prefixedTitle(args.roomTitle); + } + + @Override + public String content(Args args) { + return "모임방 활동이 시작되었어요. 모임방에서 독서 기록을 시작해보세요!"; + } + + public record Args(String roomTitle) {} +} diff --git a/src/main/java/konkuk/thip/notification/application/service/template/room/RoomCommentLikedTemplate.java b/src/main/java/konkuk/thip/notification/application/service/template/room/RoomCommentLikedTemplate.java new file mode 100644 index 000000000..09e09c86c --- /dev/null +++ b/src/main/java/konkuk/thip/notification/application/service/template/room/RoomCommentLikedTemplate.java @@ -0,0 +1,20 @@ +package konkuk.thip.notification.application.service.template.room; + +import konkuk.thip.notification.application.service.template.NotificationTemplate; +import konkuk.thip.notification.domain.value.NotificationCategory; + +public enum RoomCommentLikedTemplate implements NotificationTemplate { + INSTANCE; + + @Override + public String title(Args args) { + return NotificationCategory.ROOM.prefixedTitle("내 댓글을 좋아합니다"); + } + + @Override + public String content(Args args) { + return "@" + args.actorUsername() + " 님이 내 댓글에 좋아요를 눌렀어요!"; + } + + public record Args(String actorUsername) {} +} diff --git a/src/main/java/konkuk/thip/notification/application/service/template/room/RoomJoinToHostTemplate.java b/src/main/java/konkuk/thip/notification/application/service/template/room/RoomJoinToHostTemplate.java new file mode 100644 index 000000000..5ab0096b1 --- /dev/null +++ b/src/main/java/konkuk/thip/notification/application/service/template/room/RoomJoinToHostTemplate.java @@ -0,0 +1,20 @@ +package konkuk.thip.notification.application.service.template.room; + +import konkuk.thip.notification.application.service.template.NotificationTemplate; +import konkuk.thip.notification.domain.value.NotificationCategory; + +public enum RoomJoinToHostTemplate implements NotificationTemplate { + INSTANCE; + + @Override + public String title(Args args) { + return NotificationCategory.ROOM.prefixedTitle(args.roomTitle); + } + + @Override + public String content(Args args) { + return "@" + args.actorUsername() + " 님이 모임에 참여했어요!"; + } + + public record Args(String roomTitle, String actorUsername) {} +} diff --git a/src/main/java/konkuk/thip/notification/application/service/template/room/RoomPostCommentRepliedTemplate.java b/src/main/java/konkuk/thip/notification/application/service/template/room/RoomPostCommentRepliedTemplate.java new file mode 100644 index 000000000..860c30c9c --- /dev/null +++ b/src/main/java/konkuk/thip/notification/application/service/template/room/RoomPostCommentRepliedTemplate.java @@ -0,0 +1,20 @@ +package konkuk.thip.notification.application.service.template.room; + +import konkuk.thip.notification.application.service.template.NotificationTemplate; +import konkuk.thip.notification.domain.value.NotificationCategory; + +public enum RoomPostCommentRepliedTemplate implements NotificationTemplate { + INSTANCE; + + @Override + public String title(Args args) { + return NotificationCategory.ROOM.prefixedTitle("새로운 답글이 달렸어요"); + } + + @Override + public String content(Args args) { + return "@" + args.actorUsername() + " 님이 내 댓글에 답글을 달았어요!"; + } + + public record Args(String actorUsername) {} +} diff --git a/src/main/java/konkuk/thip/notification/application/service/template/room/RoomPostCommentedTemplate.java b/src/main/java/konkuk/thip/notification/application/service/template/room/RoomPostCommentedTemplate.java new file mode 100644 index 000000000..c10510b20 --- /dev/null +++ b/src/main/java/konkuk/thip/notification/application/service/template/room/RoomPostCommentedTemplate.java @@ -0,0 +1,20 @@ +package konkuk.thip.notification.application.service.template.room; + +import konkuk.thip.notification.application.service.template.NotificationTemplate; +import konkuk.thip.notification.domain.value.NotificationCategory; + +public enum RoomPostCommentedTemplate implements NotificationTemplate { + INSTANCE; + + @Override + public String title(Args args) { + return NotificationCategory.ROOM.prefixedTitle("새로운 댓글이 달렸어요"); + } + + @Override + public String content(Args args) { + return "@" + args.actorUsername() + " 님이 내 독서기록에 댓글을 달았어요!"; + } + + public record Args(String actorUsername) {} +} diff --git a/src/main/java/konkuk/thip/notification/application/service/template/room/RoomPostLikedTemplate.java b/src/main/java/konkuk/thip/notification/application/service/template/room/RoomPostLikedTemplate.java new file mode 100644 index 000000000..37f3133b7 --- /dev/null +++ b/src/main/java/konkuk/thip/notification/application/service/template/room/RoomPostLikedTemplate.java @@ -0,0 +1,20 @@ +package konkuk.thip.notification.application.service.template.room; + +import konkuk.thip.notification.application.service.template.NotificationTemplate; +import konkuk.thip.notification.domain.value.NotificationCategory; + +public enum RoomPostLikedTemplate implements NotificationTemplate { + INSTANCE; + + @Override + public String title(Args args) { + return NotificationCategory.ROOM.prefixedTitle("좋아요 알림"); + } + + @Override + public String content(Args args) { + return "@" + args.actorUsername() + " 님이 내 독서기록에 좋아요를 눌렀어요!"; + } + + public record Args(String actorUsername) {} +} diff --git a/src/main/java/konkuk/thip/notification/application/service/template/room/RoomRecordCreatedTemplate.java b/src/main/java/konkuk/thip/notification/application/service/template/room/RoomRecordCreatedTemplate.java new file mode 100644 index 000000000..a1180550e --- /dev/null +++ b/src/main/java/konkuk/thip/notification/application/service/template/room/RoomRecordCreatedTemplate.java @@ -0,0 +1,20 @@ +package konkuk.thip.notification.application.service.template.room; + +import konkuk.thip.notification.application.service.template.NotificationTemplate; +import konkuk.thip.notification.domain.value.NotificationCategory; + +public enum RoomRecordCreatedTemplate implements NotificationTemplate { + INSTANCE; + + @Override + public String title(Args args) { + return NotificationCategory.ROOM.prefixedTitle(args.roomTitle); + } + + @Override + public String content(Args args) { + return "@" + args.actorUsername() + " 님이 새로운 독서 기록을 작성했어요!"; + } + + public record Args(String roomTitle, String actorUsername) {} +} diff --git a/src/main/java/konkuk/thip/notification/application/service/template/room/RoomRecruitClosedEarlyTemplate.java b/src/main/java/konkuk/thip/notification/application/service/template/room/RoomRecruitClosedEarlyTemplate.java new file mode 100644 index 000000000..600f3c778 --- /dev/null +++ b/src/main/java/konkuk/thip/notification/application/service/template/room/RoomRecruitClosedEarlyTemplate.java @@ -0,0 +1,20 @@ +package konkuk.thip.notification.application.service.template.room; + +import konkuk.thip.notification.application.service.template.NotificationTemplate; +import konkuk.thip.notification.domain.value.NotificationCategory; + +public enum RoomRecruitClosedEarlyTemplate implements NotificationTemplate { + INSTANCE; + + @Override + public String title(Args args) { + return NotificationCategory.ROOM.prefixedTitle(args.roomTitle); + } + + @Override + public String content(Args args) { + return "모임방 활동이 시작되었어요. 모임방에서 독서 기록을 시작해보세요!"; + } + + public record Args(String roomTitle) {} +} diff --git a/src/main/java/konkuk/thip/notification/application/service/template/room/RoomVoteStartedTemplate.java b/src/main/java/konkuk/thip/notification/application/service/template/room/RoomVoteStartedTemplate.java new file mode 100644 index 000000000..2384361eb --- /dev/null +++ b/src/main/java/konkuk/thip/notification/application/service/template/room/RoomVoteStartedTemplate.java @@ -0,0 +1,20 @@ +package konkuk.thip.notification.application.service.template.room; + +import konkuk.thip.notification.application.service.template.NotificationTemplate; +import konkuk.thip.notification.domain.value.NotificationCategory; + +public enum RoomVoteStartedTemplate implements NotificationTemplate { + INSTANCE; + + @Override + public String title(Args args) { + return NotificationCategory.ROOM.prefixedTitle(args.roomTitle); + } + + @Override + public String content(Args args) { + return "새로운 투표가 시작되었어요!"; + } + + public record Args(String roomTitle) {} +} diff --git a/src/main/java/konkuk/thip/notification/domain/Notification.java b/src/main/java/konkuk/thip/notification/domain/Notification.java index 5438446f0..6c35bd815 100644 --- a/src/main/java/konkuk/thip/notification/domain/Notification.java +++ b/src/main/java/konkuk/thip/notification/domain/Notification.java @@ -17,4 +17,13 @@ public class Notification extends BaseDomainEntity { private boolean isChecked; private Long targetUserId; + + public static Notification withoutId (String title, String content, Long targetUserId) { + return Notification.builder() + .title(title) + .content(content) + .isChecked(false) + .targetUserId(targetUserId) + .build(); + } } diff --git a/src/main/java/konkuk/thip/message/domain/NotificationCategory.java b/src/main/java/konkuk/thip/notification/domain/value/NotificationCategory.java similarity index 86% rename from src/main/java/konkuk/thip/message/domain/NotificationCategory.java rename to src/main/java/konkuk/thip/notification/domain/value/NotificationCategory.java index 3280395a2..54408bc75 100644 --- a/src/main/java/konkuk/thip/message/domain/NotificationCategory.java +++ b/src/main/java/konkuk/thip/notification/domain/value/NotificationCategory.java @@ -1,4 +1,4 @@ -package konkuk.thip.message.domain; +package konkuk.thip.notification.domain.value; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/konkuk/thip/post/application/service/PostLikeService.java b/src/main/java/konkuk/thip/post/application/service/PostLikeService.java index 4ab27d818..4d0004ab3 100644 --- a/src/main/java/konkuk/thip/post/application/service/PostLikeService.java +++ b/src/main/java/konkuk/thip/post/application/service/PostLikeService.java @@ -1,7 +1,7 @@ package konkuk.thip.post.application.service; -import konkuk.thip.message.application.port.out.FeedEventCommandPort; -import konkuk.thip.message.application.port.out.RoomEventCommandPort; +import konkuk.thip.notification.application.port.in.FeedNotificationOrchestrator; +import konkuk.thip.notification.application.port.in.RoomNotificationOrchestrator; import konkuk.thip.post.application.port.out.dto.PostQueryDto; import konkuk.thip.post.application.service.handler.PostHandler; import konkuk.thip.post.domain.CountUpdatable; @@ -31,8 +31,8 @@ public class PostLikeService implements PostLikeUseCase { private final PostCountService postCountService; private final PostLikeAuthorizationValidator postLikeAuthorizationValidator; - private final FeedEventCommandPort feedEventCommandPort; - private final RoomEventCommandPort roomEventCommandPort; + private final FeedNotificationOrchestrator feedNotificationOrchestrator; + private final RoomNotificationOrchestrator roomNotificationOrchestrator; @Override @Transactional @@ -74,10 +74,10 @@ private void sendNotifications(PostIsLikeCommand command) { User actorUser = userCommandPort.findById(command.userId()); // 좋아요 푸쉬알림 전송 if (command.postType() == PostType.FEED) { - feedEventCommandPort.publishFeedLikedEvent(postQueryDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.postId()); + feedNotificationOrchestrator.notifyFeedLiked(postQueryDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.postId()); } if (command.postType() == PostType.RECORD || command.postType() == PostType.VOTE) { - roomEventCommandPort.publishRoomPostLikedEvent(postQueryDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.roomId(), postQueryDto.page(), postQueryDto.postId(), postQueryDto.postType()); + roomNotificationOrchestrator.notifyRoomPostLiked(postQueryDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.roomId(), postQueryDto.page(), postQueryDto.postId(), postQueryDto.postType()); } } } diff --git a/src/main/java/konkuk/thip/room/application/service/RoomJoinService.java b/src/main/java/konkuk/thip/room/application/service/RoomJoinService.java index 3f4167821..409887131 100644 --- a/src/main/java/konkuk/thip/room/application/service/RoomJoinService.java +++ b/src/main/java/konkuk/thip/room/application/service/RoomJoinService.java @@ -2,7 +2,7 @@ import konkuk.thip.common.exception.BusinessException; import konkuk.thip.common.exception.code.ErrorCode; -import konkuk.thip.message.application.port.out.RoomEventCommandPort; +import konkuk.thip.notification.application.port.in.RoomNotificationOrchestrator; import konkuk.thip.room.application.port.in.RoomJoinUseCase; import konkuk.thip.room.application.port.in.dto.RoomJoinCommand; import konkuk.thip.room.application.port.in.dto.RoomJoinResult; @@ -27,7 +27,7 @@ public class RoomJoinService implements RoomJoinUseCase { private final RoomParticipantCommandPort roomParticipantCommandPort; private final UserCommandPort userCommandPort; - private final RoomEventCommandPort roomEventCommandPort; + private final RoomNotificationOrchestrator roomNotificationOrchestrator; @Override @Transactional @@ -45,7 +45,7 @@ public RoomJoinResult changeJoinState(RoomJoinCommand roomJoinCommand) { // 방 참여 상태 변경 요청에 따라 분기 처리 switch (type) { case JOIN -> handleJoin(roomJoinCommand, roomParticipantOptional, room); - case CANCEL -> handleCancel(roomJoinCommand, roomParticipantOptional, roomParticipantOptional, room); + case CANCEL -> handleCancel(roomJoinCommand, roomParticipantOptional, room); } // 방의 상태 업데이트 @@ -62,10 +62,10 @@ public RoomJoinResult changeJoinState(RoomJoinCommand roomJoinCommand) { private void sendNotifications(RoomJoinCommand roomJoinCommand, Room room) { RoomParticipant targetUser = roomParticipantCommandPort.findHostByRoomId(room.getId()); User actorUser = userCommandPort.findById(roomJoinCommand.userId()); - roomEventCommandPort.publishRoomJoinEventToHost(targetUser.getUserId(), room.getId(), room.getTitle(), actorUser.getId(), actorUser.getNickname()); + roomNotificationOrchestrator.notifyRoomJoinToHost(targetUser.getUserId(), room.getId(), room.getTitle(), actorUser.getId(), actorUser.getNickname()); } - private void handleCancel(RoomJoinCommand roomJoinCommand, Optional participantOptional, Optional roomParticipantOptional, Room room) { + private void handleCancel(RoomJoinCommand roomJoinCommand, Optional participantOptional, Room room) { // 참여하지 않은 상태 RoomParticipant participant = participantOptional.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_PARTICIPATED_CANNOT_CANCEL) diff --git a/src/main/java/konkuk/thip/room/application/service/RoomRecruitCloseService.java b/src/main/java/konkuk/thip/room/application/service/RoomRecruitCloseService.java index 0b8140a9a..6073a0cf7 100644 --- a/src/main/java/konkuk/thip/room/application/service/RoomRecruitCloseService.java +++ b/src/main/java/konkuk/thip/room/application/service/RoomRecruitCloseService.java @@ -2,7 +2,7 @@ import konkuk.thip.common.exception.BusinessException; import konkuk.thip.common.exception.code.ErrorCode; -import konkuk.thip.message.application.port.out.RoomEventCommandPort; +import konkuk.thip.notification.application.port.in.RoomNotificationOrchestrator; import konkuk.thip.room.application.port.in.RoomRecruitCloseUseCase; import konkuk.thip.room.application.port.out.RoomCommandPort; import konkuk.thip.room.application.port.out.RoomParticipantCommandPort; @@ -21,7 +21,7 @@ public class RoomRecruitCloseService implements RoomRecruitCloseUseCase { private final RoomParticipantCommandPort roomParticipantCommandPort; private final RoomCommandPort roomCommandPort; - private final RoomEventCommandPort roomEventCommandPort; + private final RoomNotificationOrchestrator roomNotificationOrchestrator; @Override @Transactional @@ -51,7 +51,7 @@ private void sendNotifications(Long roomId, Room room) { List actorUsers = roomParticipantCommandPort.findAllByRoomId(roomId); for (RoomParticipant participant : actorUsers) { if(participant.isHost()) continue; // 호스트는 제외 - roomEventCommandPort.publishRoomRecruitClosedEarlyEvent(participant.getUserId(), roomId, room.getTitle()); + roomNotificationOrchestrator.notifyRoomRecruitClosedEarly(participant.getUserId(), roomId, room.getTitle()); } } diff --git a/src/main/java/konkuk/thip/room/application/service/RoomStateChangeService.java b/src/main/java/konkuk/thip/room/application/service/RoomStateChangeService.java index 7bb9604ff..b371df2ea 100644 --- a/src/main/java/konkuk/thip/room/application/service/RoomStateChangeService.java +++ b/src/main/java/konkuk/thip/room/application/service/RoomStateChangeService.java @@ -1,6 +1,6 @@ package konkuk.thip.room.application.service; -import konkuk.thip.message.application.port.out.RoomEventCommandPort; +import konkuk.thip.notification.application.port.in.RoomNotificationOrchestrator; import konkuk.thip.room.application.port.in.RoomStateChangeUseCase; import konkuk.thip.room.application.port.out.RoomCommandPort; import konkuk.thip.room.application.port.out.RoomParticipantCommandPort; @@ -22,7 +22,7 @@ public class RoomStateChangeService implements RoomStateChangeUseCase { private final RoomCommandPort roomCommandPort; private final RoomParticipantCommandPort roomParticipantCommandPort; - private final RoomEventCommandPort roomEventCommandPort; + private final RoomNotificationOrchestrator roomNotificationOrchestrator; /** * end_date < 오늘 => EXPIRED @@ -54,7 +54,7 @@ private void sendNotifications() { for (Room room : targetRooms) { List targetUsers = roomParticipantCommandPort.findAllByRoomId(room.getId()); for (RoomParticipant participant : targetUsers) { - roomEventCommandPort.publishRoomActivityStartedEvent(participant.getUserId(), room.getId(), room.getTitle()); + roomNotificationOrchestrator.notifyRoomActivityStarted(participant.getUserId(), room.getId(), room.getTitle()); } } } diff --git a/src/main/java/konkuk/thip/roompost/application/service/RecordCreateService.java b/src/main/java/konkuk/thip/roompost/application/service/RecordCreateService.java index 7e075f9cf..6dc6d8cb8 100644 --- a/src/main/java/konkuk/thip/roompost/application/service/RecordCreateService.java +++ b/src/main/java/konkuk/thip/roompost/application/service/RecordCreateService.java @@ -3,7 +3,7 @@ import konkuk.thip.book.application.port.out.BookCommandPort; import konkuk.thip.book.domain.Book; import konkuk.thip.common.exception.BusinessException; -import konkuk.thip.message.application.port.out.RoomEventCommandPort; +import konkuk.thip.notification.application.port.in.RoomNotificationOrchestrator; import konkuk.thip.roompost.application.port.in.RecordCreateUseCase; import konkuk.thip.roompost.application.port.in.dto.record.RecordCreateCommand; import konkuk.thip.roompost.application.port.in.dto.record.RecordCreateResult; @@ -38,7 +38,7 @@ public class RecordCreateService implements RecordCreateUseCase { private final RoomParticipantValidator roomParticipantValidator; private final RoomProgressManager roomProgressManager; - private final RoomEventCommandPort roomEventCommandPort; + private final RoomNotificationOrchestrator roomNotificationOrchestrator; @Override @Transactional @@ -82,7 +82,7 @@ private void sendNotifications(RecordCreateCommand command, Room room, Record re List targetUsers = roomParticipantCommandPort.findAllByRoomId(command.roomId()); for (RoomParticipant targetUser : targetUsers) { if (targetUser.getUserId().equals(command.userId())) continue; // 본인 제외 - roomEventCommandPort.publishRoomRecordCreatedEvent(targetUser.getUserId(), actorUser.getId(), actorUser.getNickname(), room.getId(), room.getTitle(), record.getPage(), newRecordId); + roomNotificationOrchestrator.notifyRoomRecordCreated(targetUser.getUserId(), actorUser.getId(), actorUser.getNickname(), room.getId(), room.getTitle(), record.getPage(), newRecordId); } } diff --git a/src/main/java/konkuk/thip/roompost/application/service/VoteCreateService.java b/src/main/java/konkuk/thip/roompost/application/service/VoteCreateService.java index ce7cf3788..a91888206 100644 --- a/src/main/java/konkuk/thip/roompost/application/service/VoteCreateService.java +++ b/src/main/java/konkuk/thip/roompost/application/service/VoteCreateService.java @@ -2,7 +2,7 @@ import konkuk.thip.book.application.port.out.BookCommandPort; import konkuk.thip.book.domain.Book; -import konkuk.thip.message.application.port.out.RoomEventCommandPort; +import konkuk.thip.notification.application.port.in.RoomNotificationOrchestrator; import konkuk.thip.room.application.port.out.RoomCommandPort; import konkuk.thip.room.application.port.out.RoomParticipantCommandPort; import konkuk.thip.room.application.service.validator.RoomParticipantValidator; @@ -35,7 +35,7 @@ public class VoteCreateService implements VoteCreateUseCase { private final RoomProgressManager roomProgressManager; - private final RoomEventCommandPort roomEventCommandPort; + private final RoomNotificationOrchestrator roomNotificationOrchestrator; @Transactional @Override @@ -81,7 +81,7 @@ private void sendNotifications(VoteCreateCommand command, Room room, Vote vote, List targetUsers = roomParticipantCommandPort.findAllByRoomId(command.roomId()); for (RoomParticipant targetUser : targetUsers) { if (targetUser.getUserId().equals(command.userId())) continue; // 본인 제외 - roomEventCommandPort.publishRoomVoteStartedEvent(targetUser.getUserId(), room.getId(), room.getTitle(), vote.getPage(), newVoteId); + roomNotificationOrchestrator.notifyRoomVoteStarted(targetUser.getUserId(), room.getId(), room.getTitle(), vote.getPage(), newVoteId); } } diff --git a/src/main/java/konkuk/thip/user/application/service/following/UserFollowService.java b/src/main/java/konkuk/thip/user/application/service/following/UserFollowService.java index 24e9eeb4c..c0316e510 100644 --- a/src/main/java/konkuk/thip/user/application/service/following/UserFollowService.java +++ b/src/main/java/konkuk/thip/user/application/service/following/UserFollowService.java @@ -1,7 +1,7 @@ package konkuk.thip.user.application.service.following; import konkuk.thip.common.exception.BusinessException; -import konkuk.thip.message.application.port.out.FeedEventCommandPort; +import konkuk.thip.notification.application.port.in.FeedNotificationOrchestrator; import konkuk.thip.user.application.port.in.UserFollowUsecase; import konkuk.thip.user.application.port.in.dto.UserFollowCommand; import konkuk.thip.user.application.port.out.FollowingCommandPort; @@ -23,7 +23,7 @@ public class UserFollowService implements UserFollowUsecase { private final FollowingCommandPort followingCommandPort; private final UserCommandPort userCommandPort; - private final FeedEventCommandPort feedEventCommandPort; + private final FeedNotificationOrchestrator feedNotificationOrchestrator; @Override @Transactional @@ -55,7 +55,7 @@ public Boolean changeFollowingState(UserFollowCommand followCommand) { private void sendNotifications(Long userId, Long targetUserId) { User actorUser = userCommandPort.findById(userId); - feedEventCommandPort.publishFollowEvent(targetUserId, actorUser.getId(), actorUser.getNickname()); + feedNotificationOrchestrator.notifyFollowed(targetUserId, actorUser.getId(), actorUser.getNickname()); } private void validateParams(Long userId, Long targetUserId) { diff --git a/src/test/java/konkuk/thip/notification/application/service/FeedNotificationOrchestratorSyncImplTest.java b/src/test/java/konkuk/thip/notification/application/service/FeedNotificationOrchestratorSyncImplTest.java new file mode 100644 index 000000000..6bfa02b5b --- /dev/null +++ b/src/test/java/konkuk/thip/notification/application/service/FeedNotificationOrchestratorSyncImplTest.java @@ -0,0 +1,139 @@ +package konkuk.thip.notification.application.service; + +import konkuk.thip.common.util.TestEntityFactory; +import konkuk.thip.message.adapter.out.event.dto.FeedEvents; +import konkuk.thip.message.application.port.in.FeedNotificationDispatchUseCase; +import konkuk.thip.notification.adapter.out.jpa.NotificationJpaEntity; +import konkuk.thip.notification.adapter.out.persistence.repository.NotificationJpaRepository; +import konkuk.thip.notification.application.port.in.FeedNotificationOrchestrator; +import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; +import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; +import konkuk.thip.user.domain.value.Alias; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.context.transaction.TestTransaction; +import org.springframework.transaction.IllegalTransactionStateException; +import org.springframework.transaction.annotation.Transactional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@SpringBootTest +@ActiveProfiles("test") +@DisplayName("[통합] 피드 알림 (동기화 방식) 헬퍼 서비스 통합 테스트") +class FeedNotificationOrchestratorSyncImplTest { + + @Autowired FeedNotificationOrchestrator orchestrator; // 프록시를 타기 위해 인터페이스 타입 주입 + @Autowired NotificationJpaRepository notificationJpaRepository; + @Autowired UserJpaRepository userJpaRepository; + + @MockitoBean FeedNotificationDispatchUseCase feedNotificationDispatchUseCase; + + private Long targetUserId; + + @BeforeEach + void setUp() { + // Notification 저장 시 FK 검사 통과를 위해 대상 유저 하나 만들어 둠 + UserJpaEntity target = userJpaRepository.save(TestEntityFactory.createUser(Alias.WRITER, "노성준")); + targetUserId = target.getUserId(); + } + + @AfterEach + void tearDown() { + // 롤백이 아닌 경우를 대비한 안전 정리 + notificationJpaRepository.deleteAllInBatch(); + userJpaRepository.deleteAllInBatch(); + } + + @Test + @DisplayName("상위 트랜잭션 없이 호출하면 IllegalTransactionStateException 발생 (MANDATORY)") + void mandatory_without_transaction_throws() { + // when & then + assertThatThrownBy(() -> + orchestrator.notifyFeedCommented( + targetUserId, /*actor*/ 999L, "alice", /*feedId*/ 123L + ) + ).isInstanceOf(IllegalTransactionStateException.class); + } + + @Test + @Transactional + @DisplayName("상위 트랜잭션 안에서 호출하면 정상 동작하고, Notification이 저장된다") + void mandatory_with_transaction_succeeds_and_persists() { + // when + orchestrator.notifyFeedCommented( + targetUserId, /*actor*/ 1000L, "bob", /*feedId*/ 777L + ); + + // then (같은 트랜잭션 안에서 즉시 조회 가능) + var all = notificationJpaRepository.findAll(); + assertThat(all).hasSize(1); + + NotificationJpaEntity saved = all.get(0); + assertThat(saved.getUserJpaEntity().getUserId()).isEqualTo(targetUserId); + assertThat(saved.getTitle()).isNotBlank(); + assertThat(saved.getContent()).contains("bob"); + } + + @Test + @Transactional + @DisplayName("커밋 시: AFTER_COMMIT 리스너가 handleFeedCommented 호출 & Notification 커밋됨") + void notifyFeedCommented_afterCommit_listenerInvoked_andNotificationPersisted() { + // given + Long actorUserId = 200L; + String actorUsername = "alice"; + Long feedId = 999L; + + // when (트랜잭션 안) + orchestrator.notifyFeedCommented(targetUserId, actorUserId, actorUsername, feedId); + + // 실제 커밋 트리거 + TestTransaction.flagForCommit(); + TestTransaction.end(); // 여기서 @TransactionalEventListener(AFTER_COMMIT) 실행됨 (테스트 프로필은 동기 실행) + + // then : 리스너에 전달되는 DTO 필드 검증 + ArgumentCaptor captor = + ArgumentCaptor.forClass(FeedEvents.FeedCommentedEvent.class); + verify(feedNotificationDispatchUseCase).handleFeedCommented(captor.capture()); + + FeedEvents.FeedCommentedEvent event = captor.getValue(); + assertThat(event).isNotNull(); + assertThat(event.title()).isNotBlank(); + assertThat(event.content()).contains(actorUsername); + assertThat(event.targetUserId()).isEqualTo(targetUserId); + assertThat(event.actorUserId()).isEqualTo(actorUserId); + assertThat(event.actorUsername()).isEqualTo(actorUsername); + assertThat(event.feedId()).isEqualTo(feedId); + } + + @Test + @Transactional + @DisplayName("롤백 시: AFTER_COMMIT 리스너는 호출되지 않고, Notification도 저장되지 않음") + void notifyFeedCommented_rollback_listenerNotInvoked_andNotificationNotPersisted() { + // given + Long actorUserId = 201L; + String actorUsername = "bob"; + Long feedId = 1000L; + + // when (트랜잭션 안) + orchestrator.notifyFeedCommented(targetUserId, actorUserId, actorUsername, feedId); + + // 롤백 트리거 + TestTransaction.flagForRollback(); + TestTransaction.end(); // 커밋이 아니므로 AFTER_COMMIT 리스너는 실행되지 않음 + + // then + verify(feedNotificationDispatchUseCase, times(0)).handleFeedCommented(any()); + assertThat(notificationJpaRepository.findAll()).isEmpty(); + } +} diff --git a/src/test/java/konkuk/thip/notification/application/service/FeedNotificationOrchestratorSyncImplUnitTest.java b/src/test/java/konkuk/thip/notification/application/service/FeedNotificationOrchestratorSyncImplUnitTest.java new file mode 100644 index 000000000..3972f9803 --- /dev/null +++ b/src/test/java/konkuk/thip/notification/application/service/FeedNotificationOrchestratorSyncImplUnitTest.java @@ -0,0 +1,51 @@ +package konkuk.thip.notification.application.service; + +import konkuk.thip.message.application.port.out.FeedEventCommandPort; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +@DisplayName("[단위] 피드 알림 (동기화 방식) 헬퍼 서비스 단위 테스트") +class FeedNotificationOrchestratorSyncImplUnitTest { + + @Mock NotificationSyncExecutor notificationSyncExecutor; + @Mock FeedEventCommandPort feedEventCommandPort; + + @InjectMocks FeedNotificationOrchestratorSyncImpl sut; + + @Test + @DisplayName("피드 댓글 알림: NotificationSyncExecutor 실행 (= DB notification 저장 + 이벤트 퍼블리시)") + void notify_feed_commented_test() { + // given + Long targetUserId = 10L; + Long actorUserId = 20L; + String actorUsername = "alice"; + Long feedId = 99L; + + // when + sut.notifyFeedCommented(targetUserId, actorUserId, actorUsername, feedId); + + // then: NotificationSyncExecutor 가 올바르게 호출되었는지 검증 + ArgumentCaptor invokerCaptor = ArgumentCaptor.forClass(EventCommandInvoker.class); + verify(notificationSyncExecutor).execute( + org.mockito.ArgumentMatchers.any(), + org.mockito.ArgumentMatchers.any(), + org.mockito.ArgumentMatchers.eq(targetUserId), + invokerCaptor.capture() + ); + + // then: invoker 가 EventCommandPort 메서드를 올바르게 호출하는지 검증 + EventCommandInvoker invoker = invokerCaptor.getValue(); + invoker.publish("title", "content"); + verify(feedEventCommandPort).publishFeedCommentedEvent( + "title", "content", targetUserId, actorUserId, actorUsername, feedId + ); + } +} diff --git a/src/test/java/konkuk/thip/notification/application/service/NotificationSyncExecutorTest.java b/src/test/java/konkuk/thip/notification/application/service/NotificationSyncExecutorTest.java new file mode 100644 index 000000000..d988c278d --- /dev/null +++ b/src/test/java/konkuk/thip/notification/application/service/NotificationSyncExecutorTest.java @@ -0,0 +1,52 @@ +package konkuk.thip.notification.application.service; + +import konkuk.thip.notification.application.port.out.NotificationCommandPort; +import konkuk.thip.notification.application.service.template.NotificationTemplate; +import konkuk.thip.notification.domain.Notification; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.mockito.Mockito.*; + +@DisplayName("[단위] NotificationSyncExecutor - 동기 알림 저장 및 이벤트 퍼블리시") +class NotificationSyncExecutorTest { + + @Test + @DisplayName("execute() 메서드 : 푸시 알림 이벤트 퍼블리시 과정에서 예외가 발생하더라도 예외를 외부로 던지지 않는다.") + void execute_publish_failure_does_not_throw() { + // given + NotificationCommandPort commandPort = mock(NotificationCommandPort.class); + NotificationSyncExecutor executor = new NotificationSyncExecutor(commandPort); + + // 간단한 템플릿 스텁 (title/content 고정) + NotificationTemplate template = new NotificationTemplate<>() { + @Override + public String title(String args) { return "테스트제목"; } + @Override + public String content(String args) { return "테스트내용"; } + }; + + // publish 호출 시 강제로 예외를 던지는 invoker + EventCommandInvoker invoker = (title, content) -> { + throw new RuntimeException("강제 퍼블리시 실패"); + }; + + // when & then + assertThatCode(() -> + executor.execute(template, "dummyArgs", 123L, invoker) + ).doesNotThrowAnyException(); + + // NotificationCommandPort은 정상적으로 호출되었는지 검증 + ArgumentCaptor captor = ArgumentCaptor.forClass(Notification.class); + verify(commandPort, times(1)).save(captor.capture()); + + Notification saved = captor.getValue(); + // 템플릿에서 설정한 title/content 값이 그대로 들어갔는지 확인 + assertThat(saved.getTitle()).isEqualTo("테스트제목"); + assertThat(saved.getContent()).isEqualTo("테스트내용"); + assertThat(saved.getTargetUserId()).isEqualTo(123L); + } +} diff --git a/src/test/java/konkuk/thip/notification/application/service/RoomNotificationOrchestratorSyncImplTest.java b/src/test/java/konkuk/thip/notification/application/service/RoomNotificationOrchestratorSyncImplTest.java new file mode 100644 index 000000000..65cd80564 --- /dev/null +++ b/src/test/java/konkuk/thip/notification/application/service/RoomNotificationOrchestratorSyncImplTest.java @@ -0,0 +1,167 @@ +package konkuk.thip.notification.application.service; + +import konkuk.thip.common.util.TestEntityFactory; +import konkuk.thip.message.adapter.out.event.dto.RoomEvents; +import konkuk.thip.message.application.port.in.RoomNotificationDispatchUseCase; +import konkuk.thip.notification.adapter.out.jpa.NotificationJpaEntity; +import konkuk.thip.notification.adapter.out.persistence.repository.NotificationJpaRepository; +import konkuk.thip.notification.application.port.in.RoomNotificationOrchestrator; +import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; +import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; +import konkuk.thip.user.domain.value.Alias; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.context.transaction.TestTransaction; +import org.springframework.transaction.IllegalTransactionStateException; +import org.springframework.transaction.annotation.Transactional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@SpringBootTest +@ActiveProfiles("test") +@DisplayName("[통합] 모임방 알림 (동기화 방식) 헬퍼 서비스 통합 테스트") +class RoomNotificationOrchestratorSyncImplTest { + + @Autowired RoomNotificationOrchestrator orchestrator; // 반드시 인터페이스 타입으로 주입(트랜잭션 프록시 적용) + @Autowired NotificationJpaRepository notificationJpaRepository; + @Autowired UserJpaRepository userJpaRepository; + + @MockitoBean RoomNotificationDispatchUseCase roomNotificationDispatchUseCase; + + private Long targetUserId; + + @BeforeEach + void setUp() { + // Notification FK를 만족시키기 위한 대상 사용자 준비 + UserJpaEntity target = userJpaRepository.save(TestEntityFactory.createUser(Alias.WRITER, "노성준")); + targetUserId = target.getUserId(); + } + + @AfterEach + void tearDown() { + notificationJpaRepository.deleteAllInBatch(); + userJpaRepository.deleteAllInBatch(); + } + + @Test + @DisplayName("상위 트랜잭션 없이 호출하면 IllegalTransactionStateException 발생 (MANDATORY)") + void mandatory_without_transaction_throws() { + // given + Long actorUserId = 200L; + String actorUsername = "carol"; + Long roomId = 11L; + Integer page = 1; + Long postId = 22L; + String postType = "RECORD"; + + // when & then + assertThatThrownBy(() -> + orchestrator.notifyRoomPostCommented( + targetUserId, actorUserId, actorUsername, roomId, page, postId, postType + ) + ).isInstanceOf(IllegalTransactionStateException.class); + } + + @Test + @Transactional // 상위 트랜잭션 존재 + @DisplayName("상위 트랜잭션 안에서 호출하면 정상 동작하고, Notification이 저장된다") + void mandatory_with_transaction_succeeds_and_persists() { + // given + Long actorUserId = 201L; + String actorUsername = "dave"; + Long roomId = 12L; + Integer page = 3; + Long postId = 33L; + String postType = "RECORD"; + + // when + orchestrator.notifyRoomPostCommented( + targetUserId, actorUserId, actorUsername, roomId, page, postId, postType + ); + + // then + var all = notificationJpaRepository.findAll(); + assertThat(all).hasSize(1); + + NotificationJpaEntity saved = all.get(0); + assertThat(saved.getUserJpaEntity().getUserId()).isEqualTo(targetUserId); + assertThat(saved.getTitle()).isNotBlank(); + assertThat(saved.getContent()).contains(actorUsername); + } + + @Test + @Transactional + @DisplayName("커밋 시: AFTER_COMMIT 리스너가 handleRoomPostCommented 호출 & Notification 커밋됨") + void roomPostCommented_afterCommit_listenerInvoked_andNotificationCommitted() { + // given + Long actorUserId = 301L; + String actorUsername = "alice"; + Long roomId = 1001L; + Integer page = 7; + Long postId = 5001L; + String postType = "RECORD"; + + // when (트랜잭션 안) + orchestrator.notifyRoomPostCommented( + targetUserId, actorUserId, actorUsername, roomId, page, postId, postType + ); + + // 실제 커밋 트리거 → AFTER_COMMIT 리스너 실행 (test 프로필은 @Async 동기화) + TestTransaction.flagForCommit(); + TestTransaction.end(); + + // then : 리스너에 전달되는 DTO 필드 검증 + ArgumentCaptor captor = + ArgumentCaptor.forClass(RoomEvents.RoomPostCommentedEvent.class); + verify(roomNotificationDispatchUseCase).handleRoomPostCommented(captor.capture()); + + RoomEvents.RoomPostCommentedEvent event = captor.getValue(); + assertThat(event).isNotNull(); + assertThat(event.title()).isNotBlank(); + assertThat(event.content()).contains(actorUsername); + assertThat(event.targetUserId()).isEqualTo(targetUserId); + assertThat(event.actorUserId()).isEqualTo(actorUserId); + assertThat(event.actorUsername()).isEqualTo(actorUsername); + assertThat(event.roomId()).isEqualTo(roomId); + assertThat(event.page()).isEqualTo(page); + assertThat(event.postId()).isEqualTo(postId); + assertThat(event.postType()).isEqualTo(postType); + } + + @Test + @Transactional + @DisplayName("롤백 시: AFTER_COMMIT 리스너는 호출되지 않고, Notification도 저장되지 않음") + void roomPostCommented_rollback_listenerNotInvoked_andNotificationNotCommitted() { + // given + Long actorUserId = 302L; + String actorUsername = "bob"; + Long roomId = 1002L; + Integer page = 2; + Long postId = 5002L; + String postType = "RECORD"; + + // when + orchestrator.notifyRoomPostCommented( + targetUserId, actorUserId, actorUsername, roomId, page, postId, postType + ); + + // 롤백 트리거 → AFTER_COMMIT 미실행 + TestTransaction.flagForRollback(); + TestTransaction.end(); + + // then + verify(roomNotificationDispatchUseCase, times(0)).handleRoomPostCommented(any()); + assertThat(notificationJpaRepository.findAll()).isEmpty(); + } +} diff --git a/src/test/java/konkuk/thip/notification/application/service/RoomNotificationOrchestratorSyncImplUnitTest.java b/src/test/java/konkuk/thip/notification/application/service/RoomNotificationOrchestratorSyncImplUnitTest.java new file mode 100644 index 000000000..93e15df72 --- /dev/null +++ b/src/test/java/konkuk/thip/notification/application/service/RoomNotificationOrchestratorSyncImplUnitTest.java @@ -0,0 +1,50 @@ +package konkuk.thip.notification.application.service; + +import konkuk.thip.message.application.port.out.RoomEventCommandPort; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +@DisplayName("[단위] 모임방 알림 (동기화 방식) 헬퍼 서비스 단위 테스트") +class RoomNotificationOrchestratorSyncImplUnitTest { + + @Mock NotificationSyncExecutor notificationSyncExecutor; + @Mock RoomEventCommandPort roomEventCommandPort; + + @InjectMocks RoomNotificationOrchestratorSyncImpl sut; + + @Test + @DisplayName("모임방 게시글에 댓글: NotificationSyncExecutor 실행 (= DB notification 저장 + 이벤트 퍼블리시)") + void notify_room_post_commented() { + // given + Long targetUserId = 10L; + Long actorUserId = 20L; + String actorUsername = "alice"; + Long roomId = 1L; int page = 2; Long postId = 3L; String postType = "RECORD"; + + // when + sut.notifyRoomPostCommented(targetUserId, actorUserId, actorUsername, roomId, page, postId, postType); + + // then: NotificationSyncExecutor 가 올바르게 호출되었는지 검증 + ArgumentCaptor invokerCaptor = ArgumentCaptor.forClass(EventCommandInvoker.class); + verify(notificationSyncExecutor).execute( + any(), any(), eq(targetUserId), invokerCaptor.capture() + ); + + // then: invoker 가 EventCommandPort 메서드를 올바르게 호출하는지 검증 + EventCommandInvoker invoker = invokerCaptor.getValue(); + invoker.publish("title", "content"); + verify(roomEventCommandPort).publishRoomPostCommentedEvent( + "title", "content", targetUserId, actorUserId, actorUsername, roomId, page, postId, postType + ); + } +} diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomCloseJoinApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomCloseJoinApiTest.java index 2a399c451..42161bbb1 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomCloseJoinApiTest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomCloseJoinApiTest.java @@ -13,7 +13,6 @@ import konkuk.thip.user.domain.value.UserRole; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -96,14 +95,6 @@ void setup() { roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room, member, RoomParticipantRole.MEMBER, 0.0)); } - @AfterEach - void tearDown() { - roomParticipantJpaRepository.deleteAllInBatch(); - roomJpaRepository.deleteAllInBatch(); - bookJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAllInBatch(); - } - @Test @DisplayName("모집 마감 성공 - 방 시작일이 오늘로 바뀜") void closeRoomRecruit_success() throws Exception { diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomJoinApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomJoinApiTest.java index f8e187a2e..ce1c0ff9d 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomJoinApiTest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomJoinApiTest.java @@ -4,6 +4,7 @@ import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; import konkuk.thip.book.adapter.out.persistence.repository.BookJpaRepository; import konkuk.thip.common.util.TestEntityFactory; +import konkuk.thip.notification.adapter.out.persistence.repository.NotificationJpaRepository; import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; import konkuk.thip.room.adapter.out.jpa.RoomParticipantJpaEntity; import konkuk.thip.room.domain.value.RoomParticipantRole; @@ -48,6 +49,7 @@ class RoomJoinApiTest { @Autowired private RoomParticipantJpaRepository roomParticipantJpaRepository; @Autowired private BookJpaRepository bookJpaRepository; @Autowired private UserJpaRepository userJpaRepository; + @Autowired private NotificationJpaRepository notificationJpaRepository; private RoomJpaEntity room; private UserJpaEntity host; @@ -110,9 +112,10 @@ private void createUsers(Alias alias) { @AfterEach void tearDown() { roomParticipantJpaRepository.deleteAllInBatch(); - roomJpaRepository.deleteAll(); - bookJpaRepository.deleteAll(); - userJpaRepository.deleteAll(); + roomJpaRepository.deleteAllInBatch(); + bookJpaRepository.deleteAllInBatch(); + notificationJpaRepository.deleteAllInBatch(); + userJpaRepository.deleteAllInBatch(); } @Test diff --git a/src/test/java/konkuk/thip/room/application/service/RoomJoinServiceTest.java b/src/test/java/konkuk/thip/room/application/service/RoomJoinServiceTest.java index 383d0933d..a7ad258f6 100644 --- a/src/test/java/konkuk/thip/room/application/service/RoomJoinServiceTest.java +++ b/src/test/java/konkuk/thip/room/application/service/RoomJoinServiceTest.java @@ -2,7 +2,7 @@ import konkuk.thip.common.exception.BusinessException; import konkuk.thip.common.exception.code.ErrorCode; -import konkuk.thip.message.application.port.out.RoomEventCommandPort; +import konkuk.thip.notification.application.service.RoomNotificationOrchestratorSyncImpl; import konkuk.thip.room.application.port.in.dto.RoomJoinCommand; import konkuk.thip.room.application.port.out.RoomCommandPort; import konkuk.thip.room.application.port.out.RoomParticipantCommandPort; @@ -32,7 +32,7 @@ class RoomJoinServiceTest { private RoomParticipantCommandPort roomParticipantCommandPort; private RoomJoinService roomJoinService; private UserCommandPort userCommandPort; - private RoomEventCommandPort roomEventCommandPort; + private RoomNotificationOrchestratorSyncImpl roomNotificationOrchestratorSyncImpl; private final Long ROOM_ID = 1L; private final Long USER_ID = 2L; @@ -46,13 +46,13 @@ void setUp() { roomCommandPort = mock(RoomCommandPort.class); roomParticipantCommandPort = mock(RoomParticipantCommandPort.class); userCommandPort = mock(UserCommandPort.class); - roomEventCommandPort = mock(RoomEventCommandPort.class); + roomNotificationOrchestratorSyncImpl = mock(RoomNotificationOrchestratorSyncImpl.class); roomJoinService = new RoomJoinService( roomCommandPort, roomParticipantCommandPort, userCommandPort, - roomEventCommandPort + roomNotificationOrchestratorSyncImpl ); } diff --git a/src/test/java/konkuk/thip/roompost/adapter/in/web/RecordCreateControllerTest.java b/src/test/java/konkuk/thip/roompost/adapter/in/web/RecordCreateApiTest.java similarity index 99% rename from src/test/java/konkuk/thip/roompost/adapter/in/web/RecordCreateControllerTest.java rename to src/test/java/konkuk/thip/roompost/adapter/in/web/RecordCreateApiTest.java index dd779b8e0..fd50178e8 100644 --- a/src/test/java/konkuk/thip/roompost/adapter/in/web/RecordCreateControllerTest.java +++ b/src/test/java/konkuk/thip/roompost/adapter/in/web/RecordCreateApiTest.java @@ -42,7 +42,7 @@ @Transactional @AutoConfigureMockMvc(addFilters = false) @DisplayName("[통합] RecordCommandController 테스트") -class RecordCreateControllerTest { +class RecordCreateApiTest { @Autowired MockMvc mockMvc; diff --git a/src/test/java/konkuk/thip/roompost/adapter/in/web/VoteCreateApiTest.java b/src/test/java/konkuk/thip/roompost/adapter/in/web/VoteCreateApiTest.java index 5cc332584..3275f9b77 100644 --- a/src/test/java/konkuk/thip/roompost/adapter/in/web/VoteCreateApiTest.java +++ b/src/test/java/konkuk/thip/roompost/adapter/in/web/VoteCreateApiTest.java @@ -5,6 +5,7 @@ import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; import konkuk.thip.book.adapter.out.persistence.repository.BookJpaRepository; import konkuk.thip.common.util.TestEntityFactory; +import konkuk.thip.notification.adapter.out.persistence.repository.NotificationJpaRepository; import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; import konkuk.thip.room.domain.value.RoomParticipantRole; import konkuk.thip.room.adapter.out.persistence.repository.RoomJpaRepository; @@ -66,6 +67,9 @@ class VoteCreateApiTest { @Autowired private VoteItemJpaRepository voteItemJpaRepository; + @Autowired + private NotificationJpaRepository notificationJpaRepository; + @Autowired private JdbcTemplate jdbcTemplate; @@ -76,6 +80,7 @@ void tearDown() { roomParticipantJpaRepository.deleteAllInBatch(); roomJpaRepository.deleteAllInBatch(); bookJpaRepository.deleteAllInBatch(); + notificationJpaRepository.deleteAllInBatch(); userJpaRepository.deleteAllInBatch(); } diff --git a/src/test/java/konkuk/thip/roompost/application/service/VoteCreateServiceTest.java b/src/test/java/konkuk/thip/roompost/application/service/VoteCreateServiceTest.java index dbb3603fd..2491a9723 100644 --- a/src/test/java/konkuk/thip/roompost/application/service/VoteCreateServiceTest.java +++ b/src/test/java/konkuk/thip/roompost/application/service/VoteCreateServiceTest.java @@ -3,6 +3,7 @@ import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; import konkuk.thip.book.adapter.out.persistence.repository.BookJpaRepository; import konkuk.thip.common.util.TestEntityFactory; +import konkuk.thip.notification.adapter.out.persistence.repository.NotificationJpaRepository; import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; import konkuk.thip.room.adapter.out.jpa.RoomParticipantJpaEntity; import konkuk.thip.room.domain.value.RoomParticipantRole; @@ -55,6 +56,9 @@ class VoteCreateServiceTest { @Autowired private VoteCreateService voteCreateService; + @Autowired + private NotificationJpaRepository notificationJpaRepository; + @AfterEach void tearDown() { voteItemJpaRepository.deleteAllInBatch(); @@ -62,6 +66,7 @@ void tearDown() { roomParticipantJpaRepository.deleteAllInBatch(); roomJpaRepository.deleteAllInBatch(); bookJpaRepository.deleteAllInBatch(); + notificationJpaRepository.deleteAllInBatch(); userJpaRepository.deleteAllInBatch(); } diff --git a/src/test/java/konkuk/thip/user/adapter/in/web/UserFollowApiTest.java b/src/test/java/konkuk/thip/user/adapter/in/web/UserFollowApiTest.java index 77814ec56..0344345fa 100644 --- a/src/test/java/konkuk/thip/user/adapter/in/web/UserFollowApiTest.java +++ b/src/test/java/konkuk/thip/user/adapter/in/web/UserFollowApiTest.java @@ -1,6 +1,7 @@ package konkuk.thip.user.adapter.in.web; import konkuk.thip.common.util.TestEntityFactory; +import konkuk.thip.notification.adapter.out.persistence.repository.NotificationJpaRepository; import konkuk.thip.user.adapter.out.jpa.FollowingJpaEntity; import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import konkuk.thip.user.domain.value.UserRole; @@ -34,16 +35,15 @@ class UserFollowApiTest { @Autowired private MockMvc mockMvc; - @Autowired - private UserJpaRepository userJpaRepository; - - @Autowired - private FollowingJpaRepository followingJpaRepository; + @Autowired private UserJpaRepository userJpaRepository; + @Autowired private FollowingJpaRepository followingJpaRepository; + @Autowired private NotificationJpaRepository notificationJpaRepository; @AfterEach void tearDown() { followingJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAll(); + notificationJpaRepository.deleteAllInBatch(); + userJpaRepository.deleteAllInBatch(); } @Test diff --git a/src/test/java/konkuk/thip/user/application/service/UserFollowServiceTest.java b/src/test/java/konkuk/thip/user/application/service/UserFollowServiceTest.java index 84471a5d9..050b5c692 100644 --- a/src/test/java/konkuk/thip/user/application/service/UserFollowServiceTest.java +++ b/src/test/java/konkuk/thip/user/application/service/UserFollowServiceTest.java @@ -1,7 +1,7 @@ package konkuk.thip.user.application.service; import konkuk.thip.common.exception.BusinessException; -import konkuk.thip.message.application.port.out.FeedEventCommandPort; +import konkuk.thip.notification.application.service.FeedNotificationOrchestratorSyncImpl; import konkuk.thip.user.application.port.in.dto.UserFollowCommand; import konkuk.thip.user.application.port.out.FollowingCommandPort; import konkuk.thip.user.application.port.out.UserCommandPort; @@ -29,14 +29,14 @@ class UserFollowServiceTest { private UserCommandPort userCommandPort; private UserFollowService userFollowService; - private FeedEventCommandPort feedEventCommandPort; + private FeedNotificationOrchestratorSyncImpl feedNotificationOrchestratorSyncImpl; @BeforeEach void setUp() { followingCommandPort = mock(FollowingCommandPort.class); userCommandPort = mock(UserCommandPort.class); - feedEventCommandPort = mock(FeedEventCommandPort.class); - userFollowService = new UserFollowService(followingCommandPort, userCommandPort, feedEventCommandPort); + feedNotificationOrchestratorSyncImpl = mock(FeedNotificationOrchestratorSyncImpl.class); + userFollowService = new UserFollowService(followingCommandPort, userCommandPort, feedNotificationOrchestratorSyncImpl); } @Nested @@ -157,4 +157,4 @@ private User createUserWithFollowerCount(int count) { .alias(null) .build(); } -} \ No newline at end of file +}