Skip to content
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

merge: (#523) topic 구독 설정 저장 및 조회 api #524

Merged
merged 25 commits into from
Jun 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8beeb09
feat: (#523) topic 구독 조회 api
rlaisqls Jun 16, 2023
d266841
feat: (#523) topicSubscribe Domain & Entity setting
rlaisqls Jun 16, 2023
db8c6fa
feat: (#523) tokenSubscribe 저장 로직 추가
rlaisqls Jun 16, 2023
bd88fc9
chore: (#523) readOnly 제거
rlaisqls Jun 16, 2023
d6fb0f8
feat: (#523) topic group
rlaisqls Jun 17, 2023
4f763af
refactor: (#523) 직관적인 변수명으로 변경
rlaisqls Jun 18, 2023
2dd47d2
refactor: (#523) TopicGroupsResponse 반환 로직 가독성 개선
rlaisqls Jun 18, 2023
8ba2c01
chore: (#523) 불필요한 MapsId 삭제
rlaisqls Jun 18, 2023
9f39a1f
chore: (#523) deviceTokenId 기준으로 조회
rlaisqls Jun 18, 2023
e6c9be1
chore: (#523) readonly 삭제
rlaisqls Jun 18, 2023
b12fb7b
chore: (#523) 문구 수정
rlaisqls Jun 18, 2023
c7bc56f
chore: (#523) delete query 수정
rlaisqls Jun 18, 2023
671a2ca
chore: (#523) saveAll시 select 쿼리 묶어서 실행
rlaisqls Jun 18, 2023
ede38d8
feat: (#523) isSubscribed 추가
rlaisqls Jun 18, 2023
0e84a51
chore: (#523) string devicetoken 파라미터명 token으로 통일
rlaisqls Jun 19, 2023
88e4944
chore: (#523) detekt
rlaisqls Jun 19, 2023
b3f810d
chore: (#523) 실수로 커밋한 부분 삭제
rlaisqls Jun 19, 2023
da54f11
chore: (#523) subscribe, unsubscribe 함수 분리
rlaisqls Jun 19, 2023
cbf5b61
chore: (#523) 쓰이지 않는 메서드 삭제
rlaisqls Jun 19, 2023
871bb15
chore: (#523) groupName 추가
rlaisqls Jun 19, 2023
4f2bbae
chore: (#523) 함수 분리
rlaisqls Jun 19, 2023
3335aea
chore: (#523) 불필요한 조회 삭제
rlaisqls Jun 20, 2023
d5b3b40
chore: (#523) order_updates=true
rlaisqls Jun 20, 2023
d9fc21f
feat: (#523) topicSubscription
rlaisqls Jun 20, 2023
45e42f9
chore: (#523) batch_size 100으로 변경
rlaisqls Jun 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package team.aliens.dms.common.spi
import team.aliens.dms.domain.notification.model.Notification

interface NotificationEventPort {
fun publishNotification(deviceToken: String, notification: Notification)
fun publishNotification(token: String, notification: Notification)
fun publishNotificationToAllByTopic(notification: Notification)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import team.aliens.dms.domain.notification.model.DeviceToken
import team.aliens.dms.domain.user.model.User

class SetDeviceTokenRequest(
val deviceToken: String
val token: String
) {
fun toDeviceToken(user: User) = DeviceToken(
userId = user.id,
schoolId = user.schoolId,
deviceToken = deviceToken
token = token
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package team.aliens.dms.domain.notification.dto

import team.aliens.dms.domain.notification.model.Topic
import team.aliens.dms.domain.notification.model.TopicGroup
import team.aliens.dms.domain.notification.model.TopicSubscription

data class TopicSubscriptionGroupsResponse(
val topicGroups: List<TopicGroupSubscriptionResponse>
) {
companion object {
fun of(topicSubscriptions: List<TopicSubscription>): TopicSubscriptionGroupsResponse {

return TopicSubscriptionGroupsResponse(
TopicGroup.values()
.associateWith { mutableListOf<TopicSubscriptionResponse>() }
.also { topicGroupsMap ->
addTopicSubscriptionsToTopicGroupsMap(
topicGroupsMap = topicGroupsMap,
topicSubscriptionsMap = topicSubscriptions.associateBy { it.topic }
)
}
.map {
TopicGroupSubscriptionResponse(
topicGroup = it.key,
topicSubscriptions = it.value
)
}
)
}

private fun addTopicSubscriptionsToTopicGroupsMap(
topicGroupsMap: Map<TopicGroup, MutableList<TopicSubscriptionResponse>>,
topicSubscriptionsMap: Map<Topic, TopicSubscription>
) {
Topic.values().forEach {
topicGroupsMap[it.topicGroup]?.add(
TopicSubscriptionResponse(
topic = it,
isSubscribed = topicSubscriptionsMap[it]?.isSubscribed ?: false
)
)
}
}
}
}

data class TopicGroupSubscriptionResponse(
val topicGroup: TopicGroup,
val groupName: String = topicGroup.groupName,
val topicSubscriptions: List<TopicSubscriptionResponse>
)

data class TopicSubscriptionResponse(
val topic: Topic,
val title: String = topic.title,
val description: String = topic.content,
val isSubscribed: Boolean
)
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ data class DeviceToken(
val id: UUID = UUID(0, 0),
val userId: UUID,
val schoolId: UUID,
val deviceToken: String
val token: String
)
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
package team.aliens.dms.domain.notification.model

enum class Topic {
NOTICE
enum class TopicGroup(
val groupName: String
) {
NOTICE("공지"),
STUDY_ROOM("자습실")
}

enum class Topic(
val topicGroup: TopicGroup,
rlaisqls marked this conversation as resolved.
Show resolved Hide resolved
val title: String,
val content: String
) {
NOTICE(
topicGroup = TopicGroup.NOTICE,
title = "공지 알림",
content = "기숙사 공지에 대한 알림입니다."
),

STUDY_ROOM_TIME_SLOT(
topicGroup = TopicGroup.STUDY_ROOM,
title = "이용 시간 알림",
content = "자습실 이용 시작 10분 전에 알림을 받을 수 있습니다."
),
STUDY_ROOM_APPLY(
topicGroup = TopicGroup.STUDY_ROOM,
title = "신청 시간 알림",
content = "자습실 신청 시간을 알리는 알림입니다."
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package team.aliens.dms.domain.notification.model

import java.util.UUID

data class TopicSubscription(

val deviceTokenId: UUID,

val topic: Topic,

val isSubscribed: Boolean

) {
companion object {

fun subscribe(
deviceTokenId: UUID,
topic: Topic,
) = TopicSubscription(
deviceTokenId = deviceTokenId,
topic = topic,
isSubscribed = true
)

fun unsubscribe(
deviceTokenId: UUID,
topic: Topic,
) = TopicSubscription(
deviceTokenId = deviceTokenId,
topic = topic,
isSubscribed = false
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@ package team.aliens.dms.domain.notification.service
import team.aliens.dms.domain.notification.model.DeviceToken
import team.aliens.dms.domain.notification.model.Notification
import team.aliens.dms.domain.notification.model.Topic
import team.aliens.dms.domain.notification.model.TopicSubscription

interface NotificationService {

fun saveDeviceToken(deviceToken: DeviceToken)

fun unsubscribeTopic(deviceToken: String, topic: Topic)
fun unsubscribeTopic(token: String, topic: Topic)

fun subscribeTopic(deviceToken: String, topic: Topic)
fun subscribeTopic(token: String, topic: Topic)

fun updateSubscribes(deviceToken: String, topicsToSubscribe: List<Pair<Topic, Boolean>>)
fun updateSubscribes(token: String, topicsToSubscribe: List<Pair<Topic, Boolean>>)

fun sendMessage(
deviceToken: String,
token: String,
notification: Notification
)

Expand All @@ -24,4 +25,6 @@ interface NotificationService {
fun sendMessagesByTopic(
notification: Notification
)

fun getTopicSubscriptionsByToken(token: String): List<TopicSubscription>
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,67 +5,97 @@ import team.aliens.dms.domain.notification.exception.DeviceTokenNotFoundExceptio
import team.aliens.dms.domain.notification.model.DeviceToken
import team.aliens.dms.domain.notification.model.Notification
import team.aliens.dms.domain.notification.model.Topic
import team.aliens.dms.domain.notification.model.TopicSubscription
import team.aliens.dms.domain.notification.spi.DeviceTokenPort
import team.aliens.dms.domain.notification.spi.NotificationPort
import team.aliens.dms.domain.notification.spi.TopicSubscriptionPort

@Component
class NotificationServiceImpl(
private val notificationPort: NotificationPort,
private val topicSubscriptionPort: TopicSubscriptionPort,
private val deviceTokenPort: DeviceTokenPort
) : NotificationService {

override fun saveDeviceToken(deviceToken: DeviceToken) {
deviceTokenPort.saveDeviceToken(deviceToken)
notificationPort.subscribeAllTopics(
deviceToken = deviceToken.deviceToken
token = deviceToken.token
)
}

override fun subscribeTopic(deviceToken: String, topic: Topic) {
override fun subscribeTopic(token: String, topic: Topic) {
val deviceToken = this.getDeviceTokenByToken(token)
topicSubscriptionPort.saveTopicSubscription(
TopicSubscription.subscribe(
deviceTokenId = deviceToken.id,
topic = topic,
)
)
notificationPort.subscribeTopic(
deviceToken = this.getDeviceTokenByDeviceToken(deviceToken).deviceToken,
token = token,
topic = topic
)
}

override fun unsubscribeTopic(deviceToken: String, topic: Topic) {
override fun unsubscribeTopic(token: String, topic: Topic) {
val deviceToken = this.getDeviceTokenByToken(token)
topicSubscriptionPort.saveTopicSubscription(
TopicSubscription.unsubscribe(
deviceTokenId = deviceToken.id,
topic = topic,
)
)
notificationPort.unsubscribeTopic(
deviceToken = this.getDeviceTokenByDeviceToken(deviceToken).deviceToken,
token = token,
topic = topic
)
}

override fun updateSubscribes(deviceToken: String, topicsToSubscribe: List<Pair<Topic, Boolean>>) {
override fun updateSubscribes(token: String, topicsToSubscribe: List<Pair<Topic, Boolean>>) {
val deviceToken = this.getDeviceTokenByToken(token)
val topicSubscriptions = topicsToSubscribe.map { (topic, isSubscribe) ->
this.subscribeOrUnsubscribeTopic(isSubscribe, token, topic)
TopicSubscription(
deviceTokenId = deviceToken.id,
topic = topic,
isSubscribed = isSubscribe
)
}
topicSubscriptionPort.saveAllTopicSubscriptions(topicSubscriptions)
}

val deviceToken = this.getDeviceTokenByDeviceToken(deviceToken)
topicsToSubscribe.forEach { (topic, isSubscribe) ->
if (isSubscribe) {
notificationPort.subscribeTopic(
deviceToken = deviceToken.deviceToken,
topic = topic
)
} else {
notificationPort.unsubscribeTopic(
deviceToken = deviceToken.deviceToken,
topic = topic
)
}
private fun subscribeOrUnsubscribeTopic(
isSubscribe: Boolean,
token: String,
topic: Topic
) {
if (isSubscribe) {
notificationPort.subscribeTopic(
token = token,
topic = topic
)
} else {
notificationPort.unsubscribeTopic(
token = token,
topic = topic
)
}
}

private fun getDeviceTokenByDeviceToken(deviceToken: String) =
deviceTokenPort.queryDeviceTokenByDeviceToken(deviceToken) ?: throw DeviceTokenNotFoundException
private fun getDeviceTokenByToken(token: String) =
deviceTokenPort.queryDeviceTokenByToken(token) ?: throw DeviceTokenNotFoundException
softpeanut marked this conversation as resolved.
Show resolved Hide resolved

override fun sendMessage(deviceToken: String, notification: Notification) {
override fun sendMessage(token: String, notification: Notification) {
notificationPort.sendMessage(
deviceToken = deviceToken,
token = token,
notification = notification
)
}

override fun sendMessages(deviceTokens: List<String>, notification: Notification) {
notificationPort.sendMessages(
deviceTokens = deviceTokens,
tokens = deviceTokens,
notification = notification
)
}
Expand All @@ -75,4 +105,9 @@ class NotificationServiceImpl(
notification = notification
)
}

override fun getTopicSubscriptionsByToken(token: String): List<TopicSubscription> {
val savedToken = getDeviceTokenByToken(token)
return topicSubscriptionPort.queryTopicSubscriptionsByDeviceTokenId(savedToken.id)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface DeviceTokenPort {

fun queryDeviceTokenByUserId(userId: UUID): DeviceToken?

fun queryDeviceTokenByDeviceToken(deviceToken: String): DeviceToken?
fun queryDeviceTokenByToken(token: String): DeviceToken?

fun deleteDeviceTokenByUserId(userId: UUID)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ import team.aliens.dms.domain.notification.model.Topic
interface NotificationPort {

fun sendMessage(
deviceToken: String,
token: String,
notification: Notification
)

fun sendMessages(deviceTokens: List<String>, notification: Notification)
fun sendMessages(tokens: List<String>, notification: Notification)

fun sendByTopic(
notification: Notification
)

fun subscribeTopic(deviceToken: String, topic: Topic)
fun subscribeTopic(token: String, topic: Topic)

fun subscribeAllTopics(deviceToken: String)
fun subscribeAllTopics(token: String)

fun unsubscribeTopic(deviceToken: String, topic: Topic)
fun unsubscribeTopic(token: String, topic: Topic)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package team.aliens.dms.domain.notification.spi

import java.util.UUID
import team.aliens.dms.domain.notification.model.TopicSubscription

interface TopicSubscriptionPort {

fun saveTopicSubscription(topicSubscription: TopicSubscription): TopicSubscription

fun queryTopicSubscriptionsByDeviceTokenId(deviceTokenId: UUID): List<TopicSubscription>

fun saveAllTopicSubscriptions(topicSubscriptions: List<TopicSubscription>)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package team.aliens.dms.domain.notification.usecase

import team.aliens.dms.common.annotation.ReadOnlyUseCase
import team.aliens.dms.domain.notification.dto.TopicSubscriptionGroupsResponse
import team.aliens.dms.domain.notification.service.NotificationService

@ReadOnlyUseCase
class QueryTopicSubscriptionUseCase(
private val notificationService: NotificationService
) {

fun execute(token: String): TopicSubscriptionGroupsResponse {
return TopicSubscriptionGroupsResponse.of(
notificationService.getTopicSubscriptionsByToken(token)
)
}
}
Loading