Skip to content

카테고리별 푸시 on/off #341

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

Merged
merged 6 commits into from
Apr 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 29 additions & 0 deletions api/src/main/kotlin/handler/PushPreferenceHandler.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.wafflestudio.snutt.handler

import com.wafflestudio.snutt.middleware.SnuttRestApiDefaultMiddleware
import com.wafflestudio.snutt.notification.dto.PushPreferenceDto
import com.wafflestudio.snutt.notification.service.PushPreferenceService
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.awaitBody

@Component
class PushPreferenceHandler(
private val pushPreferenceService: PushPreferenceService,
snuttRestApiDefaultMiddleware: SnuttRestApiDefaultMiddleware,
) : ServiceHandler(
handlerMiddleware = snuttRestApiDefaultMiddleware,
) {
suspend fun getPushPreferences(req: ServerRequest) =
handle(req) {
val user = req.getContext().user!!
pushPreferenceService.getPushPreferenceDto(user)
}

suspend fun savePushPreferences(req: ServerRequest) =
handle(req) {
val user = req.getContext().user!!
val pushPreferenceDto = req.awaitBody<PushPreferenceDto>()
pushPreferenceService.savePushPreference(user, pushPreferenceDto)
}
}
13 changes: 13 additions & 0 deletions api/src/main/kotlin/router/MainRouter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.wafflestudio.snutt.handler.FriendTableHandler
import com.wafflestudio.snutt.handler.LectureSearchHandler
import com.wafflestudio.snutt.handler.NotificationHandler
import com.wafflestudio.snutt.handler.PopupHandler
import com.wafflestudio.snutt.handler.PushPreferenceHandler
import com.wafflestudio.snutt.handler.StaticPageHandler
import com.wafflestudio.snutt.handler.TagHandler
import com.wafflestudio.snutt.handler.TimetableHandler
Expand All @@ -34,6 +35,7 @@ import com.wafflestudio.snutt.router.docs.FriendDocs
import com.wafflestudio.snutt.router.docs.LectureSearchDocs
import com.wafflestudio.snutt.router.docs.NotificationDocs
import com.wafflestudio.snutt.router.docs.PopupDocs
import com.wafflestudio.snutt.router.docs.PushDocs
import com.wafflestudio.snutt.router.docs.TagDocs
import com.wafflestudio.snutt.router.docs.ThemeDocs
import com.wafflestudio.snutt.router.docs.TimetableDocs
Expand Down Expand Up @@ -70,6 +72,7 @@ class MainRouter(
private val feedbackHandler: FeedbackHandler,
private val staticPageHandler: StaticPageHandler,
private val evServiceHandler: EvServiceHandler,
private val pushPreferenceHandler: PushPreferenceHandler,
) {
@Bean
fun healthCheck() =
Expand Down Expand Up @@ -343,4 +346,14 @@ class MainRouter(
GET("/privacy_policy").invoke { staticPageHandler.privacyPolicy() }
GET("/terms_of_service").invoke { staticPageHandler.termsOfService() }
}

@Bean
@PushDocs
fun pushPreferenceRouter() =
v1CoRouter {
"/push/preferences".nest {
GET("", pushPreferenceHandler::getPushPreferences)
POST("", pushPreferenceHandler::savePushPreferences)
}
}
}
95 changes: 95 additions & 0 deletions api/src/main/kotlin/router/docs/PushDocs.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.wafflestudio.snutt.router.docs

import com.wafflestudio.snutt.notification.dto.PushPreferenceDto
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.media.ExampleObject
import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.parameters.RequestBody
import io.swagger.v3.oas.annotations.responses.ApiResponse
import org.springdoc.core.annotations.RouterOperation
import org.springdoc.core.annotations.RouterOperations
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.RequestMethod

@RouterOperations(
RouterOperation(
path = "/v1/push/preferences",
method = [RequestMethod.GET],
produces = [MediaType.APPLICATION_JSON_VALUE],
operation =
Operation(
operationId = "getPushPreferences",
responses = [
ApiResponse(
responseCode = "200",
content = [
Content(
schema = Schema(implementation = PushPreferenceDto::class),
examples = [
ExampleObject(
value = """
{
"pushPreferences": [
{
"type": "LECTURE_UPDATE",
"isEnabled": "true"
},
{
"type": "VACANCY_NOTIFICATION",
"isEnabled": "true"
}
]
}
""",
),
],
),
],
),
],
),
),
RouterOperation(
path = "/v1/push/preferences",
method = [RequestMethod.POST],
produces = [MediaType.APPLICATION_JSON_VALUE],
operation =
Operation(
operationId = "savePushPreferences",
requestBody =
RequestBody(
content = [
Content(
schema = Schema(implementation = PushPreferenceDto::class),
mediaType = MediaType.APPLICATION_JSON_VALUE,
examples = [
ExampleObject(
value = """
{
"pushPreferences": [
{
"type": "LECTURE_UPDATE",
"isEnabled": false
},
{
"type": "VACANCY_NOTIFICATION",
"isEnabled": false
}
]
}
""",
),
],
),
],
),
responses = [
ApiResponse(
responseCode = "200",
),
],
),
),
)
annotation class PushDocs()
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.wafflestudio.snutt.common.push.dto.PushMessage
import com.wafflestudio.snutt.coursebook.data.Coursebook
import com.wafflestudio.snutt.notification.data.Notification
import com.wafflestudio.snutt.notification.data.NotificationType
import com.wafflestudio.snutt.notification.data.PushPreferenceType
import com.wafflestudio.snutt.notification.service.NotificationService
import com.wafflestudio.snutt.notification.service.PushService
import com.wafflestudio.snutt.notification.service.PushWithNotificationService
Expand Down Expand Up @@ -74,7 +75,7 @@ class SugangSnuNotificationServiceImpl(
urlScheme = DeeplinkType.NOTIFICATIONS,
)
}
pushService.sendTargetPushes(userIdToMessage)
pushService.sendTargetPushes(userIdToMessage, PushPreferenceType.LECTURE_UPDATE)
}

override suspend fun notifyCoursebookUpdate(coursebook: Coursebook) {
Expand Down
17 changes: 17 additions & 0 deletions core/src/main/kotlin/notification/data/PushPreference.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.wafflestudio.snutt.notification.data

import org.springframework.data.annotation.Id
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 = "pushPreference")
data class PushPreference(
@Id
val id: String? = null,
@Indexed(unique = true)
@Field(targetType = FieldType.OBJECT_ID)
val userId: String,
val pushPreferences: List<PushPreferenceItem>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.wafflestudio.snutt.notification.data

data class PushPreferenceItem(
val type: PushPreferenceType,
val isEnabled: Boolean,
)
14 changes: 14 additions & 0 deletions core/src/main/kotlin/notification/data/PushPreferenceType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.wafflestudio.snutt.notification.data

enum class PushPreferenceType {
NORMAL,
LECTURE_UPDATE,
VACANCY_NOTIFICATION,
}

fun PushPreferenceType(notificationType: NotificationType) =
when (notificationType) {
NotificationType.LECTURE_UPDATE -> PushPreferenceType.LECTURE_UPDATE
NotificationType.LECTURE_VACANCY -> PushPreferenceType.VACANCY_NOTIFICATION
else -> PushPreferenceType.NORMAL
}
10 changes: 10 additions & 0 deletions core/src/main/kotlin/notification/dto/PushPreferenceDto.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.wafflestudio.snutt.notification.dto

import com.wafflestudio.snutt.notification.data.PushPreference
import com.wafflestudio.snutt.notification.data.PushPreferenceItem

data class PushPreferenceDto(
val pushPreferences: List<PushPreferenceItem>,
)

fun PushPreferenceDto(pushPreference: PushPreference) = PushPreferenceDto(pushPreference.pushPreferences.toList())
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.wafflestudio.snutt.notification.repository

import com.wafflestudio.snutt.notification.data.PushPreference
import org.springframework.data.repository.kotlin.CoroutineCrudRepository

interface PushPreferenceRepository : CoroutineCrudRepository<PushPreference, String> {
suspend fun findByUserId(userId: String): PushPreference?

suspend fun findByUserIdIn(userIds: List<String>): List<PushPreference>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.wafflestudio.snutt.notification.service

import com.wafflestudio.snutt.notification.data.PushPreference
import com.wafflestudio.snutt.notification.data.PushPreferenceType
import com.wafflestudio.snutt.notification.dto.PushPreferenceDto
import com.wafflestudio.snutt.notification.repository.PushPreferenceRepository
import com.wafflestudio.snutt.users.data.User
import org.springframework.stereotype.Service

interface PushPreferenceService {
suspend fun savePushPreference(
user: User,
pushPreferenceDto: PushPreferenceDto,
)

suspend fun getPushPreferenceDto(user: User): PushPreferenceDto

suspend fun isPushPreferenceEnabled(
userId: String,
pushPreferenceType: PushPreferenceType,
): Boolean

suspend fun filterUsersByPushPreference(
userIds: List<String>,
pushPreferenceType: PushPreferenceType,
): List<String>
}

@Service
class PushPreferenceServiceImpl(
private val pushPreferenceRepository: PushPreferenceRepository,
) : PushPreferenceService {
override suspend fun savePushPreference(
user: User,
pushPreferenceDto: PushPreferenceDto,
) {
pushPreferenceRepository.save(
pushPreferenceRepository.findByUserId(user.id!!)
?.copy(pushPreferences = pushPreferenceDto.pushPreferences)
?: PushPreference(
userId = user.id,
pushPreferences = pushPreferenceDto.pushPreferences,
),
)
}

override suspend fun getPushPreferenceDto(user: User): PushPreferenceDto =
pushPreferenceRepository.findByUserId(user.id!!)
?.let { PushPreferenceDto(it) }
?: PushPreferenceDto(
pushPreferences = emptyList(),
)

override suspend fun isPushPreferenceEnabled(
userId: String,
pushPreferenceType: PushPreferenceType,
): Boolean {
if (pushPreferenceType == PushPreferenceType.NORMAL) {
return true
}

return pushPreferenceRepository
.findByUserId(userId)
?.pushPreferences
?.any { it.type == pushPreferenceType && it.isEnabled }
?: false
}

override suspend fun filterUsersByPushPreference(
userIds: List<String>,
pushPreferenceType: PushPreferenceType,
): List<String> {
if (pushPreferenceType == PushPreferenceType.NORMAL) {
return userIds
}

return pushPreferenceRepository
.findByUserIdIn(userIds)
.filter { pushPreference ->
pushPreference.pushPreferences
.any { it.type == pushPreferenceType && it.isEnabled }
}
.map { it.userId }
}
}
Loading