Skip to content

Commit

Permalink
feat: 채팅에 대한 알림
Browse files Browse the repository at this point in the history
  • Loading branch information
Dltmd202 committed Aug 25, 2024
1 parent 1b1480a commit 31803ca
Show file tree
Hide file tree
Showing 21 changed files with 187 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import com.backgu.amaker.application.chat.service.ChatUserCacheFacadeService
import com.backgu.amaker.application.event.service.EventAssignedUserService
import com.backgu.amaker.application.user.service.UserCacheService
import com.backgu.amaker.application.user.service.UserService
import com.backgu.amaker.infra.jpa.chat.repository.ChatRepository
import com.backgu.amaker.infra.jpa.chat.query.ChatRepository
import com.backgu.amaker.infra.jpa.chat.repository.ChatRoomRepository
import com.backgu.amaker.infra.jpa.chat.repository.ChatRoomUserRepository
import com.backgu.amaker.infra.redis.chat.repository.ChatCacheRepository
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.backgu.amaker.application.chat.service.ChatService
import com.backgu.amaker.application.chat.service.ChatUserCacheFacadeService
import com.backgu.amaker.application.event.service.EventAssignedUserService
import com.backgu.amaker.application.event.service.EventService
import com.backgu.amaker.application.notification.service.NotificationEventService
import com.backgu.amaker.application.user.service.UserService
import com.backgu.amaker.common.exception.BusinessException
import com.backgu.amaker.common.status.StatusCode
Expand All @@ -25,6 +26,7 @@ import com.backgu.amaker.domain.chat.ChatType
import com.backgu.amaker.domain.chat.DefaultChatWithUser
import com.backgu.amaker.domain.event.Event
import com.backgu.amaker.domain.event.EventAssignedUser
import com.backgu.amaker.domain.notifiacation.chat.NewChat
import com.backgu.amaker.domain.user.User
import org.springframework.context.ApplicationEventPublisher
import org.springframework.stereotype.Service
Expand All @@ -42,6 +44,7 @@ class ChatFacadeService(
private val eventAssignedUserService: EventAssignedUserService,
private val chatUserCacheFacadeService: ChatUserCacheFacadeService,
private val eventPublisher: ApplicationEventPublisher,
private val notificationEventService: NotificationEventService,
) {
@Transactional
fun createChat(
Expand All @@ -57,6 +60,7 @@ class ChatFacadeService(
chatRoomService.save(chatRoom.updateLastChatId(chat))

eventPublisher.publishEvent(DefaultChatSaveEvent.of(chatRoomId, chat.createDefaultChatWithUser(user)))
notificationEventService.publishNotificationEvent(NewChat.of(user, chat, chatRoom))

return DefaultChatWithUserDto.of(chat, user)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.backgu.amaker.domain.notifiacation

import com.backgu.amaker.domain.notifiacation.method.RealTimeNotificationMethod

open class ChatRoomNotification(
val chatRoomId: Long,
override val method: RealTimeNotificationMethod,
) : RealTimeBasedNotification(method) {
override val keyPrefix: String
get() = "CHAT_ROOM"

override val keyValue: String
get() = chatRoomId.toString()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.backgu.amaker.domain.notifiacation.chat

import com.backgu.amaker.domain.chat.Chat
import com.backgu.amaker.domain.chat.ChatRoom
import com.backgu.amaker.domain.notifiacation.ChatRoomNotification
import com.backgu.amaker.domain.notifiacation.method.TemplateEmailNotificationMethod
import com.backgu.amaker.domain.user.User

class NewChat(
chatRoom: ChatRoom,
method: TemplateEmailNotificationMethod,
) : ChatRoomNotification(
chatRoom.id,
method,
) {
companion object {
private fun buildDetailMessage(
chatRoom: ChatRoom,
chat: Chat,
): String =
"${chatRoom.name}에 새로운 메시지가 도착했습니다.\n" +
"메시지 내용: ${chat.content}"

fun of(
sender: User,
chat: Chat,
chatRoom: ChatRoom,
): NewChat =
NewChat(
chatRoom,
TemplateEmailNotificationMethod(
"${sender.name}님이 보낸 메시지",
chat.content,
buildDetailMessage(chatRoom, chat),
"chat-notification",
),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package com.backgu.amaker.application.chat.service
import com.backgu.amaker.common.exception.BusinessException
import com.backgu.amaker.common.status.StatusCode
import com.backgu.amaker.domain.chat.DefaultChatWithUser
import com.backgu.amaker.infra.jpa.chat.repository.ChatRepository
import com.backgu.amaker.infra.jpa.chat.query.ChatRepository
import org.springframework.stereotype.Service

@Service
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,6 @@ class ChatRoomUserService(

fun findAllByChatRoom(chatRoom: ChatRoom): List<ChatRoomUser> =
chatRoomUserRepository.findAllByChatRoomId(chatRoom.id).map { it.toDomain() }

fun findUserIdsByChatRoomId(chatRoomId: Long): List<String> = chatRoomUserRepository.findUserIdsByChatRoomId(chatRoomId)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.backgu.amaker.common.exception.BusinessException
import com.backgu.amaker.common.status.StatusCode
import com.backgu.amaker.domain.chat.Chat
import com.backgu.amaker.infra.jpa.chat.entity.ChatEntity
import com.backgu.amaker.infra.jpa.chat.repository.ChatRepository
import com.backgu.amaker.infra.jpa.chat.query.ChatRepository
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,10 @@ class UserService(
if (users.isEmpty()) throw BusinessException(StatusCode.USER_NOT_FOUND)
return users
}

fun getByChatRoomId(chatRoomId: Long): List<User> {
val users = userRepository.findByChatRoomId(chatRoomId).map { it.toDomain() }
if (users.isEmpty()) throw BusinessException(StatusCode.USER_NOT_FOUND)
return users
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,8 @@ class WorkspaceService(
fun getWorkspaceById(workspaceId: Long): Workspace =
workspaceRepository.findByIdOrNull(workspaceId)?.toDomain()
?: throw BusinessException(StatusCode.WORKSPACE_NOT_FOUND)

fun getWorkspaceIdByChatRoomId(chatRoomId: Long): Long =
workspaceRepository.getWorkspaceIdByChatRoomId(chatRoomId)
?: throw BusinessException(StatusCode.WORKSPACE_NOT_FOUND)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
package com.backgu.amaker.infra.jpa.chat.repository

import com.backgu.amaker.infra.jpa.chat.query.ChatWithUserQuery
package com.backgu.amaker.infra.jpa.chat.query

interface ChatQueryRepository {
fun findTopByChatRoomIdLittleThanCursorLimitCountWithUser(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.backgu.amaker.infra.jpa.chat.repository
package com.backgu.amaker.infra.jpa.chat.query

import com.backgu.amaker.infra.jpa.chat.entity.QChatEntity.chatEntity
import com.backgu.amaker.infra.jpa.chat.query.ChatWithUserQuery
import com.backgu.amaker.infra.jpa.chat.query.QChatWithUserQuery
import com.backgu.amaker.infra.jpa.user.entity.QUserEntity.userEntity
import com.querydsl.jpa.impl.JPAQueryFactory

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.backgu.amaker.infra.jpa.chat.repository

import com.backgu.amaker.infra.jpa.chat.entity.ChatRoomUserEntity
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query

interface ChatRoomUserRepository : JpaRepository<ChatRoomUserEntity, Long> {
fun existsByUserIdAndChatRoomId(
Expand All @@ -24,4 +25,7 @@ interface ChatRoomUserRepository : JpaRepository<ChatRoomUserEntity, Long> {
): List<ChatRoomUserEntity>

fun findAllByChatRoomId(chatRoomId: Long): List<ChatRoomUserEntity>

@Query("select cru.userId from ChatRoomUser cru where cru.chatRoomId = :chatRoomId")
fun findUserIdsByChatRoomId(chatRoomId: Long): List<String>
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,7 @@ interface UserRepository : JpaRepository<UserEntity, String> {

@Query("select u from User u join fetch WorkspaceUser wu on u.id = wu.userId where wu.workspaceId = :workspaceId")
fun findByWorkspaceId(workspaceId: Long): List<UserEntity>

@Query("select u from User u join fetch ChatRoomUser cru on u.id = cru.userId where cru.chatRoomId = :chatRoomId")
fun findByChatRoomId(chatRoomId: Long): List<UserEntity>
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,7 @@ interface WorkspaceRepository : JpaRepository<WorkspaceEntity, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select w from Workspace w where w.id = :id")
fun getLockedWorkspaceById(id: Long): WorkspaceEntity?

@Query("select ch.workspaceId from ChatRoom ch where ch.id = :chatRoomId")
fun getWorkspaceIdByChatRoomId(chatRoomId: Long): Long?
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import org.springframework.scheduling.annotation.EnableAsync
@EnableAsync
@SpringBootApplication
@EntityScan(basePackages = ["com.backgu.amaker.infra"])
@EnableJpaRepositories(basePackages = ["com.backgu.amaker.infra.jpa.user", "com.backgu.amaker.infra.jpa.workspace"])
@EnableJpaRepositories(
basePackages = ["com.backgu.amaker.infra.jpa.user", "com.backgu.amaker.infra.jpa.workspace", "com.backgu.amaker.infra.jpa.chat.repository"],
)
@EnableRedisRepositories(basePackages = ["com.backgu.amaker.infra.redis"])
class NotificationApplication

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.backgu.amaker.notification.chatroom.config

import com.backgu.amaker.application.chat.service.ChatRoomUserService
import com.backgu.amaker.infra.jpa.chat.repository.ChatRoomUserRepository
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class ChatRoomServiceConfig {
@Bean
fun chatRoomUserService(chatRoomUserRepository: ChatRoomUserRepository) = ChatRoomUserService(chatRoomUserRepository)
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ class RealTimeHandler(
private val realTimeCallService: RealTimeCallService,
) {
fun handleUserRealTimeNotification(
userId: String,
userIds: List<String>,
realTimeServer: RealTimeServer,
event: RealTimeBasedNotification,
): List<String> = realTimeCallService.sendUserRealTimeNotification(listOf(userId), realTimeServer, event)
): List<String> = realTimeCallService.sendUserRealTimeNotification(userIds, realTimeServer, event)

fun handlerWorkspaceRealTimeNotification(
workspaceId: Long,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.backgu.amaker.notification.service.adapter

import com.backgu.amaker.application.chat.service.ChatRoomUserService
import com.backgu.amaker.application.user.service.UserDeviceService
import com.backgu.amaker.application.workspace.WorkspaceService
import com.backgu.amaker.domain.notifiacation.ChatRoomNotification
import com.backgu.amaker.domain.notifiacation.Notification
import com.backgu.amaker.domain.notifiacation.method.NotificationMethod
import com.backgu.amaker.domain.notifiacation.method.RealTimeNotificationMethod
import com.backgu.amaker.domain.realtime.RealTimeServer
import com.backgu.amaker.domain.session.Session
import com.backgu.amaker.notification.realtime.handler.RealTimeHandler
import com.backgu.amaker.notification.realtime.service.RealTimeService
import com.backgu.amaker.notification.workspace.service.WorkspaceSessionService
import org.springframework.context.ApplicationEventPublisher
import org.springframework.stereotype.Component

@Component
class RealTimeChatRoomHandlerAdapter(
private val realTimeHandler: RealTimeHandler,
private val realTimeService: RealTimeService,
private val workspaceSessionService: WorkspaceSessionService,
private val workspaceService: WorkspaceService,
private val chatRoomUserService: ChatRoomUserService,
private val userDeviceService: UserDeviceService,
private val applicationEventPublisher: ApplicationEventPublisher,
) : NotificationHandlerAdapter<ChatRoomNotification, RealTimeNotificationMethod> {
override fun supportsNotification(notification: Notification): Boolean = notification is ChatRoomNotification

override fun supportsMethod(method: NotificationMethod): Boolean = method is RealTimeNotificationMethod

override fun process(
notification: ChatRoomNotification,
method: RealTimeNotificationMethod,
) {
val workspaceId: Long = workspaceService.getWorkspaceIdByChatRoomId(notification.chatRoomId)
val sessions: List<Session> = workspaceSessionService.findByWorkspaceId(workspaceId)
val realTimeServerSet: Set<RealTimeServer> =
realTimeService.findByIdsToSet(sessions.mapTo(mutableSetOf()) { it.realtimeId })

val chatRoomUsers = chatRoomUserService.findUserIdsByChatRoomId(notification.chatRoomId)
val successUsers: List<String> =
realTimeServerSet
.map {
realTimeHandler.handleUserRealTimeNotification(chatRoomUsers, it, notification)
}.flatten()

val failedUsers: List<String> = chatRoomUsers.filterNot { it in successUsers }

val pushNotification = notification.toPushNotification(userDeviceService.findByUserIds(failedUsers))
applicationEventPublisher.publishEvent(pushNotification)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class RealTimeUserHandlerAdapter(
realTimeService.findByIdsToSet(sessions.mapTo(mutableSetOf()) { it.realtimeId })
val successUser =
realTimeServerSet.map {
realTimeHandler.handleUserRealTimeNotification(notification.userId, it, notification)
realTimeHandler.handleUserRealTimeNotification(listOf(notification.userId), it, notification)
}

if (successUser.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.backgu.amaker.notification.service.adapter

import com.backgu.amaker.application.user.service.UserService
import com.backgu.amaker.domain.notifiacation.ChatRoomNotification
import com.backgu.amaker.domain.notifiacation.Notification
import com.backgu.amaker.domain.notifiacation.method.NotificationMethod
import com.backgu.amaker.domain.notifiacation.method.TemplateEmailNotificationMethod
import com.backgu.amaker.notification.email.service.TemplateEmailHandler
import org.springframework.stereotype.Component

@Component
class TemplateEmailChatRoomHandlerAdapter(
private val userService: UserService,
private val templateEmailHandler: TemplateEmailHandler,
) : NotificationHandlerAdapter<ChatRoomNotification, TemplateEmailNotificationMethod> {
override fun supportsNotification(notification: Notification): Boolean = notification is ChatRoomNotification

override fun supportsMethod(method: NotificationMethod): Boolean = method is TemplateEmailNotificationMethod

override fun process(
notification: ChatRoomNotification,
method: TemplateEmailNotificationMethod,
) {
val users = userService.getByChatRoomId(notification.chatRoomId)
users.forEach { templateEmailHandler.handleEmailEvent(it, method) }
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package com.backgu.amaker.notification.workspace.config

import com.backgu.amaker.application.workspace.WorkspaceService
import com.backgu.amaker.application.workspace.WorkspaceUserService
import com.backgu.amaker.infra.jpa.workspace.repository.WorkspaceRepository
import com.backgu.amaker.infra.jpa.workspace.repository.WorkspaceUserRepository
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class WorkspaceServiceConfig {
@Bean
fun workspaceService(workspaceRepository: WorkspaceRepository): WorkspaceService = WorkspaceService(workspaceRepository)

@Bean
fun workspaceUserService(workspaceUserRepository: WorkspaceUserRepository): WorkspaceUserService =
WorkspaceUserService(workspaceUserRepository)
Expand Down

0 comments on commit 31803ca

Please sign in to comment.