From 5484604247b58686fd4e6f229bcbb23f3792d386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9C=A0=EC=A0=95?= Date: Sun, 22 Dec 2024 17:56:50 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EB=AF=B8=EC=85=98=20=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=20=ED=91=B8=EC=8B=9C=20=EC=95=8C=EB=A6=BC=20(#114)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 미션 삭제 시 호스트에게 알림이 가지 않도록 푸시 알림을 보내기 전에 구독 취소 * comment: 디버깅용 로그 추가 --- .../device/DeviceSubscriptionService.java | 23 ++++++++++++++++++- .../mission/MissionMemberService.java | 7 ++++++ .../application/mission/MissionService.java | 4 +++- .../mission/event/DeleteMissionEvent.java | 1 + .../handler/MissionMemberEventHandler.java | 3 +++ .../goalpanzi/domain/device/Devices.java | 6 +++++ .../DeviceSubscriptionRepository.java | 13 +++++++---- .../device/DeviceSubscriptionServiceTest.java | 20 ++++++++++++++++ .../DeviceSubscriptionRepositoryTest.java | 19 +++++++++++++++ 9 files changed, 90 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/nexters/goalpanzi/application/device/DeviceSubscriptionService.java b/src/main/java/com/nexters/goalpanzi/application/device/DeviceSubscriptionService.java index 2b49831e..3c73ff8d 100644 --- a/src/main/java/com/nexters/goalpanzi/application/device/DeviceSubscriptionService.java +++ b/src/main/java/com/nexters/goalpanzi/application/device/DeviceSubscriptionService.java @@ -69,7 +69,7 @@ public void subscribeToMission(final Long memberId, final Mission mission) { * 미션 취소/종료 시 미션 구독 취소
* 해당 미션을 구독한 디바이스를 대상으로 구독 취소 * - * @param missionId 취소/종료된 미션 + * @param missionId 취소/종료된 미션 아이디 */ @Transactional public void unsubscribeFromMission(final Long missionId) { @@ -79,6 +79,27 @@ public void unsubscribeFromMission(final Long missionId) { topicSubscriber.unsubscribeFromTopic(deviceTokens, TopicGenerator.getTopic(missionId)); } + /** + * 미션 삭제 시 호스트의 미션 구독 취소
+ * 삭제 푸시 알림을 보내기 전, 호스트는 구독을 취소하여 푸시 알림이 가지 않도록 처리 + * + * @param memberId 호스트 멤버 아이디 + * @param missionId 호스트가 삭제한 미션 아이디 + */ + @Transactional + public void unsubscribeFromDeletedMissionForHost(final Long memberId, final Long missionId) { + String topic = TopicGenerator.getTopic(missionId); + Devices devices = new Devices( + deviceRepository.findAllByMemberId(memberId) + ); + + deviceSubscriptionRepository.findAllWithDeviceByMissionIdAndDeviceIds(missionId, devices.getActivatedDeviceIds()) + .forEach(it -> { + topicSubscriber.unsubscribeFromTopic(List.of(it.getDevice().getDeviceToken()), topic); + deviceSubscriptionRepository.deleteById(it.getId()); + }); + } + private List findTopicSubscribers(final Long missionId) { List subscriptions = deviceSubscriptionRepository.findAllWithDeviceAndMissionByMissionId(missionId); diff --git a/src/main/java/com/nexters/goalpanzi/application/mission/MissionMemberService.java b/src/main/java/com/nexters/goalpanzi/application/mission/MissionMemberService.java index d5a30671..7b3ecb08 100644 --- a/src/main/java/com/nexters/goalpanzi/application/mission/MissionMemberService.java +++ b/src/main/java/com/nexters/goalpanzi/application/mission/MissionMemberService.java @@ -20,6 +20,7 @@ import com.nexters.goalpanzi.exception.NotFoundException; import com.nexters.goalpanzi.infrastructure.firebase.PushMessageSender; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -32,6 +33,7 @@ import static com.nexters.goalpanzi.domain.firebase.PushMessage.MISSION_CANCELLATION_WARNING; import static com.nexters.goalpanzi.domain.firebase.PushMessage.MISSION_READY; +@Slf4j @Transactional(readOnly = true) @RequiredArgsConstructor @Service @@ -82,7 +84,12 @@ private void validateAlreadyJoin(final Member member, final Mission mission) { }); } + // FIXME: 호스트에게 알림 가는 데 확인 필요 private void sendJoinPushMessage(final Member member, final Mission mission) { + // TODO: 오류 확인 후 삭제 + log.info("Host member: " + mission.getHostMemberId()); + log.info("Join member: " + member.getId()); + if (mission.isHostMember(member.getId())) { return; } diff --git a/src/main/java/com/nexters/goalpanzi/application/mission/MissionService.java b/src/main/java/com/nexters/goalpanzi/application/mission/MissionService.java index f8ad37b3..c5a420d2 100644 --- a/src/main/java/com/nexters/goalpanzi/application/mission/MissionService.java +++ b/src/main/java/com/nexters/goalpanzi/application/mission/MissionService.java @@ -76,7 +76,9 @@ public void deleteMission(final Long memberId, final Long missionId) { Mission mission = missionRepository.getMission(missionId); validateAuthority(memberId, mission); mission.delete(); - eventPublisher.publishEvent(new DeleteMissionEvent(mission.getId())); + eventPublisher.publishEvent( + new DeleteMissionEvent(memberId, mission.getId()) + ); } private void validateAuthority(final Long memberId, final Mission mission) { diff --git a/src/main/java/com/nexters/goalpanzi/application/mission/event/DeleteMissionEvent.java b/src/main/java/com/nexters/goalpanzi/application/mission/event/DeleteMissionEvent.java index 7b0ba6dc..1cb2c8d7 100644 --- a/src/main/java/com/nexters/goalpanzi/application/mission/event/DeleteMissionEvent.java +++ b/src/main/java/com/nexters/goalpanzi/application/mission/event/DeleteMissionEvent.java @@ -1,6 +1,7 @@ package com.nexters.goalpanzi.application.mission.event; public record DeleteMissionEvent( + Long memberId, Long missionId ) { } diff --git a/src/main/java/com/nexters/goalpanzi/application/mission/event/handler/MissionMemberEventHandler.java b/src/main/java/com/nexters/goalpanzi/application/mission/event/handler/MissionMemberEventHandler.java index 7ec1e4c0..a50f1ead 100644 --- a/src/main/java/com/nexters/goalpanzi/application/mission/event/handler/MissionMemberEventHandler.java +++ b/src/main/java/com/nexters/goalpanzi/application/mission/event/handler/MissionMemberEventHandler.java @@ -1,5 +1,6 @@ package com.nexters.goalpanzi.application.mission.event.handler; +import com.nexters.goalpanzi.application.device.DeviceSubscriptionService; import com.nexters.goalpanzi.application.firebase.TopicGenerator; import com.nexters.goalpanzi.application.member.event.DeleteMemberEvent; import com.nexters.goalpanzi.application.mission.MissionMemberService; @@ -30,6 +31,7 @@ public class MissionMemberEventHandler { private final MissionMemberService missionMemberService; private final MissionVerificationService missionVerificationService; private final MissionRetryPushMessageService missionRetryPushMessageService; + private final DeviceSubscriptionService deviceSubscriptionService; private final PushMessageSender pushMessageSender; @@ -59,6 +61,7 @@ void handleDeleteMissionEvent(final DeleteMissionEvent event) { Map data = new HashMap<>(); data.put("missionId", event.missionId().toString()); + deviceSubscriptionService.unsubscribeFromDeletedMissionForHost(event.memberId(), event.missionId()); pushMessageSender.sendGroupNotificationWithData( MISSION_DELETED.getTitle(), MISSION_DELETED.getBody(), diff --git a/src/main/java/com/nexters/goalpanzi/domain/device/Devices.java b/src/main/java/com/nexters/goalpanzi/domain/device/Devices.java index 8a74ec87..cc4aa469 100644 --- a/src/main/java/com/nexters/goalpanzi/domain/device/Devices.java +++ b/src/main/java/com/nexters/goalpanzi/domain/device/Devices.java @@ -15,6 +15,12 @@ public List getActivatedDevices() { .toList(); } + public List getActivatedDeviceIds() { + return getActivatedDevices().stream() + .map(Device::getId) + .toList(); + } + public List getActivatedDeviceTokens() { return getActivatedDevices().stream() .map(Device::getDeviceToken) diff --git a/src/main/java/com/nexters/goalpanzi/domain/device/repository/DeviceSubscriptionRepository.java b/src/main/java/com/nexters/goalpanzi/domain/device/repository/DeviceSubscriptionRepository.java index 68abf943..9bd47dc6 100644 --- a/src/main/java/com/nexters/goalpanzi/domain/device/repository/DeviceSubscriptionRepository.java +++ b/src/main/java/com/nexters/goalpanzi/domain/device/repository/DeviceSubscriptionRepository.java @@ -9,16 +9,21 @@ public interface DeviceSubscriptionRepository extends JpaRepository { @Query("SELECT ds FROM DeviceSubscription ds" - + " JOIN FETCH ds.mission JOIN FETCH ds.device" - + " WHERE ds.device.id = :deviceId" + + " JOIN FETCH ds.mission JOIN FETCH ds.device dd" + + " WHERE dd.id = :deviceId" ) List findAllWithMissionAndDeviceByDeviceId(final Long deviceId); @Query("SELECT ds FROM DeviceSubscription ds" - + " JOIN FETCH ds.device JOIN FETCH ds.mission" - + " WHERE ds.mission.id = :missionId" + + " JOIN FETCH ds.device JOIN FETCH ds.mission dm" + + " WHERE dm.id = :missionId" ) List findAllWithDeviceAndMissionByMissionId(final Long missionId); + @Query("SELECT ds FROM DeviceSubscription ds" + + " JOIN FETCH ds.device dd" + + " WHERE ds.mission.id = :missionId AND dd.id IN :deviceIds") + List findAllWithDeviceByMissionIdAndDeviceIds(final Long missionId, final List deviceIds); + void deleteAllByMissionId(final Long missionId); } diff --git a/src/test/java/com/nexters/goalpanzi/application/device/DeviceSubscriptionServiceTest.java b/src/test/java/com/nexters/goalpanzi/application/device/DeviceSubscriptionServiceTest.java index 203c3e79..558c90d7 100644 --- a/src/test/java/com/nexters/goalpanzi/application/device/DeviceSubscriptionServiceTest.java +++ b/src/test/java/com/nexters/goalpanzi/application/device/DeviceSubscriptionServiceTest.java @@ -111,6 +111,26 @@ void setUp() { .unsubscribeFromTopic(List.of(DEVICE_TOKEN), TopicGenerator.getTopic(MISSION_ID)); } + @Test + void 미션_호스트는_삭제한_미션에_대해_구독을_해지한다() { + Device mockDevice = mock(Device.class); + when(mockDevice.getId()).thenReturn(DEVICE_ID); + when(mockDevice.getDeviceToken()).thenReturn(DEVICE_TOKEN); + when(mockDevice.getPushActivationStatus()).thenReturn(true); + + DeviceSubscription mockDeviceSubscription = mock(DeviceSubscription.class); + when(mockDeviceSubscription.getDevice()).thenReturn(mockDevice); + + when(deviceRepository.findAllByMemberId(MEMBER_ID)).thenReturn(List.of(mockDevice)); + when(deviceSubscriptionRepository.findAllWithDeviceByMissionIdAndDeviceIds(MISSION_ID, List.of(mockDevice.getId()))) + .thenReturn(List.of(mockDeviceSubscription)); + + deviceSubscriptionService.unsubscribeFromDeletedMissionForHost(MEMBER_ID, MISSION_ID); + + verify(topicSubscriber) + .unsubscribeFromTopic(List.of(DEVICE_TOKEN), TopicGenerator.getTopic(MISSION_ID)); + } + @Test void 구독했거나_구독_가능한_미션을_찾아_구독을_시작한다() { Long SUBSCRIBED_MISSION_ID = 1L; diff --git a/src/test/java/com/nexters/goalpanzi/domain/device/repository/DeviceSubscriptionRepositoryTest.java b/src/test/java/com/nexters/goalpanzi/domain/device/repository/DeviceSubscriptionRepositoryTest.java index a99f40b5..ccae305e 100644 --- a/src/test/java/com/nexters/goalpanzi/domain/device/repository/DeviceSubscriptionRepositoryTest.java +++ b/src/test/java/com/nexters/goalpanzi/domain/device/repository/DeviceSubscriptionRepositoryTest.java @@ -98,4 +98,23 @@ void setUp() { List subscriptions = deviceSubscriptionRepository.findAllWithDeviceAndMissionByMissionId(mission.getId()); assertThat(subscriptions.size()).isEqualTo(0); } + + @Test + void 특정_디바이스들이_구독한_특정_미션_구독_현황을_디바이스와_함께_조회한다() { + Device device1 = deviceRepository.save(new Device(member, "deviceIdentifier1", "deviceToken2", OsType.AOS)); + Device device2 = deviceRepository.save(new Device(member, "deviceIdentifier2", "deviceToken2", OsType.AOS)); + + deviceSubscriptionRepository.save(new DeviceSubscription(device1, mission)); + deviceSubscriptionRepository.save(new DeviceSubscription(device2, mission)); + + List subscriptions = deviceSubscriptionRepository.findAllWithDeviceByMissionIdAndDeviceIds( + mission.getId(), + List.of(device1.getId(), device2.getId()) + ); + assertAll( + () -> assertThat(subscriptions.size()).isEqualTo(2), + () -> assertThat(subscriptions.stream().map(DeviceSubscription::getDevice)) + .containsExactlyInAnyOrder(device1, device2) + ); + } } \ No newline at end of file