Skip to content

Commit

Permalink
implement admin push notification controller (#420)
Browse files Browse the repository at this point in the history
* implement admin push notification controller

* detekt 적용

* 리뷰: request body dto class 이름을 따로 따기

---------

Co-authored-by: SuhwanJee <[email protected]>
  • Loading branch information
bellatoris and SuhwanJee authored Nov 13, 2024
1 parent 0a4ccb1 commit c8510a2
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 0 deletions.
35 changes: 35 additions & 0 deletions api-admin/api-spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,19 @@ paths:
schema:
$ref: '#/components/schemas/AdminCreateImageUploadUrlsResponseDTO'

/user/sendPushNotification:
post:
summary: 유저들에게 push notification을 보낸다.
operationId: adminSendPushNotification
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/AdminSendPushNotificationRequestDto'
responses:
'200': {}

components:
# Reusable schemas (data models)
schemas:
Expand Down Expand Up @@ -1206,3 +1219,25 @@ components:
required:
- url
- expireAt

AdminSendPushNotificationRequestDto:
type: object
properties:
userIds:
type: array
items:
type: string
notification:
type: object
properties:
title:
type: string
body:
type: string
deepLink:
type: string
required:
- body
required:
- userIds
- notification
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
dependencies {
implementation(projects.crossCuttingConcern.application.serverEvent)
implementation(projects.boundedContext.notification.application)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package club.staircrusher.user.application.port.`in`

import club.staircrusher.application.server_event.port.`in`.SccServerEventRecorder
import club.staircrusher.domain.server_event.NewsletterSubscribedOnSignupPayload
import club.staircrusher.notification.port.`in`.PushService
import club.staircrusher.stdlib.clock.SccClock
import club.staircrusher.stdlib.di.annotation.Component
import club.staircrusher.stdlib.domain.SccDomainException
Expand All @@ -17,6 +18,11 @@ import club.staircrusher.user.domain.model.User
import club.staircrusher.user.domain.model.UserMobilityTool
import club.staircrusher.user.domain.service.PasswordEncryptor
import club.staircrusher.user.domain.service.UserAuthService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import mu.KotlinLogging
import org.springframework.data.repository.findByIdOrNull
Expand All @@ -30,6 +36,7 @@ class UserApplicationService(
private val userAuthInfoRepository: UserAuthInfoRepository,
private val stibeeSubscriptionService: StibeeSubscriptionService,
private val sccServerEventRecorder: SccServerEventRecorder,
private val pushService: PushService,
) {
private val logger = KotlinLogging.logger {}

Expand Down Expand Up @@ -100,6 +107,33 @@ class UserApplicationService(
userRepository.save(user)
}

fun sendPushNotification(
userIds: List<String>,
title: String?,
body: String,
deepLink: String?,
) = transactionManager.doInTransaction {
val users = userRepository.findAllById(userIds)
val notifications = users.mapNotNull { user ->
user.pushToken ?: return@mapNotNull null
user.pushToken!! to PushService.Notification(
// just poc for now, but not sure this substitution needs to be placed here
title = title?.replace("{{nickname}}", user.nickname),
body = body.replace("{{nickname}}", user.nickname),
link = deepLink,
collapseKey = null,
)
}

transactionManager.doAfterCommit {
CoroutineScope(Dispatchers.IO).launch {
notifications.map { (t, n) ->
async { pushService.send(t, emptyMap(), n) }
}.joinAll()
}
}
}

fun updateUserInfo(
userId: String,
nickname: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package club.staircrusher.user.infra.adapter.`in`.controller

import club.staircrusher.admin_api.spec.dto.AdminSendPushNotificationRequestDto
import club.staircrusher.api.spec.dto.GetUserInfoResponseDto
import club.staircrusher.api.spec.dto.UpdatePushTokenPostRequest
import club.staircrusher.api.spec.dto.UpdateUserInfoPost200Response
Expand Down Expand Up @@ -114,6 +115,19 @@ class UserController(
)
}

@PostMapping("/admin/user/sendPushNotification")
fun adminSendPushNotification(
@RequestBody request: AdminSendPushNotificationRequestDto,
@Suppress("UnusedPrivateMember") authentication: SccAppAuthentication,
) {
userApplicationService.sendPushNotification(
userIds = request.userIds,
title = request.notification.title,
body = request.notification.body,
deepLink = request.notification.deepLink,
)
}

@PostMapping("/deleteUser")
fun deleteUser(authentication: SccAppAuthentication): ResponseEntity<Unit> {
userApplicationService.deleteUser(authentication.principal)
Expand Down

0 comments on commit c8510a2

Please sign in to comment.