Skip to content
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

[RELEASE] v24.09.26.01 #424

Merged
merged 4 commits into from
Sep 26, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ class SubscriptionDao(
dslContext.insertInto(SUBSCRIPTION)
.set(SUBSCRIPTION.MEMBER_ID, command.memberId)
.set(SUBSCRIPTION.TARGET_WORKBOOK_ID, command.workbookId)
.set(SUBSCRIPTION.SEND_DAY, command.sendDay)
.set(SUBSCRIPTION.SEND_TIME, command.sendTime)

fun reSubscribeWorkbookSubscription(command: InsertWorkbookSubscriptionCommand) {
reSubscribeWorkBookSubscriptionCommand(command)
Expand All @@ -53,6 +55,8 @@ class SubscriptionDao(
.set(SUBSCRIPTION.DELETED_AT, null as LocalDateTime?)
.set(SUBSCRIPTION.UNSUBS_OPINION, null as String?)
.set(SUBSCRIPTION.MODIFIED_AT, LocalDateTime.now())
.set(SUBSCRIPTION.SEND_DAY, command.sendDay)
.set(SUBSCRIPTION.SEND_TIME, command.sendTime)
.where(SUBSCRIPTION.MEMBER_ID.eq(command.memberId))
.and(SUBSCRIPTION.TARGET_WORKBOOK_ID.eq(command.workbookId))

Expand Down Expand Up @@ -282,4 +286,20 @@ class SubscriptionDao(
.from(SUBSCRIPTION)
.where(SUBSCRIPTION.MEMBER_ID.eq(query.memberId))
.and(SUBSCRIPTION.TARGET_WORKBOOK_ID.eq(query.workbookId))

fun selectSubscriptionSendStatus(query: SelectSubscriptionSendStatusQuery): List<SubscriptionSendStatus> {
return selectSubscriptionSendStatusQuery(query)
.fetchInto(SubscriptionSendStatus::class.java)
}

fun selectSubscriptionSendStatusQuery(query: SelectSubscriptionSendStatusQuery) =
dslContext.select(
SUBSCRIPTION.MEMBER_ID,
SUBSCRIPTION.TARGET_WORKBOOK_ID,
SUBSCRIPTION.SEND_TIME,
SUBSCRIPTION.SEND_DAY
)
.from(SUBSCRIPTION)
.where(SUBSCRIPTION.MEMBER_ID.eq(query.memberId))
.and(SUBSCRIPTION.DELETED_AT.isNull)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package com.few.api.repo.dao.subscription.command

import com.few.data.common.code.DayCode
import java.time.LocalTime

data class InsertWorkbookSubscriptionCommand(
val workbookId: Long,
val memberId: Long,
val sendDay: String? = DayCode.MON_TUE_WED_THU_FRI.code,
val sendTime: LocalTime? = LocalTime.of(8, 0),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.few.api.repo.dao.subscription.query

data class SelectSubscriptionSendStatusQuery(
val memberId: Long,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.few.api.repo.dao.subscription.record

import java.time.LocalTime

data class SubscriptionSendStatus(
val memberId: Long,
val workbookId: Long,
val sendDay: String,
val sendTime: LocalTime,
)
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.few.api.domain.subscription.usecase.model.WorkbookSubscriptionStatus
import com.few.api.exception.common.NotFoundException
import com.few.api.exception.subscribe.SubscribeIllegalArgumentException
import com.few.api.repo.dao.subscription.query.CountWorkbookMappedArticlesQuery
import com.few.api.repo.dao.subscription.query.SelectSubscriptionSendStatusQuery
import org.springframework.context.ApplicationEventPublisher
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
Expand All @@ -28,10 +29,28 @@ class SubscribeWorkbookUseCase(
fun execute(useCaseIn: SubscribeWorkbookUseCaseIn) {
val subTargetWorkbookId = useCaseIn.workbookId
val memberId = useCaseIn.memberId
val command = InsertWorkbookSubscriptionCommand(
memberId = memberId,
workbookId = subTargetWorkbookId
)

val command = subscriptionDao.selectSubscriptionSendStatus(
SelectSubscriptionSendStatusQuery(
memberId = memberId
)
).takeIf {
it.isNotEmpty()
}?.let {
/** ํ˜„์žฌ ๊ตฌ๋… ์ค‘์ธ ์ •๋ณด๊ฐ€ ์žˆ๋‹ค๋ฉด ํ•ด๋‹น ์ •๋ณด๋ฅผ ํ†ตํ•ด ๊ตฌ๋… ์ •๋ณด๋ฅผ ์ƒ์„ฑ */
InsertWorkbookSubscriptionCommand(
memberId = memberId,
workbookId = subTargetWorkbookId,
sendDay = it[0].sendDay,
sendTime = it[0].sendTime
)
} ?: run {
/** ํ˜„์žฌ ๊ตฌ๋… ์ค‘์ธ ์ •๋ณด๊ฐ€ ์—†๋‹ค๋ฉด ๊ธฐ๋ณธ ์ •๋ณด๋กœ ๊ตฌ๋… ์ •๋ณด๋ฅผ ์ƒ์„ฑ */
InsertWorkbookSubscriptionCommand(
memberId = memberId,
workbookId = subTargetWorkbookId
)
}

val workbookSubscriptionHistory = subscriptionDao.selectTopWorkbookSubscriptionStatus(
SelectAllWorkbookSubscriptionStatusNotConsiderDeletedAtQuery(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class SaveMemberUseCaseTest : BehaviorSpec({
val token = "encryptedToken"
every { idEncryption.encrypt(any()) } returns token

every { sendAuthEmailService.send(any()) } returns Unit
every { sendAuthEmailService.send(any()) } returns "messageId"

then("์ธ์ฆ ์ด๋ฉ”์ผ ๋ฐœ์†ก ์„ฑ๊ณต ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค") {
val useCaseOut = useCase.execute(useCaseIn)
Expand All @@ -64,7 +64,7 @@ class SaveMemberUseCaseTest : BehaviorSpec({
val token = "encryptedToken"
every { idEncryption.encrypt(any()) } returns token

every { sendAuthEmailService.send(any()) } returns Unit
every { sendAuthEmailService.send(any()) } returns "messageId"

then("์ธ์ฆ ์ด๋ฉ”์ผ ๋ฐœ์†ก ์„ฑ๊ณต ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค") {
val useCaseOut = useCase.execute(useCaseIn)
Expand All @@ -90,7 +90,7 @@ class SaveMemberUseCaseTest : BehaviorSpec({
val token = "encryptedToken"
every { idEncryption.encrypt(any()) } returns token

every { sendAuthEmailService.send(any()) } returns Unit
every { sendAuthEmailService.send(any()) } returns "messageId"

then("์ธ์ฆ ์ด๋ฉ”์ผ ๋ฐœ์†ก ์„ฑ๊ณต ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค") {
val useCaseOut = useCase.execute(useCaseIn)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package com.few.api.domain.subscription.usecase
import com.few.api.domain.subscription.event.dto.WorkbookSubscriptionEvent
import com.few.api.domain.subscription.usecase.dto.SubscribeWorkbookUseCaseIn
import com.few.api.repo.dao.subscription.SubscriptionDao
import com.few.api.repo.dao.subscription.record.SubscriptionSendStatus
import com.few.api.repo.dao.subscription.record.WorkbookSubscriptionStatus
import com.few.data.common.code.DayCode
import io.github.oshai.kotlinlogging.KotlinLogging
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.BehaviorSpec
Expand All @@ -13,6 +15,7 @@ import io.mockk.verify
import io.mockk.just
import io.mockk.Runs
import org.springframework.context.ApplicationEventPublisher
import java.time.LocalTime

class SubscribeWorkbookUseCaseTest : BehaviorSpec({
val log = KotlinLogging.logger {}
Expand All @@ -33,6 +36,15 @@ class SubscribeWorkbookUseCaseTest : BehaviorSpec({
val useCaseIn = SubscribeWorkbookUseCaseIn(workbookId = workbookId, memberId = memberId)

`when`("๋ฉค๋ฒ„์˜ ๊ตฌ๋… ํžˆ์Šคํ† ๋ฆฌ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ") {
every { subscriptionDao.selectSubscriptionSendStatus(any()) } returns listOf(
SubscriptionSendStatus(
workbookId = workbookId,
memberId = memberId,
sendDay = DayCode.MON_TUE_WED_THU_FRI.code,
sendTime = LocalTime.of(8, 0)
)
)

every { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } returns null

every { subscriptionDao.insertWorkbookSubscription(any()) } just Runs
Expand All @@ -45,6 +57,7 @@ class SubscribeWorkbookUseCaseTest : BehaviorSpec({
then("๊ตฌ๋…ํ•œ๋‹ค") {
useCase.execute(useCaseIn)

verify(exactly = 1) { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) }
verify(exactly = 1) { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) }
verify(exactly = 1) { subscriptionDao.insertWorkbookSubscription(any()) }
verify(exactly = 0) { subscriptionDao.countWorkbookMappedArticles(any()) }
Expand All @@ -54,6 +67,15 @@ class SubscribeWorkbookUseCaseTest : BehaviorSpec({
}

`when`("์ด๋ฏธ ๊ตฌ๋…ํ•œ ํžˆ์Šคํ† ๋ฆฌ๊ฐ€ ์žˆ๊ณ  ๊ตฌ๋…์ด ์ทจ์†Œ๋œ ๊ฒฝ์šฐ") {
every { subscriptionDao.selectSubscriptionSendStatus(any()) } returns listOf(
SubscriptionSendStatus(
workbookId = workbookId,
memberId = memberId,
sendDay = DayCode.MON_TUE_WED_THU_FRI.code,
sendTime = LocalTime.of(8, 0)
)
)

val day = 2
every { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } returns WorkbookSubscriptionStatus(
workbookId = workbookId,
Expand All @@ -74,6 +96,7 @@ class SubscribeWorkbookUseCaseTest : BehaviorSpec({
then("์žฌ๊ตฌ๋…ํ•œ๋‹ค") {
useCase.execute(useCaseIn)

verify(exactly = 1) { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) }
verify(exactly = 1) { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) }
verify(exactly = 0) { subscriptionDao.insertWorkbookSubscription(any()) }
verify(exactly = 1) { subscriptionDao.countWorkbookMappedArticles(any()) }
Expand All @@ -83,6 +106,15 @@ class SubscribeWorkbookUseCaseTest : BehaviorSpec({
}

`when`("์ด๋ฏธ ๊ตฌ๋…ํ•œ ํžˆ์Šคํ† ๋ฆฌ๊ฐ€ ์žˆ๊ณ  ๊ตฌ๋…์„ ์™„๋ฃŒํ•œ ๊ฒฝ์šฐ") {
every { subscriptionDao.selectSubscriptionSendStatus(any()) } returns listOf(
SubscriptionSendStatus(
workbookId = workbookId,
memberId = memberId,
sendDay = DayCode.MON_TUE_WED_THU_FRI.code,
sendTime = LocalTime.of(8, 0)
)
)

val day = 3
every { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } returns WorkbookSubscriptionStatus(
workbookId = workbookId,
Expand All @@ -103,6 +135,7 @@ class SubscribeWorkbookUseCaseTest : BehaviorSpec({
then("์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค") {
shouldThrow<Exception> { useCase.execute(useCaseIn) }

verify(exactly = 1) { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) }
verify(exactly = 1) { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) }
verify(exactly = 0) { subscriptionDao.insertWorkbookSubscription(any()) }
verify(exactly = 1) { subscriptionDao.countWorkbookMappedArticles(any()) }
Expand All @@ -112,12 +145,22 @@ class SubscribeWorkbookUseCaseTest : BehaviorSpec({
}

`when`("๊ตฌ๋… ์ค‘์ธ ๊ฒฝ์šฐ") {
every { subscriptionDao.selectSubscriptionSendStatus(any()) } returns listOf(
SubscriptionSendStatus(
workbookId = workbookId,
memberId = memberId,
sendDay = DayCode.MON_TUE_WED_THU_FRI.code,
sendTime = LocalTime.of(8, 0)
)
)

val day = 2
every { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } returns WorkbookSubscriptionStatus(workbookId = workbookId, isActiveSub = true, day)

then("์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค") {
shouldThrow<Exception> { useCase.execute(useCaseIn) }

verify(exactly = 1) { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) }
verify(exactly = 1) { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) }
verify(exactly = 0) { subscriptionDao.insertWorkbookSubscription(any()) }
verify(exactly = 0) { subscriptionDao.countWorkbookMappedArticles(any()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,19 @@ class WorkBookSubscriberReader(
}

private fun sendDayCondition(sendDayField: TableField<SubscriptionRecord, String>, sendDayCode: BatchDayCode): Condition {
return if (sendDayCode == BatchDayCode.MON_TUE_WED_THU_FRI_SAT_SUN) {
sendDayField.eq(BatchDayCode.MON_TUE_WED_THU_FRI.code).or(sendDayField.eq(BatchDayCode.MON_TUE_WED_THU_FRI_SAT_SUN.code))
} else {
sendDayField.eq(sendDayCode.code)
return when (sendDayCode) {
/** ํ‰์ผ์ธ ๊ฒฝ์šฐ ๋งค์ผ์„ ํฌํ•จํ•˜์—ฌ ์ „์†กํ•œ๋‹ค */
BatchDayCode.MON_TUE_WED_THU_FRI -> {
sendDayField.eq(BatchDayCode.MON_TUE_WED_THU_FRI.code)
.or(sendDayField.eq(BatchDayCode.MON_TUE_WED_THU_FRI_SAT_SUN.code))
}
/** ๋งค์ผ์˜ ๊ฒฝ์šฐ ๋งค์ผ๋งŒ ์ „์†กํ•œ๋‹ค */
BatchDayCode.MON_TUE_WED_THU_FRI_SAT_SUN -> {
sendDayField.eq(BatchDayCode.MON_TUE_WED_THU_FRI_SAT_SUN.code)
}
else -> {
throw IllegalArgumentException("Invalid sendDayCode: $sendDayCode")
}
}
}
}
14 changes: 11 additions & 3 deletions email/src/main/kotlin/com/few/email/sender/EmailSender.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,23 @@ import org.springframework.boot.autoconfigure.mail.MailProperties

abstract class EmailSender<T : SendMailArgs<*, *>>(
private val mailProperties: MailProperties,
private val emailSendProvider: EmailSendProvider,
private val defaultEmailSendProvider: EmailSendProvider,
) {

fun send(args: T) {
fun send(args: T, emailSendProvider: EmailSendProvider? = null): String {
val from = mailProperties.username
val to = args.to
val subject = args.subject
val message = getHtml(args)
emailSendProvider.sendEmail("FEW Letter <$from>", to, subject, message)
return emailSendProvider?.sendEmail("FEW Letter <$from>", to, subject, message)
?: run {
defaultEmailSendProvider.sendEmail(
"FEW Letter <$from>",
to,
subject,
message
)
}
}

abstract fun getHtml(args: T): String
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.few.email.sender.provider

import com.amazonaws.services.simpleemail.AmazonSimpleEmailService
import org.springframework.stereotype.Component

@Component
class ArticleAwsSESEmailSendProvider(
amazonSimpleEmailService: AmazonSimpleEmailService,
javaEmailSendProvider: JavaEmailSendProvider,
) : AwsSESEmailSendProvider(
amazonSimpleEmailService,
javaEmailSendProvider
) {
override fun getWithConfigurationSetName(): String {
return "few-article-configuration-set"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ import com.amazonaws.services.simpleemail.AmazonSimpleEmailService
import com.amazonaws.services.simpleemail.model.*
import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.context.annotation.Primary
import org.springframework.context.annotation.Profile
import org.springframework.stereotype.Component

@Primary
@Profile("prd")
@Component
class AwsSESEmailSendProvider(
private val amazonSimpleEmailService: AmazonSimpleEmailService,
Expand All @@ -19,7 +17,7 @@ class AwsSESEmailSendProvider(
private const val UTF_8 = "utf-8"
}

override fun sendEmail(from: String, to: String, subject: String, message: String) {
override fun sendEmail(from: String, to: String, subject: String, message: String): String {
val destination = Destination().withToAddresses(to)
val sendMessage = Message()
.withSubject(Content().withCharset(UTF_8).withData(subject))
Expand All @@ -29,10 +27,10 @@ class AwsSESEmailSendProvider(
.withSource(from)
.withDestination(destination)
.withMessage(sendMessage)
.withConfigurationSetName("few-configuration-set")
.withConfigurationSetName(getWithConfigurationSetName())

runCatching {
amazonSimpleEmailService.sendEmail(sendEmailRequest)
amazonSimpleEmailService.sendEmail(sendEmailRequest).messageId
}.onFailure {
log.warn {
"Failed to send email using AWS SES. Falling back to JavaMailSender. Error: $it"
Expand All @@ -48,6 +46,15 @@ class AwsSESEmailSendProvider(
}
throw it
}
}.let {
return it.getOrThrow()
}
}

/**
* Default configuration set name is "few-configuration-set"
*/
fun getWithConfigurationSetName(): String {
return "few-configuration-set"
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.few.email.sender.provider

interface EmailSendProvider {
fun sendEmail(from: String, to: String, subject: String, message: String)
/**
* @return ์ „์†กํ•œ ์ด๋ฉ”์ผ ์‹๋ฒฝ์„ ์œ„ํ•œ ๊ฐ’
*/
fun sendEmail(from: String, to: String, subject: String, message: String): String
}
Loading
Loading