From eaa3ff542bfa6182d4a3af13aada86e69d364dd8 Mon Sep 17 00:00:00 2001 From: belljun3395 <195850@jnu.ac.kr> Date: Tue, 27 Aug 2024 10:16:32 +0900 Subject: [PATCH 1/8] =?UTF-8?q?[Refactor/#291]=20Explain=EA=B3=BC=20Explai?= =?UTF-8?q?n=20Anaylze=20=EC=BF=BC=EB=A6=AC=20=EC=8B=A4=ED=96=89=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20(#357)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../few/api/repo/explain/ExplainGenerator.kt | 78 +++++++++++++++++++ .../article/ArticleDaoExplainGenerateTest.kt | 11 +-- .../ArticleMainCardDaoExplainGenerateTest.kt | 8 +- .../ArticleViewCountDaoExplainGenerateTest.kt | 7 +- .../ArticleViewHisDaoExplainGenerateTest.kt | 3 +- .../member/MemberDaoExplainGenerateTest.kt | 52 ++----------- .../problem/ProblemDaoExplainGenerateTest.kt | 7 +- .../SubscriptionDaoExplainGenerateTest.kt | 11 +-- .../WorkbookDaoExplainGenerateTest.kt | 7 +- 9 files changed, 116 insertions(+), 68 deletions(-) create mode 100644 api-repo/src/test/kotlin/com/few/api/repo/explain/ExplainGenerator.kt diff --git a/api-repo/src/test/kotlin/com/few/api/repo/explain/ExplainGenerator.kt b/api-repo/src/test/kotlin/com/few/api/repo/explain/ExplainGenerator.kt new file mode 100644 index 000000000..2ea54043f --- /dev/null +++ b/api-repo/src/test/kotlin/com/few/api/repo/explain/ExplainGenerator.kt @@ -0,0 +1,78 @@ +package com.few.api.repo.explain + +import org.jooq.DSLContext +import org.jooq.Query + +class ExplainGenerator { + companion object { + /** + * Execute EXPLAIN and EXPLAIN ANALYZE FORMAT=TREE + */ + fun execute(dslContext: DSLContext, query: Query): String { + val sql = query.sql + val values = query.bindValues + mapSqlAndValues(sql, values).let { + val explain = StringBuilder() + explain.append("EXPLAIN $it\n") + explain.append("\n") + dslContext.fetch("EXPLAIN $it").let { + explain.append(it) + } + explain.append("\n\n") + explain.append("EXPLAIN ANALYZE FORMAT=TREE $it\n") + explain.append("\n") + dslContext.fetch("EXPLAIN ANALYZE FORMAT=TREE $it").let { + it.forEach { record -> + explain.append(record[0].toString()) + } + } + return explain.toString() + } + } + + /** + * Execute EXPLAIN + */ + fun explain(dslContext: DSLContext, query: Query): String { + val sql = query.sql + val values = query.bindValues + mapSqlAndValues(sql, values).let { + val explain = StringBuilder() + explain.append("EXPLAIN $it\n") + explain.append("\n") + dslContext.fetch("EXPLAIN $it").let { + explain.append(it) + } + return explain.toString() + } + } + + /** + * Execute EXPLAIN ANALYZE FORMAT=TREE + */ + fun analyzeFormatTree(dslContext: DSLContext, query: Query): String { + val sql = query.sql + val values = query.bindValues + mapSqlAndValues(sql, values).let { + val explain = StringBuilder() + explain.append("EXPLAIN ANALYZE FORMAT=TREE $it\n") + explain.append("\n") + dslContext.fetch("EXPLAIN ANALYZE FORMAT=TREE $it").let { + it.forEach { record -> + explain.append(record[0].toString()) + } + } + return explain.toString() + } + } + + private fun mapSqlAndValues(sql: String, values: List) = + values.foldIndexed(sql) { index, acc, value -> + if (value is String) { + acc.replaceFirst("?", "'$value'") + } else { + acc.replaceFirst("?", "$value") + } + } + } +} \ No newline at end of file diff --git a/api-repo/src/test/kotlin/com/few/api/repo/explain/article/ArticleDaoExplainGenerateTest.kt b/api-repo/src/test/kotlin/com/few/api/repo/explain/article/ArticleDaoExplainGenerateTest.kt index 7c7d2b88a..ac2c99967 100644 --- a/api-repo/src/test/kotlin/com/few/api/repo/explain/article/ArticleDaoExplainGenerateTest.kt +++ b/api-repo/src/test/kotlin/com/few/api/repo/explain/article/ArticleDaoExplainGenerateTest.kt @@ -6,6 +6,7 @@ import com.few.api.repo.dao.article.query.SelectArticleIdByWorkbookIdAndDayQuery import com.few.api.repo.dao.article.query.SelectArticleRecordQuery import com.few.api.repo.dao.article.query.SelectWorkBookArticleRecordQuery import com.few.api.repo.dao.article.query.SelectWorkbookMappedArticleRecordsQuery +import com.few.api.repo.explain.ExplainGenerator import com.few.api.repo.explain.InsertUpdateExplainGenerator import com.few.api.repo.explain.ResultGenerator import com.few.api.repo.jooq.JooqTestSpec @@ -63,7 +64,7 @@ class ArticleDaoExplainGenerateTest : JooqTestSpec() { articleDao.selectArticleRecordQuery(it) } - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "selectArticleRecordQueryExplain") } @@ -74,7 +75,7 @@ class ArticleDaoExplainGenerateTest : JooqTestSpec() { articleDao.selectWorkBookArticleRecordQuery(it) } - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "selectWorkBookArticleRecordQueryExplain") } @@ -85,7 +86,7 @@ class ArticleDaoExplainGenerateTest : JooqTestSpec() { articleDao.selectWorkbookMappedArticleRecordsQuery(it) } - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "selectWorkbookMappedArticleRecordsQueryExplain") } @@ -130,7 +131,7 @@ class ArticleDaoExplainGenerateTest : JooqTestSpec() { articleDao.selectArticleIdByWorkbookIdAndDayQuery(it) } - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "selectArticleIdByWorkbookIdAndDayQueryExplain") } @@ -141,7 +142,7 @@ class ArticleDaoExplainGenerateTest : JooqTestSpec() { articleDao.selectArticleContentsQuery(it) } - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "selectArticleContentsQueryExplain") } diff --git a/api-repo/src/test/kotlin/com/few/api/repo/explain/article/ArticleMainCardDaoExplainGenerateTest.kt b/api-repo/src/test/kotlin/com/few/api/repo/explain/article/ArticleMainCardDaoExplainGenerateTest.kt index de27d1285..1a4a58ce6 100644 --- a/api-repo/src/test/kotlin/com/few/api/repo/explain/article/ArticleMainCardDaoExplainGenerateTest.kt +++ b/api-repo/src/test/kotlin/com/few/api/repo/explain/article/ArticleMainCardDaoExplainGenerateTest.kt @@ -4,6 +4,8 @@ import com.few.api.repo.dao.article.ArticleMainCardDao import com.few.api.repo.dao.article.command.ArticleMainCardExcludeWorkbookCommand import com.few.api.repo.dao.article.command.UpdateArticleMainCardWorkbookCommand import com.few.api.repo.dao.article.command.WorkbookCommand +import com.few.api.repo.explain.ExplainGenerator +import com.few.api.repo.explain.InsertUpdateExplainGenerator import com.few.api.repo.explain.ResultGenerator import com.few.api.repo.jooq.JooqTestSpec import com.few.data.common.code.CategoryType @@ -59,7 +61,7 @@ class ArticleMainCardDaoExplainGenerateTest : JooqTestSpec() { fun selectArticleMainCardsRecordQueryExplain() { val query = articleMainCardDao.selectArticleMainCardsRecordQuery(setOf(1L)) - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "selectArticleMainCardsRecordQueryExplain") } @@ -80,7 +82,7 @@ class ArticleMainCardDaoExplainGenerateTest : JooqTestSpec() { articleMainCardDao.insertArticleMainCardCommand(it) } - val explain = command.toString() + val explain = InsertUpdateExplainGenerator.execute(dslContext, command.sql, command.bindValues) ResultGenerator.execute(command, explain, "insertArticleMainCardCommandExplain") } @@ -103,7 +105,7 @@ class ArticleMainCardDaoExplainGenerateTest : JooqTestSpec() { articleMainCardDao.updateArticleMainCardSetWorkbookCommand(it) } - val explain = command.toString() + val explain = InsertUpdateExplainGenerator.execute(dslContext, command.sql, command.bindValues) ResultGenerator.execute(command, explain, "updateArticleMainCardSetWorkbookCommandExplain") } diff --git a/api-repo/src/test/kotlin/com/few/api/repo/explain/article/ArticleViewCountDaoExplainGenerateTest.kt b/api-repo/src/test/kotlin/com/few/api/repo/explain/article/ArticleViewCountDaoExplainGenerateTest.kt index 2902167e1..dd7cfc3d5 100644 --- a/api-repo/src/test/kotlin/com/few/api/repo/explain/article/ArticleViewCountDaoExplainGenerateTest.kt +++ b/api-repo/src/test/kotlin/com/few/api/repo/explain/article/ArticleViewCountDaoExplainGenerateTest.kt @@ -5,6 +5,7 @@ import com.few.api.repo.dao.article.command.ArticleViewCountCommand import com.few.api.repo.dao.article.query.ArticleViewCountQuery import com.few.api.repo.dao.article.query.SelectArticlesOrderByViewsQuery import com.few.api.repo.dao.article.query.SelectRankByViewsQuery +import com.few.api.repo.explain.ExplainGenerator import com.few.api.repo.explain.InsertUpdateExplainGenerator import com.few.api.repo.explain.ResultGenerator import com.few.api.repo.jooq.JooqTestSpec @@ -46,7 +47,7 @@ class ArticleViewCountDaoExplainGenerateTest : JooqTestSpec() { articleViewCountDao.selectArticleViewCountQuery(it) } - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "selectArticleViewCountQueryExplain") } @@ -84,7 +85,7 @@ class ArticleViewCountDaoExplainGenerateTest : JooqTestSpec() { articleViewCountDao.selectRankByViewsQuery(it) } - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "selectRankByViewsQueryExplain") } @@ -97,7 +98,7 @@ class ArticleViewCountDaoExplainGenerateTest : JooqTestSpec() { articleViewCountDao.selectArticlesOrderByViewsQuery(it) } - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "selectArticlesOrderByViewsQueryExplain") } } \ No newline at end of file diff --git a/api-repo/src/test/kotlin/com/few/api/repo/explain/article/ArticleViewHisDaoExplainGenerateTest.kt b/api-repo/src/test/kotlin/com/few/api/repo/explain/article/ArticleViewHisDaoExplainGenerateTest.kt index 69572d698..11b526e93 100644 --- a/api-repo/src/test/kotlin/com/few/api/repo/explain/article/ArticleViewHisDaoExplainGenerateTest.kt +++ b/api-repo/src/test/kotlin/com/few/api/repo/explain/article/ArticleViewHisDaoExplainGenerateTest.kt @@ -3,6 +3,7 @@ package com.few.api.repo.explain.article import com.few.api.repo.dao.article.ArticleViewHisDao import com.few.api.repo.dao.article.command.ArticleViewHisCommand import com.few.api.repo.dao.article.query.ArticleViewHisCountQuery +import com.few.api.repo.explain.ExplainGenerator import com.few.api.repo.explain.InsertUpdateExplainGenerator import com.few.api.repo.explain.ResultGenerator import com.few.api.repo.jooq.JooqTestSpec @@ -42,7 +43,7 @@ class ArticleViewHisDaoExplainGenerateTest : JooqTestSpec() { articleViewHisDao.countArticleViewsQuery(it) } - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "selectArticleViewCountQueryExplain") } diff --git a/api-repo/src/test/kotlin/com/few/api/repo/explain/member/MemberDaoExplainGenerateTest.kt b/api-repo/src/test/kotlin/com/few/api/repo/explain/member/MemberDaoExplainGenerateTest.kt index 3f85b76b5..dbc8eceaf 100644 --- a/api-repo/src/test/kotlin/com/few/api/repo/explain/member/MemberDaoExplainGenerateTest.kt +++ b/api-repo/src/test/kotlin/com/few/api/repo/explain/member/MemberDaoExplainGenerateTest.kt @@ -9,21 +9,17 @@ import com.few.api.repo.dao.member.query.BrowseWorkbookWritersQuery import com.few.api.repo.dao.member.query.SelectMemberByEmailNotConsiderDeletedAtQuery import com.few.api.repo.dao.member.query.SelectMemberByEmailQuery import com.few.api.repo.dao.member.query.SelectWriterQuery -import com.few.api.repo.dao.member.support.WriterDescription import com.few.api.repo.dao.member.support.WriterDescriptionJsonMapper +import com.few.api.repo.explain.ExplainGenerator import com.few.api.repo.explain.InsertUpdateExplainGenerator import com.few.api.repo.explain.ResultGenerator import com.few.api.repo.jooq.JooqTestSpec import com.few.data.common.code.MemberType import io.github.oshai.kotlinlogging.KotlinLogging -import jooq.jooq_dsl.tables.Member import org.jooq.DSLContext -import org.jooq.JSON -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired -import java.net.URL @Tag("explain") class MemberDaoExplainGenerateTest : JooqTestSpec() { @@ -39,47 +35,13 @@ class MemberDaoExplainGenerateTest : JooqTestSpec() { @Autowired private lateinit var writerDescriptionJsonMapper: WriterDescriptionJsonMapper - @BeforeEach - fun setUp() { - log.debug { "===== start setUp =====" } - dslContext.deleteFrom(Member.MEMBER).execute() - dslContext.insertInto(Member.MEMBER) - .set(Member.MEMBER.ID, 1) - .set(Member.MEMBER.EMAIL, "member@gmail.com") - .set(Member.MEMBER.TYPE_CD, MemberType.NORMAL.code) - .execute() - - val writerDescription = writerDescriptionJsonMapper.toJson( - WriterDescription( - "few2", - URL("http://localhost:8080/writers/url2"), - URL("https://github.com/user-attachments/assets/28df9078-488c-49d6-9375-54ce5a250742") - ) - ) - - dslContext.insertInto(Member.MEMBER) - .set(Member.MEMBER.ID, 2) - .set(Member.MEMBER.EMAIL, "writer2@gmail.com") - .set(Member.MEMBER.TYPE_CD, MemberType.WRITER.code) - .set(Member.MEMBER.DESCRIPTION, JSON.valueOf(writerDescription)) - .execute() - - dslContext.insertInto(Member.MEMBER) - .set(Member.MEMBER.ID, 3) - .set(Member.MEMBER.EMAIL, "writer3@gmail.com") - .set(Member.MEMBER.TYPE_CD, MemberType.WRITER.code) - .set(Member.MEMBER.DESCRIPTION, JSON.valueOf(writerDescription)) - .execute() - log.debug { "===== finish setUp =====" } - } - @Test fun selectWriterQueryExplain() { val query = SelectWriterQuery(1).let { memberDao.selectWriterQuery(it) } - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "selectWriterQueryExplain") } @@ -90,7 +52,7 @@ class MemberDaoExplainGenerateTest : JooqTestSpec() { memberDao.selectWritersQuery(it) } - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "selectWritersQueryExplain") } @@ -101,7 +63,7 @@ class MemberDaoExplainGenerateTest : JooqTestSpec() { memberDao.selectWritersQuery(it) } - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "selectWritersQueryExplainByWorkbookIds") } @@ -112,7 +74,7 @@ class MemberDaoExplainGenerateTest : JooqTestSpec() { memberDao.selectMemberByEmailQuery(it) } - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "selectMemberByEmailQueryExplainExplain") } @@ -123,7 +85,7 @@ class MemberDaoExplainGenerateTest : JooqTestSpec() { memberDao.selectMemberByEmailQuery(it) } - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "selectMemberByEmailNotConsiderDeletedAtQueryExplain") } @@ -148,7 +110,7 @@ class MemberDaoExplainGenerateTest : JooqTestSpec() { memberDao.selectMemberIdAndTypeQuery(it) } - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "selectMemberIdAndTypeQueryExplain") } diff --git a/api-repo/src/test/kotlin/com/few/api/repo/explain/problem/ProblemDaoExplainGenerateTest.kt b/api-repo/src/test/kotlin/com/few/api/repo/explain/problem/ProblemDaoExplainGenerateTest.kt index 3b795b15b..454d3d5b8 100644 --- a/api-repo/src/test/kotlin/com/few/api/repo/explain/problem/ProblemDaoExplainGenerateTest.kt +++ b/api-repo/src/test/kotlin/com/few/api/repo/explain/problem/ProblemDaoExplainGenerateTest.kt @@ -10,6 +10,7 @@ import com.few.api.repo.dao.problem.query.SelectProblemsByArticleIdQuery import com.few.api.repo.dao.problem.support.Content import com.few.api.repo.dao.problem.support.Contents import com.few.api.repo.dao.problem.support.ContentsJsonMapper +import com.few.api.repo.explain.ExplainGenerator import com.few.api.repo.explain.InsertUpdateExplainGenerator import com.few.api.repo.explain.ResultGenerator import com.few.api.repo.jooq.JooqTestSpec @@ -69,7 +70,7 @@ class ProblemDaoExplainGenerateTest : JooqTestSpec() { problemDao.selectProblemContentsQuery(it) } - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "selectProblemContentsQueryExplain") } @@ -80,7 +81,7 @@ class ProblemDaoExplainGenerateTest : JooqTestSpec() { problemDao.selectProblemAnswerQuery(it) } - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "selectProblemAnswerQueryExplain") } @@ -91,7 +92,7 @@ class ProblemDaoExplainGenerateTest : JooqTestSpec() { problemDao.selectProblemsByArticleIdQuery(it) } - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "selectProblemsByArticleIdQueryExplain") } diff --git a/api-repo/src/test/kotlin/com/few/api/repo/explain/subscription/SubscriptionDaoExplainGenerateTest.kt b/api-repo/src/test/kotlin/com/few/api/repo/explain/subscription/SubscriptionDaoExplainGenerateTest.kt index 6bfb9a271..e72bd8669 100644 --- a/api-repo/src/test/kotlin/com/few/api/repo/explain/subscription/SubscriptionDaoExplainGenerateTest.kt +++ b/api-repo/src/test/kotlin/com/few/api/repo/explain/subscription/SubscriptionDaoExplainGenerateTest.kt @@ -8,6 +8,7 @@ import com.few.api.repo.dao.subscription.query.CountWorkbookMappedArticlesQuery import com.few.api.repo.dao.subscription.query.SelectAllMemberWorkbookActiveSubscription import com.few.api.repo.dao.subscription.query.SelectAllWorkbookSubscriptionStatusNotConsiderDeletedAtQuery import com.few.api.repo.dao.subscription.query.SelectAllMemberWorkbookInActiveSubscription +import com.few.api.repo.explain.ExplainGenerator import com.few.api.repo.explain.InsertUpdateExplainGenerator import com.few.api.repo.explain.ResultGenerator import com.few.api.repo.jooq.JooqTestSpec @@ -57,7 +58,7 @@ class SubscriptionDaoExplainGenerateTest : JooqTestSpec() { subscriptionDao.selectTopWorkbookSubscriptionStatusQuery(it) } - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "selectTopWorkbookSubscriptionStatusQueryExplain") } @@ -71,7 +72,7 @@ class SubscriptionDaoExplainGenerateTest : JooqTestSpec() { subscriptionDao.selectAllWorkbookInActiveSubscriptionStatusQuery(it) } - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "selectAllWorkbookInActiveSubscriptionStatusQueryExplain") } @@ -84,7 +85,7 @@ class SubscriptionDaoExplainGenerateTest : JooqTestSpec() { subscriptionDao.selectAllWorkbookActiveSubscriptionStatusQuery(it) } - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "selectAllWorkbookActiveSubscriptionStatusQueryExplain") } @@ -97,7 +98,7 @@ class SubscriptionDaoExplainGenerateTest : JooqTestSpec() { subscriptionDao.countWorkbookMappedArticlesQuery(it) } - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "countWorkbookMappedArticlesQueryExplain") } @@ -134,7 +135,7 @@ class SubscriptionDaoExplainGenerateTest : JooqTestSpec() { fun countAllWorkbookSubscriptionQueryExplain() { val query = subscriptionDao.countAllWorkbookSubscriptionQuery() - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "countAllWorkbookSubscriptionQueryExplain") } diff --git a/api-repo/src/test/kotlin/com/few/api/repo/explain/workbook/WorkbookDaoExplainGenerateTest.kt b/api-repo/src/test/kotlin/com/few/api/repo/explain/workbook/WorkbookDaoExplainGenerateTest.kt index f736d1c69..8c90b1117 100644 --- a/api-repo/src/test/kotlin/com/few/api/repo/explain/workbook/WorkbookDaoExplainGenerateTest.kt +++ b/api-repo/src/test/kotlin/com/few/api/repo/explain/workbook/WorkbookDaoExplainGenerateTest.kt @@ -5,6 +5,7 @@ import com.few.api.repo.dao.workbook.command.InsertWorkBookCommand import com.few.api.repo.dao.workbook.command.MapWorkBookToArticleCommand import com.few.api.repo.dao.workbook.query.BrowseWorkBookQueryWithSubscriptionCount import com.few.api.repo.dao.workbook.query.SelectWorkBookRecordQuery +import com.few.api.repo.explain.ExplainGenerator import com.few.api.repo.explain.InsertUpdateExplainGenerator import com.few.api.repo.explain.ResultGenerator import com.few.api.repo.jooq.JooqTestSpec @@ -49,7 +50,7 @@ class WorkbookDaoExplainGenerateTest : JooqTestSpec() { workbookDao.selectWorkBookQuery(it) } - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "selectWorkBookQueryExplain") } @@ -75,7 +76,7 @@ class WorkbookDaoExplainGenerateTest : JooqTestSpec() { workbookDao.browseWorkBookQuery(it) } - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "browseWorkBookQueryNoConditionQuery") } @@ -86,7 +87,7 @@ class WorkbookDaoExplainGenerateTest : JooqTestSpec() { workbookDao.browseWorkBookQuery(it) } - val explain = dslContext.explain(query).toString() + val explain = ExplainGenerator.execute(dslContext, query) ResultGenerator.execute(query, explain, "browseWorkBookQueryCategoryCondition") } From 93eb1b871506532921e972e5cb13ffffe5ae3668 Mon Sep 17 00:00:00 2001 From: belljun3395 <195850@jnu.ac.kr> Date: Tue, 27 Aug 2024 10:21:06 +0900 Subject: [PATCH 2/8] =?UTF-8?q?[Refactor/#358]=20=EC=95=84=ED=8B=B0?= =?UTF-8?q?=ED=81=B4=20=EC=9D=BD=EC=9D=8C=20=EA=B8=B0=EB=A1=9D=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EC=B2=98=EB=A6=AC=EB=A1=9C=20=EB=A6=AC?= =?UTF-8?q?=ED=8E=99=ED=86=A0=EB=A7=81=20(#359)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: ReadArticleEvent 생성 및 처리 구현 * refactor: 기존 구현 이벤트 방식으로 수정 * chore: ReadArticlesUseCase -> BrowseArticlesUseCase로 컨벤션 통일 * chore: 불필요 주석 제거 --- .../article/event/ReadArticleEventListener.kt | 17 ++++++++++++ .../article/event/dto/ReadArticleEvent.kt | 9 +++++++ ...lesUseCase.kt => BrowseArticlesUseCase.kt} | 2 +- .../article/usecase/ReadArticleUseCase.kt | 24 ++++++++--------- .../usecase/ReadWorkBookArticleUseCase.kt | 26 +++++++++---------- .../controller/article/ArticleController.kt | 6 ++--- .../article/usecase/ReadArticleUseCaseTest.kt | 22 ++++++++-------- .../api/web/controller/ControllerTestSpec.kt | 4 +-- .../article/ArticleControllerTest.kt | 2 +- 9 files changed, 67 insertions(+), 45 deletions(-) create mode 100644 api/src/main/kotlin/com/few/api/domain/article/event/ReadArticleEventListener.kt create mode 100644 api/src/main/kotlin/com/few/api/domain/article/event/dto/ReadArticleEvent.kt rename api/src/main/kotlin/com/few/api/domain/article/usecase/{ReadArticlesUseCase.kt => BrowseArticlesUseCase.kt} (99%) diff --git a/api/src/main/kotlin/com/few/api/domain/article/event/ReadArticleEventListener.kt b/api/src/main/kotlin/com/few/api/domain/article/event/ReadArticleEventListener.kt new file mode 100644 index 000000000..644c1c541 --- /dev/null +++ b/api/src/main/kotlin/com/few/api/domain/article/event/ReadArticleEventListener.kt @@ -0,0 +1,17 @@ +package com.few.api.domain.article.event + +import com.few.api.domain.article.event.dto.ReadArticleEvent +import com.few.api.domain.article.handler.ArticleViewHisAsyncHandler +import org.springframework.context.event.EventListener +import org.springframework.stereotype.Component + +@Component +class ReadArticleEventListener( + private val articleViewHisAsyncHandler: ArticleViewHisAsyncHandler, +) { + + @EventListener + fun handleEvent(event: ReadArticleEvent) { + articleViewHisAsyncHandler.addArticleViewHis(event.articleId, event.memberId, event.category) + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/article/event/dto/ReadArticleEvent.kt b/api/src/main/kotlin/com/few/api/domain/article/event/dto/ReadArticleEvent.kt new file mode 100644 index 000000000..c06c38a5e --- /dev/null +++ b/api/src/main/kotlin/com/few/api/domain/article/event/dto/ReadArticleEvent.kt @@ -0,0 +1,9 @@ +package com.few.api.domain.article.event.dto + +import com.few.data.common.code.CategoryType + +data class ReadArticleEvent( + val articleId: Long, + val memberId: Long, + val category: CategoryType, +) \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticlesUseCase.kt b/api/src/main/kotlin/com/few/api/domain/article/usecase/BrowseArticlesUseCase.kt similarity index 99% rename from api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticlesUseCase.kt rename to api/src/main/kotlin/com/few/api/domain/article/usecase/BrowseArticlesUseCase.kt index 490221779..777e83be6 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticlesUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/usecase/BrowseArticlesUseCase.kt @@ -17,7 +17,7 @@ import java.util.* import kotlin.Comparator @Component -class ReadArticlesUseCase( +class BrowseArticlesUseCase( private val articleViewCountDao: ArticleViewCountDao, private val articleMainCardDao: ArticleMainCardDao, private val articleDao: ArticleDao, diff --git a/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt b/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt index 2623ff980..d967e4f11 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt @@ -1,7 +1,7 @@ package com.few.api.domain.article.usecase +import com.few.api.domain.article.event.dto.ReadArticleEvent import com.few.api.domain.article.handler.ArticleViewCountHandler -import com.few.api.domain.article.handler.ArticleViewHisAsyncHandler import com.few.api.domain.article.usecase.dto.ReadArticleUseCaseIn import com.few.api.domain.article.usecase.dto.ReadArticleUseCaseOut import com.few.api.domain.article.usecase.dto.WriterDetail @@ -13,6 +13,7 @@ import com.few.api.exception.common.NotFoundException import com.few.api.repo.dao.article.ArticleDao import com.few.api.repo.dao.article.query.SelectArticleRecordQuery import com.few.data.common.code.CategoryType +import org.springframework.context.ApplicationEventPublisher import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional @@ -21,8 +22,8 @@ class ReadArticleUseCase( private val articleDao: ArticleDao, private val readArticleWriterRecordService: ReadArticleWriterRecordService, private val browseArticleProblemsService: BrowseArticleProblemsService, - private val articleViewHisAsyncHandler: ArticleViewHisAsyncHandler, private val articleViewCountHandler: ArticleViewCountHandler, + private val applicationEventPublisher: ApplicationEventPublisher, ) { @Transactional(readOnly = true) @@ -40,20 +41,17 @@ class ReadArticleUseCase( browseArticleProblemsService.execute(query) } - // ARTICLE VIEW HIS에 저장하기 전에 먼저 VIEW COUNT 조회하는 순서 변경 금지 val views = articleViewCountHandler.browseArticleViewCount(useCaseIn.articleId) - articleViewHisAsyncHandler.addArticleViewHis( - useCaseIn.articleId, - useCaseIn.memberId, - CategoryType.fromCode(articleRecord.category) ?: throw NotFoundException("article.invalid.category") + applicationEventPublisher.publishEvent( + ReadArticleEvent( + articleId = useCaseIn.articleId, + memberId = useCaseIn.memberId, + category = CategoryType.fromCode(articleRecord.category) ?: throw NotFoundException( + "article.invalid.category" + ) + ) ) - /** - * NOTE: The articleViewHisAsyncHandler creates a new transaction that is separate from the current context. - * So this section, the logic after the articleViewHisAsyncHandler call, - * is where the mismatch between the two transactions can occur if an exception is thrown. - */ - return ReadArticleUseCaseOut( id = articleRecord.articleId, writer = WriterDetail( diff --git a/api/src/main/kotlin/com/few/api/domain/workbook/article/usecase/ReadWorkBookArticleUseCase.kt b/api/src/main/kotlin/com/few/api/domain/workbook/article/usecase/ReadWorkBookArticleUseCase.kt index 8734a1401..dfa7f209d 100644 --- a/api/src/main/kotlin/com/few/api/domain/workbook/article/usecase/ReadWorkBookArticleUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/workbook/article/usecase/ReadWorkBookArticleUseCase.kt @@ -1,7 +1,7 @@ package com.few.api.domain.workbook.article.usecase +import com.few.api.domain.article.event.dto.ReadArticleEvent import com.few.api.domain.article.handler.ArticleViewCountHandler -import com.few.api.domain.article.handler.ArticleViewHisAsyncHandler import com.few.api.domain.article.service.BrowseArticleProblemsService import com.few.api.domain.article.service.ReadArticleWriterRecordService import com.few.api.domain.article.service.dto.BrowseArticleProblemIdsInDto @@ -13,6 +13,7 @@ import com.few.api.exception.common.NotFoundException import com.few.api.repo.dao.article.ArticleDao import com.few.api.repo.dao.article.query.SelectWorkBookArticleRecordQuery import com.few.data.common.code.CategoryType +import org.springframework.context.ApplicationEventPublisher import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -21,8 +22,8 @@ class ReadWorkBookArticleUseCase( private val articleDao: ArticleDao, private val readArticleWriterRecordService: ReadArticleWriterRecordService, private val browseArticleProblemsService: BrowseArticleProblemsService, - private val articleViewHisAsyncHandler: ArticleViewHisAsyncHandler, private val articleViewCountHandler: ArticleViewCountHandler, + private val applicationEventPublisher: ApplicationEventPublisher, ) { @Transactional(readOnly = true) fun execute(useCaseIn: ReadWorkBookArticleUseCaseIn): ReadWorkBookArticleOut { @@ -46,20 +47,17 @@ class ReadWorkBookArticleUseCase( /** * @see com.few.api.domain.article.usecase.ReadArticleUseCase */ - // ARTICLE VIEW HIS에 저장하기 전에 먼저 VIEW COUNT 조회하는 순서 변경 금지 - val views = articleViewCountHandler.browseArticleViewCount(useCaseIn.articleId) - articleViewHisAsyncHandler.addArticleViewHis( - useCaseIn.articleId, - useCaseIn.memberId, - CategoryType.fromCode(articleRecord.category) ?: throw NotFoundException("article.invalid.category") + articleViewCountHandler.browseArticleViewCount(useCaseIn.articleId) + applicationEventPublisher.publishEvent( + ReadArticleEvent( + articleId = useCaseIn.articleId, + memberId = useCaseIn.memberId, + category = CategoryType.fromCode(articleRecord.category) ?: throw NotFoundException( + "article.invalid.category" + ) + ) ) - /** - * NOTE: The articleViewHisAsyncHandler creates a new transaction that is separate from the current context. - * So this section, the logic after the articleViewHisAsyncHandler call, - * is where the mismatch between the two transactions can occur if an exception is thrown. - */ - return ReadWorkBookArticleOut( id = articleRecord.articleId, writer = WriterDetail( diff --git a/api/src/main/kotlin/com/few/api/web/controller/article/ArticleController.kt b/api/src/main/kotlin/com/few/api/web/controller/article/ArticleController.kt index e9e32e7a1..c5ce0f123 100644 --- a/api/src/main/kotlin/com/few/api/web/controller/article/ArticleController.kt +++ b/api/src/main/kotlin/com/few/api/web/controller/article/ArticleController.kt @@ -1,7 +1,7 @@ package com.few.api.web.controller.article import com.few.api.domain.article.usecase.ReadArticleUseCase -import com.few.api.domain.article.usecase.ReadArticlesUseCase +import com.few.api.domain.article.usecase.BrowseArticlesUseCase import com.few.api.domain.article.usecase.dto.ReadArticleUseCaseIn import com.few.api.domain.article.usecase.dto.ReadArticlesUseCaseIn import com.few.api.security.filter.token.AccessTokenResolver @@ -25,7 +25,7 @@ import org.springframework.web.bind.annotation.* @RequestMapping(value = ["/api/v1/articles"], produces = [MediaType.APPLICATION_JSON_VALUE]) class ArticleController( private val readArticleUseCase: ReadArticleUseCase, - private val readArticlesUseCase: ReadArticlesUseCase, + private val browseArticlesUseCase: BrowseArticlesUseCase, private val tokenResolver: TokenResolver, ) { @@ -81,7 +81,7 @@ class ArticleController( defaultValue = "-1" ) categoryCd: Byte, ): ApiResponse> { - val useCaseOut = readArticlesUseCase.execute(ReadArticlesUseCaseIn(prevArticleId, categoryCd)) + val useCaseOut = browseArticlesUseCase.execute(ReadArticlesUseCaseIn(prevArticleId, categoryCd)) val articles: List = useCaseOut.articles.map { a -> ReadArticleResponse( diff --git a/api/src/test/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCaseTest.kt b/api/src/test/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCaseTest.kt index 439bade14..5150daeeb 100644 --- a/api/src/test/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCaseTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCaseTest.kt @@ -1,7 +1,7 @@ package com.few.api.domain.article.usecase +import com.few.api.domain.article.event.dto.ReadArticleEvent import com.few.api.domain.article.handler.ArticleViewCountHandler -import com.few.api.domain.article.handler.ArticleViewHisAsyncHandler import com.few.api.domain.article.service.BrowseArticleProblemsService import com.few.api.domain.article.service.ReadArticleWriterRecordService import com.few.api.domain.article.service.dto.BrowseArticleProblemsOutDto @@ -15,6 +15,7 @@ import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe import io.mockk.* +import org.springframework.context.ApplicationEventPublisher import java.net.URL import java.time.LocalDateTime @@ -26,21 +27,22 @@ class ReadArticleUseCaseTest : BehaviorSpec({ lateinit var readArticleWriterRecordService: ReadArticleWriterRecordService lateinit var browseArticleProblemsService: BrowseArticleProblemsService lateinit var useCase: ReadArticleUseCase - lateinit var articleViewHisAsyncHandler: ArticleViewHisAsyncHandler lateinit var articleViewCountHandler: ArticleViewCountHandler + lateinit var applicationEventPublisher: ApplicationEventPublisher beforeContainer { articleDao = mockk() readArticleWriterRecordService = mockk() browseArticleProblemsService = mockk() - articleViewHisAsyncHandler = mockk() articleViewCountHandler = mockk() + applicationEventPublisher = mockk() + useCase = ReadArticleUseCase( articleDao, readArticleWriterRecordService, browseArticleProblemsService, - articleViewHisAsyncHandler, - articleViewCountHandler + articleViewCountHandler, + applicationEventPublisher ) } @@ -80,9 +82,7 @@ class ReadArticleUseCaseTest : BehaviorSpec({ val views = 1L every { articleViewCountHandler.browseArticleViewCount(any()) } returns views - every { articleViewHisAsyncHandler.addArticleViewHis(any(), any(), any()) } answers { - log.debug { "Inserting article view history asynchronously" } - } + every { applicationEventPublisher.publishEvent(any(ReadArticleEvent::class)) } just Runs then("아티클과 연관된 정보를 조회한다") { val useCaseOut = useCase.execute(useCaseIn) @@ -103,7 +103,7 @@ class ReadArticleUseCaseTest : BehaviorSpec({ verify(exactly = 1) { readArticleWriterRecordService.execute(any()) } verify(exactly = 1) { browseArticleProblemsService.execute(any()) } verify(exactly = 1) { articleViewCountHandler.browseArticleViewCount(any()) } - verify(exactly = 1) { articleViewHisAsyncHandler.addArticleViewHis(any(), any(), any()) } + verify(exactly = 1) { applicationEventPublisher.publishEvent(any(ReadArticleEvent::class)) } } } @@ -117,7 +117,7 @@ class ReadArticleUseCaseTest : BehaviorSpec({ verify(exactly = 0) { readArticleWriterRecordService.execute(any()) } verify(exactly = 0) { browseArticleProblemsService.execute(any()) } verify(exactly = 0) { articleViewCountHandler.browseArticleViewCount(any()) } - verify(exactly = 0) { articleViewHisAsyncHandler.addArticleViewHis(any(), any(), any()) } + verify(exactly = 0) { applicationEventPublisher.publishEvent(any(ReadArticleEvent::class)) } } } @@ -146,7 +146,7 @@ class ReadArticleUseCaseTest : BehaviorSpec({ verify(exactly = 1) { readArticleWriterRecordService.execute(any()) } verify(exactly = 0) { browseArticleProblemsService.execute(any()) } verify(exactly = 0) { articleViewCountHandler.browseArticleViewCount(any()) } - verify(exactly = 0) { articleViewHisAsyncHandler.addArticleViewHis(any(), any(), any()) } + verify(exactly = 0) { applicationEventPublisher.publishEvent(any(ReadArticleEvent::class)) } } } } diff --git a/api/src/test/kotlin/com/few/api/web/controller/ControllerTestSpec.kt b/api/src/test/kotlin/com/few/api/web/controller/ControllerTestSpec.kt index ac5d49cb7..7a1603e49 100644 --- a/api/src/test/kotlin/com/few/api/web/controller/ControllerTestSpec.kt +++ b/api/src/test/kotlin/com/few/api/web/controller/ControllerTestSpec.kt @@ -4,7 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.few.api.ApiMain import com.few.api.domain.admin.document.usecase.* import com.few.api.domain.article.usecase.ReadArticleUseCase -import com.few.api.domain.article.usecase.ReadArticlesUseCase +import com.few.api.domain.article.usecase.BrowseArticlesUseCase import com.few.api.domain.log.AddApiLogUseCase import com.few.api.domain.member.usecase.SaveMemberUseCase import com.few.api.domain.member.usecase.TokenUseCase @@ -73,7 +73,7 @@ abstract class ControllerTestSpec { lateinit var readArticleUseCase: ReadArticleUseCase @MockBean - lateinit var readArticlesUseCase: ReadArticlesUseCase + lateinit var browseArticlesUseCase: BrowseArticlesUseCase /** MemberControllerTest */ @MockBean diff --git a/api/src/test/kotlin/com/few/api/web/controller/article/ArticleControllerTest.kt b/api/src/test/kotlin/com/few/api/web/controller/article/ArticleControllerTest.kt index aa7772367..e4a920c80 100644 --- a/api/src/test/kotlin/com/few/api/web/controller/article/ArticleControllerTest.kt +++ b/api/src/test/kotlin/com/few/api/web/controller/article/ArticleControllerTest.kt @@ -169,7 +169,7 @@ class ArticleControllerTest : ControllerTestSpec() { }.toList(), true ) - `when`(readArticlesUseCase.execute(useCaseIn)).thenReturn(useCaseOut) + `when`(browseArticlesUseCase.execute(useCaseIn)).thenReturn(useCaseOut) // when mockMvc.perform( From bf3edecc498b5e78e4502f803f7e911947b6247e Mon Sep 17 00:00:00 2001 From: belljun3395 <195850@jnu.ac.kr> Date: Tue, 27 Aug 2024 21:41:50 +0900 Subject: [PATCH 3/8] =?UTF-8?q?[Fix/#126]=20CreatedAt=EC=9D=B4=20=EB=B0=B0?= =?UTF-8?q?=EC=97=B4=EB=A1=9C=20=EB=82=B4=EB=A0=A4=EA=B0=80=EB=8A=94=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0=20(#366)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: EnableWebMvc 제거 * refactor: @JsonFormat(pattern ="yyyy-MM-dd HH:mm:sss") 제거 --- api/src/main/kotlin/com/few/api/config/ApiConfig.kt | 2 -- .../api/web/controller/article/response/ReadArticleResponse.kt | 2 -- .../response/MainViewBrowseSubscribeWorkbooksResponse.kt | 2 -- .../workbook/article/response/ReadWorkBookArticleResponse.kt | 2 -- .../web/controller/workbook/response/BrowseWorkBooksResponse.kt | 2 -- .../web/controller/workbook/response/ReadWorkBookResponse.kt | 2 -- 6 files changed, 12 deletions(-) diff --git a/api/src/main/kotlin/com/few/api/config/ApiConfig.kt b/api/src/main/kotlin/com/few/api/config/ApiConfig.kt index 4f90acaa8..be241e227 100644 --- a/api/src/main/kotlin/com/few/api/config/ApiConfig.kt +++ b/api/src/main/kotlin/com/few/api/config/ApiConfig.kt @@ -10,7 +10,6 @@ import org.springframework.context.annotation.ComponentScan import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Import import org.springframework.scheduling.annotation.EnableAsync -import org.springframework.web.servlet.config.annotation.EnableWebMvc @Configuration @ComponentScan(basePackages = [ApiConfig.BASE_PACKAGE]) @@ -21,7 +20,6 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc ImageStorageConfig::class, DocumentStorageConfig::class ) -@EnableWebMvc @EnableAsync @ConfigurationPropertiesScan(basePackages = [ApiConfig.BASE_PACKAGE]) class ApiConfig { diff --git a/api/src/main/kotlin/com/few/api/web/controller/article/response/ReadArticleResponse.kt b/api/src/main/kotlin/com/few/api/web/controller/article/response/ReadArticleResponse.kt index fa609cfa0..ecb76a624 100644 --- a/api/src/main/kotlin/com/few/api/web/controller/article/response/ReadArticleResponse.kt +++ b/api/src/main/kotlin/com/few/api/web/controller/article/response/ReadArticleResponse.kt @@ -1,6 +1,5 @@ package com.few.api.web.controller.article.response -import com.fasterxml.jackson.annotation.JsonFormat import java.net.URL import java.time.LocalDateTime @@ -12,7 +11,6 @@ data class ReadArticleResponse( val content: String, val problemIds: List, val category: String, - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") val createdAt: LocalDateTime, val views: Long, val workbooks: List = emptyList(), diff --git a/api/src/main/kotlin/com/few/api/web/controller/subscription/response/MainViewBrowseSubscribeWorkbooksResponse.kt b/api/src/main/kotlin/com/few/api/web/controller/subscription/response/MainViewBrowseSubscribeWorkbooksResponse.kt index ccf23008a..c7a8b2972 100644 --- a/api/src/main/kotlin/com/few/api/web/controller/subscription/response/MainViewBrowseSubscribeWorkbooksResponse.kt +++ b/api/src/main/kotlin/com/few/api/web/controller/subscription/response/MainViewBrowseSubscribeWorkbooksResponse.kt @@ -1,6 +1,5 @@ package com.few.api.web.controller.subscription.response -import com.fasterxml.jackson.annotation.JsonFormat import com.fasterxml.jackson.annotation.JsonInclude import com.few.api.domain.workbook.usecase.dto.WriterDetail import java.net.URL @@ -17,7 +16,6 @@ data class MainViewSubscribeWorkbookInfo( val title: String, val description: String, val category: String, - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") val createdAt: LocalDateTime, val writerDetails: List, val subscriptionCount: Long, diff --git a/api/src/main/kotlin/com/few/api/web/controller/workbook/article/response/ReadWorkBookArticleResponse.kt b/api/src/main/kotlin/com/few/api/web/controller/workbook/article/response/ReadWorkBookArticleResponse.kt index 4279a4f10..2d10346aa 100644 --- a/api/src/main/kotlin/com/few/api/web/controller/workbook/article/response/ReadWorkBookArticleResponse.kt +++ b/api/src/main/kotlin/com/few/api/web/controller/workbook/article/response/ReadWorkBookArticleResponse.kt @@ -1,6 +1,5 @@ package com.few.api.web.controller.workbook.article.response -import com.fasterxml.jackson.annotation.JsonFormat import com.few.api.domain.workbook.article.dto.ReadWorkBookArticleOut import com.few.api.web.controller.workbook.response.WriterInfo import java.time.LocalDateTime @@ -12,7 +11,6 @@ data class ReadWorkBookArticleResponse( val content: String, val problemIds: List, val category: String, - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") val createdAt: LocalDateTime, val day: Long, ) { diff --git a/api/src/main/kotlin/com/few/api/web/controller/workbook/response/BrowseWorkBooksResponse.kt b/api/src/main/kotlin/com/few/api/web/controller/workbook/response/BrowseWorkBooksResponse.kt index 23434471d..bedb84de9 100644 --- a/api/src/main/kotlin/com/few/api/web/controller/workbook/response/BrowseWorkBooksResponse.kt +++ b/api/src/main/kotlin/com/few/api/web/controller/workbook/response/BrowseWorkBooksResponse.kt @@ -1,6 +1,5 @@ package com.few.api.web.controller.workbook.response -import com.fasterxml.jackson.annotation.JsonFormat import java.net.URL import java.time.LocalDateTime @@ -14,7 +13,6 @@ data class BrowseWorkBookInfo( val title: String, val description: String, val category: String, - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") val createdAt: LocalDateTime, val writers: List, val subscriberCount: Long, diff --git a/api/src/main/kotlin/com/few/api/web/controller/workbook/response/ReadWorkBookResponse.kt b/api/src/main/kotlin/com/few/api/web/controller/workbook/response/ReadWorkBookResponse.kt index 26e4ff94c..090ef090f 100644 --- a/api/src/main/kotlin/com/few/api/web/controller/workbook/response/ReadWorkBookResponse.kt +++ b/api/src/main/kotlin/com/few/api/web/controller/workbook/response/ReadWorkBookResponse.kt @@ -1,6 +1,5 @@ package com.few.api.web.controller.workbook.response -import com.fasterxml.jackson.annotation.JsonFormat import com.few.api.domain.workbook.usecase.dto.ReadWorkbookUseCaseOut import java.net.URL import java.time.LocalDateTime @@ -11,7 +10,6 @@ data class ReadWorkBookResponse( val title: String, val description: String, val category: String, - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") val createdAt: LocalDateTime, val writers: List, val articles: List, From 42e9c7aab22faee35c980370ee1ea802d959007b Mon Sep 17 00:00:00 2001 From: belljun3395 <195850@jnu.ac.kr> Date: Wed, 28 Aug 2024 02:51:17 +0900 Subject: [PATCH 4/8] =?UTF-8?q?Revert=20"[Fix/#126]=20CreatedAt=EC=9D=B4?= =?UTF-8?q?=20=EB=B0=B0=EC=97=B4=EB=A1=9C=20=EB=82=B4=EB=A0=A4=EA=B0=80?= =?UTF-8?q?=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0=20(#366)"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit bf3edecc498b5e78e4502f803f7e911947b6247e. --- api/src/main/kotlin/com/few/api/config/ApiConfig.kt | 2 ++ .../api/web/controller/article/response/ReadArticleResponse.kt | 2 ++ .../response/MainViewBrowseSubscribeWorkbooksResponse.kt | 2 ++ .../workbook/article/response/ReadWorkBookArticleResponse.kt | 2 ++ .../web/controller/workbook/response/BrowseWorkBooksResponse.kt | 2 ++ .../web/controller/workbook/response/ReadWorkBookResponse.kt | 2 ++ 6 files changed, 12 insertions(+) diff --git a/api/src/main/kotlin/com/few/api/config/ApiConfig.kt b/api/src/main/kotlin/com/few/api/config/ApiConfig.kt index be241e227..4f90acaa8 100644 --- a/api/src/main/kotlin/com/few/api/config/ApiConfig.kt +++ b/api/src/main/kotlin/com/few/api/config/ApiConfig.kt @@ -10,6 +10,7 @@ import org.springframework.context.annotation.ComponentScan import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Import import org.springframework.scheduling.annotation.EnableAsync +import org.springframework.web.servlet.config.annotation.EnableWebMvc @Configuration @ComponentScan(basePackages = [ApiConfig.BASE_PACKAGE]) @@ -20,6 +21,7 @@ import org.springframework.scheduling.annotation.EnableAsync ImageStorageConfig::class, DocumentStorageConfig::class ) +@EnableWebMvc @EnableAsync @ConfigurationPropertiesScan(basePackages = [ApiConfig.BASE_PACKAGE]) class ApiConfig { diff --git a/api/src/main/kotlin/com/few/api/web/controller/article/response/ReadArticleResponse.kt b/api/src/main/kotlin/com/few/api/web/controller/article/response/ReadArticleResponse.kt index ecb76a624..fa609cfa0 100644 --- a/api/src/main/kotlin/com/few/api/web/controller/article/response/ReadArticleResponse.kt +++ b/api/src/main/kotlin/com/few/api/web/controller/article/response/ReadArticleResponse.kt @@ -1,5 +1,6 @@ package com.few.api.web.controller.article.response +import com.fasterxml.jackson.annotation.JsonFormat import java.net.URL import java.time.LocalDateTime @@ -11,6 +12,7 @@ data class ReadArticleResponse( val content: String, val problemIds: List, val category: String, + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") val createdAt: LocalDateTime, val views: Long, val workbooks: List = emptyList(), diff --git a/api/src/main/kotlin/com/few/api/web/controller/subscription/response/MainViewBrowseSubscribeWorkbooksResponse.kt b/api/src/main/kotlin/com/few/api/web/controller/subscription/response/MainViewBrowseSubscribeWorkbooksResponse.kt index c7a8b2972..ccf23008a 100644 --- a/api/src/main/kotlin/com/few/api/web/controller/subscription/response/MainViewBrowseSubscribeWorkbooksResponse.kt +++ b/api/src/main/kotlin/com/few/api/web/controller/subscription/response/MainViewBrowseSubscribeWorkbooksResponse.kt @@ -1,5 +1,6 @@ package com.few.api.web.controller.subscription.response +import com.fasterxml.jackson.annotation.JsonFormat import com.fasterxml.jackson.annotation.JsonInclude import com.few.api.domain.workbook.usecase.dto.WriterDetail import java.net.URL @@ -16,6 +17,7 @@ data class MainViewSubscribeWorkbookInfo( val title: String, val description: String, val category: String, + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") val createdAt: LocalDateTime, val writerDetails: List, val subscriptionCount: Long, diff --git a/api/src/main/kotlin/com/few/api/web/controller/workbook/article/response/ReadWorkBookArticleResponse.kt b/api/src/main/kotlin/com/few/api/web/controller/workbook/article/response/ReadWorkBookArticleResponse.kt index 2d10346aa..4279a4f10 100644 --- a/api/src/main/kotlin/com/few/api/web/controller/workbook/article/response/ReadWorkBookArticleResponse.kt +++ b/api/src/main/kotlin/com/few/api/web/controller/workbook/article/response/ReadWorkBookArticleResponse.kt @@ -1,5 +1,6 @@ package com.few.api.web.controller.workbook.article.response +import com.fasterxml.jackson.annotation.JsonFormat import com.few.api.domain.workbook.article.dto.ReadWorkBookArticleOut import com.few.api.web.controller.workbook.response.WriterInfo import java.time.LocalDateTime @@ -11,6 +12,7 @@ data class ReadWorkBookArticleResponse( val content: String, val problemIds: List, val category: String, + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") val createdAt: LocalDateTime, val day: Long, ) { diff --git a/api/src/main/kotlin/com/few/api/web/controller/workbook/response/BrowseWorkBooksResponse.kt b/api/src/main/kotlin/com/few/api/web/controller/workbook/response/BrowseWorkBooksResponse.kt index bedb84de9..23434471d 100644 --- a/api/src/main/kotlin/com/few/api/web/controller/workbook/response/BrowseWorkBooksResponse.kt +++ b/api/src/main/kotlin/com/few/api/web/controller/workbook/response/BrowseWorkBooksResponse.kt @@ -1,5 +1,6 @@ package com.few.api.web.controller.workbook.response +import com.fasterxml.jackson.annotation.JsonFormat import java.net.URL import java.time.LocalDateTime @@ -13,6 +14,7 @@ data class BrowseWorkBookInfo( val title: String, val description: String, val category: String, + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") val createdAt: LocalDateTime, val writers: List, val subscriberCount: Long, diff --git a/api/src/main/kotlin/com/few/api/web/controller/workbook/response/ReadWorkBookResponse.kt b/api/src/main/kotlin/com/few/api/web/controller/workbook/response/ReadWorkBookResponse.kt index 090ef090f..26e4ff94c 100644 --- a/api/src/main/kotlin/com/few/api/web/controller/workbook/response/ReadWorkBookResponse.kt +++ b/api/src/main/kotlin/com/few/api/web/controller/workbook/response/ReadWorkBookResponse.kt @@ -1,5 +1,6 @@ package com.few.api.web.controller.workbook.response +import com.fasterxml.jackson.annotation.JsonFormat import com.few.api.domain.workbook.usecase.dto.ReadWorkbookUseCaseOut import java.net.URL import java.time.LocalDateTime @@ -10,6 +11,7 @@ data class ReadWorkBookResponse( val title: String, val description: String, val category: String, + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") val createdAt: LocalDateTime, val writers: List, val articles: List, From 0eb26b2eb8860cf10cf0804e4021e4cad7aa74ea Mon Sep 17 00:00:00 2001 From: belljun3395 <195850@jnu.ac.kr> Date: Wed, 28 Aug 2024 02:58:51 +0900 Subject: [PATCH 5/8] =?UTF-8?q?[Refactor/#360]=20=EA=B5=AC=EB=8F=85?= =?UTF-8?q?=EA=B3=BC=20=EB=8F=99=EC=8B=9C=EC=97=90=20=EC=95=84=ED=8B=B0?= =?UTF-8?q?=ED=81=B4=20=EC=A0=84=EC=86=A1=ED=95=A0=20=EC=88=98=20=EC=9E=88?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20(#364)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Article 쿼리 추가 구현 * feat: Member 쿼리 추가 구현 * feat: Subscription 쿼리 추가 구현 * feat: Workbook 쿼리 추가 구현 * feat: readArticleContent 구현 * feat: sendArticleEmail 구현 * feat: readMemberEmail 구현 * feat: readWorkbookLastArticleId 구현 * feat: SendArticleEmailArgs create 생성자 추가 * feat: WorkbookSubscriptionClientAsyncHandler 구현 * feat: WorkbookSubscriptionNotificationAsyncHandler 구현 * refactor: WorkbookSubscriptionEvent 멤버 추가 * feat: WorkbookSubscriptionEventListener 구현 * feat: WorkbookSubscriptionAfterCompletionEventListener 구현 * refactor: 변경된 구독 이벤트 발행 * test: 추가된 쿼리 Explain 테스트 추가 * fix: 코드 수정에 따른 테스트 수정 * test: 구독 완료 경우 테스트 추가 --- .../few/api/repo/dao/article/ArticleDao.kt | 40 ++++++++-- .../query/SelectArticleContentQuery.kt | 5 ++ .../article/record/ArticleContentRecord.kt | 12 +++ .../com/few/api/repo/dao/member/MemberDao.kt | 17 ++-- .../member/query/SelectMemberEmailQuery.kt | 5 ++ .../repo/dao/subscription/SubscriptionDao.kt | 28 ++++++- .../command/UpdateArticleProgressCommand.kt | 6 ++ .../UpdateLastArticleProgressCommand.kt | 7 ++ .../few/api/repo/dao/workbook/WorkbookDao.kt | 15 ++++ .../query/SelectWorkBookLastArticleIdQuery.kt | 5 ++ .../member/MemberDaoExplainGenerateTest.kt | 16 +++- .../SubscriptionDaoExplainGenerateTest.kt | 28 ++++++- .../WorkbookDaoExplainGenerateTest.kt | 12 +++ ...ubscriptionAfterCompletionEventListener.kt | 25 ++++++ .../WorkbookSubscriptionEventListener.kt | 30 +------ .../event/dto/WorkbookSubscriptionEvent.kt | 2 + .../SendWorkbookArticleAsyncHandler.kt | 80 +++++++++++++++++++ .../WorkbookSubscriptionClientAsyncHandler.kt | 36 +++++++++ .../service/SubscriptionArticleService.kt | 18 +++++ .../service/SubscriptionEmailService.kt | 31 +++++++ ...ervice.kt => SubscriptionMemberService.kt} | 15 +++- ...vice.kt => SubscriptionWorkbookService.kt} | 13 ++- .../service/dto/ReadArticleContentInDto.kt | 5 ++ .../service/dto/ReadArticleContentOutDto.kt | 12 +++ .../service/dto/ReadMemberEmailInDto.kt | 5 ++ .../service/dto/ReadMemberEmailOutDto.kt | 5 ++ .../dto/ReadWorkbookLastArticleIdInDto.kt | 5 ++ .../dto/ReadWorkbookLastArticleIdOutDto.kt | 5 ++ .../service/dto/SendArticleInDto.kt | 12 +++ .../usecase/SubscribeWorkbookUseCase.kt | 16 +++- .../usecase/SubscribeWorkbookUseCaseTest.kt | 39 ++++++++- .../article/dto/SendArticleEmailArgs.kt | 28 ++++++- 32 files changed, 517 insertions(+), 61 deletions(-) create mode 100644 api-repo/src/main/kotlin/com/few/api/repo/dao/article/query/SelectArticleContentQuery.kt create mode 100644 api-repo/src/main/kotlin/com/few/api/repo/dao/article/record/ArticleContentRecord.kt create mode 100644 api-repo/src/main/kotlin/com/few/api/repo/dao/member/query/SelectMemberEmailQuery.kt create mode 100644 api-repo/src/main/kotlin/com/few/api/repo/dao/subscription/command/UpdateArticleProgressCommand.kt create mode 100644 api-repo/src/main/kotlin/com/few/api/repo/dao/subscription/command/UpdateLastArticleProgressCommand.kt create mode 100644 api-repo/src/main/kotlin/com/few/api/repo/dao/workbook/query/SelectWorkBookLastArticleIdQuery.kt create mode 100644 api/src/main/kotlin/com/few/api/domain/subscription/event/WorkbookSubscriptionAfterCompletionEventListener.kt create mode 100644 api/src/main/kotlin/com/few/api/domain/subscription/handler/SendWorkbookArticleAsyncHandler.kt create mode 100644 api/src/main/kotlin/com/few/api/domain/subscription/handler/WorkbookSubscriptionClientAsyncHandler.kt create mode 100644 api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionEmailService.kt rename api/src/main/kotlin/com/few/api/domain/subscription/service/{MemberService.kt => SubscriptionMemberService.kt} (66%) rename api/src/main/kotlin/com/few/api/domain/subscription/service/{WorkbookService.kt => SubscriptionWorkbookService.kt} (53%) create mode 100644 api/src/main/kotlin/com/few/api/domain/subscription/service/dto/ReadArticleContentInDto.kt create mode 100644 api/src/main/kotlin/com/few/api/domain/subscription/service/dto/ReadArticleContentOutDto.kt create mode 100644 api/src/main/kotlin/com/few/api/domain/subscription/service/dto/ReadMemberEmailInDto.kt create mode 100644 api/src/main/kotlin/com/few/api/domain/subscription/service/dto/ReadMemberEmailOutDto.kt create mode 100644 api/src/main/kotlin/com/few/api/domain/subscription/service/dto/ReadWorkbookLastArticleIdInDto.kt create mode 100644 api/src/main/kotlin/com/few/api/domain/subscription/service/dto/ReadWorkbookLastArticleIdOutDto.kt create mode 100644 api/src/main/kotlin/com/few/api/domain/subscription/service/dto/SendArticleInDto.kt diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/article/ArticleDao.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/ArticleDao.kt index 5290f5f37..8fd32d185 100644 --- a/api-repo/src/main/kotlin/com/few/api/repo/dao/article/ArticleDao.kt +++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/ArticleDao.kt @@ -3,18 +3,15 @@ package com.few.api.repo.dao.article import com.few.api.repo.config.LocalCacheConfig.Companion.LOCAL_CM import com.few.api.repo.config.LocalCacheConfig.Companion.SELECT_ARTICLE_RECORD_CACHE import com.few.api.repo.dao.article.command.InsertFullArticleRecordCommand -import com.few.api.repo.dao.article.query.SelectArticleIdByWorkbookIdAndDayQuery -import com.few.api.repo.dao.article.query.SelectArticleRecordQuery -import com.few.api.repo.dao.article.query.SelectWorkBookArticleRecordQuery -import com.few.api.repo.dao.article.query.SelectWorkbookMappedArticleRecordsQuery -import com.few.api.repo.dao.article.record.SelectArticleContentsRecord -import com.few.api.repo.dao.article.record.SelectArticleRecord -import com.few.api.repo.dao.article.record.SelectWorkBookArticleRecord -import com.few.api.repo.dao.article.record.SelectWorkBookMappedArticleRecord +import com.few.api.repo.dao.article.query.* +import com.few.api.repo.dao.article.record.* +import com.few.data.common.code.MemberType import jooq.jooq_dsl.tables.ArticleIfo import jooq.jooq_dsl.tables.ArticleMst import jooq.jooq_dsl.tables.MappingWorkbookArticle +import jooq.jooq_dsl.tables.Member import org.jooq.* +import org.jooq.impl.DSL import org.springframework.cache.annotation.Cacheable import org.springframework.stereotype.Repository @@ -139,4 +136,31 @@ class ArticleDao( ).from(ArticleIfo.ARTICLE_IFO) .where(ArticleIfo.ARTICLE_IFO.ARTICLE_MST_ID.`in`(articleIds)) .and(ArticleIfo.ARTICLE_IFO.DELETED_AT.isNull) + + fun selectArticleContent(query: SelectArticleContentQuery): ArticleContentRecord? { + return selectArticleContentQuery(query) + .fetchOneInto(ArticleContentRecord::class.java) + } + + fun selectArticleContentQuery(query: SelectArticleContentQuery) = + dslContext.select( + ArticleIfo.ARTICLE_IFO.ARTICLE_MST_ID.`as`(ArticleContentRecord::id.name), + ArticleIfo.ARTICLE_IFO.CONTENT.`as`(ArticleContentRecord::articleContent.name), + ArticleMst.ARTICLE_MST.TITLE.`as`(ArticleContentRecord::articleTitle.name), + ArticleMst.ARTICLE_MST.CATEGORY_CD.`as`(ArticleContentRecord::category.name), + DSL.jsonGetAttributeAsText(Member.MEMBER.DESCRIPTION, "name") + .`as`(ArticleContentRecord::writerName.name), + DSL.jsonGetAttribute(Member.MEMBER.DESCRIPTION, "url") + .`as`(ArticleContentRecord::writerLink.name) + ) + .from(ArticleIfo.ARTICLE_IFO) + .join(ArticleMst.ARTICLE_MST) + .on(ArticleIfo.ARTICLE_IFO.ARTICLE_MST_ID.eq(ArticleMst.ARTICLE_MST.ID)) + .join(Member.MEMBER) + .on( + ArticleMst.ARTICLE_MST.MEMBER_ID.eq(Member.MEMBER.ID) + .and(Member.MEMBER.TYPE_CD.eq(MemberType.WRITER.code)) + ) + .where(ArticleIfo.ARTICLE_IFO.ARTICLE_MST_ID.eq(query.articleId)) + .and(ArticleIfo.ARTICLE_IFO.DELETED_AT.isNull) } \ No newline at end of file diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/article/query/SelectArticleContentQuery.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/query/SelectArticleContentQuery.kt new file mode 100644 index 000000000..dde507ad2 --- /dev/null +++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/query/SelectArticleContentQuery.kt @@ -0,0 +1,5 @@ +package com.few.api.repo.dao.article.query + +data class SelectArticleContentQuery( + val articleId: Long, +) \ No newline at end of file diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/article/record/ArticleContentRecord.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/record/ArticleContentRecord.kt new file mode 100644 index 000000000..3b121be38 --- /dev/null +++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/record/ArticleContentRecord.kt @@ -0,0 +1,12 @@ +package com.few.api.repo.dao.article.record + +import java.net.URL + +data class ArticleContentRecord( + val id: Long, + val category: String, + val articleTitle: String, + val articleContent: String, + val writerName: String, + val writerLink: URL, +) \ No newline at end of file diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/member/MemberDao.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/member/MemberDao.kt index 3a0934c73..d72022b8b 100644 --- a/api-repo/src/main/kotlin/com/few/api/repo/dao/member/MemberDao.kt +++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/member/MemberDao.kt @@ -4,13 +4,9 @@ import com.few.api.repo.config.LocalCacheConfig.Companion.LOCAL_CM import com.few.api.repo.config.LocalCacheConfig.Companion.SELECT_WRITER_CACHE import com.few.api.repo.dao.member.command.DeleteMemberCommand import com.few.api.repo.dao.member.command.InsertMemberCommand -import com.few.api.repo.dao.member.query.BrowseWorkbookWritersQuery import com.few.api.repo.dao.member.command.UpdateDeletedMemberTypeCommand import com.few.api.repo.dao.member.command.UpdateMemberTypeCommand -import com.few.api.repo.dao.member.query.SelectMemberByEmailNotConsiderDeletedAtQuery -import com.few.api.repo.dao.member.query.SelectMemberByEmailQuery -import com.few.api.repo.dao.member.query.SelectWriterQuery -import com.few.api.repo.dao.member.query.SelectWritersQuery +import com.few.api.repo.dao.member.query.* import com.few.api.repo.dao.member.record.MemberIdAndIsDeletedRecord import com.few.api.repo.dao.member.record.MemberRecord import com.few.api.repo.dao.member.record.MemberEmailAndTypeRecord @@ -171,6 +167,17 @@ class MemberDao( .set(Member.MEMBER.EMAIL, command.email) .set(Member.MEMBER.TYPE_CD, command.memberType.code) + fun selectMemberEmail(query: SelectMemberEmailQuery): String? { + return selectMemberEmailQuery(query) + .fetchOne() + ?.value1() + } + + fun selectMemberEmailQuery(query: SelectMemberEmailQuery) = dslContext.select(Member.MEMBER.EMAIL) + .from(Member.MEMBER) + .where(Member.MEMBER.ID.eq(query.memberId)) + .and(Member.MEMBER.DELETED_AT.isNull) + fun selectMemberEmailAndType(memberId: Long): MemberEmailAndTypeRecord? { return selectMemberIdAndTypeQuery(memberId) .fetchOne() diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/member/query/SelectMemberEmailQuery.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/member/query/SelectMemberEmailQuery.kt new file mode 100644 index 000000000..329f31035 --- /dev/null +++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/member/query/SelectMemberEmailQuery.kt @@ -0,0 +1,5 @@ +package com.few.api.repo.dao.member.query + +data class SelectMemberEmailQuery( + val memberId: Long, +) \ No newline at end of file diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/subscription/SubscriptionDao.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/subscription/SubscriptionDao.kt index e4ddd84d1..67899a045 100644 --- a/api-repo/src/main/kotlin/com/few/api/repo/dao/subscription/SubscriptionDao.kt +++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/subscription/SubscriptionDao.kt @@ -1,8 +1,6 @@ package com.few.api.repo.dao.subscription -import com.few.api.repo.dao.subscription.command.InsertWorkbookSubscriptionCommand -import com.few.api.repo.dao.subscription.command.UpdateDeletedAtInAllSubscriptionCommand -import com.few.api.repo.dao.subscription.command.UpdateDeletedAtInWorkbookSubscriptionCommand +import com.few.api.repo.dao.subscription.command.* import com.few.api.repo.dao.subscription.query.* import com.few.api.repo.dao.subscription.record.WorkbookSubscriptionStatus import com.few.api.repo.dao.subscription.record.CountAllSubscriptionStatusRecord @@ -163,4 +161,28 @@ class SubscriptionDao( .from(SUBSCRIPTION) .groupBy(SUBSCRIPTION.TARGET_WORKBOOK_ID) .query + + fun updateArticleProgress(command: UpdateArticleProgressCommand) { + updateArticleProgressCommand(command) + .execute() + } + + fun updateArticleProgressCommand( + command: UpdateArticleProgressCommand, + ) = dslContext.update(SUBSCRIPTION) + .set(SUBSCRIPTION.PROGRESS, SUBSCRIPTION.PROGRESS.add(1)) + .where(SUBSCRIPTION.MEMBER_ID.eq(command.memberId)) + .and(SUBSCRIPTION.TARGET_WORKBOOK_ID.eq(command.workbookId)) + + fun updateLastArticleProgress(command: UpdateLastArticleProgressCommand) { + updateLastArticleProgressCommand(command) + .execute() + } + + fun updateLastArticleProgressCommand(command: UpdateLastArticleProgressCommand) = + dslContext.update(SUBSCRIPTION) + .set(SUBSCRIPTION.DELETED_AT, LocalDateTime.now()) + .set(SUBSCRIPTION.UNSUBS_OPINION, command.opinion) + .where(SUBSCRIPTION.MEMBER_ID.eq(command.memberId)) + .and(SUBSCRIPTION.TARGET_WORKBOOK_ID.eq(command.workbookId)) } \ No newline at end of file diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/subscription/command/UpdateArticleProgressCommand.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/subscription/command/UpdateArticleProgressCommand.kt new file mode 100644 index 000000000..9d08c36be --- /dev/null +++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/subscription/command/UpdateArticleProgressCommand.kt @@ -0,0 +1,6 @@ +package com.few.api.repo.dao.subscription.command + +data class UpdateArticleProgressCommand( + val memberId: Long, + val workbookId: Long, +) \ No newline at end of file diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/subscription/command/UpdateLastArticleProgressCommand.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/subscription/command/UpdateLastArticleProgressCommand.kt new file mode 100644 index 000000000..389d7e159 --- /dev/null +++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/subscription/command/UpdateLastArticleProgressCommand.kt @@ -0,0 +1,7 @@ +package com.few.api.repo.dao.subscription.command + +data class UpdateLastArticleProgressCommand( + val memberId: Long, + val workbookId: Long, + val opinion: String = "receive.all", +) \ No newline at end of file diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/workbook/WorkbookDao.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/workbook/WorkbookDao.kt index 16462a44f..e18c65555 100644 --- a/api-repo/src/main/kotlin/com/few/api/repo/dao/workbook/WorkbookDao.kt +++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/workbook/WorkbookDao.kt @@ -5,6 +5,7 @@ import com.few.api.repo.config.LocalCacheConfig.Companion.LOCAL_CM import com.few.api.repo.dao.workbook.command.InsertWorkBookCommand import com.few.api.repo.dao.workbook.command.MapWorkBookToArticleCommand import com.few.api.repo.dao.workbook.query.BrowseWorkBookQueryWithSubscriptionCount +import com.few.api.repo.dao.workbook.query.SelectWorkBookLastArticleIdQuery import com.few.api.repo.dao.workbook.query.SelectWorkBookRecordQuery import com.few.api.repo.dao.workbook.record.SelectWorkBookRecord import com.few.api.repo.dao.workbook.record.SelectWorkBookRecordWithSubscriptionCount @@ -129,4 +130,18 @@ class WorkbookDao( else -> Workbook.WORKBOOK.CATEGORY_CD.eq(query.category) } } + + fun selectWorkBookLastArticleId(query: SelectWorkBookLastArticleIdQuery): Long? { + return selectWorkBookLastArticleIdQuery(query) + .fetchOneInto(Long::class.java) + } + + fun selectWorkBookLastArticleIdQuery(query: SelectWorkBookLastArticleIdQuery) = + dslContext.select( + DSL.max(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DAY_COL) + ) + .from(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE) + .where(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID.eq(query.workbookId)) + .and(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DELETED_AT.isNull) + .groupBy(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID) } \ No newline at end of file diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/workbook/query/SelectWorkBookLastArticleIdQuery.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/workbook/query/SelectWorkBookLastArticleIdQuery.kt new file mode 100644 index 000000000..428d4d315 --- /dev/null +++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/workbook/query/SelectWorkBookLastArticleIdQuery.kt @@ -0,0 +1,5 @@ +package com.few.api.repo.dao.workbook.query + +data class SelectWorkBookLastArticleIdQuery( + val workbookId: Long, +) \ No newline at end of file diff --git a/api-repo/src/test/kotlin/com/few/api/repo/explain/member/MemberDaoExplainGenerateTest.kt b/api-repo/src/test/kotlin/com/few/api/repo/explain/member/MemberDaoExplainGenerateTest.kt index dbc8eceaf..cd2315063 100644 --- a/api-repo/src/test/kotlin/com/few/api/repo/explain/member/MemberDaoExplainGenerateTest.kt +++ b/api-repo/src/test/kotlin/com/few/api/repo/explain/member/MemberDaoExplainGenerateTest.kt @@ -5,10 +5,7 @@ import com.few.api.repo.dao.member.command.DeleteMemberCommand import com.few.api.repo.dao.member.command.InsertMemberCommand import com.few.api.repo.dao.member.command.UpdateDeletedMemberTypeCommand import com.few.api.repo.dao.member.command.UpdateMemberTypeCommand -import com.few.api.repo.dao.member.query.BrowseWorkbookWritersQuery -import com.few.api.repo.dao.member.query.SelectMemberByEmailNotConsiderDeletedAtQuery -import com.few.api.repo.dao.member.query.SelectMemberByEmailQuery -import com.few.api.repo.dao.member.query.SelectWriterQuery +import com.few.api.repo.dao.member.query.* import com.few.api.repo.dao.member.support.WriterDescriptionJsonMapper import com.few.api.repo.explain.ExplainGenerator import com.few.api.repo.explain.InsertUpdateExplainGenerator @@ -104,6 +101,17 @@ class MemberDaoExplainGenerateTest : JooqTestSpec() { ResultGenerator.execute(command, explain, "insertMemberCommandExplain") } + @Test + fun selectMemberEmailQueryExplain() { + val query = SelectMemberEmailQuery(1).let { + memberDao.selectMemberEmailQuery(it) + } + + val explain = ExplainGenerator.execute(dslContext, query) + + ResultGenerator.execute(query, explain, "selectMemberEmailQueryExplain") + } + @Test fun selectMemberIdAndTypeQueryExplain() { val query = 1L.let { diff --git a/api-repo/src/test/kotlin/com/few/api/repo/explain/subscription/SubscriptionDaoExplainGenerateTest.kt b/api-repo/src/test/kotlin/com/few/api/repo/explain/subscription/SubscriptionDaoExplainGenerateTest.kt index e72bd8669..f21428246 100644 --- a/api-repo/src/test/kotlin/com/few/api/repo/explain/subscription/SubscriptionDaoExplainGenerateTest.kt +++ b/api-repo/src/test/kotlin/com/few/api/repo/explain/subscription/SubscriptionDaoExplainGenerateTest.kt @@ -1,9 +1,7 @@ package com.few.api.repo.explain.subscription import com.few.api.repo.dao.subscription.SubscriptionDao -import com.few.api.repo.dao.subscription.command.InsertWorkbookSubscriptionCommand -import com.few.api.repo.dao.subscription.command.UpdateDeletedAtInAllSubscriptionCommand -import com.few.api.repo.dao.subscription.command.UpdateDeletedAtInWorkbookSubscriptionCommand +import com.few.api.repo.dao.subscription.command.* import com.few.api.repo.dao.subscription.query.CountWorkbookMappedArticlesQuery import com.few.api.repo.dao.subscription.query.SelectAllMemberWorkbookActiveSubscription import com.few.api.repo.dao.subscription.query.SelectAllWorkbookSubscriptionStatusNotConsiderDeletedAtQuery @@ -168,4 +166,28 @@ class SubscriptionDaoExplainGenerateTest : JooqTestSpec() { ResultGenerator.execute(command, explain, "updateDeletedAtInWorkbookSubscriptionCommandExplain") } + + @Test + fun updateArticleProgressCommandExplain() { + val command = UpdateArticleProgressCommand(1L, 1L) + .let { + subscriptionDao.updateArticleProgressCommand(it) + } + + val explain = InsertUpdateExplainGenerator.execute(dslContext, command.sql, command.bindValues) + + ResultGenerator.execute(command, explain, "updateArticleProgressCommandExplain") + } + + @Test + fun updateLastArticleProgressCommandExplain() { + val command = UpdateLastArticleProgressCommand(1L, 1L) + .let { + subscriptionDao.updateLastArticleProgressCommand(it) + } + + val explain = InsertUpdateExplainGenerator.execute(dslContext, command.sql, command.bindValues) + + ResultGenerator.execute(command, explain, "updateLastArticleProgressCommandExplain") + } } \ No newline at end of file diff --git a/api-repo/src/test/kotlin/com/few/api/repo/explain/workbook/WorkbookDaoExplainGenerateTest.kt b/api-repo/src/test/kotlin/com/few/api/repo/explain/workbook/WorkbookDaoExplainGenerateTest.kt index 8c90b1117..9406418ba 100644 --- a/api-repo/src/test/kotlin/com/few/api/repo/explain/workbook/WorkbookDaoExplainGenerateTest.kt +++ b/api-repo/src/test/kotlin/com/few/api/repo/explain/workbook/WorkbookDaoExplainGenerateTest.kt @@ -4,6 +4,7 @@ import com.few.api.repo.dao.workbook.WorkbookDao import com.few.api.repo.dao.workbook.command.InsertWorkBookCommand import com.few.api.repo.dao.workbook.command.MapWorkBookToArticleCommand import com.few.api.repo.dao.workbook.query.BrowseWorkBookQueryWithSubscriptionCount +import com.few.api.repo.dao.workbook.query.SelectWorkBookLastArticleIdQuery import com.few.api.repo.dao.workbook.query.SelectWorkBookRecordQuery import com.few.api.repo.explain.ExplainGenerator import com.few.api.repo.explain.InsertUpdateExplainGenerator @@ -106,4 +107,15 @@ class WorkbookDaoExplainGenerateTest : JooqTestSpec() { ResultGenerator.execute(command, explain, "mapWorkBookToArticleCommandExplain") } + + @Test + fun selectWorkBookLastArticleIdQueryExplain() { + val query = SelectWorkBookLastArticleIdQuery(1L).let { + workbookDao.selectWorkBookLastArticleIdQuery(it) + } + + val explain = ExplainGenerator.execute(dslContext, query) + + ResultGenerator.execute(query, explain, "selectWorkBookLastArticleIdQueryExplain") + } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/event/WorkbookSubscriptionAfterCompletionEventListener.kt b/api/src/main/kotlin/com/few/api/domain/subscription/event/WorkbookSubscriptionAfterCompletionEventListener.kt new file mode 100644 index 000000000..13796ab42 --- /dev/null +++ b/api/src/main/kotlin/com/few/api/domain/subscription/event/WorkbookSubscriptionAfterCompletionEventListener.kt @@ -0,0 +1,25 @@ +package com.few.api.domain.subscription.event + +import com.few.api.domain.subscription.event.dto.WorkbookSubscriptionEvent +import com.few.api.domain.subscription.handler.SendWorkbookArticleAsyncHandler +import org.springframework.stereotype.Component +import org.springframework.transaction.annotation.Propagation +import org.springframework.transaction.annotation.Transactional +import org.springframework.transaction.event.TransactionPhase +import org.springframework.transaction.event.TransactionalEventListener + +@Component +class WorkbookSubscriptionAfterCompletionEventListener( + private val sendWorkbookArticleAsyncHandler: SendWorkbookArticleAsyncHandler, +) { + + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION) + @Transactional(propagation = Propagation.REQUIRES_NEW) + fun handleEvent(event: WorkbookSubscriptionEvent) { + sendWorkbookArticleAsyncHandler.sendWorkbookArticle( + event.memberId, + event.workbookId, + event.articleDayCol.toByte() + ) + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/event/WorkbookSubscriptionEventListener.kt b/api/src/main/kotlin/com/few/api/domain/subscription/event/WorkbookSubscriptionEventListener.kt index e3c5a75ad..ec4c8a15c 100644 --- a/api/src/main/kotlin/com/few/api/domain/subscription/event/WorkbookSubscriptionEventListener.kt +++ b/api/src/main/kotlin/com/few/api/domain/subscription/event/WorkbookSubscriptionEventListener.kt @@ -1,39 +1,17 @@ package com.few.api.domain.subscription.event -import com.few.api.client.subscription.SubscriptionClient -import com.few.api.client.subscription.dto.WorkbookSubscriptionArgs -import com.few.api.config.ApiThreadPoolConfig.Companion.DISCORD_HOOK_EVENT_POOL import com.few.api.domain.subscription.event.dto.WorkbookSubscriptionEvent -import com.few.api.domain.subscription.service.WorkbookService -import com.few.api.domain.subscription.service.dto.ReadWorkbookTitleInDto -import com.few.api.exception.common.NotFoundException -import com.few.api.repo.dao.subscription.SubscriptionDao +import com.few.api.domain.subscription.handler.WorkbookSubscriptionClientAsyncHandler import org.springframework.context.event.EventListener -import org.springframework.scheduling.annotation.Async import org.springframework.stereotype.Component @Component class WorkbookSubscriptionEventListener( - private val subscriptionDao: SubscriptionDao, - private val subscriptionClient: SubscriptionClient, - private val workbookService: WorkbookService, + private val workbookSubscriptionClientAsyncHandler: WorkbookSubscriptionClientAsyncHandler, ) { - @Async(value = DISCORD_HOOK_EVENT_POOL) @EventListener - fun handleWorkbookSubscriptionEvent(event: WorkbookSubscriptionEvent) { - val title = ReadWorkbookTitleInDto(event.workbookId).let { dto -> - workbookService.readWorkbookTitle(dto)?.workbookTitle - ?: throw NotFoundException("workbook.notfound.id") - } - subscriptionDao.countAllSubscriptionStatus().let { record -> - WorkbookSubscriptionArgs( - totalSubscriptions = record.totalSubscriptions, - activeSubscriptions = record.activeSubscriptions, - workbookTitle = title - ).let { args -> - subscriptionClient.announceWorkbookSubscription(args) - } - } + fun handleEvent(event: WorkbookSubscriptionEvent) { + workbookSubscriptionClientAsyncHandler.sendSubscriptionEvent(event.workbookId) } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/event/dto/WorkbookSubscriptionEvent.kt b/api/src/main/kotlin/com/few/api/domain/subscription/event/dto/WorkbookSubscriptionEvent.kt index 87763fe84..4f9baf8e4 100644 --- a/api/src/main/kotlin/com/few/api/domain/subscription/event/dto/WorkbookSubscriptionEvent.kt +++ b/api/src/main/kotlin/com/few/api/domain/subscription/event/dto/WorkbookSubscriptionEvent.kt @@ -1,5 +1,7 @@ package com.few.api.domain.subscription.event.dto data class WorkbookSubscriptionEvent( + val memberId: Long, val workbookId: Long, + val articleDayCol: Int, ) \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/handler/SendWorkbookArticleAsyncHandler.kt b/api/src/main/kotlin/com/few/api/domain/subscription/handler/SendWorkbookArticleAsyncHandler.kt new file mode 100644 index 000000000..cd810d14c --- /dev/null +++ b/api/src/main/kotlin/com/few/api/domain/subscription/handler/SendWorkbookArticleAsyncHandler.kt @@ -0,0 +1,80 @@ +package com.few.api.domain.subscription.handler + +import com.few.api.config.DatabaseAccessThreadPoolConfig.Companion.DATABASE_ACCESS_POOL +import com.few.api.domain.subscription.service.SubscriptionArticleService +import com.few.api.domain.subscription.service.SubscriptionMemberService +import com.few.api.domain.subscription.service.SubscriptionEmailService +import com.few.api.domain.subscription.service.SubscriptionWorkbookService +import com.few.api.domain.subscription.service.dto.* +import com.few.api.exception.common.NotFoundException +import com.few.api.repo.dao.subscription.SubscriptionDao +import com.few.api.repo.dao.subscription.command.UpdateArticleProgressCommand +import com.few.api.repo.dao.subscription.command.UpdateLastArticleProgressCommand +import com.few.data.common.code.CategoryType +import com.few.email.service.article.dto.Content +import org.springframework.scheduling.annotation.Async +import org.springframework.stereotype.Component +import java.time.LocalDate + +@Component +class SendWorkbookArticleAsyncHandler( + private val memberService: SubscriptionMemberService, + private val articleService: SubscriptionArticleService, + private val workbookService: SubscriptionWorkbookService, + private val subscriptionDao: SubscriptionDao, + private val emailService: SubscriptionEmailService, +) { + + @Async(value = DATABASE_ACCESS_POOL) + fun sendWorkbookArticle(memberId: Long, workbookId: Long, articleDayCol: Byte) { + val date = LocalDate.now() + val memberEmail = ReadMemberEmailInDto(memberId).let { memberService.readMemberEmail(it) }.let { it?.email } + ?: throw NotFoundException("member.notfound.id") + val article = ReadArticleIdByWorkbookIdAndDayDto(workbookId, articleDayCol.toInt()).let { + articleService.readArticleIdByWorkbookIdAndDay(it) + }?.let { articleId -> + ReadArticleContentInDto(articleId).let { + articleService.readArticleContent(it) + } + } ?: throw NotFoundException("article.notfound.id") + + SendArticleInDto( + memberId = memberId, + workbookId = workbookId, + toEmail = memberEmail, + articleDayCol = articleDayCol, + articleTitle = article.articleTitle, + articleContent = Content.create( + memberEmail = memberEmail, + workbookId = workbookId, + articleId = article.id, + currentDate = date, + category = CategoryType.convertToDisplayName(article.category.toByte()), + articleDay = articleDayCol.toInt(), + articleTitle = article.articleTitle, + writerName = article.writerName, + writerLink = article.writerLink, + articleContent = article.articleContent + ) + ).let { + runCatching { emailService.sendArticleEmail(it) } + .onSuccess { + val lastDayArticleId = ReadWorkbookLastArticleIdInDto( + workbookId + ).let { + workbookService.readWorkbookLastArticleId(it) + }?.lastArticleId ?: throw NotFoundException("workbook.notfound.id") + + if (article.id == lastDayArticleId) { + UpdateArticleProgressCommand(workbookId, memberId).let { + subscriptionDao.updateArticleProgress(it) + } + } else { + UpdateLastArticleProgressCommand(workbookId, memberId).let { + subscriptionDao.updateLastArticleProgress(it) + } + } + } + } + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/handler/WorkbookSubscriptionClientAsyncHandler.kt b/api/src/main/kotlin/com/few/api/domain/subscription/handler/WorkbookSubscriptionClientAsyncHandler.kt new file mode 100644 index 000000000..0fb285ca2 --- /dev/null +++ b/api/src/main/kotlin/com/few/api/domain/subscription/handler/WorkbookSubscriptionClientAsyncHandler.kt @@ -0,0 +1,36 @@ +package com.few.api.domain.subscription.handler + +import com.few.api.client.subscription.SubscriptionClient +import com.few.api.client.subscription.dto.WorkbookSubscriptionArgs +import com.few.api.config.ApiThreadPoolConfig.Companion.DISCORD_HOOK_EVENT_POOL +import com.few.api.domain.subscription.service.SubscriptionWorkbookService +import com.few.api.domain.subscription.service.dto.ReadWorkbookTitleInDto +import com.few.api.exception.common.NotFoundException +import com.few.api.repo.dao.subscription.SubscriptionDao +import org.springframework.scheduling.annotation.Async +import org.springframework.stereotype.Component + +@Component +class WorkbookSubscriptionClientAsyncHandler( + private val subscriptionDao: SubscriptionDao, + private val subscriptionClient: SubscriptionClient, + private val workbookService: SubscriptionWorkbookService, +) { + + @Async(value = DISCORD_HOOK_EVENT_POOL) + fun sendSubscriptionEvent(workbookId: Long) { + val title = ReadWorkbookTitleInDto(workbookId).let { dto -> + workbookService.readWorkbookTitle(dto)?.workbookTitle + ?: throw NotFoundException("workbook.notfound.id") + } + subscriptionDao.countAllSubscriptionStatus().let { record -> + WorkbookSubscriptionArgs( + totalSubscriptions = record.totalSubscriptions, + activeSubscriptions = record.activeSubscriptions, + workbookTitle = title + ).let { args -> + subscriptionClient.announceWorkbookSubscription(args) + } + } + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionArticleService.kt b/api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionArticleService.kt index ba1111c44..a6068e272 100644 --- a/api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionArticleService.kt +++ b/api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionArticleService.kt @@ -1,7 +1,10 @@ package com.few.api.domain.subscription.service +import com.few.api.domain.subscription.service.dto.ReadArticleContentInDto +import com.few.api.domain.subscription.service.dto.ReadArticleContentOutDto import com.few.api.domain.subscription.service.dto.ReadArticleIdByWorkbookIdAndDayDto import com.few.api.repo.dao.article.ArticleDao +import com.few.api.repo.dao.article.query.SelectArticleContentQuery import com.few.api.repo.dao.article.query.SelectArticleIdByWorkbookIdAndDayQuery import org.springframework.stereotype.Service @@ -14,4 +17,19 @@ class SubscriptionArticleService( return articleDao.selectArticleIdByWorkbookIdAndDay(query) } } + + fun readArticleContent(dto: ReadArticleContentInDto): ReadArticleContentOutDto? { + return SelectArticleContentQuery(dto.articleId).let { query -> + articleDao.selectArticleContent(query) + }?.let { + ReadArticleContentOutDto( + it.id, + it.category, + it.articleTitle, + it.articleContent, + it.writerName, + it.writerLink + ) + } + } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionEmailService.kt b/api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionEmailService.kt new file mode 100644 index 000000000..e1347fa4b --- /dev/null +++ b/api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionEmailService.kt @@ -0,0 +1,31 @@ +package com.few.api.domain.subscription.service + +import com.few.api.domain.subscription.service.dto.SendArticleInDto +import com.few.email.service.article.SendArticleEmailService +import com.few.email.service.article.dto.SendArticleEmailArgs +import org.springframework.stereotype.Service + +@Service +class SubscriptionEmailService( + private val sendArticleEmailService: SendArticleEmailService, +) { + + companion object { + private const val ARTICLE_SUBJECT_TEMPLATE = "Day%d %s" + private const val ARTICLE_TEMPLATE = "article" + } + + fun sendArticleEmail(dto: SendArticleInDto) { + SendArticleEmailArgs( + dto.toEmail, + ARTICLE_SUBJECT_TEMPLATE.format( + dto.articleDayCol, + dto.articleTitle + ), + ARTICLE_TEMPLATE, + dto.articleContent + ).let { + sendArticleEmailService.send(it) + } + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/service/MemberService.kt b/api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionMemberService.kt similarity index 66% rename from api/src/main/kotlin/com/few/api/domain/subscription/service/MemberService.kt rename to api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionMemberService.kt index 68482dca2..43e49c4d3 100644 --- a/api/src/main/kotlin/com/few/api/domain/subscription/service/MemberService.kt +++ b/api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionMemberService.kt @@ -1,16 +1,15 @@ package com.few.api.domain.subscription.service -import com.few.api.domain.subscription.service.dto.InsertMemberInDto -import com.few.api.domain.subscription.service.dto.MemberIdOutDto -import com.few.api.domain.subscription.service.dto.ReadMemberIdInDto +import com.few.api.domain.subscription.service.dto.* import com.few.api.exception.common.InsertException import com.few.api.repo.dao.member.MemberDao import com.few.api.repo.dao.member.command.InsertMemberCommand import com.few.api.repo.dao.member.query.SelectMemberByEmailQuery +import com.few.api.repo.dao.member.query.SelectMemberEmailQuery import org.springframework.stereotype.Service @Service -class MemberService( +class SubscriptionMemberService( private val memberDao: MemberDao, ) { @@ -22,4 +21,12 @@ class MemberService( return memberDao.insertMember(InsertMemberCommand(dto.email, dto.memberType))?.let { MemberIdOutDto(it) } ?: throw InsertException("member.insertfail.record") } + + fun readMemberEmail(dto: ReadMemberEmailInDto): ReadMemberEmailOutDto? { + return SelectMemberEmailQuery(dto.memberId).let { query -> + memberDao.selectMemberEmail(query) + }?.let { + ReadMemberEmailOutDto(it) + } + } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/service/WorkbookService.kt b/api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionWorkbookService.kt similarity index 53% rename from api/src/main/kotlin/com/few/api/domain/subscription/service/WorkbookService.kt rename to api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionWorkbookService.kt index 35d4dc1c4..b2890c412 100644 --- a/api/src/main/kotlin/com/few/api/domain/subscription/service/WorkbookService.kt +++ b/api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionWorkbookService.kt @@ -1,13 +1,16 @@ package com.few.api.domain.subscription.service +import com.few.api.domain.subscription.service.dto.ReadWorkbookLastArticleIdInDto +import com.few.api.domain.subscription.service.dto.ReadWorkbookLastArticleIdOutDto import com.few.api.domain.subscription.service.dto.ReadWorkbookTitleInDto import com.few.api.domain.subscription.service.dto.ReadWorkbookTitleOutDto import com.few.api.repo.dao.workbook.WorkbookDao +import com.few.api.repo.dao.workbook.query.SelectWorkBookLastArticleIdQuery import com.few.api.repo.dao.workbook.query.SelectWorkBookRecordQuery import org.springframework.stereotype.Service @Service -class WorkbookService( +class SubscriptionWorkbookService( private val workbookDao: WorkbookDao, ) { @@ -16,4 +19,12 @@ class WorkbookService( workbookDao.selectWorkBook(query)?.title?.let { ReadWorkbookTitleOutDto(it) } } } + + fun readWorkbookLastArticleId(dto: ReadWorkbookLastArticleIdInDto): ReadWorkbookLastArticleIdOutDto? { + return SelectWorkBookLastArticleIdQuery(dto.workbookId).let { query -> + workbookDao.selectWorkBookLastArticleId(query) + }?.let { + ReadWorkbookLastArticleIdOutDto(it) + } + } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/service/dto/ReadArticleContentInDto.kt b/api/src/main/kotlin/com/few/api/domain/subscription/service/dto/ReadArticleContentInDto.kt new file mode 100644 index 000000000..d47bcc540 --- /dev/null +++ b/api/src/main/kotlin/com/few/api/domain/subscription/service/dto/ReadArticleContentInDto.kt @@ -0,0 +1,5 @@ +package com.few.api.domain.subscription.service.dto + +data class ReadArticleContentInDto( + val articleId: Long, +) \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/service/dto/ReadArticleContentOutDto.kt b/api/src/main/kotlin/com/few/api/domain/subscription/service/dto/ReadArticleContentOutDto.kt new file mode 100644 index 000000000..2e67c947d --- /dev/null +++ b/api/src/main/kotlin/com/few/api/domain/subscription/service/dto/ReadArticleContentOutDto.kt @@ -0,0 +1,12 @@ +package com.few.api.domain.subscription.service.dto + +import java.net.URL + +data class ReadArticleContentOutDto( + val id: Long, + val category: String, + val articleTitle: String, + val articleContent: String, + val writerName: String, + val writerLink: URL, +) \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/service/dto/ReadMemberEmailInDto.kt b/api/src/main/kotlin/com/few/api/domain/subscription/service/dto/ReadMemberEmailInDto.kt new file mode 100644 index 000000000..112ced7f1 --- /dev/null +++ b/api/src/main/kotlin/com/few/api/domain/subscription/service/dto/ReadMemberEmailInDto.kt @@ -0,0 +1,5 @@ +package com.few.api.domain.subscription.service.dto + +data class ReadMemberEmailInDto( + val memberId: Long, +) \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/service/dto/ReadMemberEmailOutDto.kt b/api/src/main/kotlin/com/few/api/domain/subscription/service/dto/ReadMemberEmailOutDto.kt new file mode 100644 index 000000000..ec08abeae --- /dev/null +++ b/api/src/main/kotlin/com/few/api/domain/subscription/service/dto/ReadMemberEmailOutDto.kt @@ -0,0 +1,5 @@ +package com.few.api.domain.subscription.service.dto + +data class ReadMemberEmailOutDto( + val email: String, +) \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/service/dto/ReadWorkbookLastArticleIdInDto.kt b/api/src/main/kotlin/com/few/api/domain/subscription/service/dto/ReadWorkbookLastArticleIdInDto.kt new file mode 100644 index 000000000..9d4670aeb --- /dev/null +++ b/api/src/main/kotlin/com/few/api/domain/subscription/service/dto/ReadWorkbookLastArticleIdInDto.kt @@ -0,0 +1,5 @@ +package com.few.api.domain.subscription.service.dto + +data class ReadWorkbookLastArticleIdInDto( + val workbookId: Long, +) \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/service/dto/ReadWorkbookLastArticleIdOutDto.kt b/api/src/main/kotlin/com/few/api/domain/subscription/service/dto/ReadWorkbookLastArticleIdOutDto.kt new file mode 100644 index 000000000..fa3ecac2a --- /dev/null +++ b/api/src/main/kotlin/com/few/api/domain/subscription/service/dto/ReadWorkbookLastArticleIdOutDto.kt @@ -0,0 +1,5 @@ +package com.few.api.domain.subscription.service.dto + +data class ReadWorkbookLastArticleIdOutDto( + val lastArticleId: Long, +) \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/service/dto/SendArticleInDto.kt b/api/src/main/kotlin/com/few/api/domain/subscription/service/dto/SendArticleInDto.kt new file mode 100644 index 000000000..bc1f11724 --- /dev/null +++ b/api/src/main/kotlin/com/few/api/domain/subscription/service/dto/SendArticleInDto.kt @@ -0,0 +1,12 @@ +package com.few.api.domain.subscription.service.dto + +import com.few.email.service.article.dto.Content + +data class SendArticleInDto( + val memberId: Long, + val workbookId: Long, + val toEmail: String, + val articleDayCol: Byte, + val articleTitle: String, + val articleContent: Content, +) \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/SubscribeWorkbookUseCase.kt b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/SubscribeWorkbookUseCase.kt index 69449cc32..8136cf594 100644 --- a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/SubscribeWorkbookUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/SubscribeWorkbookUseCase.kt @@ -20,8 +20,6 @@ class SubscribeWorkbookUseCase( @Transactional fun execute(useCaseIn: SubscribeWorkbookUseCaseIn) { - // TODO: request sending email - val subTargetWorkbookId = useCaseIn.workbookId val memberId = useCaseIn.memberId val command = InsertWorkbookSubscriptionCommand( @@ -54,6 +52,18 @@ class SubscribeWorkbookUseCase( throw SubscribeIllegalArgumentException("subscribe.state.subscribed") } } - applicationEventPublisher.publishEvent(WorkbookSubscriptionEvent(workbookId = subTargetWorkbookId)) + + /** + * 구독 이벤트 발행 + * @see com.few.api.domain.subscription.event.WorkbookSubscriptionEventListener + * @see com.few.api.domain.subscription.event.WorkbookSubscriptionAfterCompletionEventListener + */ + applicationEventPublisher.publishEvent( + WorkbookSubscriptionEvent( + workbookId = subTargetWorkbookId, + memberId = memberId, + articleDayCol = subscriptionStatus?.day ?: 1 + ) + ) } } \ No newline at end of file diff --git a/api/src/test/kotlin/com/few/api/domain/subscription/usecase/SubscribeWorkbookUseCaseTest.kt b/api/src/test/kotlin/com/few/api/domain/subscription/usecase/SubscribeWorkbookUseCaseTest.kt index 6b86b8eb5..dc0c18949 100644 --- a/api/src/test/kotlin/com/few/api/domain/subscription/usecase/SubscribeWorkbookUseCaseTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/subscription/usecase/SubscribeWorkbookUseCaseTest.kt @@ -37,7 +37,7 @@ class SubscribeWorkbookUseCaseTest : BehaviorSpec({ every { subscriptionDao.insertWorkbookSubscription(any()) } just Runs - val event = WorkbookSubscriptionEvent(workbookId) + val event = WorkbookSubscriptionEvent(workbookId, memberId, 1) every { applicationEventPublisher.publishEvent(event) } answers { log.debug { "Mocking applicationEventPublisher.publishEvent(any()) was called" } } @@ -66,7 +66,7 @@ class SubscribeWorkbookUseCaseTest : BehaviorSpec({ every { subscriptionDao.reSubscribeWorkbookSubscription(any()) } just Runs - val event = WorkbookSubscriptionEvent(workbookId) + val event = WorkbookSubscriptionEvent(workbookId, memberId, day) every { applicationEventPublisher.publishEvent(event) } answers { log.debug { "Mocking applicationEventPublisher.publishEvent(any()) was called" } } @@ -82,6 +82,35 @@ class SubscribeWorkbookUseCaseTest : BehaviorSpec({ } } + `when`("이미 구독한 히스토리가 있고 구독을 완료한 경우") { + val day = 3 + every { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } returns WorkbookSubscriptionStatus( + workbookId = workbookId, + isActiveSub = false, + day + ) + + val lastDay = day + every { subscriptionDao.countWorkbookMappedArticles(any()) } returns lastDay + + every { subscriptionDao.reSubscribeWorkbookSubscription(any()) } just Runs + + val event = WorkbookSubscriptionEvent(workbookId, memberId, day) + every { applicationEventPublisher.publishEvent(event) } answers { + log.debug { "Mocking applicationEventPublisher.publishEvent(any()) was called" } + } + + then("예외가 발생한다") { + shouldThrow { useCase.execute(useCaseIn) } + + verify(exactly = 1) { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } + verify(exactly = 0) { subscriptionDao.insertWorkbookSubscription(any()) } + verify(exactly = 1) { subscriptionDao.countWorkbookMappedArticles(any()) } + verify(exactly = 0) { subscriptionDao.reSubscribeWorkbookSubscription(any()) } + verify(exactly = 0) { applicationEventPublisher.publishEvent(event) } + } + } + `when`("구독 중인 경우") { val day = 2 every { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } returns WorkbookSubscriptionStatus(workbookId = workbookId, isActiveSub = true, day) @@ -93,7 +122,11 @@ class SubscribeWorkbookUseCaseTest : BehaviorSpec({ verify(exactly = 0) { subscriptionDao.insertWorkbookSubscription(any()) } verify(exactly = 0) { subscriptionDao.countWorkbookMappedArticles(any()) } verify(exactly = 0) { subscriptionDao.reSubscribeWorkbookSubscription(any()) } - verify(exactly = 0) { applicationEventPublisher.publishEvent(WorkbookSubscriptionEvent(workbookId)) } + verify(exactly = 0) { + applicationEventPublisher.publishEvent( + WorkbookSubscriptionEvent(workbookId, memberId, day) + ) + } } } } diff --git a/email/src/main/kotlin/com/few/email/service/article/dto/SendArticleEmailArgs.kt b/email/src/main/kotlin/com/few/email/service/article/dto/SendArticleEmailArgs.kt index 4b96bd1e1..c34483ae1 100644 --- a/email/src/main/kotlin/com/few/email/service/article/dto/SendArticleEmailArgs.kt +++ b/email/src/main/kotlin/com/few/email/service/article/dto/SendArticleEmailArgs.kt @@ -23,4 +23,30 @@ data class Content( val articleContent: String, val problemLink: URL, val unsubscribeLink: URL, -) \ No newline at end of file +) { + companion object { + fun create( + memberEmail: String, + workbookId: Long, + articleId: Long, + currentDate: LocalDate, + category: String, + articleDay: Int, + articleTitle: String, + writerName: String, + writerLink: URL, + articleContent: String, + ) = Content( + articleLink = URL("https://www.fewletter.com/article/$articleId"), + currentDate, + category, + articleDay, + articleTitle, + writerName, + writerLink, + articleContent, + problemLink = URL("https://www.fewletter.com/problem?articleId=$articleId"), + unsubscribeLink = URL("https://www.fewletter.com/unsubscribe?user=$memberEmail&workbookId=$workbookId&articleId=$articleId") + ) + } +} \ No newline at end of file From dd4fb328b6bdf038cd6d1b46fe3ce223f61cceb8 Mon Sep 17 00:00:00 2001 From: belljun3395 <195850@jnu.ac.kr> Date: Wed, 28 Aug 2024 03:11:22 +0900 Subject: [PATCH 6/8] =?UTF-8?q?[Refactor/#363]=20SubscribeWorkbookUseCase?= =?UTF-8?q?=20=EB=AA=A8=EB=8D=B8=20=EB=B3=80=ED=99=98=20(#365)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: WorkbookSubscriptionStatus 구현 * feat: WorkbookSubscriptionHistory 구현 * feat: CancelledWorkbookSubscriptionHistory 구현 * test: WorkbookSubscriptionHistoryTest 구현 * refactor: Subscribe 모델 적용하여 리펙토링 * refactor: workbookSubscriptionStatus 접근 제어자 protected로 수정 * chore: init 메서드 위치 수정 * refactor: 생성자 변경에 따른 테스트 수정 --- .../usecase/SubscribeWorkbookUseCase.kt | 40 +++- .../CancelledWorkbookSubscriptionHistory.kt | 21 ++ .../model/WorkbookSubscriptionHistory.kt | 37 ++++ .../model/WorkbookSubscriptionStatus.kt | 7 + .../model/WorkbookSubscriptionHistoryTest.kt | 179 ++++++++++++++++++ 5 files changed, 276 insertions(+), 8 deletions(-) create mode 100644 api/src/main/kotlin/com/few/api/domain/subscription/usecase/model/CancelledWorkbookSubscriptionHistory.kt create mode 100644 api/src/main/kotlin/com/few/api/domain/subscription/usecase/model/WorkbookSubscriptionHistory.kt create mode 100644 api/src/main/kotlin/com/few/api/domain/subscription/usecase/model/WorkbookSubscriptionStatus.kt create mode 100644 api/src/test/kotlin/com/few/api/domain/subscription/usecase/model/WorkbookSubscriptionHistoryTest.kt diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/SubscribeWorkbookUseCase.kt b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/SubscribeWorkbookUseCase.kt index 8136cf594..f3a8ce912 100644 --- a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/SubscribeWorkbookUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/SubscribeWorkbookUseCase.kt @@ -5,6 +5,9 @@ import com.few.api.repo.dao.subscription.SubscriptionDao import com.few.api.repo.dao.subscription.command.InsertWorkbookSubscriptionCommand import com.few.api.repo.dao.subscription.query.SelectAllWorkbookSubscriptionStatusNotConsiderDeletedAtQuery import com.few.api.domain.subscription.usecase.dto.SubscribeWorkbookUseCaseIn +import com.few.api.domain.subscription.usecase.model.CancelledWorkbookSubscriptionHistory +import com.few.api.domain.subscription.usecase.model.WorkbookSubscriptionHistory +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 @@ -27,24 +30,45 @@ class SubscribeWorkbookUseCase( workbookId = subTargetWorkbookId ) - val subscriptionStatus = subscriptionDao.selectTopWorkbookSubscriptionStatus( + val workbookSubscriptionHistory = subscriptionDao.selectTopWorkbookSubscriptionStatus( SelectAllWorkbookSubscriptionStatusNotConsiderDeletedAtQuery(memberId = memberId, workbookId = subTargetWorkbookId) - ) + ).run { + if (this != null) { + WorkbookSubscriptionHistory( + false, + WorkbookSubscriptionStatus( + workbookId = this.workbookId, + isActiveSub = this.isActiveSub, + day = this.day + ) + ) + } else { + WorkbookSubscriptionHistory( + true + ) + } + } when { /** 구독한 히스토리가 없는 경우 */ - subscriptionStatus == null -> { + workbookSubscriptionHistory.isNew -> { subscriptionDao.insertWorkbookSubscription(command) } /** 이미 구독한 히스토리가 있고 구독이 취소된 경우 */ - !subscriptionStatus.isActiveSub -> { - val lastDay = subscriptionDao.countWorkbookMappedArticles(CountWorkbookMappedArticlesQuery(subTargetWorkbookId)) ?: throw NotFoundException("workbook.notfound.id") - if (lastDay <= subscriptionStatus.day) { + workbookSubscriptionHistory.isCancelSub -> { + val cancelledWorkbookSubscriptionHistory = CancelledWorkbookSubscriptionHistory(workbookSubscriptionHistory) + val lastDay = CountWorkbookMappedArticlesQuery(subTargetWorkbookId).let { + subscriptionDao.countWorkbookMappedArticles(it) + } ?: throw NotFoundException("workbook.notfound.id") + + if (cancelledWorkbookSubscriptionHistory.isSubEnd(lastDay)) { + /** 이미 구독이 종료된 경우 */ throw SubscribeIllegalArgumentException("subscribe.state.end") + } else { + /** 재구독인 경우 */ + subscriptionDao.reSubscribeWorkbookSubscription(command) } - /** 재구독 */ - subscriptionDao.reSubscribeWorkbookSubscription(command) } /** 구독 중인 경우 */ diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/model/CancelledWorkbookSubscriptionHistory.kt b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/model/CancelledWorkbookSubscriptionHistory.kt new file mode 100644 index 000000000..024c46790 --- /dev/null +++ b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/model/CancelledWorkbookSubscriptionHistory.kt @@ -0,0 +1,21 @@ +package com.few.api.domain.subscription.usecase.model + +class CancelledWorkbookSubscriptionHistory( + workbookSubscriptionHistory: WorkbookSubscriptionHistory, +) : WorkbookSubscriptionHistory( + workbookSubscriptionHistory +) { + + init { + require(isCancelSub) { + "CanceledWorkbookSubscriptionHistory is not for active subscription." + } + } + + /** + * 구독이 종료되었는지 여부 확인 + */ + fun isSubEnd(lastDay: Int): Boolean { + return (lastDay <= workbookSubscriptionStatus!!.day) + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/model/WorkbookSubscriptionHistory.kt b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/model/WorkbookSubscriptionHistory.kt new file mode 100644 index 000000000..f32f975aa --- /dev/null +++ b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/model/WorkbookSubscriptionHistory.kt @@ -0,0 +1,37 @@ +package com.few.api.domain.subscription.usecase.model + +open class WorkbookSubscriptionHistory( + val isNew: Boolean, + protected val workbookSubscriptionStatus: WorkbookSubscriptionStatus? = null, +) { + + init { + if (isNew) { + require(workbookSubscriptionStatus == null) { + "If new subscription, workbookSubscriptionStatus should be null." + } + } else { + require(workbookSubscriptionStatus != null) { + "If not new subscription, workbookSubscriptionStatus should not be null." + } + } + } + + constructor(workbookSubscriptionHistory: WorkbookSubscriptionHistory) : this( + workbookSubscriptionHistory.isNew, + workbookSubscriptionHistory.workbookSubscriptionStatus + ) + + /** + * 이전 구독 히스토리가 존재하고, 현재 구독이 취소된 경우 + */ + val isCancelSub: Boolean + get() { + return !isNew && !workbookSubscriptionStatus!!.isActiveSub + } + + val subDay: Int + get() { + return workbookSubscriptionStatus?.day ?: 1 + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/model/WorkbookSubscriptionStatus.kt b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/model/WorkbookSubscriptionStatus.kt new file mode 100644 index 000000000..ee78b3e11 --- /dev/null +++ b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/model/WorkbookSubscriptionStatus.kt @@ -0,0 +1,7 @@ +package com.few.api.domain.subscription.usecase.model + +class WorkbookSubscriptionStatus( + val workbookId: Long, + val isActiveSub: Boolean, + val day: Int, +) \ No newline at end of file diff --git a/api/src/test/kotlin/com/few/api/domain/subscription/usecase/model/WorkbookSubscriptionHistoryTest.kt b/api/src/test/kotlin/com/few/api/domain/subscription/usecase/model/WorkbookSubscriptionHistoryTest.kt new file mode 100644 index 000000000..322b1d3f5 --- /dev/null +++ b/api/src/test/kotlin/com/few/api/domain/subscription/usecase/model/WorkbookSubscriptionHistoryTest.kt @@ -0,0 +1,179 @@ +package com.few.api.domain.subscription.usecase.model + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test + +class WorkbookSubscriptionHistoryTest { + + @Test + fun `새로 생성된 구독인데 구독 상태가 존재하는 경우`() { + // given & when & then + assertThrows(IllegalArgumentException::class.java) { + WorkbookSubscriptionHistory( + isNew = true, + workbookSubscriptionStatus = WorkbookSubscriptionStatus( + workbookId = 1, + isActiveSub = true, + day = 1 + ) + ) + } + } + + @Test + fun `새로 생성된 구독이 아닌데 구독 상태가 존재하지 않는 경우`() { + // given & when & then + assertThrows(IllegalArgumentException::class.java) { + WorkbookSubscriptionHistory( + isNew = false + ) + } + } + + @Test + fun `새로 생성된 구독이 아니고 구독 상태가 취소된 경우`() { + // given + val workbookSubscriptionHistory = WorkbookSubscriptionHistory( + isNew = false, + workbookSubscriptionStatus = WorkbookSubscriptionStatus( + workbookId = 1, + isActiveSub = false, + day = 1 + ) + ) + + // when + val isCancel = workbookSubscriptionHistory.isCancelSub + + // then + assertTrue(isCancel) + } + + @Test + fun `새로 생성된 구독이 아니고 구독 상태가 취소되지 않은 경우`() { + // given + val workbookSubscriptionHistory = WorkbookSubscriptionHistory( + isNew = false, + workbookSubscriptionStatus = WorkbookSubscriptionStatus( + workbookId = 1, + isActiveSub = true, + day = 1 + ) + ) + + // when + val isCancel = workbookSubscriptionHistory.isCancelSub + + // then + assertFalse(isCancel) + } + + @Test + fun `새로 생성된 구독인 경우 구독 날짜`() { + // given + val workbookSubscriptionHistory = WorkbookSubscriptionHistory( + isNew = true + ) + + // when + val subDay = workbookSubscriptionHistory.subDay + + // then + assertEquals(1, subDay) + } + + @Test + fun `새로 생성된 구독이 아닌 경우 구독 날짜`() { + // given + val workbookSubscriptionHistory = WorkbookSubscriptionHistory( + isNew = false, + workbookSubscriptionStatus = WorkbookSubscriptionStatus( + workbookId = 1, + isActiveSub = true, + day = 2 + ) + ) + + // when + val subDay = workbookSubscriptionHistory.subDay + + // then + assertEquals(2, subDay) + } + + @Nested + inner class CancelledWorkbookSubscriptionHistoryTest { + + @Test + fun `구독 취소된 히스토리가 취소되지 않은 구독 히스토리로 생성되는 경우`() { + // given + val workbookSubscriptionHistory = WorkbookSubscriptionHistory( + isNew = false, + workbookSubscriptionStatus = WorkbookSubscriptionStatus( + workbookId = 1, + isActiveSub = true, + day = 1 + ) + ) + + // when & then + assertThrows(IllegalArgumentException::class.java) { + CancelledWorkbookSubscriptionHistory(workbookSubscriptionHistory) + } + } + + @Test + fun `구독 취소된 히스토리가 새로운 구독 히스토리로 생성되는 경우`() { + // given + val workbookSubscriptionHistory = WorkbookSubscriptionHistory( + isNew = true + ) + + // when & then + assertThrows(IllegalArgumentException::class.java) { + CancelledWorkbookSubscriptionHistory(workbookSubscriptionHistory) + } + } + + @Test + fun `구독 취소된 히스토리가 종료된 경우`() { + // given + val workbookSubscriptionHistory = WorkbookSubscriptionHistory( + isNew = false, + workbookSubscriptionStatus = WorkbookSubscriptionStatus( + workbookId = 1, + isActiveSub = false, + day = 1 + ) + ) + + // when + val cancelledWorkbookSubscriptionHistory = CancelledWorkbookSubscriptionHistory(workbookSubscriptionHistory) + val isSubEnd = cancelledWorkbookSubscriptionHistory.isSubEnd(1) + + // then + assertTrue(isSubEnd) + } + + @Test + fun `구독 취소된 히스토리가 종료되지 않은 경우`() { + // given + val workbookSubscriptionHistory = WorkbookSubscriptionHistory( + isNew = false, + workbookSubscriptionStatus = WorkbookSubscriptionStatus( + workbookId = 1, + isActiveSub = false, + day = 1 + ) + ) + + // when + val cancelledWorkbookSubscriptionHistory = CancelledWorkbookSubscriptionHistory(workbookSubscriptionHistory) + val isSubEnd = cancelledWorkbookSubscriptionHistory.isSubEnd(2) + + // then + assertFalse(isSubEnd) + } + } +} \ No newline at end of file From ca25f1cff1729ba9041d5a64c07b1df3dda7e8d9 Mon Sep 17 00:00:00 2001 From: belljun3395 <195850@jnu.ac.kr> Date: Wed, 28 Aug 2024 10:09:59 +0900 Subject: [PATCH 7/8] =?UTF-8?q?[Fix/#367]=20#363=20=EB=A8=B8=EC=A7=80?= =?UTF-8?q?=ED=95=98=EB=A9=B0=20=EB=B9=8C=EB=93=9C=EA=B0=80=20=EB=8F=99?= =?UTF-8?q?=EC=9E=91=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=ED=95=B4=EA=B2=B0=20(#368)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/subscription/usecase/SubscribeWorkbookUseCase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/SubscribeWorkbookUseCase.kt b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/SubscribeWorkbookUseCase.kt index f3a8ce912..209b12a22 100644 --- a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/SubscribeWorkbookUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/SubscribeWorkbookUseCase.kt @@ -86,7 +86,7 @@ class SubscribeWorkbookUseCase( WorkbookSubscriptionEvent( workbookId = subTargetWorkbookId, memberId = memberId, - articleDayCol = subscriptionStatus?.day ?: 1 + articleDayCol = workbookSubscriptionHistory.subDay ) ) } From 699e8b0da31d613d0feb388e073efc757401325e Mon Sep 17 00:00:00 2001 From: belljun3395 <195850@jnu.ac.kr> Date: Wed, 28 Aug 2024 21:59:10 +0900 Subject: [PATCH 8/8] =?UTF-8?q?[Refactor/#369]=20=EC=9D=B4=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20=EB=B0=9C=EC=8B=A0=EC=9E=90=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?(#370)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 이메일 발신자 설정 * chore: 파라미터 오타 수정 form -> from --- email/src/main/kotlin/com/few/email/sender/EmailSender.kt | 2 +- .../few/email/sender/provider/AwsSESEmailSendProvider.kt | 6 +++--- .../com/few/email/sender/provider/EmailSendProvider.kt | 2 +- .../com/few/email/sender/provider/JavaEmailSendProvider.kt | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/email/src/main/kotlin/com/few/email/sender/EmailSender.kt b/email/src/main/kotlin/com/few/email/sender/EmailSender.kt index bca896a51..3c7b13890 100644 --- a/email/src/main/kotlin/com/few/email/sender/EmailSender.kt +++ b/email/src/main/kotlin/com/few/email/sender/EmailSender.kt @@ -14,7 +14,7 @@ abstract class EmailSender>( val to = args.to val subject = args.subject val message = getHtml(args) - emailSendProvider.sendEmail(from, to, subject, message) + emailSendProvider.sendEmail("FEW Letter <$from>", to, subject, message) } abstract fun getHtml(args: T): String diff --git a/email/src/main/kotlin/com/few/email/sender/provider/AwsSESEmailSendProvider.kt b/email/src/main/kotlin/com/few/email/sender/provider/AwsSESEmailSendProvider.kt index 4eb34f8d0..5bbdaefc0 100644 --- a/email/src/main/kotlin/com/few/email/sender/provider/AwsSESEmailSendProvider.kt +++ b/email/src/main/kotlin/com/few/email/sender/provider/AwsSESEmailSendProvider.kt @@ -19,14 +19,14 @@ class AwsSESEmailSendProvider( private const val UTF_8 = "utf-8" } - override fun sendEmail(form: String, to: String, subject: String, message: String) { + override fun sendEmail(from: String, to: String, subject: String, message: String) { val destination = Destination().withToAddresses(to) val sendMessage = Message() .withSubject(Content().withCharset(UTF_8).withData(subject)) .withBody(Body().withHtml(Content().withCharset(UTF_8).withData(message))) val sendEmailRequest = SendEmailRequest() - .withSource(form) + .withSource(from) .withDestination(destination) .withMessage(sendMessage) .withConfigurationSetName("few-configuration-set") @@ -41,7 +41,7 @@ class AwsSESEmailSendProvider( log.info { "Sending email using JavaMailSender." } - javaEmailSendProvider.sendEmail(form, to, subject, message) + javaEmailSendProvider.sendEmail(from, to, subject, message) }.onFailure { log.error { "Failed to send email using JavaMailSender." diff --git a/email/src/main/kotlin/com/few/email/sender/provider/EmailSendProvider.kt b/email/src/main/kotlin/com/few/email/sender/provider/EmailSendProvider.kt index 109c86422..72ac099c8 100644 --- a/email/src/main/kotlin/com/few/email/sender/provider/EmailSendProvider.kt +++ b/email/src/main/kotlin/com/few/email/sender/provider/EmailSendProvider.kt @@ -1,5 +1,5 @@ package com.few.email.sender.provider interface EmailSendProvider { - fun sendEmail(form: String, to: String, subject: String, message: String) + fun sendEmail(from: String, to: String, subject: String, message: String) } \ No newline at end of file diff --git a/email/src/main/kotlin/com/few/email/sender/provider/JavaEmailSendProvider.kt b/email/src/main/kotlin/com/few/email/sender/provider/JavaEmailSendProvider.kt index 25b4c0839..0309d3bdc 100644 --- a/email/src/main/kotlin/com/few/email/sender/provider/JavaEmailSendProvider.kt +++ b/email/src/main/kotlin/com/few/email/sender/provider/JavaEmailSendProvider.kt @@ -13,11 +13,11 @@ class JavaEmailSendProvider( companion object { private const val UTF_8 = "utf-8" } - override fun sendEmail(form: String, to: String, subject: String, message: String) { + override fun sendEmail(from: String, to: String, subject: String, message: String) { val sendMessage: MimeMessage = emailSender.createMimeMessage() val helper = MimeMessageHelper(sendMessage, UTF_8) try { - helper.setFrom(form) + helper.setFrom(from) helper.setTo(to) helper.setSubject(subject) helper.setText(message, true)