Skip to content

Commit

Permalink
#40,41,42,43,44,45,46,47 - 알림 관련 기능 구현했습니다. (#61)
Browse files Browse the repository at this point in the history
* refactor: 기존 파일 삭제

* chore: Firebase config 추가

* feature: Ballots get Sender 추가

* feature: Vote Get Sender 추가

* refactor: findLoginUserId -> findLoginUser 변경

* feature: Notification 추가

* refactor: SendMessageService 수정

* featrue: UserClass 추가

* featrue: Print 문 제거

* refactor: Modify Message Service 수정

* feature: Event 추가

* feature: disableMessageNotification Domain Service 추가

* feature: Encourage Vote Notification Domain Service 추가

* config: FirebaseConfig 추가

* feat: Finder 추가

* feat: Notification infra 영역 추가

* feat: ClassmateValidator 추가

* test: UserClassTest 추가

* feat: VoteEventListener 추가

* feat: Notification infra 쪽 추가

* feat: NotificationController 추가

* feat: Request, Response 추가

* feat: Notification Scheduler 추가

* feat: SignUpVoteNotificationService 추가

* feat: RegisteredVoteNotificationService 추가

* feat: ReceivedMessageNotificationService 추가

* feat: ReadMessageByReceiverService 추가

* feat: DisabledNotificationUseCase 추가

* feat: InquiryNotificationUseCase 정의

* feat: MessageNotificationUseCase 정의

* feat: VoteNotificationUseCase 정의

* feat: VoteNotificationEventListener 추가

* feat: OpenMessageNotificationService 추가

* feat: MessageNotificationEventListener 추가

* feat: NotificationInfo 추가

* feat: EndVoteNotificationService 추가

* feat: DisabledNotificationService 추가

* feat: InquiryNotificationService 추가

* feat: NotificationSendService, Helper 추가

* feat: CreatedVoteNotificationService 추가

* feat: CreatedMessageNotificationService 추가

* feat: ReceivedMessageNotificationService Test 추가

* test: ReceivedVoteNotificationService Test 추가

* test: SignUpVoteNotificationService Test 추가

* test: EncourageVoteNotificationService Test 추가

* test: RegisteredVoteNotificationService Test 추가

* test: DisabledMessageNotificationByLimitService Test 추가

* refactor: Naming 변경 및 테스트 수정

* refactor: 슬래시 추가

* refactor: Void->Unit으로 변경... 자바 저리가

* refactor: 사용하지 않는 프린트 스택 제거

* feature: fcm 모듈 추가

* chore: 모듈 의존성 추가

* test: 인프라 변경 및 로직 변경 후 테스트 수정
  • Loading branch information
kpeel5839 authored Aug 3, 2024
1 parent ee069a7 commit 1eebd00
Show file tree
Hide file tree
Showing 94 changed files with 3,302 additions and 155 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ dependencies {
implementation(project(":core"))
implementation(project(":infrastructure:mysql"))
implementation(project(":infrastructure:redis"))

implementation(project(":infrastructure:fcm"))

// https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api
testImplementation("io.jsonwebtoken:jjwt-api:0.11.2")
Expand Down
29 changes: 29 additions & 0 deletions app/src/main/kotlin/com/wespot/config/FirebaseConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.wespot.config

import com.google.auth.oauth2.GoogleCredentials
import com.google.firebase.FirebaseApp
import com.google.firebase.FirebaseOptions
import jakarta.annotation.PostConstruct
import org.springframework.context.annotation.Configuration
import java.io.FileInputStream

@Configuration
class FirebaseConfig {

@PostConstruct
fun init() {
try {
val serviceAccount = FileInputStream("src/main/resources/config/serviceAccountKey.json")
val options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(serviceAccount))
.build()

if (FirebaseApp.getApps().isEmpty()) {
FirebaseApp.initializeApp(options)
}
} catch (e: Exception) {
throw IllegalArgumentException("Firebase APP 연결에 실패했습니다.")
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.wespot.notification

import com.wespot.notification.dto.NotificationResponses
import com.wespot.notification.port.`in`.InquiryNotificationUseCase
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PatchMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController


@RestController
@RequestMapping("/api/v1/notifications")
class NotificationController(
private val inquiryNotificationUseCase: InquiryNotificationUseCase
) {

@GetMapping
fun getNotifications(): ResponseEntity<NotificationResponses> {
val notifications = inquiryNotificationUseCase.getNotifications()

return ResponseEntity.ok(notifications)
}

@PatchMapping("/{notificationId}")
fun readNotification(@PathVariable notificationId: Long): ResponseEntity<Unit> {
inquiryNotificationUseCase.readNotification(notificationId)

return ResponseEntity.noContent()
.build()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.wespot.notification.schedule

import com.wespot.notification.port.`in`.DisabledNotificationUseCase
import com.wespot.notification.port.`in`.MessageNotificationUseCase
import com.wespot.notification.port.`in`.VoteNotificationUseCase
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Component
import java.time.LocalDate

@Component
class NotificationScheduler(
private val disabledNotificationUseCase: DisabledNotificationUseCase,
private val voteNotificationUseCase: VoteNotificationUseCase,
private val messageNotificationUseCase: MessageNotificationUseCase,
) {

@Scheduled(cron = "0 0 0 * * *") // 매일 00시 실행
fun disableVoteNotifications() {
val today = LocalDate.now()
disabledNotificationUseCase.disableVoteNotifications(today)
}

@Scheduled(cron = "0 0 22 * * *") // 매일 00시 실행
fun disableMessageNotifications() {
val today = LocalDate.now()
disabledNotificationUseCase.disableMessageNotifications(today)
}

@Scheduled(cron = "0 0 9,15,21 * * *") // 매일 9, 15, 21시 실행
fun encourageVote() {
voteNotificationUseCase.encourageVote()
}

@Scheduled(cron = "0 0 17 * * *") // 매일 17시 실행
fun openMessage() {
messageNotificationUseCase.openMessage()
}

}
2 changes: 1 addition & 1 deletion app/src/main/resources/config
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@ import com.wespot.message.Message
import com.wespot.message.MessageTimeValidator
import com.wespot.message.dto.request.UpdateMessageRequest
import com.wespot.message.dto.response.UpdateMessageResponse
import com.wespot.message.event.ReadMessageByReceiverEvent
import com.wespot.message.fixture.MessageFixture
import com.wespot.message.port.out.MessagePort
import com.wespot.user.User
import com.wespot.user.fixture.UserFixture
import com.wespot.user.port.out.UserPort
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
import io.mockk.*
import io.mockk.clearAllMocks
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import org.springframework.context.ApplicationEventPublisher
import java.time.Clock
import java.time.Instant
import java.time.ZoneId
Expand All @@ -21,9 +26,11 @@ class ModifyMessageServiceTest : BehaviorSpec({

val messagePort = mockk<MessagePort>()
val userPort = mockk<UserPort>()
val eventPublisher = mockk<ApplicationEventPublisher>()
val modifyMessageService = ModifyMessageService(
messagePort = messagePort,
userPort = userPort
userPort = userPort,
eventPublisher = eventPublisher
)

lateinit var sender: User
Expand Down Expand Up @@ -63,8 +70,6 @@ class ModifyMessageServiceTest : BehaviorSpec({
every { SecurityUtils.getLoginUser(userPort) } returns sender
every { messagePort.save(any()) } returns message.copy(content = updateMessageRequest.content)



then("메시지가 올바르게 업데이트되어야 한다") {
val response = modifyMessageService.updateMessage(message.id, updateMessageRequest)
response shouldBe UpdateMessageResponse.from(message.id)
Expand All @@ -90,8 +95,19 @@ class ModifyMessageServiceTest : BehaviorSpec({
val messageId = message.id

every { SecurityUtils.getLoginUser(userPort) } returns receiver
every { userPort.findById(1) } returns sender
every { messagePort.findById(messageId) } returns receivedMessage
every { messagePort.save(any()) } returns message.copy(isReceiverRead = true)
every { messagePort.save(any()) } returns receivedMessage.copy(isReceiverRead = true)
every {
eventPublisher.publishEvent(
ReadMessageByReceiverEvent(
sender,
receiver,
messageId,
false
)
)
} returns Unit

then("메시지가 읽은 상태로 업데이트되어야 한다") {
modifyMessageService.readMessage(messageId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,21 @@ import com.wespot.message.Message
import com.wespot.message.MessageTimeValidator
import com.wespot.message.dto.request.SendMessageRequest
import com.wespot.message.dto.response.SendMessageResponse
import com.wespot.message.event.MessageLimitEvent
import com.wespot.message.event.ReceivedMessageEvent
import com.wespot.message.fixture.MessageFixture
import com.wespot.message.port.out.MessagePort
import com.wespot.user.User
import com.wespot.user.fixture.UserFixture
import com.wespot.user.port.out.BlockedUserPort
import com.wespot.user.port.out.UserPort
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
import io.mockk.*
import io.mockk.clearAllMocks
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import org.springframework.context.ApplicationEventPublisher
import java.time.Clock
import java.time.Instant
import java.time.ZoneId
Expand All @@ -24,10 +29,12 @@ class SendMessageServiceTest : BehaviorSpec({
val messagePort = mockk<MessagePort>()
val userPort = mockk<UserPort>()
val blockedUserPort = mockk<BlockedUserPort>()
val eventPublisher = mockk<ApplicationEventPublisher>()
val sendMessageService = SendMessageService(
messagePort = messagePort,
userPort = userPort,
blockedUserPort = blockedUserPort
blockedUserPort = blockedUserPort,
eventPublisher = eventPublisher
)

lateinit var sender: User
Expand Down Expand Up @@ -68,12 +75,10 @@ class SendMessageServiceTest : BehaviorSpec({
every { messagePort.save(any()) } returns message
every { messagePort.sendMessageCount(sender.id) } returns 0
every { messagePort.hasSentMessageToday(sender.id, receiver.id) } returns false
every { blockedUserPort.existsByBlockerIdAndBlockedId(sender.id, receiver.id) } returns false
every { blockedUserPort.existsByBlockerIdAndBlockedId(receiver.id, sender.id) } returns false

// 시간을 조작하여 테스트 시간 설정
val fixedClock = Clock.fixed(Instant.parse("2023-03-18T18:00:00Z"), ZoneId.of("UTC"))
MessageTimeValidator.setClock(fixedClock)
every { eventPublisher.publishEvent(MessageLimitEvent(0, 0)) } returns Unit
every { eventPublisher.publishEvent(ReceivedMessageEvent(receiver, 0)) } returns Unit
every { blockedUserPort.existsByBlockerIdAndBlockedId(1, 2) } returns false
every { blockedUserPort.existsByBlockerIdAndBlockedId(2, 1) } returns false

val response = sendMessageService.send(sendMessageRequest)

Expand All @@ -92,33 +97,6 @@ class SendMessageServiceTest : BehaviorSpec({
then("메시지를 저장해야 한다") {
verify { messagePort.save(any()) }
}

then("차단된 사용자를 확인해야 한다") {
verify { blockedUserPort.existsByBlockerIdAndBlockedId(sender.id, receiver.id) }
verify { blockedUserPort.existsByBlockerIdAndBlockedId(receiver.id, sender.id) }
}
}

`when`("차단된 사용자가 메시지를 보내려 할 때") {
val sendMessageRequest = SendMessageRequest(
content = "Hello",
receiverId = receiver.id,
senderName = "sender",
isAnonymous = false
)

every { userPort.findById(sender.id) } returns sender
every { userPort.findById(receiver.id) } returns receiver
every { userPort.findByEmail(sender.email) } returns sender
every { messagePort.sendMessageCount(sender.id) } returns 0
every { messagePort.hasSentMessageToday(sender.id, receiver.id) } returns false
every { blockedUserPort.existsByBlockerIdAndBlockedId(sender.id, receiver.id) } returns true

then("예외를 발생시켜야 한다") {
shouldThrow<IllegalStateException> {
sendMessageService.send(sendMessageRequest)
}.message shouldBe "차단된 유저에게 메시지를 보낼 수 없습니다."
}
}
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.wespot.notification.domain

import com.google.firebase.messaging.Notification
import com.wespot.notification.NotificationInfo
import com.wespot.notification.NotificationType
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.mockkStatic
import java.time.LocalDate

class NotificationInfoTest : BehaviorSpec({

given("NotificationInfo에서") {
val now = LocalDate.now()
val notificationInfo = NotificationInfo.createInitialState(
com.wespot.notification.Notification.createVoteInitialState(
0,
NotificationType.VOTE,
now,
"title",
"body"
)
)
`when`("Notification을") {
val notification = notificationInfo.getNotification()
then("반환받는다.") {
notification::class shouldBe Notification::class
}
}
`when`("Data를") {
val data = notificationInfo.getData()
then("반환받는다.") {
data["targetId"] shouldBe "0"
data["date"] shouldBe now.toString()
data["type"] shouldBe "VOTE"
}
}
}

given("NotificationInfo를 생성할 때") {
mockkStatic(LocalDate::class)

`when`("Vote용으로") {
val now = LocalDate.now()
val notification = com.wespot.notification.Notification.createVoteInitialState(
0,
NotificationType.VOTE,
now,
"title",
"body"
)
val notificationInfo = NotificationInfo.createInitialState(notification)
then("생성한다.") {
notificationInfo.targetId shouldBe 0
notificationInfo.date shouldBe now
notificationInfo.type shouldBe NotificationType.VOTE
notificationInfo.title shouldBe "title"
notificationInfo.body shouldBe "body"
}
}
`when`("Message용으로") {
val now = LocalDate.now()
every { LocalDate.now() } returns now
val notification = com.wespot.notification.Notification.createMessageInitialState(
0,
NotificationType.MESSAGE,
1,
"title",
"body"
)
val notificationInfo = NotificationInfo.createInitialState(notification)
then("생성한다.") {
notificationInfo.targetId shouldBe 1
notificationInfo.date shouldBe now
notificationInfo.type shouldBe NotificationType.MESSAGE
notificationInfo.title shouldBe "title"
notificationInfo.body shouldBe "body"
}
}
}

})
Loading

0 comments on commit 1eebd00

Please sign in to comment.