diff --git a/backend/src/main/java/com/happy/friendogly/footprint/service/FootprintCommandService.java b/backend/src/main/java/com/happy/friendogly/footprint/service/FootprintCommandService.java index 662373531..09228dd72 100644 --- a/backend/src/main/java/com/happy/friendogly/footprint/service/FootprintCommandService.java +++ b/backend/src/main/java/com/happy/friendogly/footprint/service/FootprintCommandService.java @@ -1,5 +1,8 @@ package com.happy.friendogly.footprint.service; +import static com.happy.friendogly.footprint.domain.WalkStatus.AFTER; +import static com.happy.friendogly.footprint.domain.WalkStatus.ONGOING; + import com.happy.friendogly.exception.FriendoglyException; import com.happy.friendogly.footprint.domain.Footprint; import com.happy.friendogly.footprint.domain.Location; @@ -12,14 +15,11 @@ import com.happy.friendogly.footprint.repository.FootprintRepository; import com.happy.friendogly.member.domain.Member; import com.happy.friendogly.member.repository.MemberRepository; -import com.happy.friendogly.notification.domain.DeviceToken; -import com.happy.friendogly.notification.repository.DeviceTokenRepository; -import com.happy.friendogly.notification.service.NotificationService; +import com.happy.friendogly.notification.service.FootprintNotificationService; import com.happy.friendogly.pet.domain.Pet; import com.happy.friendogly.pet.repository.PetRepository; import java.time.LocalDateTime; import java.util.List; -import java.util.Optional; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -33,21 +33,18 @@ public class FootprintCommandService { private final FootprintRepository footprintRepository; private final MemberRepository memberRepository; private final PetRepository petRepository; - private final NotificationService notificationService; - private final DeviceTokenRepository deviceTokenRepository; + private final FootprintNotificationService footprintNotificationService; public FootprintCommandService( FootprintRepository footprintRepository, MemberRepository memberRepository, PetRepository petRepository, - NotificationService notificationService, - DeviceTokenRepository deviceTokenRepository + FootprintNotificationService footprintNotificationService ) { this.footprintRepository = footprintRepository; this.memberRepository = memberRepository; this.petRepository = petRepository; - this.notificationService = notificationService; - this.deviceTokenRepository = deviceTokenRepository; + this.footprintNotificationService = footprintNotificationService; } public SaveFootprintResponse save(Long memberId, SaveFootprintRequest request) { @@ -67,9 +64,9 @@ public SaveFootprintResponse save(Long memberId, SaveFootprintRequest request) { .build() ); - List nearDeviceTokens = findNearDeviceTokensWithoutMine(footprint, member); + List nearFootprints = findNearFootprintsWithoutMine(footprint, member); String comingMemberName = member.getName().getValue(); - sendWalkComingNotification(comingMemberName, nearDeviceTokens); + footprintNotificationService.sendWalkComingNotification(comingMemberName, nearFootprints); return new SaveFootprintResponse( footprint.getId(), @@ -97,14 +94,6 @@ private void validateRecentFootprintExists(Long memberId) { } } - private void sendWalkComingNotification(String memberName, List nearDeviceTokens) { - notificationService.sendFootprintNotification( - "반갑개", - "내 산책 장소에 " + memberName + "님도 산책온대요!", - nearDeviceTokens - ); - } - public UpdateWalkStatusAutoResponse updateWalkStatusAuto(Long memberId, UpdateWalkStatusAutoRequest request) { Footprint footprint = footprintRepository.getTopOneByMemberIdOrderByCreatedAtDesc(memberId); if (footprint.isDeleted()) { @@ -115,22 +104,25 @@ public UpdateWalkStatusAutoResponse updateWalkStatusAuto(Long memberId, UpdateWa footprint.updateWalkStatusWithCurrentLocation(new Location(request.latitude(), request.longitude())); + Member walkStartMember = memberRepository.getById(memberId); if (beforeWalkStatus.isBefore() && footprint.getWalkStatus().isOngoing()) { - Member startWalkMember = memberRepository.getById(memberId); - List nearDeviceTokens = findNearDeviceTokensWithoutMine(footprint, startWalkMember); - String memberName = footprint.getMember().getName().getValue(); - sendWalkStartNotification(memberName, nearDeviceTokens); + List nearFootprints = findNearFootprintsWithoutMine(footprint, walkStartMember); + String memberName = walkStartMember.getName().getValue(); + footprintNotificationService.sendWalkStartNotificationToNear(memberName, nearFootprints); + footprintNotificationService.sendWalkNotificationToMe( + memberId, + ONGOING + ); } - return new UpdateWalkStatusAutoResponse(footprint.getWalkStatus(), footprint.findChangedWalkStatusTime()); - } + if (beforeWalkStatus.isOngoing() && footprint.getWalkStatus().isAfter()) { + footprintNotificationService.sendWalkNotificationToMe( + memberId, + AFTER + ); + } - private void sendWalkStartNotification(String startMemberName, List nearDeviceTokens) { - notificationService.sendFootprintNotification( - "반갑개", - "내 산책장소에 " + startMemberName + "님이 산책을 시작했어요!", - nearDeviceTokens - ); + return new UpdateWalkStatusAutoResponse(footprint.getWalkStatus(), footprint.findChangedWalkStatusTime()); } public UpdateWalkStatusManualResponse updateWalkStatusManual(Long memberId) { @@ -147,7 +139,7 @@ public void delete(Long memberId, Long footprintId) { footprint.updateToDeleted(); } - private List findNearDeviceTokensWithoutMine(Footprint standardFootprint, Member member) { + private List findNearFootprintsWithoutMine(Footprint standardFootprint, Member member) { LocalDateTime startTime = LocalDateTime.now().minusHours(FOOTPRINT_DURATION_HOURS); List footprints = footprintRepository.findAllByIsDeletedFalseAndCreatedAtAfter(startTime); @@ -155,9 +147,6 @@ private List findNearDeviceTokensWithoutMine(Footprint standardFootprint .filter(otherFootprint -> otherFootprint.isInsideBoundary(standardFootprint.getLocation()) && otherFootprint.getMember() != member && !otherFootprint.getWalkStatus().isAfter()) - .map(otherFootprint -> deviceTokenRepository.findByMemberId(otherFootprint.getMember().getId())) - .flatMap(Optional::stream) - .map(DeviceToken::getDeviceToken) .toList(); } } diff --git a/backend/src/main/java/com/happy/friendogly/notification/repository/DeviceTokenRepository.java b/backend/src/main/java/com/happy/friendogly/notification/repository/DeviceTokenRepository.java index 407994e72..7d12ae7b5 100644 --- a/backend/src/main/java/com/happy/friendogly/notification/repository/DeviceTokenRepository.java +++ b/backend/src/main/java/com/happy/friendogly/notification/repository/DeviceTokenRepository.java @@ -1,5 +1,6 @@ package com.happy.friendogly.notification.repository; +import com.happy.friendogly.exception.FriendoglyException; import com.happy.friendogly.notification.domain.DeviceToken; import java.util.List; import java.util.Optional; @@ -11,6 +12,10 @@ public interface DeviceTokenRepository extends JpaRepository Optional findByMemberId(Long memberId); + default DeviceToken getByMemberId(Long memberId) { + return findByMemberId(memberId).orElseThrow(() -> new FriendoglyException("해당 멤버의 디바이스 토큰을 찾지 못했습니다.")); + } + @Query(""" SELECT dt.deviceToken FROM DeviceToken dt diff --git a/backend/src/main/java/com/happy/friendogly/notification/service/FootprintNotificationService.java b/backend/src/main/java/com/happy/friendogly/notification/service/FootprintNotificationService.java new file mode 100644 index 000000000..57dbc688c --- /dev/null +++ b/backend/src/main/java/com/happy/friendogly/notification/service/FootprintNotificationService.java @@ -0,0 +1,71 @@ +package com.happy.friendogly.notification.service; + +import com.happy.friendogly.footprint.domain.Footprint; +import com.happy.friendogly.footprint.domain.WalkStatus; +import com.happy.friendogly.notification.domain.DeviceToken; +import com.happy.friendogly.notification.repository.DeviceTokenRepository; +import java.util.List; +import java.util.Optional; +import org.springframework.stereotype.Service; + +@Service +public class FootprintNotificationService { + + private final String DEFAULT_TITLE = "반갑개"; + private final NotificationService notificationService; + private final DeviceTokenRepository deviceTokenRepository; + + public FootprintNotificationService( + NotificationService notificationService, + DeviceTokenRepository deviceTokenRepository + ) { + this.notificationService = notificationService; + this.deviceTokenRepository = deviceTokenRepository; + } + + public void sendWalkNotificationToMe(Long memberId, WalkStatus currentWalkStatus) { + String content = null; + if (currentWalkStatus.isOngoing()) { + content = "산책을 시작했습니다!"; + } + if (currentWalkStatus.isAfter()) { + content = "산책을 종료했습니다!"; + } + if (currentWalkStatus.isBefore()) { + return; + } + + notificationService.sendFootprintNotification( + DEFAULT_TITLE, + content, + deviceTokenRepository.getByMemberId(memberId).getDeviceToken() + ); + } + + public void sendWalkComingNotification(String comingMemberName, List nearFootprints) { + List nearDeviceTokens = toDeviceToken(nearFootprints); + + notificationService.sendFootprintNotification( + DEFAULT_TITLE, + "내 산책 장소에 " + comingMemberName + "님도 산책온대요!", + nearDeviceTokens + ); + } + + public void sendWalkStartNotificationToNear(String startMemberName, List nearFootprints) { + List nearDeviceTokens = toDeviceToken(nearFootprints); + + notificationService.sendFootprintNotification(DEFAULT_TITLE, + "내 산책장소에 " + startMemberName + "님이 산책을 시작했어요!", + nearDeviceTokens + ); + } + + private List toDeviceToken(List footprints) { + return footprints.stream() + .map(otherFootprint -> deviceTokenRepository.findByMemberId(otherFootprint.getMember().getId())) + .flatMap(Optional::stream) + .map(DeviceToken::getDeviceToken) + .toList(); + } +}