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

[Feat/#131] 이메일 구독 내용 채워서 구현 #156

Merged
merged 11 commits into from
Jul 8, 2024
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.few.batch.data.common.code

/**
* BatchCategoryType is origin from CategoryType in few-data module.
* @see com.few.data.common.code.CategoryType
*/
enum class BatchCategoryType(val code: Byte, val displayName: String) {
ECONOMY(0, "경제"),
IT(10, "IT"),
MARKETING(20, "마케팅"),
CULTURE(30, "교양"),
SCIENCE(40, "과학");

companion object {
fun fromCode(code: Byte): BatchCategoryType? {
return entries.find { it.code == code }
}

fun convertToCode(displayName: String): Byte {
return entries.find { it.name == displayName }?.code ?: throw IllegalArgumentException("Invalid category name")
}

fun convertToDisplayName(code: Byte): String {
return entries.find { it.code == code }?.displayName ?: throw IllegalArgumentException("Invalid category code")
}
}
}
Comment on lines +7 to +27
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CategoryType과 BatchCategoryType이 가진 카테고리 값들은 왜 서로 다른가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.few.batch.data.common.code

/**
* BatchMemberType is origin from MemberType in few-data module.
* @see com.few.data.common.code.MemberType
*/
enum class BatchMemberType(val code: Byte, val displayName: String) {
NORMAL(60, "일반멤버"),
ADMIN(0, "어드민멤버"),
WRITER(120, "작가멤버");

companion object {
fun fromCode(code: Byte): BatchMemberType? {
return values().find { it.code == code }
}
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
package com.few.batch.service.article.writer

import com.few.batch.data.common.code.BatchCategoryType
import com.few.batch.data.common.code.BatchMemberType
import com.few.batch.service.article.dto.WorkBookSubscriberItem
import com.few.batch.service.article.dto.toMemberIds
import com.few.batch.service.article.dto.toTargetWorkBookIds
import com.few.batch.service.article.dto.toTargetWorkBookProgress
import com.few.email.service.article.SendArticleEmailService
import com.few.email.service.article.dto.Content
import com.few.email.service.article.dto.SendArticleEmailArgs
import jooq.jooq_dsl.tables.ArticleIfo
import jooq.jooq_dsl.tables.MappingWorkbookArticle
import jooq.jooq_dsl.tables.Member
import jooq.jooq_dsl.tables.Subscription
import jooq.jooq_dsl.tables.*
import org.jooq.DSLContext
import org.jooq.impl.DSL
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
import java.net.URL
import java.time.LocalDate
import java.time.LocalDateTime

Expand All @@ -39,23 +40,40 @@ data class MemberReceiveArticles(
}
}

data class ArticleContent(
val id: Long,
val category: String, // todo fix
val articleTitle: String,
val articleContent: String,
val writerName: String,
val writerLink: URL
)

fun List<ArticleContent>.peek(articleId: Long): ArticleContent {
return this.find {
it.id == articleId
} ?: throw IllegalArgumentException("article not found")
}

@Component
class WorkBookSubscriberWriter(
private val dslContext: DSLContext,
private val sendArticleEmailService: SendArticleEmailService
) {

companion object {
private const val ARTICLE_SUBJECT_TEMPLATE = "Day%d %s"
private const val ARTICLE_TEMPLATE = "article"
}

@Transactional
fun execute(items: List<WorkBookSubscriberItem>): Map<Any, Any> {
val memberT = Member.MEMBER
val mappingWorkbookArticleT = MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE
val articleIfoT = ArticleIfo.ARTICLE_IFO
val articleMstT = ArticleMst.ARTICLE_MST
val subscriptionT = Subscription.SUBSCRIPTION

val ARTICLE_SUBJECT = "${LocalDate.now()} 일자 학습 아티클"
val ARTICLE_TEMPLATE = "article" // todo fix
val ARTICLE_STYLE = "style" // todo fix

val memberIds = items.toMemberIds()
val targetWorkBookIds = items.toTargetWorkBookIds()
val targetWorkBookProgress = items.toTargetWorkBookProgress()
Expand Down Expand Up @@ -90,25 +108,55 @@ class WorkBookSubscriberWriter(
MemberReceiveArticles(it)
}

/** 구독자들이 받을 학습지 정보를 기준으로 학습지의 내용을 조회한다.*/
val articleContentsMap = dslContext.select(
articleIfoT.ARTICLE_MST_ID,
articleIfoT.CONTENT
/** 구독자들이 받을 학습지 정보를 기준으로 학습지 관련 정보를 조회한다.*/
val articleContents = dslContext.select(
articleIfoT.ARTICLE_MST_ID.`as`(ArticleContent::id.name),
articleIfoT.CONTENT.`as`(ArticleContent::articleContent.name),
articleMstT.TITLE.`as`(ArticleContent::articleTitle.name),
articleMstT.CATEGORY_CD.`as`(ArticleContent::category.name),
DSL.jsonGetAttributeAsText(memberT.DESCRIPTION, "name").`as`(ArticleContent::writerName.name),
DSL.jsonGetAttribute(memberT.DESCRIPTION, "url").`as`(ArticleContent::writerLink.name)
)
.from(articleIfoT)
.join(articleMstT)
.on(articleIfoT.ARTICLE_MST_ID.eq(articleMstT.ID))
.join(memberT)
.on(
articleMstT.MEMBER_ID.eq(memberT.ID)
.and(memberT.TYPE_CD.eq(BatchMemberType.WRITER.code))
)
.where(articleIfoT.ARTICLE_MST_ID.`in`(memberReceiveArticles.getArticleIds()))
.and(articleIfoT.DELETED_AT.isNull)
.fetch()
.intoMap(articleIfoT.ARTICLE_MST_ID, articleIfoT.CONTENT)
.fetchInto(ArticleContent::class.java)

val memberSuccessRecords = memberIds.associateWith { true }.toMutableMap()
val failRecords = mutableMapOf<String, ArrayList<Long>>()
// todo check !! target is not null
val date = LocalDate.now()
val emailServiceArgs = items.map {
val toEmail = memberEmailRecords[it.memberId]!!
val memberArticle = memberReceiveArticles.getByWorkBookIdAndDayCol(it.targetWorkBookId, it.progress + 1)
val articleContent = articleContentsMap[memberArticle.articleId]!!
return@map it.memberId to SendArticleEmailArgs(toEmail, ARTICLE_SUBJECT, ARTICLE_TEMPLATE, articleContent, ARTICLE_STYLE)
val articleContent = articleContents.peek(memberArticle.articleId).let { article ->
Content(
articleLink = URL("https://www.fewletter.com/article/${memberArticle.articleId}"),
currentDate = date,
category = BatchCategoryType.convertToDisplayName(article.category.toByte()),
articleDay = memberArticle.dayCol.toInt(),
articleTitle = article.articleTitle,
writerName = article.writerName,
writerLink = article.writerLink,
articleContent = article.articleContent,
problemLink = URL("https://www.fewletter.com/problem?articleId=${memberArticle.articleId}"),
unsubscribeLink = URL("https://www.fewletter.com/unsubscribe?user=${memberEmailRecords[it.memberId]}&workbookId=${it.targetWorkBookId}&articleId=${memberArticle.articleId}")
)
}
return@map it.memberId to
SendArticleEmailArgs(
toEmail,
ARTICLE_SUBJECT_TEMPLATE.format(memberArticle.dayCol, articleContent.articleTitle),
ARTICLE_TEMPLATE,
articleContent
)
}

// todo refactoring to send email in parallel or batch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import jooq.jooq_dsl.tables.ArticleIfo
import jooq.jooq_dsl.tables.MappingWorkbookArticle
import jooq.jooq_dsl.tables.Member
import jooq.jooq_dsl.tables.Subscription
import org.jboss.logging.MDC
import org.jooq.DSLContext
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.Mockito.*
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.mock.mockito.MockBean
import java.time.LocalDate
import kotlin.random.Random

class WorkBookSubscriberWriterTest : BatchTestSpec() {
Expand All @@ -40,11 +40,15 @@ class WorkBookSubscriberWriterTest : BatchTestSpec() {

// setup member
helper.setUpMembers(10)
helper.setUpWriter(11)

// setup subscription
helper.setUpSubscriptions(start = 1, count = 5, workbookId = 1)
helper.setUpSubscriptions(start = 6, count = 10, workbookId = 2)

// setup article mst
helper.setUpArticleMst(start = 1, count = 10, writerId = 11)

// setup mapping workbook article
helper.setUpMappingWorkbookArticle(start = 1, count = 5, workbookId = 1)
helper.setUpMappingWorkbookArticle(start = 6, count = 10, workbookId = 2, dayCorrection = 5)
Expand Down Expand Up @@ -79,16 +83,16 @@ class WorkBookSubscriberWriterTest : BatchTestSpec() {
// given
val items = helper.browseItems(listOf(1, 2))
val failItem = items[Random.nextInt(6, items.size - 1)]
val failArgs = SendArticleEmailArgs(
"member${failItem.memberId}@gmail.com",
"Day${failItem.content.articleDay} ${failItem.content.articleTitle}",
"article",
failItem.content,
""
)
MDC.put("test", failArgs)
`when`(
sendArticleEmailService.send(
SendArticleEmailArgs(
"member${failItem.memberId}@gmail.com",
"${LocalDate.now()} 일자 학습 아티클",
"article",
failItem.content,
"style"
)
)
sendArticleEmailService.send(failArgs)
).thenThrow(RuntimeException("send email error"))

// when
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package com.few.batch.service.article.writer

import com.few.batch.data.common.code.BatchCategoryType
import com.few.batch.data.common.code.BatchMemberType
import com.few.batch.service.article.dto.WorkBookSubscriberItem
import jooq.jooq_dsl.tables.ArticleIfo
import jooq.jooq_dsl.tables.MappingWorkbookArticle
import jooq.jooq_dsl.tables.Member
import jooq.jooq_dsl.tables.Subscription
import com.few.email.service.article.dto.Content
import jooq.jooq_dsl.tables.*
import org.jooq.DSLContext
import org.jooq.JSON
import org.springframework.boot.test.context.TestComponent
import java.net.URL
import java.time.LocalDate
import kotlin.random.Random

fun List<TestWorkBookSubscriberDto>.toServiceDto(): List<WorkBookSubscriberItem> {
Expand All @@ -23,7 +26,14 @@ data class TestWorkBookSubscriberDto(
val memberId: Long,
val targetWorkBookId: Long,
val progress: Long,
val content: String
val content: Content
)

data class ArticleDto(
val articleId: Long,
val dayCol: Int,
val content: String,
val title: String
)

@TestComponent
Expand All @@ -36,11 +46,20 @@ class WorkBookSubscriberWriterTestSetHelper(
dslContext.insertInto(Member.MEMBER)
.set(Member.MEMBER.ID, i.toLong())
.set(Member.MEMBER.EMAIL, "[email protected]")
.set(Member.MEMBER.TYPE_CD, 0) // todo fix
.set(Member.MEMBER.TYPE_CD, BatchMemberType.NORMAL.code)
.execute()
}
}

fun setUpWriter(id: Long) {
dslContext.insertInto(Member.MEMBER)
.set(Member.MEMBER.ID, id)
.set(Member.MEMBER.EMAIL, "[email protected]")
.set(Member.MEMBER.TYPE_CD, BatchMemberType.WRITER.code)
.set(Member.MEMBER.DESCRIPTION, JSON.valueOf("{\"url\": \"http://localhost:8080\", \"name\": \"writer\"}"))
.execute()
}

fun setUpSubscriptions(start: Int = 1, count: Int, workbookId: Long = 1) {
for (i in start..count) {
dslContext.insertInto(Subscription.SUBSCRIPTION)
Expand All @@ -51,7 +70,7 @@ class WorkBookSubscriberWriterTestSetHelper(
}
}

fun setUpMappingWorkbookArticle(start: Int = 1, count: Int, workbookId: Long = 1, dayCorrection: Int = 0) {
fun setUpMappingWorkbookArticle(start: Int = 1, count: Int, workbookId: Long = 1, dayCorrection: Int = 1) {
for (i in start..count) {
dslContext.insertInto(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE)
.set(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID, workbookId)
Expand All @@ -61,6 +80,18 @@ class WorkBookSubscriberWriterTestSetHelper(
}
}

fun setUpArticleMst(start: Int = 1, count: Int, writerId: Long) {
for (i in start..count) {
dslContext.insertInto(ArticleMst.ARTICLE_MST)
.set(ArticleMst.ARTICLE_MST.ID, i.toLong())
.set(ArticleMst.ARTICLE_MST.TITLE, "title$i")
.set(ArticleMst.ARTICLE_MST.MEMBER_ID, writerId)
.set(ArticleMst.ARTICLE_MST.CATEGORY_CD, BatchCategoryType.fromCode(0)!!.code)
.set(ArticleMst.ARTICLE_MST.MAIN_IMAGE_URL, "http://localhost:8080")
.execute()
}
}

fun setUpArticleIfo(start: Int = 1, count: Int) {
for (i in start..count) {
dslContext.insertInto(ArticleIfo.ARTICLE_IFO)
Expand Down Expand Up @@ -92,11 +123,36 @@ class WorkBookSubscriberWriterTestSetHelper(
.and(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DELETED_AT.isNull)
.fetchOneInto(String::class.java)

val articleDto = dslContext.select(
MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.ARTICLE_ID,
MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DAY_COL,
ArticleIfo.ARTICLE_IFO.CONTENT,
ArticleMst.ARTICLE_MST.TITLE
).from(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE)
.join(ArticleIfo.ARTICLE_IFO)
.on(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.ARTICLE_ID.eq(ArticleIfo.ARTICLE_IFO.ARTICLE_MST_ID))
.join(ArticleMst.ARTICLE_MST)
.on(ArticleIfo.ARTICLE_IFO.ARTICLE_MST_ID.eq(ArticleMst.ARTICLE_MST.ID))
.where(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID.eq(workbookId))
.and(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DAY_COL.eq(it[Subscription.SUBSCRIPTION.PROGRESS].toInt() + 1))
.fetchOneInto(ArticleDto::class.java)

val dto = TestWorkBookSubscriberDto(
memberId = it[Subscription.SUBSCRIPTION.MEMBER_ID],
targetWorkBookId = workbookId,
progress = it[Subscription.SUBSCRIPTION.PROGRESS],
content = content!!
content = Content(
articleLink = URL("https://www.fewletter.com/article/${articleDto!!.articleId}"),
currentDate = LocalDate.now(),
category = BatchCategoryType.fromCode(0)!!.displayName,
articleDay = articleDto.dayCol,
articleTitle = articleDto.title,
writerName = "writer",
writerLink = URL("http://localhost:8080"),
articleContent = articleDto.content,
problemLink = URL("https://www.fewletter.com/problem?articleId=${articleDto.articleId}"),
unsubscribeLink = URL("https://www.fewletter.com/unsbuscribe?user=member${it[Subscription.SUBSCRIPTION.MEMBER_ID]}@gmail.com&workbookId=$workbookId&articleId=${articleDto.articleId}")
)
)
items.add(dto)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.few.data.common.code

/**
* @see com.few.batch.data.common.code.BatchCategoryType
*/
enum class CategoryType(val code: Byte, val displayName: String) {
POLITICS(0, "정치"),
ECONOMY(10, "경제"),
Expand Down
3 changes: 3 additions & 0 deletions data/src/main/kotlin/com/few/data/common/code/MemberType.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.few.data.common.code

/**
* @see com.few.batch.data.common.code.BatchMemberType
*/
enum class MemberType(val code: Byte, val displayName: String) {
NORMAL(60, "일반멤버"),
ADMIN(0, "어드민멤버"),
Expand Down
Loading
Loading