-
Notifications
You must be signed in to change notification settings - Fork 0
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
카테고리별 푸시 on/off #341
base: develop
Are you sure you want to change the base?
카테고리별 푸시 on/off #341
Changes from all commits
68bd80b
d13cd59
8c67b4b
2cf26d5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package com.wafflestudio.snutt.handler | ||
|
||
import com.wafflestudio.snutt.middleware.SnuttRestApiDefaultMiddleware | ||
import com.wafflestudio.snutt.notification.data.PushCategory | ||
import com.wafflestudio.snutt.notification.dto.PushPreferenceResponse | ||
import com.wafflestudio.snutt.notification.service.PushPreferenceService | ||
import org.springframework.stereotype.Component | ||
import org.springframework.web.reactive.function.server.ServerRequest | ||
|
||
@Component | ||
class PushPreferenceHandler( | ||
private val pushPreferenceService: PushPreferenceService, | ||
snuttRestApiDefaultMiddleware: SnuttRestApiDefaultMiddleware, | ||
) : ServiceHandler( | ||
handlerMiddleware = snuttRestApiDefaultMiddleware, | ||
) { | ||
suspend fun getPushPreferences(req: ServerRequest) = | ||
handle(req) { | ||
val user = req.getContext().user!! | ||
val pushPreferences = pushPreferenceService.getPushPreferences(user) | ||
pushPreferences.map { PushPreferenceResponse(it) } | ||
} | ||
|
||
suspend fun enableLectureUpdate(req: ServerRequest) = | ||
handle(req) { | ||
val user = req.getContext().user!! | ||
pushPreferenceService.enablePush(user, PushCategory.LECTURE_UPDATE) | ||
} | ||
|
||
suspend fun disableLectureUpdate(req: ServerRequest) = | ||
handle(req) { | ||
val user = req.getContext().user!! | ||
pushPreferenceService.disablePush(user, PushCategory.LECTURE_UPDATE) | ||
} | ||
|
||
suspend fun enableVacancyNotification(req: ServerRequest) = | ||
handle(req) { | ||
val user = req.getContext().user!! | ||
pushPreferenceService.enablePush(user, PushCategory.VACANCY_NOTIFICATION) | ||
} | ||
|
||
suspend fun disableVacancyNotification(req: ServerRequest) = | ||
handle(req) { | ||
val user = req.getContext().user!! | ||
pushPreferenceService.disablePush(user, PushCategory.VACANCY_NOTIFICATION) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package com.wafflestudio.snutt.notification.data | ||
|
||
import com.fasterxml.jackson.annotation.JsonValue | ||
import org.springframework.core.convert.converter.Converter | ||
import org.springframework.data.convert.ReadingConverter | ||
import org.springframework.data.convert.WritingConverter | ||
import org.springframework.stereotype.Component | ||
|
||
enum class PushCategory( | ||
@JsonValue val value: Int, | ||
) { | ||
NORMAL(0), | ||
LECTURE_UPDATE(1), | ||
VACANCY_NOTIFICATION(2), | ||
; | ||
|
||
companion object { | ||
private val valueMap = PushCategory.entries.associateBy { e -> e.value } | ||
|
||
fun getOfValue(value: Int): PushCategory? = valueMap[value] | ||
} | ||
} | ||
|
||
@ReadingConverter | ||
@Component | ||
class PushCategoryReadConverter : Converter<Int, PushCategory> { | ||
override fun convert(source: Int): PushCategory = PushCategory.getOfValue(source)!! | ||
} | ||
|
||
@WritingConverter | ||
@Component | ||
class PushCategoryWriteConverter : Converter<PushCategory, Int> { | ||
override fun convert(source: PushCategory): Int = source.value | ||
} | ||
|
||
fun PushCategory(notificationType: NotificationType) = | ||
when (notificationType) { | ||
NotificationType.LECTURE_UPDATE -> PushCategory.LECTURE_UPDATE | ||
NotificationType.LECTURE_VACANCY -> PushCategory.VACANCY_NOTIFICATION | ||
else -> PushCategory.NORMAL | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package com.wafflestudio.snutt.notification.data | ||
|
||
import org.springframework.data.annotation.Id | ||
import org.springframework.data.mongodb.core.index.CompoundIndex | ||
import org.springframework.data.mongodb.core.index.Indexed | ||
import org.springframework.data.mongodb.core.mapping.Document | ||
import org.springframework.data.mongodb.core.mapping.Field | ||
import org.springframework.data.mongodb.core.mapping.FieldType | ||
|
||
@Document(collection = "push_opt_out") | ||
@CompoundIndex(def = "{ 'user_id': 1, 'push_category': 1 }", unique = true) | ||
data class PushOptOut( | ||
@Id | ||
val id: String? = null, | ||
@Indexed | ||
@Field("user_id", targetType = FieldType.OBJECT_ID) | ||
val userId: String, | ||
@Field("push_category") | ||
@Indexed | ||
val pushCategory: PushCategory, | ||
) | ||
Comment on lines
+12
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. row 하나로 유저의 모든 설정값을 보면 좋을 것 같은데 현재는 좀 RDB스러운 접근이라고 생각합니다. {
"userId": "abcdefg123123",
"pushSetting": [
{
"category": "LECTURE_UPDATE",
"enable": true
},
{
"category": "VACANCY_NOTIFICATION",
"enable": false
},
...
]
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.wafflestudio.snutt.notification.dto | ||
|
||
import com.wafflestudio.snutt.notification.data.PushCategory | ||
|
||
data class PushPreference( | ||
val pushCategory: PushCategory, | ||
val enabled: Boolean, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.wafflestudio.snutt.notification.dto | ||
|
||
data class PushPreferenceResponse( | ||
val pushCategoryName: String, | ||
val enabled: Boolean, | ||
) | ||
|
||
fun PushPreferenceResponse(pushPreference: PushPreference) = | ||
PushPreferenceResponse( | ||
pushCategoryName = pushPreference.pushCategory.name, | ||
enabled = pushPreference.enabled, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package com.wafflestudio.snutt.notification.repository | ||
|
||
import com.wafflestudio.snutt.notification.data.PushCategory | ||
import com.wafflestudio.snutt.notification.data.PushOptOut | ||
import org.springframework.data.repository.kotlin.CoroutineCrudRepository | ||
|
||
interface PushOptOutRepository : CoroutineCrudRepository<PushOptOut, String> { | ||
suspend fun existsByUserIdAndPushCategory( | ||
userId: String, | ||
pushCategory: PushCategory, | ||
): Boolean | ||
|
||
suspend fun deleteByUserIdAndPushCategory( | ||
userId: String, | ||
pushCategory: PushCategory, | ||
): Long | ||
|
||
suspend fun findByUserIdInAndPushCategory( | ||
userIds: List<String>, | ||
pushCategory: PushCategory, | ||
): List<PushOptOut> | ||
|
||
suspend fun findByUserId(userId: String): List<PushOptOut> | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package com.wafflestudio.snutt.notification.service | ||
|
||
import com.wafflestudio.snutt.notification.data.PushCategory | ||
import com.wafflestudio.snutt.notification.data.PushOptOut | ||
import com.wafflestudio.snutt.notification.dto.PushPreference | ||
import com.wafflestudio.snutt.notification.repository.PushOptOutRepository | ||
import com.wafflestudio.snutt.users.data.User | ||
import org.springframework.stereotype.Service | ||
|
||
interface PushPreferenceService { | ||
suspend fun enablePush( | ||
user: User, | ||
pushCategory: PushCategory, | ||
) | ||
|
||
suspend fun disablePush( | ||
user: User, | ||
pushCategory: PushCategory, | ||
) | ||
|
||
suspend fun getPushPreferences(user: User): List<PushPreference> | ||
} | ||
|
||
@Service | ||
class PushPreferenceServiceImpl( | ||
private val pushOptOutRepository: PushOptOutRepository, | ||
) : PushPreferenceService { | ||
override suspend fun enablePush( | ||
user: User, | ||
pushCategory: PushCategory, | ||
) { | ||
pushOptOutRepository.save( | ||
PushOptOut( | ||
userId = user.id!!, | ||
pushCategory = pushCategory, | ||
), | ||
) | ||
} | ||
|
||
override suspend fun disablePush( | ||
user: User, | ||
pushCategory: PushCategory, | ||
) { | ||
pushOptOutRepository.deleteByUserIdAndPushCategory( | ||
userId = user.id!!, | ||
pushCategory = pushCategory, | ||
) | ||
} | ||
|
||
override suspend fun getPushPreferences(user: User): List<PushPreference> { | ||
val allPushCategories = PushCategory.entries.filterNot { it == PushCategory.NORMAL } | ||
val disabledPushCategories = pushOptOutRepository.findByUserId(user.id!!).map { it.pushCategory }.toSet() | ||
return allPushCategories.map { | ||
if (it in disabledPushCategories) { | ||
return@map PushPreference( | ||
pushCategory = it, | ||
enabled = false, | ||
) | ||
} else { | ||
return@map PushPreference( | ||
pushCategory = it, | ||
enabled = true, | ||
) | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,8 @@ package com.wafflestudio.snutt.notification.service | |
import com.wafflestudio.snutt.common.push.PushClient | ||
import com.wafflestudio.snutt.common.push.dto.PushMessage | ||
import com.wafflestudio.snutt.common.push.dto.TargetedPushMessageWithToken | ||
import com.wafflestudio.snutt.notification.data.PushCategory | ||
import com.wafflestudio.snutt.notification.repository.PushOptOutRepository | ||
import org.springframework.stereotype.Service | ||
|
||
/** | ||
|
@@ -22,12 +24,30 @@ interface PushService { | |
suspend fun sendGlobalPush(pushMessage: PushMessage) | ||
|
||
suspend fun sendTargetPushes(userToPushMessage: Map<String, PushMessage>) | ||
|
||
suspend fun sendCategoricalPush( | ||
pushMessage: PushMessage, | ||
userId: String, | ||
pushCategory: PushCategory, | ||
) | ||
|
||
suspend fun sendCategoricalPushes( | ||
pushMessage: PushMessage, | ||
userIds: List<String>, | ||
pushCategory: PushCategory, | ||
) | ||
|
||
suspend fun sendCategoricalTargetPushes( | ||
userToPushMessage: Map<String, PushMessage>, | ||
pushCategory: PushCategory, | ||
) | ||
} | ||
|
||
@Service | ||
class PushServiceImpl internal constructor( | ||
private val deviceService: DeviceService, | ||
private val pushClient: PushClient, | ||
private val pushOptOutRepository: PushOptOutRepository, | ||
) : PushService { | ||
override suspend fun sendPush( | ||
pushMessage: PushMessage, | ||
|
@@ -60,4 +80,57 @@ class PushServiceImpl internal constructor( | |
deviceService.getUserDevices(userId).map { it.fcmRegistrationId to pushMessage } | ||
}.map { (fcmRegistrationId, message) -> TargetedPushMessageWithToken(fcmRegistrationId, message) } | ||
.let { pushClient.sendMessages(it) } | ||
|
||
override suspend fun sendCategoricalPush( | ||
pushMessage: PushMessage, | ||
userId: String, | ||
pushCategory: PushCategory, | ||
) { | ||
if (pushCategory == PushCategory.NORMAL || !pushOptOutRepository.existsByUserIdAndPushCategory(userId, pushCategory)) { | ||
sendPush(pushMessage, userId) | ||
} | ||
} | ||
Comment on lines
+83
to
+92
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. PushService가 PushCategory에 대해 알 필요가 있을까요? |
||
|
||
override suspend fun sendCategoricalPushes( | ||
pushMessage: PushMessage, | ||
userIds: List<String>, | ||
pushCategory: PushCategory, | ||
) { | ||
if (pushCategory == PushCategory.NORMAL) { | ||
sendPushes(pushMessage, userIds) | ||
} | ||
|
||
val filteredUserIds = | ||
pushOptOutRepository | ||
.findByUserIdInAndPushCategory(userIds, pushCategory) | ||
.map { it.userId } | ||
.toSet() | ||
.let { optOutUserIds -> userIds.filterNot { it in optOutUserIds } } | ||
|
||
if (filteredUserIds.isNotEmpty()) { | ||
sendPushes(pushMessage, filteredUserIds) | ||
} | ||
} | ||
|
||
override suspend fun sendCategoricalTargetPushes( | ||
userToPushMessage: Map<String, PushMessage>, | ||
pushCategory: PushCategory, | ||
) { | ||
if (pushCategory == PushCategory.NORMAL) { | ||
sendTargetPushes(userToPushMessage) | ||
} | ||
|
||
val userIds = userToPushMessage.keys.toList() | ||
|
||
val filteredUserToPushMessage = | ||
pushOptOutRepository | ||
.findByUserIdInAndPushCategory(userIds, pushCategory) | ||
.map { it.userId } | ||
.toSet() | ||
.let { optOutUserIds -> userToPushMessage.filterKeys { it !in optOutUserIds } } | ||
|
||
if (filteredUserToPushMessage.isNotEmpty()) { | ||
sendTargetPushes(filteredUserToPushMessage) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 PushCategory라는 이름이 헷갈리네요
NotificationType이랑 거의 비슷해서 헷갈려요
유저가 설정할 수 있는 값이라는 PushPreference같은 느낌이 이름에 같이 들어갔으면 좋겠습니다.