Skip to content

Commit

Permalink
[Feat/#131] 이메일 구독 내용 채워서 구현 (#156)
Browse files Browse the repository at this point in the history
* refactor: SendArticleEmailArgs의 content 재정의

* feat: BatchCategoryType, BatchMemberType 추가

* refactor: WorkBookSubscriberWriter 수정

* test: SendArticleEmailArgs의 content 재정의 반영

* refactor: 유료 계정 전환으로 인한 이메일 테스트 Disabled 처리

* fix: 요일이 영어로 내려가는 문제 해결

* refactor: articleLink 변경 사항 반영

* test: b00b1fe 반영

* refactor: 인터페이스를 사용하여 상속 대신하고 data 클래스로 변경

* refactor: article 이메일 템플릿 변경 반영

* fix: 수신 거부 링크 오타 수정
  • Loading branch information
belljun3395 authored Jul 8, 2024
1 parent 768bfff commit 17ee380
Show file tree
Hide file tree
Showing 12 changed files with 424 additions and 190 deletions.
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")
}
}
}
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<Map<Long, String>>>()
// 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, "member$i@gmail.com")
.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
3 changes: 3 additions & 0 deletions data/src/main/kotlin/com/few/data/common/code/CategoryType.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.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

0 comments on commit 17ee380

Please sign in to comment.