From 1c71ce3968f55b2bc8c7065b635666c4ccd2f97e Mon Sep 17 00:00:00 2001 From: marshmallowing Date: Sat, 10 Jan 2026 13:51:47 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=ED=8C=8C=ED=8B=B0=20=EC=B0=B8=EC=97=AC?= =?UTF-8?q?,=20=EA=B1=B0=EC=A0=88,=20=EC=A2=85=EB=A3=8C=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/PartyNotificationService.java | 99 +++++++++---------- .../service/facade/NotificationFacade.java | 59 ++++++++--- .../PartyParticipantRepository.java | 2 + .../domain/party/service/PartyService.java | 28 ++++++ .../exception/errorcode/PartyErrorCode.java | 21 ++++ 5 files changed, 145 insertions(+), 64 deletions(-) create mode 100644 src/main/java/ita/tinybite/global/exception/errorcode/PartyErrorCode.java diff --git a/src/main/java/ita/tinybite/domain/notification/service/PartyNotificationService.java b/src/main/java/ita/tinybite/domain/notification/service/PartyNotificationService.java index f28c2cb..63a4847 100644 --- a/src/main/java/ita/tinybite/domain/notification/service/PartyNotificationService.java +++ b/src/main/java/ita/tinybite/domain/notification/service/PartyNotificationService.java @@ -26,62 +26,59 @@ public class PartyNotificationService { private final NotificationLogService notificationLogService; private final NotificationTransactionHelper notificationTransactionHelper; + // @Transactional - public void sendApprovalNotification(Long targetUserId, Long partyId) { - String title = "πŸŽ‰ νŒŒν‹° μ°Έμ—¬ 승인"; - String detail = "νŒŒν‹° μ°Έμ—¬κ°€ μŠΉμΈλ˜μ—ˆμŠ΅λ‹ˆλ‹€! μ§€κΈˆ ν™•μΈν•˜μ„Έμš”."; - notificationLogService.saveLog(targetUserId, NotificationType.PARTY_APPROVAL.name(), title, detail); + public void sendNewPartyRequestNotification(Long managerId, String requesterNickname, String partyTitle, Long partyId) { + String title = String.format("🍽️ [%s]λ‹˜μ΄ μ°Έμ—¬ μš”μ²­ν–ˆμ–΄μš”", requesterNickname); + String detail = String.format("β€˜[%s]’ νŒŒν‹° 승인 μ—¬λΆ€λ₯Ό 확인해 μ£Όμ„Έμš”", partyTitle); - List tokens = fcmTokenService.getTokensAndLogIfEmpty(targetUserId); + notificationLogService.saveLog(managerId, NotificationType.PARTY_NEW_REQUEST.name(), title, detail); + + List tokens = fcmTokenService.getTokensAndLogIfEmpty(managerId); if (tokens.isEmpty()) { return; } + NotificationMulticastRequest request = - partyMessageManager.createApprovalRequest(tokens, partyId, title, detail); + partyMessageManager.createNewPartyRequest(tokens, partyId, title, detail); BatchResponse response = fcmNotificationSender.send(request); notificationTransactionHelper.handleBatchResponse(response, tokens); } + // @Transactional - public void sendRejectionNotification(Long targetUserId, Long partyId) { - String title = "🚨 νŒŒν‹° μ°Έμ—¬ 거절"; - String detail = "μ£„μ†‘ν•©λ‹ˆλ‹€. νŒŒν‹° μ°Έμ—¬κ°€ κ±°μ ˆλ˜μ—ˆμŠ΅λ‹ˆλ‹€."; - notificationLogService.saveLog(targetUserId, NotificationType.PARTY_REJECTION.name(), title, detail); + public void sendApprovalNotification(Long targetUserId, String partyTitle, Long partyId) { + // μ°Έμ—¬ 승인 (νŒŒν‹°μ›μ—κ²Œ 전솑) + String title = String.format("🍽️ β€˜[%s]’ νŒŒν‹° 승인 μ™„λ£Œ!", partyTitle); + String detail = "νŒŒν‹° μ±„νŒ…λ°©μ— μž…μž₯ν–ˆμ–΄μš”"; + + notificationLogService.saveLog(targetUserId, NotificationType.PARTY_APPROVAL.name(), title, detail); List tokens = fcmTokenService.getTokensAndLogIfEmpty(targetUserId); - if (tokens.isEmpty()) { - return; - } + if (tokens.isEmpty()) return; + NotificationMulticastRequest request = - partyMessageManager.createRejectionRequest(tokens, partyId, title, detail); + partyMessageManager.createApprovalRequest(tokens, partyId, title, detail); BatchResponse response = fcmNotificationSender.send(request); notificationTransactionHelper.handleBatchResponse(response, tokens); } - /** - * μ•„λž˜ λ©”μ„œλ“œλ“€ νŒŒν‹°μž₯,νŒŒν‹°λ©€λ²„μ˜ μ•Œλ¦Ό λ‚΄μš© λ‹€λ₯Έμ§€μ— 따라 μΆ”ν›„ μˆ˜μ • ν•„μš” - */ - + // @Transactional - public void sendAutoCloseNotification(List memberIds, Long partyId, Long managerId) { - String title = "πŸŽ‰ νŒŒν‹° μžλ™ 마감"; - String memberDetail = "μ°Έμ—¬ 인원이 λͺ¨λ‘ μ°¨μ„œ νŒŒν‹°κ°€ λ§ˆκ°λ˜μ—ˆμŠ΅λ‹ˆλ‹€."; - String managerDetail = "μΆ•ν•˜ν•©λ‹ˆλ‹€! λͺ©ν‘œ 인원 λ‹¬μ„±μœΌλ‘œ νŒŒν‹°κ°€ μžλ™ λ§ˆκ°λ˜μ—ˆμŠ΅λ‹ˆλ‹€."; + public void sendRejectionNotification(Long targetUserId, String partyTitle, Long partyId) { + // μ°Έμ—¬ 거절 (νŒŒν‹°μ›μ—κ²Œ 전솑) + String title = String.format("🍽️ β€˜[%s]’ 😒 μ°Έμ—¬ 거절", partyTitle); + String detail = "μ•„μ‰½κ²Œλ„ 이번 νŒŒν‹°λŠ” ν•¨κ»˜ν•˜μ§€ λͺ»ν•΄μš”"; - memberIds.forEach(userId -> { - String detail = userId.equals(managerId) ? managerDetail : memberDetail; - notificationLogService.saveLog(userId, NotificationType.PARTY_AUTO_CLOSE.name(), title, detail); - }); + notificationLogService.saveLog(targetUserId, NotificationType.PARTY_REJECTION.name(), title, detail); - List tokens = fcmTokenService.getMulticastTokensAndLogIfEmpty(memberIds); - if (tokens.isEmpty()) { - return; - } + List tokens = fcmTokenService.getTokensAndLogIfEmpty(targetUserId); + if (tokens.isEmpty()) return; NotificationMulticastRequest request = - partyMessageManager.createAutoCloseRequest(tokens, partyId, title, memberDetail); + partyMessageManager.createRejectionRequest(tokens, partyId, title, detail); BatchResponse response = fcmNotificationSender.send(request); notificationTransactionHelper.handleBatchResponse(response, tokens); @@ -147,46 +144,46 @@ public void sendDeliveryReminderNotification(List memberIds, Long partyId, } } + // @Transactional - public void sendPartyCompleteNotification(List memberIds, Long partyId) { - String title = "πŸ‘‹ νŒŒν‹° μ’…λ£Œ"; - String detail = "νŒŒν‹°μž₯이 수령 μ™„λ£Œ μ²˜λ¦¬ν–ˆμŠ΅λ‹ˆλ‹€. νŒŒν‹°κ°€ μ’…λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€."; - memberIds.forEach(userId -> - notificationLogService.saveLog(userId, NotificationType.PARTY_COMPLETE.name(), title, detail) - ); + public void sendAutoCloseNotification(List memberIds, String partyTitle, Long partyId, Long managerId) { + String title = String.format("🎯 [%s] 인원 λͺ¨μ§‘ μ™„λ£Œ !", partyTitle); + String detail = "νŒŒν‹°κ°€ μ‹œμž‘ λ˜μ—ˆμ–΄μš”"; + + memberIds.forEach(userId -> { + notificationLogService.saveLog(userId, NotificationType.PARTY_AUTO_CLOSE.name(), title, detail); + }); List tokens = fcmTokenService.getMulticastTokensAndLogIfEmpty(memberIds); - if (tokens.isEmpty()) { - return; - } + if (tokens.isEmpty()) return; NotificationMulticastRequest request = - partyMessageManager.createPartyCompleteRequest(tokens, partyId, title, detail); + partyMessageManager.createAutoCloseRequest(tokens, partyId, title, detail); BatchResponse response = fcmNotificationSender.send(request); notificationTransactionHelper.handleBatchResponse(response, tokens); } + // @Transactional - public void sendNewPartyRequestNotification(Long managerId, Long partyId) { - String title = "πŸ”” μƒˆ μ°Έμ—¬ μš”μ²­"; - String detail = "μƒˆλ‘œμš΄ μ°Έμ—¬ μš”μ²­μ΄ λ„μ°©ν–ˆμŠ΅λ‹ˆλ‹€. μ§€κΈˆ μŠΉμΈν•΄ μ£Όμ„Έμš”."; + public void sendPartyCompleteNotification(List memberIds, String partyTitle, Long partyId) { + String title = String.format("βœ… [%s] νŒŒν‹° μ’…λ£Œ", partyTitle); + String detail = "μ°Έμ—¬ν•΄ μ£Όμ…”μ„œ κ°μ‚¬ν•©λ‹ˆλ‹€"; - notificationLogService.saveLog(managerId, NotificationType.PARTY_NEW_REQUEST.name(), title, detail); + memberIds.forEach(userId -> + notificationLogService.saveLog(userId, NotificationType.PARTY_COMPLETE.name(), title, detail) + ); - List tokens = fcmTokenService.getTokensAndLogIfEmpty(managerId); - if (tokens.isEmpty()) { - return; - } + List tokens = fcmTokenService.getMulticastTokensAndLogIfEmpty(memberIds); + if (tokens.isEmpty()) return; NotificationMulticastRequest request = - partyMessageManager.createNewPartyRequest(tokens, partyId, title, detail); + partyMessageManager.createPartyCompleteRequest(tokens, partyId, title, detail); BatchResponse response = fcmNotificationSender.send(request); notificationTransactionHelper.handleBatchResponse(response, tokens); } - @Transactional public void sendMemberLeaveNotification(Long managerId, Long partyId, String leaverName) { String title = "⚠️ νŒŒν‹°μ› μ΄νƒˆ"; diff --git a/src/main/java/ita/tinybite/domain/notification/service/facade/NotificationFacade.java b/src/main/java/ita/tinybite/domain/notification/service/facade/NotificationFacade.java index d232065..261ff31 100644 --- a/src/main/java/ita/tinybite/domain/notification/service/facade/NotificationFacade.java +++ b/src/main/java/ita/tinybite/domain/notification/service/facade/NotificationFacade.java @@ -7,6 +7,13 @@ import ita.tinybite.domain.notification.service.ChatNotificationService; import ita.tinybite.domain.notification.service.PartyNotificationService; +import ita.tinybite.domain.party.entity.Party; +import ita.tinybite.domain.party.repository.PartyRepository; +import ita.tinybite.domain.user.entity.User; +import ita.tinybite.domain.user.repository.UserRepository; +import ita.tinybite.global.exception.BusinessException; +import ita.tinybite.global.exception.errorcode.PartyErrorCode; +import ita.tinybite.global.exception.errorcode.UserErrorCode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -22,39 +29,65 @@ public class NotificationFacade { private final PartyNotificationService partyNotificationService; private final ChatNotificationService chatNotificationService; + private final PartyRepository partyRepository; + private final UserRepository userRepository; + + @Transactional + public void notifyNewPartyRequest(Long managerId, Long requesterId, Long partyId) { + Party party = partyRepository.findById(partyId) + .orElseThrow(() -> new BusinessException(PartyErrorCode.PARTY_NOT_FOUND)); + + User requester = userRepository.findById(requesterId) + .orElseThrow(() -> new BusinessException(UserErrorCode.USER_NOT_EXISTS)); + + partyNotificationService.sendNewPartyRequestNotification( + managerId, + requester.getNickname(), + party.getTitle(), + partyId + ); + } + @Transactional public void notifyApproval(Long targetUserId, Long partyId) { - partyNotificationService.sendApprovalNotification(targetUserId, partyId); + Party party = partyRepository.findById(partyId) + .orElseThrow(() -> new BusinessException(PartyErrorCode.PARTY_NOT_FOUND)); + partyNotificationService.sendApprovalNotification(targetUserId, party.getTitle(), partyId); } @Transactional public void notifyRejection(Long targetUserId, Long partyId) { - partyNotificationService.sendRejectionNotification(targetUserId, partyId); + Party party = partyRepository.findById(partyId) + .orElseThrow(() -> new BusinessException(PartyErrorCode.PARTY_NOT_FOUND)); + partyNotificationService.sendRejectionNotification(targetUserId, party.getTitle(), partyId); } + // 인원 λͺ¨μ§‘ μ™„λ£Œ @Transactional public void notifyPartyAutoClose(List memberIds, Long partyId, Long managerId) { - partyNotificationService.sendAutoCloseNotification(memberIds, partyId, managerId); - } + Party party = partyRepository.findById(partyId) + .orElseThrow(() -> new BusinessException(PartyErrorCode.PARTY_NOT_FOUND)); - @Transactional - public void notifyOrderComplete(List memberIds, Long partyId) { - partyNotificationService.sendOrderCompleteNotification(memberIds, partyId); + partyNotificationService.sendAutoCloseNotification(memberIds, party.getTitle(), partyId, managerId); } + // νŒŒν‹° μ’…λ£Œ @Transactional - public void notifyDeliveryReminder(List memberIds, Long partyId, Long managerId) { - partyNotificationService.sendDeliveryReminderNotification(memberIds, partyId, managerId); + public void notifyPartyComplete(List memberIds, Long partyId) { + Party party = partyRepository.findById(partyId) + .orElseThrow(() -> new BusinessException(PartyErrorCode.PARTY_NOT_FOUND)); + + partyNotificationService.sendPartyCompleteNotification(memberIds, party.getTitle(), partyId); } @Transactional - public void notifyPartyComplete(List memberIds, Long partyId) { - partyNotificationService.sendPartyCompleteNotification(memberIds, partyId); + public void notifyOrderComplete(List memberIds, Long partyId) { + partyNotificationService.sendOrderCompleteNotification(memberIds, partyId); } @Transactional - public void notifyNewPartyRequest(Long managerId, Long partyId) { - partyNotificationService.sendNewPartyRequestNotification(managerId, partyId); + public void notifyDeliveryReminder(List memberIds, Long partyId, Long managerId) { + partyNotificationService.sendDeliveryReminderNotification(memberIds, partyId, managerId); } @Transactional diff --git a/src/main/java/ita/tinybite/domain/party/repository/PartyParticipantRepository.java b/src/main/java/ita/tinybite/domain/party/repository/PartyParticipantRepository.java index f14d789..e02c6ec 100644 --- a/src/main/java/ita/tinybite/domain/party/repository/PartyParticipantRepository.java +++ b/src/main/java/ita/tinybite/domain/party/repository/PartyParticipantRepository.java @@ -80,4 +80,6 @@ List findActivePartiesByUserIdExcludingHost( ); int countByPartyIdAndStatusAndUser_UserIdNot(Long partyId, ParticipantStatus participantStatus, Long userId); + + List findAllByPartyAndStatus(Party party, ParticipantStatus status); } \ No newline at end of file diff --git a/src/main/java/ita/tinybite/domain/party/service/PartyService.java b/src/main/java/ita/tinybite/domain/party/service/PartyService.java index 4013ac0..c38c7b8 100644 --- a/src/main/java/ita/tinybite/domain/party/service/PartyService.java +++ b/src/main/java/ita/tinybite/domain/party/service/PartyService.java @@ -3,6 +3,7 @@ import ita.tinybite.domain.chat.entity.ChatRoom; import ita.tinybite.domain.chat.enums.ChatRoomType; import ita.tinybite.domain.chat.repository.ChatRoomRepository; +import ita.tinybite.domain.notification.service.facade.NotificationFacade; import ita.tinybite.domain.party.dto.request.PartyCreateRequest; import ita.tinybite.domain.party.dto.request.PartyListRequest; import ita.tinybite.domain.party.dto.request.PartyUpdateRequest; @@ -40,6 +41,7 @@ public class PartyService { private final PartyParticipantRepository partyParticipantRepository; private final ChatRoomRepository chatRoomRepository; private final PartyParticipantRepository participantRepository; + private final NotificationFacade notificationFacade; /** * νŒŒν‹° 생성 @@ -218,6 +220,12 @@ public Long joinParty(Long partyId, Long userId) { PartyParticipant saved = partyParticipantRepository.save(participant); + notificationFacade.notifyNewPartyRequest( + party.getHost().getUserId(), // νŒŒν‹°μž₯ ID + userId, // μ‹ μ²­μž ID + partyId + ); + return saved.getId(); } @@ -428,6 +436,12 @@ public void approveParticipant(Long partyId, Long participantId, Long hostId) { // 단체 μ±„νŒ…λ°©μ— μ°Έμ—¬μž μΆ”κ°€ groupChatRoom.addMember(participant.getUser()); + // 승인 μ•Œλ¦Ό + notificationFacade.notifyApproval( + participant.getUser().getUserId(), + partyId + ); + // λͺ©ν‘œ 인원 달성 확인 checkAndCloseIfFull(party); } @@ -455,6 +469,10 @@ public void rejectParticipant(Long partyId, Long participantId, Long hostId) { participant.getOneToOneChatRoom().deactivate(); } + notificationFacade.notifyRejection( + participant.getUser().getUserId(), + partyId + ); } /** @@ -546,6 +564,15 @@ public void settleParty(Long partyId, Long hostId) { // νŒŒν‹° 마감 party.close(); + + // μ•Œλ¦Ό λŒ€μƒ + List memberIds = partyParticipantRepository.findAllByPartyAndStatus(party, ParticipantStatus.APPROVED) + .stream() + .map(p -> p.getUser().getUserId()) + .toList(); + + // νŒŒν‹° μ’…λ£Œ μ•Œλ¦Ό + notificationFacade.notifyPartyComplete(memberIds, party.getId()); } // ========== Private Methods ========== @@ -614,6 +641,7 @@ private void validateGroupChatRoomAccess(Party party, Long userId) { } } + // ?? private void checkAndCloseIfFull(Party party) { if (party.getCurrentParticipants() >= party.getMaxParticipants()) { party.close(); diff --git a/src/main/java/ita/tinybite/global/exception/errorcode/PartyErrorCode.java b/src/main/java/ita/tinybite/global/exception/errorcode/PartyErrorCode.java new file mode 100644 index 0000000..907bf3a --- /dev/null +++ b/src/main/java/ita/tinybite/global/exception/errorcode/PartyErrorCode.java @@ -0,0 +1,21 @@ +package ita.tinybite.global.exception.errorcode; + +import org.springframework.http.HttpStatus; + +import lombok.Getter; + +@Getter +public enum PartyErrorCode implements ErrorCode { + PARTY_NOT_FOUND(HttpStatus.NOT_FOUND, "PARTY_NOT_FOUND", "νŒŒν‹°λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + PartyErrorCode(HttpStatus httpStatus, String code, String message) { + this.httpStatus = httpStatus; + this.code = code; + this.message = message; + } +} +