-
Notifications
You must be signed in to change notification settings - Fork 0
[FEAT] V2 모임 알림 설정 #170
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FEAT] V2 모임 알림 설정 #170
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package team.wego.wegobackend.group.v2.application.event; | ||
|
|
||
| public record GroupCreatedEvent( | ||
| Long groupId, | ||
| Long hostUserId | ||
| ) { | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package team.wego.wegobackend.group.v2.application.event; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public record GroupDeletedEvent( | ||
| // deleteHard()에서 삭제 전에 group/host 정보를 꺼내서 이벤트에 실어 보내면, | ||
| //AFTER_COMMIT에서도 DB 재조회가 필요 없어진다. | ||
| Long groupId, | ||
| Long hostId, | ||
| String hostNickName, | ||
| String groupTitle, | ||
| List<Long> attendeeUserIds | ||
| ) { | ||
|
|
||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package team.wego.wegobackend.group.v2.application.event; | ||
|
|
||
| public record GroupJoinApprovedEvent( | ||
| Long groupId, | ||
| Long approverUserId, | ||
| Long targetUserId | ||
| ) { | ||
|
|
||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package team.wego.wegobackend.group.v2.application.event; | ||
|
|
||
| public record GroupJoinKickedEvent( | ||
| Long groupId, | ||
| Long hostId, | ||
| Long targetUserId | ||
| ) { | ||
|
|
||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package team.wego.wegobackend.group.v2.application.event; | ||
|
|
||
| public record GroupJoinRejectedEvent( | ||
| Long groupId, | ||
| Long approverUserId, | ||
| Long targetUserId | ||
| ) { | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package team.wego.wegobackend.group.v2.application.event; | ||
|
|
||
| public record GroupJoinRequestedEvent( | ||
| Long groupId, | ||
| Long hostUserId, | ||
| Long requesterUserId | ||
| ) { | ||
|
|
||
| } | ||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package team.wego.wegobackend.group.v2.application.event; | ||
|
|
||
| public record GroupJoinedEvent( | ||
| Long groupId, | ||
| Long hostId, | ||
| Long joinerUserId | ||
| ) { | ||
|
|
||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package team.wego.wegobackend.group.v2.application.event; | ||
|
|
||
| public record GroupLeftEvent( | ||
| Long groupId, | ||
| Long hostId, | ||
| Long leaverUserId | ||
| ) { | ||
|
|
||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| package team.wego.wegobackend.group.v2.application.event; | ||
|
|
||
| import java.time.LocalDateTime; | ||
| import lombok.AccessLevel; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
| import team.wego.wegobackend.group.v2.domain.entity.GroupV2; | ||
| import team.wego.wegobackend.notification.application.dto.NotificationType; | ||
| import team.wego.wegobackend.notification.domain.Notification; | ||
| import team.wego.wegobackend.user.domain.User; | ||
|
|
||
| @Getter(AccessLevel.PUBLIC) | ||
| @Builder(access = AccessLevel.PUBLIC) | ||
|
||
| public class NotificationEvent { | ||
|
|
||
| private Long id; // notificationId | ||
| private String message; | ||
| private NotificationType type; | ||
|
|
||
| private LocalDateTime createdAt; | ||
| private LocalDateTime readAt; // null == unread | ||
|
|
||
| private ActorUser user; // 모임 생성자 | ||
| private GroupInfo group; // 모임 | ||
|
|
||
| public boolean isRead() { | ||
| return readAt != null; | ||
| } | ||
|
|
||
| public static NotificationEvent of(Notification n, User actor, GroupV2 group) { | ||
| return NotificationEvent.builder() | ||
| .id(n.getId()) | ||
| .message(n.getMessage()) | ||
| .type(n.getType()) | ||
| .createdAt(n.getCreatedAt()) | ||
| .readAt(n.getReadAt()) | ||
| .user(ActorUser.from(actor)) | ||
| .group(group != null ? GroupInfo.from(group) : null) | ||
| .build(); | ||
| } | ||
|
|
||
| @Getter(AccessLevel.PUBLIC) | ||
| @Builder(access = AccessLevel.PUBLIC) | ||
| public static class ActorUser { | ||
| private Long id; | ||
| private String nickName; | ||
| private String profileImage; | ||
|
|
||
| public static ActorUser from(User u) { | ||
| if (u == null) return null; | ||
| return ActorUser.builder() | ||
| .id(u.getId()) | ||
| .nickName(u.getNickName()) | ||
| .profileImage(u.getProfileImage()) | ||
| .build(); | ||
| } | ||
| } | ||
|
|
||
| @Getter(AccessLevel.PUBLIC) | ||
| @Builder(access = AccessLevel.PUBLIC) | ||
| public static class GroupInfo { | ||
| private Long id; | ||
| private String title; | ||
|
|
||
| public static GroupInfo from(GroupV2 g) { | ||
| if (g == null) return null; | ||
| return GroupInfo.builder() | ||
| .id(g.getId()) | ||
| .title(g.getTitle()) | ||
| .build(); | ||
| } | ||
| } | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,77 @@ | ||||||||||||||||||
| package team.wego.wegobackend.group.v2.application.handler; | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| import java.util.ArrayList; | ||||||||||||||||||
| import java.util.List; | ||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||
| import lombok.extern.slf4j.Slf4j; | ||||||||||||||||||
| import org.springframework.stereotype.Service; | ||||||||||||||||||
| import org.springframework.transaction.annotation.Propagation; | ||||||||||||||||||
| import org.springframework.transaction.annotation.Transactional; | ||||||||||||||||||
| import team.wego.wegobackend.group.v2.domain.entity.GroupV2; | ||||||||||||||||||
| import team.wego.wegobackend.group.v2.domain.repository.GroupV2Repository; | ||||||||||||||||||
| import team.wego.wegobackend.notification.application.dispatcher.NotificationDispatcher; | ||||||||||||||||||
| import team.wego.wegobackend.notification.domain.Notification; | ||||||||||||||||||
| import team.wego.wegobackend.user.domain.User; | ||||||||||||||||||
| import team.wego.wegobackend.user.repository.FollowRepository; | ||||||||||||||||||
| import team.wego.wegobackend.user.repository.UserRepository; | ||||||||||||||||||
| import team.wego.wegobackend.user.repository.query.FollowerNotifyRow; | ||||||||||||||||||
|
|
||||||||||||||||||
| @Slf4j | ||||||||||||||||||
| @Service | ||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||
| public class GroupCreateNotificationHandler { | ||||||||||||||||||
|
|
||||||||||||||||||
| private final FollowRepository followRepository; | ||||||||||||||||||
| private final UserRepository userRepository; | ||||||||||||||||||
| private final GroupV2Repository groupV2Repository; | ||||||||||||||||||
|
|
||||||||||||||||||
| private final NotificationDispatcher notificationDispatcher; | ||||||||||||||||||
|
|
||||||||||||||||||
| @Transactional(propagation = Propagation.REQUIRES_NEW) | ||||||||||||||||||
| public void handle(Long groupId, Long hostUserId) { | ||||||||||||||||||
| log.info("[NOTI] start handle. groupId={}, hostId={}", groupId, hostUserId); | ||||||||||||||||||
|
|
||||||||||||||||||
| List<FollowerNotifyRow> test = followRepository.findFollowersForNotify(hostUserId, null, 10); | ||||||||||||||||||
| log.info("[NOTI] follower rows size={}", test.size()); | ||||||||||||||||||
|
Comment on lines
+35
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 디버그/테스트 코드 제거 필요 Lines 35-36은 10개의 팔로워를 조회하여 로깅하는 테스트용 코드로 보입니다. 실제 배치 처리 로직(Lines 47-74)과 중복되며 프로덕션 코드에는 불필요합니다. 🔎 수정 제안 @Transactional(propagation = Propagation.REQUIRES_NEW)
public void handle(Long groupId, Long hostUserId) {
log.info("[NOTI] start handle. groupId={}, hostId={}", groupId, hostUserId);
- List<FollowerNotifyRow> test = followRepository.findFollowersForNotify(hostUserId, null, 10);
- log.info("[NOTI] follower rows size={}", test.size());
-
User host = userRepository.findById(hostUserId)
.orElseThrow();📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Comment on lines
+35
to
+36
|
||||||||||||||||||
|
|
||||||||||||||||||
| User host = userRepository.findById(hostUserId) | ||||||||||||||||||
| .orElseThrow(); | ||||||||||||||||||
|
|
||||||||||||||||||
| GroupV2 group = groupV2Repository.findById(groupId) | ||||||||||||||||||
| .orElseThrow(); | ||||||||||||||||||
|
|
||||||||||||||||||
| Long cursor = null; | ||||||||||||||||||
| final int size = 500; | ||||||||||||||||||
|
|
||||||||||||||||||
| while (true) { | ||||||||||||||||||
| List<FollowerNotifyRow> rows = | ||||||||||||||||||
| followRepository.findFollowersForNotify( | ||||||||||||||||||
| hostUserId, cursor, size | ||||||||||||||||||
| ); | ||||||||||||||||||
|
|
||||||||||||||||||
| if (rows.isEmpty()) { | ||||||||||||||||||
| break; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| List<Notification> notifications = new ArrayList<>(rows.size()); | ||||||||||||||||||
|
|
||||||||||||||||||
| for (FollowerNotifyRow row : rows) { | ||||||||||||||||||
| User receiver = userRepository.getReferenceById(row.userId()); | ||||||||||||||||||
| notifications.add( | ||||||||||||||||||
| Notification.createGroupCreateNotification( | ||||||||||||||||||
| receiver, | ||||||||||||||||||
| host, | ||||||||||||||||||
| group | ||||||||||||||||||
| ) | ||||||||||||||||||
| ); | ||||||||||||||||||
|
Comment on lines
+59
to
+67
|
||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // 공통 디스패처 호출 | ||||||||||||||||||
| notificationDispatcher.dispatch(notifications, host, group); | ||||||||||||||||||
|
|
||||||||||||||||||
| cursor = rows.getLast().followId(); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| package team.wego.wegobackend.group.v2.application.handler; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Propagation; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
| import team.wego.wegobackend.group.domain.exception.GroupErrorCode; | ||
| import team.wego.wegobackend.group.domain.exception.GroupException; | ||
| import team.wego.wegobackend.group.v2.application.event.GroupDeletedEvent; | ||
| import team.wego.wegobackend.notification.application.dispatcher.NotificationDispatcher; | ||
| import team.wego.wegobackend.notification.domain.Notification; | ||
| import team.wego.wegobackend.user.domain.User; | ||
| import team.wego.wegobackend.user.repository.UserRepository; | ||
|
|
||
| @Slf4j | ||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class GroupDeleteNotificationHandler { | ||
|
|
||
| private final UserRepository userRepository; | ||
| private final NotificationDispatcher notificationDispatcher; | ||
|
|
||
| @Transactional(propagation = Propagation.REQUIRES_NEW) | ||
| public void handle(GroupDeletedEvent event) { | ||
| List<Long> ids = event.attendeeUserIds(); | ||
| int size = (ids == null ? 0 : ids.size()); | ||
|
|
||
| log.info("[GROUP_DELETE][HANDLER] start groupId={} hostId={} attendeeCount={}", | ||
| event.groupId(), event.hostId(), size); | ||
|
|
||
| if (ids == null || ids.isEmpty()) { | ||
| return; | ||
| } | ||
|
|
||
| User host = userRepository.findById(event.hostId()) | ||
| .orElseThrow(() -> new GroupException(GroupErrorCode.HOST_USER_NOT_FOUND, | ||
| event.hostId())); | ||
|
|
||
| List<Notification> notifications = new ArrayList<>(ids.size()); | ||
| for (Long receiverId : ids) { | ||
| if (receiverId.equals(event.hostId())) { | ||
| continue; | ||
| } | ||
| User receiver = userRepository.getReferenceById(receiverId); | ||
| notifications.add( | ||
| Notification.createGroupDeleteNotification(receiver, host, event.groupId(), | ||
| event.groupTitle())); | ||
| } | ||
|
|
||
| log.info("[GROUP_DELETE][HANDLER] built notifications size={}", notifications.size()); | ||
| notificationDispatcher.dispatch(notifications, host, null); | ||
|
|
||
| log.info("[GROUP_DELETE][HANDLER] done groupId={}", event.groupId()); | ||
| } | ||
| } | ||
|
|
||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package team.wego.wegobackend.group.v2.application.handler; | ||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Propagation; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
| import team.wego.wegobackend.group.domain.exception.GroupErrorCode; | ||
| import team.wego.wegobackend.group.domain.exception.GroupException; | ||
| import team.wego.wegobackend.group.v2.application.event.NotificationEvent; | ||
| import team.wego.wegobackend.group.v2.domain.entity.GroupV2; | ||
| import team.wego.wegobackend.group.v2.domain.repository.GroupV2Repository; | ||
| import team.wego.wegobackend.notification.domain.Notification; | ||
| import team.wego.wegobackend.notification.repository.NotificationRepository; | ||
| import team.wego.wegobackend.notification.application.SseEmitterService; | ||
| import team.wego.wegobackend.user.domain.User; | ||
| import team.wego.wegobackend.user.exception.UserNotFoundException; | ||
| import team.wego.wegobackend.user.repository.UserRepository; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class GroupJoinDecisionNotificationHandler { | ||
|
|
||
| private final UserRepository userRepository; | ||
| private final GroupV2Repository groupV2Repository; | ||
|
|
||
| private final NotificationRepository notificationRepository; | ||
| private final SseEmitterService sseEmitterService; | ||
|
|
||
| @Transactional(propagation = Propagation.REQUIRES_NEW) | ||
| public void handleApproved(Long groupId, Long approverUserId, Long targetUserId) { | ||
| handle(groupId, approverUserId, targetUserId, true); | ||
| } | ||
|
|
||
| @Transactional(propagation = Propagation.REQUIRES_NEW) | ||
| public void handleRejected(Long groupId, Long approverUserId, Long targetUserId) { | ||
| handle(groupId, approverUserId, targetUserId, false); | ||
| } | ||
|
|
||
| private void handle(Long groupId, Long approverUserId, Long targetUserId, boolean approved) { | ||
| User actor = userRepository.findById(approverUserId) | ||
| .orElseThrow(() -> new GroupException(GroupErrorCode.GROUP_USER_NOT_FOUND, approverUserId)); | ||
|
|
||
| User receiver = userRepository.findById(targetUserId) | ||
| .orElseThrow(() -> new GroupException(GroupErrorCode.GROUP_USER_NOT_FOUND, targetUserId)); | ||
|
|
||
| GroupV2 group = groupV2Repository.findById(groupId) | ||
| .orElseThrow(() -> new GroupException(GroupErrorCode.GROUP_NOT_FOUND_BY_ID, groupId)); | ||
|
|
||
| Notification notification = approved | ||
| ? Notification.createGroupJoinApprovedNotification(receiver, actor, group) | ||
| : Notification.createGroupJoinRejectedNotification(receiver, actor, group); | ||
|
|
||
| Notification saved = notificationRepository.save(notification); | ||
|
|
||
| NotificationEvent dto = NotificationEvent.of(saved, actor, group); | ||
| sseEmitterService.sendNotification(receiver.getId(), dto); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unnecessary blank lines should be removed. There are extra blank lines at the end of the file that should be cleaned up for consistency.