Skip to content

Commit

Permalink
[DPMBE-128] 유저 도착 이벤트를 받아 FCM Message 발송과 알림을 저장한다 (#220)
Browse files Browse the repository at this point in the history
* feat: 유저 도착 이벤트를 받아 FCM Message 발송과 알림을 저장한다

* feat: 유저 도착 알림을 조회 리스트에 추가한다

* fix: fcmToken null 체크 always true 문제 해결

* test: 테스트 Mock 객체 추가
  • Loading branch information
kdomo authored Jul 20, 2023
1 parent 32b42e4 commit a36acb9
Show file tree
Hide file tree
Showing 11 changed files with 132 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.depromeet.whatnow.api.notification.dto

import com.depromeet.whatnow.domains.notification.domain.ArrivalNotification
import com.depromeet.whatnow.domains.notification.domain.NotificationType
import java.time.LocalDateTime

class ArrivalNotificationResponse(
val promiseId: Long,
val senderUserId: Long,
override val createdAt: LocalDateTime,
) : NotificationAbstract(NotificationType.START_SHARING, createdAt) {
companion object {
fun from(notification: ArrivalNotification): ArrivalNotificationResponse {
return ArrivalNotificationResponse(
notification.promiseId,
notification.senderUserId,
notification.createdAt,
)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.depromeet.whatnow.api.notification.usecase

import com.depromeet.whatnow.annotation.UseCase
import com.depromeet.whatnow.api.notification.dto.ArrivalNotificationResponse
import com.depromeet.whatnow.api.notification.dto.EndSharingNotificationResponse
import com.depromeet.whatnow.api.notification.dto.ImageNotificationResponse
import com.depromeet.whatnow.api.notification.dto.InteractionAttainmentNotificationResponse
Expand All @@ -9,6 +10,7 @@ import com.depromeet.whatnow.api.notification.dto.NotificationResponse
import com.depromeet.whatnow.api.notification.dto.StartSharingNotificationResponse
import com.depromeet.whatnow.api.notification.dto.TimeOverNotificationResponse
import com.depromeet.whatnow.config.security.SecurityUtils
import com.depromeet.whatnow.domains.notification.domain.ArrivalNotification
import com.depromeet.whatnow.domains.notification.domain.EndSharingNotification
import com.depromeet.whatnow.domains.notification.domain.ImageNotification
import com.depromeet.whatnow.domains.notification.domain.InteractionAttainmentNotification
Expand Down Expand Up @@ -46,6 +48,9 @@ class NotificationReadUseCase(
is TimeOverNotification -> {
TimeOverNotificationResponse.from(notification)
}
is ArrivalNotification -> {
ArrivalNotificationResponse.from(notification)
}
else -> throw UnknownNotificationTypeException.EXCEPTION
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.depromeet.whatnow.domains.notification.domain

import javax.persistence.DiscriminatorValue
import javax.persistence.Entity

@Entity
@DiscriminatorValue("ARRIVAL")
class ArrivalNotification(
var promiseId: Long,

var senderUserId: Long,

override var targetUserId: Long,
) : Notification(targetUserId)
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.depromeet.whatnow.domains.notification.service

import com.depromeet.whatnow.domains.interaction.domain.InteractionType
import com.depromeet.whatnow.domains.notification.adapter.NotificationAdapter
import com.depromeet.whatnow.domains.notification.domain.ArrivalNotification
import com.depromeet.whatnow.domains.notification.domain.EndSharingNotification
import com.depromeet.whatnow.domains.notification.domain.ImageNotification
import com.depromeet.whatnow.domains.notification.domain.InteractionAttainmentNotification
Expand Down Expand Up @@ -49,6 +50,10 @@ class NotificationDomainService(
notificationAdapter.save(InteractionAttainmentNotification(promiseId, senderUserId, interactionType, targetUserId))
}

fun saveForArrival(promiseId: Long, senderUserId: Long, targetUserId: Long) {
notificationAdapter.save(ArrivalNotification(promiseId, senderUserId, targetUserId))
}

@Transactional(readOnly = true)
fun getMyNotifications(userId: Long, pageable: Pageable): Slice<Notification> {
return notificationAdapter.getMyNotifications(userId, pageable)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ import com.depromeet.whatnow.common.aop.event.DomainEvent
class PromiseUserUpdateLocationEvent(
val promiseId: Long,
val userId: Long,
val id: Long,
val promiseUserId: Long,
) : DomainEvent()
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class ImageRegisterEventHandler(

// 앱 알람 허용한 유저
val appAlarmPermitUsers = usersExcludingSelf.filter { user ->
user.fcmNotification.fcmToken != null && user.fcmNotification.appAlarm
user.fcmNotification.fcmToken != "" && user.fcmNotification.appAlarm
}

val data: MutableMap<String, String> = mutableMapOf()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class PromiseTimeEndEventHandler(

// 앱 알람 허용한 유저
val appAlarmPermitUsers = users
.filter { user -> user.fcmNotification.fcmToken != null && user.fcmNotification.appAlarm }
.filter { user -> user.fcmNotification.fcmToken != "" && user.fcmNotification.appAlarm }

val lateData: MutableMap<String, String> = mutableMapOf()
lateData["notificationType"] = NotificationType.TIMEOVER.name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class PromiseTimeStartEventHandler(

// 앱 알람 허용한 유저
val appAlarmPermitUsers = users
.filter { user -> user.fcmNotification.fcmToken != null && user.fcmNotification.appAlarm }
.filter { user -> user.fcmNotification.fcmToken != "" && user.fcmNotification.appAlarm }

val data: MutableMap<String, String> = mutableMapOf()
data["notificationType"] = NotificationType.START_SHARING.name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class PromiseTrackingTimeEndEventHandler(

// 앱 알람 허용한 유저
val appAlarmPermitUsers = users
.filter { user -> user.fcmNotification.fcmToken != null && user.fcmNotification.appAlarm }
.filter { user -> user.fcmNotification.fcmToken != "" && user.fcmNotification.appAlarm }

val data: MutableMap<String, String> = mutableMapOf()
data["notificationType"] = NotificationType.END_SHARING.name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@ package com.depromeet.whatnow.events.handler

import ch.hsr.geohash.GeoHash
import com.depromeet.whatnow.common.vo.CoordinateVo
import com.depromeet.whatnow.config.fcm.FcmService
import com.depromeet.whatnow.consts.RADIUS_CONVERT_METER
import com.depromeet.whatnow.domains.district.repository.DistrictRepository
import com.depromeet.whatnow.domains.notification.domain.NotificationType
import com.depromeet.whatnow.domains.notification.service.NotificationDomainService
import com.depromeet.whatnow.domains.promise.adaptor.PromiseAdaptor
import com.depromeet.whatnow.domains.promiseuser.adaptor.PromiseUserAdaptor
import com.depromeet.whatnow.domains.promiseuser.service.PromiseUserDomainService
import com.depromeet.whatnow.domains.user.adapter.UserAdapter
import com.depromeet.whatnow.events.domainEvent.PromiseUserUpdateLocationEvent
import com.mongodb.client.model.geojson.Point
import com.mongodb.client.model.geojson.Position
import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Propagation
Expand All @@ -18,6 +25,10 @@ class PromiseUserUpdateLocationEventHandler(
val promiseAdaptor: PromiseAdaptor,
val promiseUserAdaptor: PromiseUserAdaptor,
val promiseUserDomainService: PromiseUserDomainService,
val userAdapter: UserAdapter,
val fcmService: FcmService,
val notificationDomainService: NotificationDomainService,
val districtRepository: DistrictRepository,
) {
// logger

Expand All @@ -26,14 +37,59 @@ class PromiseUserUpdateLocationEventHandler(
@TransactionalEventListener(classes = [PromiseUserUpdateLocationEvent::class], phase = TransactionPhase.AFTER_COMMIT)
fun handlerPromiseUserUpdateLocationEventHandler(promiseUserUpdateLocationEvent: PromiseUserUpdateLocationEvent) {
val promiseId = promiseUserUpdateLocationEvent.promiseId
val userId = promiseUserUpdateLocationEvent.userId
val promiseUserId = promiseUserUpdateLocationEvent.promiseUserId

val promise = promiseAdaptor.queryPromise(promiseId)
val promiseUser = promiseUserAdaptor.findByPromiseIdAndUserId(promiseUserUpdateLocationEvent.promiseId, promiseUserUpdateLocationEvent.userId)
val promiseUser = promiseUserAdaptor.findByPromiseIdAndUserId(promiseId, userId)
// if (promiseUserDomainService.isArrived(promiseUser, promise.meetPlace!!.coordinate)) {
if (isArrived(promiseUser.userLocation, promise.meetPlace!!.coordinate)) {
// 활성화된 약속이 종료되기 전일 때
if (promise.isBeforePromiseEndTime() && promise.isActive()) {
// 약속유저 상태 도착 상태(WAIT)로 변경
promiseUser.updatePromiseUserTypeToWait()

val promiseUsers = promiseUserAdaptor.findByPromiseId(promiseId)

// 약속에 참여한 유저들 조회 (자기 자신 제외)
val users = promiseUsers
.map { promiseUser -> userAdapter.queryUser(promiseUser.userId) }
.filter { user -> user.id != userId }

// 도착한 유저
val arrivalUser = userAdapter.queryUser(userId)

// 앱 알람 허용한 유저
val appAlarmPermitUsers = users
.filter { user -> user.fcmNotification.fcmToken != "" && user.fcmNotification.appAlarm }

val data: MutableMap<String, String> = mutableMapOf()
data["notificationType"] = NotificationType.ARRIVAL.name
data["promiseId"] = promiseId.toString()
data["senderUserId"] = promiseUserUpdateLocationEvent.userId.toString()

// 행정동 조회
val intersects = districtRepository.findByLocationIntersects(
Point(
Position(promise.meetPlace!!.coordinate.longitude, promise.meetPlace!!.coordinate.latitude),
),
)
val district = intersects.properties.adm_nm

// 앱 알람 허용한 유저에게 알람 보내기
if (appAlarmPermitUsers.isNotEmpty()) {
fcmService.sendGroupMessageAsync(
tokenList = appAlarmPermitUsers.map { user -> user.fcmNotification.fcmToken!! },
title = "${arrivalUser.nickname} 도착완료",
content = "\uD83C\uDFC1 $district",
data = data,
)
}

// notification 저장
users.forEach { user ->
notificationDomainService.saveForArrival(promiseId, arrivalUser.id!!, user.id!!)
}
}
// 활성화된 약속이 종료되고 나서 도착 시 사전에 LATE처리를 해서 별도의 처리가 필요 없음
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ package com.depromeet.whatnow.events.handler

import com.depromeet.whatnow.common.vo.CoordinateVo
import com.depromeet.whatnow.config.DomainIntegrateSpringBootTest
import com.depromeet.whatnow.config.fcm.FcmService
import com.depromeet.whatnow.domains.district.repository.DistrictRepository
import com.depromeet.whatnow.domains.notification.service.NotificationDomainService
import com.depromeet.whatnow.domains.promise.adaptor.PromiseAdaptor
import com.depromeet.whatnow.domains.promiseuser.adaptor.PromiseUserAdaptor
import com.depromeet.whatnow.domains.promiseuser.domain.PromiseUser
import com.depromeet.whatnow.domains.promiseuser.domain.PromiseUserType
import com.depromeet.whatnow.domains.promiseuser.service.PromiseUserDomainService
import com.depromeet.whatnow.domains.user.adapter.UserAdapter
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
Expand All @@ -24,12 +28,32 @@ class PromiseUserUpdateLocationEventHandlerTest {
@Mock
lateinit var promiseUserDomainService: PromiseUserDomainService

@Mock
lateinit var userAdapter: UserAdapter

@Mock
lateinit var fcmService: FcmService

@Mock
lateinit var notificationDomainService: NotificationDomainService

@Mock
lateinit var districtRepository: DistrictRepository

@Autowired
lateinit var promiseUserUpdateLocationEventHandler: PromiseUserUpdateLocationEventHandler

@BeforeEach
fun setUp() {
promiseUserUpdateLocationEventHandler = PromiseUserUpdateLocationEventHandler(promiseAdaptor, promiseUserAdaptor, promiseUserDomainService)
promiseUserUpdateLocationEventHandler = PromiseUserUpdateLocationEventHandler(
promiseAdaptor,
promiseUserAdaptor,
promiseUserDomainService,
userAdapter,
fcmService,
notificationDomainService,
districtRepository,
)
}

@Test
Expand Down

0 comments on commit a36acb9

Please sign in to comment.