From 4c8c4ce35ae0da9b250159e4679f342d29abe77d Mon Sep 17 00:00:00 2001
From: belljun3395 <195850@jnu.ac.kr>
Date: Fri, 19 Jul 2024 14:19:35 +0900
Subject: [PATCH 1/2] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?=
=?UTF-8?q?=EC=A0=9C=EB=AA=A9=EC=9D=B4=20=EA=B8=B4=20=EA=B2=BD=EC=9A=B0=20?=
=?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0=ED=95=9C=20=ED=85=9C?=
=?UTF-8?q?=ED=94=8C=EB=A6=BF=20=EB=B0=98=EC=98=81=20(#219)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/main/resources/templates/article.html | 44 ++++++++++---------
1 file changed, 24 insertions(+), 20 deletions(-)
diff --git a/email/src/main/resources/templates/article.html b/email/src/main/resources/templates/article.html
index 265329a8c..c5739e993 100644
--- a/email/src/main/resources/templates/article.html
+++ b/email/src/main/resources/templates/article.html
@@ -17,7 +17,7 @@
BlinkMacSystemFont, system-ui, Roboto, 'Helvetica Neue', 'Segoe UI',
'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic',
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', sans-serif;
- background-color: #ffffff;
+ background-color: #ffffff;
"
>
@@ -35,7 +40,7 @@
|
@@ -125,27 +130,26 @@
- 작가
+ 작가
+ >
Date: Fri, 19 Jul 2024 16:21:36 +0900
Subject: [PATCH 2/2] =?UTF-8?q?[Feat/#215]=20article=5Fview=5Fhis=20?=
=?UTF-8?q?=ED=85=8C=EC=9D=B4=EB=B8=94=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?=
=?UTF-8?q?=EC=A1=B0=ED=9A=8C=EC=88=98=20=EC=9D=91=EB=8B=B5=20=EC=B6=94?=
=?UTF-8?q?=EA=B0=80=20(#216)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat: flyway V1.00.0.7 배포(ARTICLE_VIEW_HIS 테이블 추가)
* feat: article view history 저장 로직 추가
* feat: article 조회시 views 필드 추가
* test: article 조회수 추가 관련 test 코드 수정
* feat: ARTICLE_VIEW_HIS 테이블 인덱스 추가(article_mst_id 컬럼)
* fix: 인덱스 테이블 명 수정
* refactor: DAO 메소드 명 수정
* refactor: 아티클 뷰와 아티클 도메인 통합
* refactor: DAO 빈 지정 stereotype 변경(@Component -> @Repository)
* refactor: 조회수 결정에 대한 로직을 DAO -> UC로 이동
* chore: member Id 사용에 대한 TODO 주석 추가
* feat: 아티클 조회 기록 Insert과정을 다른 쓰레드에서 수행하도록 변경
* fix: add ArticleViewHisAsyncEvent
* test: ReadArticleUseCaseTest 수정반영
* refactor: SubscribeWorkbookUseCaseTest 에서 print 삭제 및 log 적용
* refactor: rename ArticleViewHisAsyncHandler
* test: application-test.yaml에 database async thread pool 설정 추가
* fix: 테스트 통과하지 못하는 문제 해결
* feat: 아티클 조회응답 바디 views 필드 추가
---------
Co-authored-by: belljun3395 <195850@jnu.ac.kr>
---
.gitignore | 7 ++++
api-repo/.gitignore | 2 -
.../api/repo/dao/article/ArticleViewHisDao.kt | 31 +++++++++++++++
.../article/command/ArticleViewHisCommand.kt | 6 +++
.../article/query/ArticleViewHisCountQuery.kt | 5 +++
.../few/api/repo/dao/problem/ProblemDao.kt | 4 +-
.../api/repo/dao/problem/SubmitHistoryDao.kt | 4 +-
.../repo/dao/subscription/SubscriptionDao.kt | 4 +-
api/.gitignore | 2 -
.../config/DatabaseAccessThreadPoolConfig.kt | 38 +++++++++++++++++++
.../handler/ArticleViewHisAsyncHandler.kt | 27 +++++++++++++
.../article/usecase/ReadArticleUseCase.kt | 19 ++++++++--
.../usecase/dto/ReadArticleUseCaseIn.kt | 1 +
.../usecase/dto/ReadArticleUseCaseOut.kt | 1 +
.../controller/article/ArticleController.kt | 2 +-
.../article/response/ReadArticleResponse.kt | 4 +-
.../resources/application-client-local.yml | 8 ++++
.../main/resources/application-client-prd.yml | 8 ++++
.../article/usecase/ReadArticleUseCaseTest.kt | 28 +++++++++++---
.../usecase/SubscribeWorkbookUseCaseTest.kt | 8 ++--
.../article/ArticleControllerTest.kt | 10 +++--
api/src/test/resources/application-test.yml | 8 ++++
batch/.gitignore | 2 -
data/.gitignore | 2 -
.../V1.00.0.7__article_view_his_table.sql | 13 +++++++
email/.gitignore | 2 -
storage/.gitignore | 2 -
27 files changed, 213 insertions(+), 35 deletions(-)
delete mode 100644 api-repo/.gitignore
create mode 100644 api-repo/src/main/kotlin/com/few/api/repo/dao/article/ArticleViewHisDao.kt
create mode 100644 api-repo/src/main/kotlin/com/few/api/repo/dao/article/command/ArticleViewHisCommand.kt
create mode 100644 api-repo/src/main/kotlin/com/few/api/repo/dao/article/query/ArticleViewHisCountQuery.kt
delete mode 100644 api/.gitignore
create mode 100644 api/src/main/kotlin/com/few/api/config/DatabaseAccessThreadPoolConfig.kt
create mode 100644 api/src/main/kotlin/com/few/api/domain/article/handler/ArticleViewHisAsyncHandler.kt
delete mode 100644 batch/.gitignore
delete mode 100644 data/.gitignore
create mode 100644 data/db/migration/entity/V1.00.0.7__article_view_his_table.sql
delete mode 100644 email/.gitignore
delete mode 100644 storage/.gitignore
diff --git a/.gitignore b/.gitignore
index 965c592e9..58041a262 100644
--- a/.gitignore
+++ b/.gitignore
@@ -95,3 +95,10 @@ Footer
*.log
*.tmp
+# DB schema migration path (except data module)
+data/src/main/resources/**/*.sql
+api/**/*.sql
+api-repo/**/*.sql
+batch/**/*.sql
+email/**/*.sql
+storage/**/*.sql
diff --git a/api-repo/.gitignore b/api-repo/.gitignore
deleted file mode 100644
index aabab5468..000000000
--- a/api-repo/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-# DB schema migration path
-**/*.sql
diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/article/ArticleViewHisDao.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/ArticleViewHisDao.kt
new file mode 100644
index 000000000..77bbab5b7
--- /dev/null
+++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/ArticleViewHisDao.kt
@@ -0,0 +1,31 @@
+package com.few.api.repo.dao.article
+
+import com.few.api.repo.dao.article.command.ArticleViewHisCommand
+import com.few.api.repo.dao.article.query.ArticleViewHisCountQuery
+import jooq.jooq_dsl.tables.ArticleViewHis
+import org.jooq.DSLContext
+import org.springframework.stereotype.Repository
+
+@Repository
+class ArticleViewHisDao(
+ private val dslContext: DSLContext,
+) {
+
+ fun insertArticleViewHis(command: ArticleViewHisCommand) {
+ dslContext.insertInto(
+ ArticleViewHis.ARTICLE_VIEW_HIS,
+ ArticleViewHis.ARTICLE_VIEW_HIS.ARTICLE_MST_ID,
+ ArticleViewHis.ARTICLE_VIEW_HIS.MEMBER_ID
+ ).values(
+ command.articleId,
+ command.memberId
+ ).execute()
+ }
+
+ fun countArticleViews(query: ArticleViewHisCountQuery): Long? {
+ return dslContext.selectCount()
+ .from(ArticleViewHis.ARTICLE_VIEW_HIS)
+ .where(ArticleViewHis.ARTICLE_VIEW_HIS.ARTICLE_MST_ID.eq(query.articleId))
+ .fetchOne(0, Long::class.java)
+ }
+}
\ No newline at end of file
diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/article/command/ArticleViewHisCommand.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/command/ArticleViewHisCommand.kt
new file mode 100644
index 000000000..e986fe85f
--- /dev/null
+++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/command/ArticleViewHisCommand.kt
@@ -0,0 +1,6 @@
+package com.few.api.repo.dao.article.command
+
+data class ArticleViewHisCommand(
+ val articleId: Long,
+ val memberId: Long,
+)
\ No newline at end of file
diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/article/query/ArticleViewHisCountQuery.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/query/ArticleViewHisCountQuery.kt
new file mode 100644
index 000000000..50dfec413
--- /dev/null
+++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/query/ArticleViewHisCountQuery.kt
@@ -0,0 +1,5 @@
+package com.few.api.repo.dao.article.query
+
+data class ArticleViewHisCountQuery(
+ val articleId: Long,
+)
\ No newline at end of file
diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/problem/ProblemDao.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/problem/ProblemDao.kt
index 8d6a0b1ea..63d2f4d90 100644
--- a/api-repo/src/main/kotlin/com/few/api/repo/dao/problem/ProblemDao.kt
+++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/problem/ProblemDao.kt
@@ -12,9 +12,9 @@ import jooq.jooq_dsl.tables.Problem
import org.jooq.DSLContext
import org.jooq.JSON
import org.jooq.impl.DSL
-import org.springframework.stereotype.Component
+import org.springframework.stereotype.Repository
-@Component
+@Repository
class ProblemDao(
private val dslContext: DSLContext,
private val contentsJsonMapper: ContentsJsonMapper,
diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/problem/SubmitHistoryDao.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/problem/SubmitHistoryDao.kt
index 2bcde4740..7336a95b1 100644
--- a/api-repo/src/main/kotlin/com/few/api/repo/dao/problem/SubmitHistoryDao.kt
+++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/problem/SubmitHistoryDao.kt
@@ -3,9 +3,9 @@ package com.few.api.repo.dao.problem
import com.few.api.repo.dao.problem.command.InsertSubmitHistoryCommand
import jooq.jooq_dsl.Tables.SUBMIT_HISTORY
import org.jooq.DSLContext
-import org.springframework.stereotype.Component
+import org.springframework.stereotype.Repository
-@Component
+@Repository
class SubmitHistoryDao(
private val dslContext: DSLContext,
) {
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 ad6faecba..c0c942e73 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
@@ -10,10 +10,10 @@ import com.few.api.repo.dao.subscription.record.CountAllSubscriptionStatusRecord
import jooq.jooq_dsl.Tables.MAPPING_WORKBOOK_ARTICLE
import jooq.jooq_dsl.Tables.SUBSCRIPTION
import org.jooq.DSLContext
-import org.springframework.stereotype.Component
+import org.springframework.stereotype.Repository
import java.time.LocalDateTime
-@Component
+@Repository
class SubscriptionDao(
private val dslContext: DSLContext,
) {
diff --git a/api/.gitignore b/api/.gitignore
deleted file mode 100644
index aabab5468..000000000
--- a/api/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-# DB schema migration path
-**/*.sql
diff --git a/api/src/main/kotlin/com/few/api/config/DatabaseAccessThreadPoolConfig.kt b/api/src/main/kotlin/com/few/api/config/DatabaseAccessThreadPoolConfig.kt
new file mode 100644
index 000000000..69cb08dcf
--- /dev/null
+++ b/api/src/main/kotlin/com/few/api/config/DatabaseAccessThreadPoolConfig.kt
@@ -0,0 +1,38 @@
+package com.few.api.config
+
+import com.few.api.config.properties.ThreadPoolProperties
+import io.github.oshai.kotlinlogging.KotlinLogging
+import org.springframework.boot.context.properties.ConfigurationProperties
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
+
+@Configuration
+class DatabaseAccessThreadPoolConfig {
+ private val log = KotlinLogging.logger {}
+
+ companion object {
+ const val DATABASE_ACCESS_POOL = "database-task-"
+ }
+
+ @Bean
+ @ConfigurationProperties(prefix = "database.thread-pool")
+ fun databaseAccessThreadPoolProperties(): ThreadPoolProperties {
+ return ThreadPoolProperties()
+ }
+
+ @Bean(DATABASE_ACCESS_POOL)
+ fun databaseAccessThreadPool() = ThreadPoolTaskExecutor().apply {
+ val properties = databaseAccessThreadPoolProperties()
+ corePoolSize = properties.getCorePoolSize()
+ maxPoolSize = properties.getMaxPoolSize()
+ queueCapacity = properties.getQueueCapacity()
+ setWaitForTasksToCompleteOnShutdown(properties.getWaitForTasksToCompleteOnShutdown())
+ setAwaitTerminationSeconds(properties.getAwaitTerminationSeconds())
+ setThreadNamePrefix("databaseAccessThreadPool-")
+ setRejectedExecutionHandler { r, _ ->
+ log.warn { "Database Access Task Rejected: $r" }
+ }
+ initialize()
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/kotlin/com/few/api/domain/article/handler/ArticleViewHisAsyncHandler.kt b/api/src/main/kotlin/com/few/api/domain/article/handler/ArticleViewHisAsyncHandler.kt
new file mode 100644
index 000000000..c8a22ac3f
--- /dev/null
+++ b/api/src/main/kotlin/com/few/api/domain/article/handler/ArticleViewHisAsyncHandler.kt
@@ -0,0 +1,27 @@
+package com.few.api.domain.article.handler
+
+import com.few.api.config.DatabaseAccessThreadPoolConfig.Companion.DATABASE_ACCESS_POOL
+import com.few.api.repo.dao.article.ArticleViewHisDao
+import com.few.api.repo.dao.article.command.ArticleViewHisCommand
+import io.github.oshai.kotlinlogging.KotlinLogging
+import org.springframework.scheduling.annotation.Async
+import org.springframework.stereotype.Component
+import org.springframework.transaction.annotation.Transactional
+
+@Component
+class ArticleViewHisAsyncHandler(
+ private val articleViewHisDao: ArticleViewHisDao,
+) {
+ private val log = KotlinLogging.logger {}
+
+ @Async(value = DATABASE_ACCESS_POOL)
+ @Transactional
+ fun addArticleViewHis(articleId: Long, memberId: Long) {
+ try {
+ articleViewHisDao.insertArticleViewHis(ArticleViewHisCommand(articleId, memberId))
+ log.debug { "Successfully inserted article view history for articleId: $articleId and memberId: $memberId" }
+ } catch (e: Exception) {
+ log.error { "Failed to insert article view history for articleId: $articleId and memberId: $memberId" }
+ }
+ }
+}
\ No newline at end of file
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 78d05a117..fc80437e4 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,5 +1,6 @@
package com.few.api.domain.article.usecase
+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
@@ -9,6 +10,8 @@ import com.few.api.domain.article.service.dto.BrowseArticleProblemIdsInDto
import com.few.api.domain.article.service.dto.ReadWriterRecordInDto
import com.few.api.exception.common.NotFoundException
import com.few.api.repo.dao.article.ArticleDao
+import com.few.api.repo.dao.article.ArticleViewHisDao
+import com.few.api.repo.dao.article.query.ArticleViewHisCountQuery
import com.few.api.repo.dao.article.query.SelectArticleRecordQuery
import com.few.data.common.code.CategoryType
import org.springframework.stereotype.Component
@@ -19,6 +22,8 @@ class ReadArticleUseCase(
private val articleDao: ArticleDao,
private val readArticleWriterRecordService: ReadArticleWriterRecordService,
private val browseArticleProblemsService: BrowseArticleProblemsService,
+ private val articleViewHisDao: ArticleViewHisDao,
+ private val articleViewHisAsyncHandler: ArticleViewHisAsyncHandler,
) {
@Transactional(readOnly = true)
@@ -31,9 +36,14 @@ class ReadArticleUseCase(
readArticleWriterRecordService.execute(query) ?: throw NotFoundException("writer.notfound.id")
}
- val problemIds = BrowseArticleProblemIdsInDto(articleRecord.articleId).let { query: BrowseArticleProblemIdsInDto ->
- browseArticleProblemsService.execute(query)
- }
+ val problemIds =
+ BrowseArticleProblemIdsInDto(articleRecord.articleId).let { query: BrowseArticleProblemIdsInDto ->
+ browseArticleProblemsService.execute(query)
+ }
+
+ val views = (articleViewHisDao.countArticleViews(ArticleViewHisCountQuery(useCaseIn.articleId)) ?: 0L) + 1L
+
+ articleViewHisAsyncHandler.addArticleViewHis(useCaseIn.articleId, useCaseIn.memberId)
return ReadArticleUseCaseOut(
id = articleRecord.articleId,
@@ -46,7 +56,8 @@ class ReadArticleUseCase(
content = articleRecord.content,
problemIds = problemIds.problemIds,
category = CategoryType.convertToDisplayName(articleRecord.category),
- createdAt = articleRecord.createdAt
+ createdAt = articleRecord.createdAt,
+ views = views
)
}
}
\ No newline at end of file
diff --git a/api/src/main/kotlin/com/few/api/domain/article/usecase/dto/ReadArticleUseCaseIn.kt b/api/src/main/kotlin/com/few/api/domain/article/usecase/dto/ReadArticleUseCaseIn.kt
index c380b2987..6eb1b2df6 100644
--- a/api/src/main/kotlin/com/few/api/domain/article/usecase/dto/ReadArticleUseCaseIn.kt
+++ b/api/src/main/kotlin/com/few/api/domain/article/usecase/dto/ReadArticleUseCaseIn.kt
@@ -2,4 +2,5 @@ package com.few.api.domain.article.usecase.dto
data class ReadArticleUseCaseIn(
val articleId: Long,
+ val memberId: Long,
)
\ No newline at end of file
diff --git a/api/src/main/kotlin/com/few/api/domain/article/usecase/dto/ReadArticleUseCaseOut.kt b/api/src/main/kotlin/com/few/api/domain/article/usecase/dto/ReadArticleUseCaseOut.kt
index 10f880674..7425affa2 100644
--- a/api/src/main/kotlin/com/few/api/domain/article/usecase/dto/ReadArticleUseCaseOut.kt
+++ b/api/src/main/kotlin/com/few/api/domain/article/usecase/dto/ReadArticleUseCaseOut.kt
@@ -11,6 +11,7 @@ data class ReadArticleUseCaseOut(
val problemIds: List,
val category: String,
val createdAt: LocalDateTime,
+ val views: Long,
)
data class 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 c8fe072e1..0eeff9c42 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
@@ -27,7 +27,7 @@ class ArticleController(
@Min(value = 1, message = "{min.id}")
articleId: Long,
): ApiResponse> {
- val useCaseOut = ReadArticleUseCaseIn(articleId).let { useCaseIn: ReadArticleUseCaseIn ->
+ val useCaseOut = ReadArticleUseCaseIn(articleId = articleId, memberId = 0L).let { useCaseIn: ReadArticleUseCaseIn -> //TODO: membberId검토
readArticleUseCase.execute(useCaseIn)
}
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 95ea9cfc5..3d6dd1e2c 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
@@ -12,6 +12,7 @@ data class ReadArticleResponse(
val problemIds: List,
val category: String,
val createdAt: LocalDateTime,
+ val views: Long,
) {
constructor(
useCaseOut: ReadArticleUseCaseOut,
@@ -26,7 +27,8 @@ data class ReadArticleResponse(
content = useCaseOut.content,
problemIds = useCaseOut.problemIds,
category = useCaseOut.category,
- createdAt = useCaseOut.createdAt
+ createdAt = useCaseOut.createdAt,
+ views = useCaseOut.views
)
}
diff --git a/api/src/main/resources/application-client-local.yml b/api/src/main/resources/application-client-local.yml
index 6d13c3012..332cf3815 100644
--- a/api/src/main/resources/application-client-local.yml
+++ b/api/src/main/resources/application-client-local.yml
@@ -13,3 +13,11 @@ discord:
queue-capacity: 30
wait-for-tasks-to-complete-on-shutdown: true
await-termination-seconds: 60
+
+database:
+ thread-pool:
+ core-pool-size: 10
+ max-pool-size: 30
+ queue-capacity: 70
+ wait-for-tasks-to-complete-on-shutdown: true
+ await-termination-seconds: 60
diff --git a/api/src/main/resources/application-client-prd.yml b/api/src/main/resources/application-client-prd.yml
index 2b009b3ce..871d97be1 100644
--- a/api/src/main/resources/application-client-prd.yml
+++ b/api/src/main/resources/application-client-prd.yml
@@ -13,3 +13,11 @@ discord:
queue-capacity: ${DISCORD_THREAD_POOL_QUEUE_CAPACITY:30}
wait-for-tasks-to-complete-on-shutdown: ${DISCORD_THREAD_POOL_WAIT_FOR_TASKS_TO_COMPLETE_ON_SHUTDOWN:true}
await-termination-seconds: ${DISCORD_THREAD_POOL_AWAIT_TERMINATION_SECONDS:60}
+
+database:
+ thread-pool:
+ core-pool-size: 10
+ max-pool-size: 30
+ queue-capacity: 70
+ wait-for-tasks-to-complete-on-shutdown: true
+ await-termination-seconds: 60
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 914fa4812..f333538f4 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,34 +1,46 @@
package com.few.api.domain.article.usecase
+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
import com.few.api.domain.article.service.dto.ReadWriterOutDto
import com.few.api.domain.article.usecase.dto.ReadArticleUseCaseIn
import com.few.api.repo.dao.article.ArticleDao
+import com.few.api.repo.dao.article.ArticleViewHisDao
import com.few.api.repo.dao.article.record.SelectArticleRecord
+import io.github.oshai.kotlinlogging.KotlinLogging
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.BehaviorSpec
-import io.mockk.every
-import io.mockk.mockk
-import io.mockk.verify
+import io.mockk.*
import java.net.URL
import java.time.LocalDateTime
class ReadArticleUseCaseTest : BehaviorSpec({
+ val log = KotlinLogging.logger {}
lateinit var articleDao: ArticleDao
lateinit var readArticleWriterRecordService: ReadArticleWriterRecordService
lateinit var browseArticleProblemsService: BrowseArticleProblemsService
lateinit var useCase: ReadArticleUseCase
- val useCaseIn = ReadArticleUseCaseIn(articleId = 1L)
+ lateinit var articleViewHisDao: ArticleViewHisDao
+ lateinit var articleViewHisAsyncHandler: ArticleViewHisAsyncHandler
+ val useCaseIn = ReadArticleUseCaseIn(articleId = 1L, memberId = 1L)
beforeContainer {
articleDao = mockk()
readArticleWriterRecordService = mockk()
browseArticleProblemsService = mockk()
- useCase = ReadArticleUseCase(articleDao, readArticleWriterRecordService, browseArticleProblemsService)
+ articleViewHisDao = mockk()
+ articleViewHisAsyncHandler = mockk()
+ useCase = ReadArticleUseCase(
+ articleDao,
+ readArticleWriterRecordService,
+ browseArticleProblemsService,
+ articleViewHisDao,
+ articleViewHisAsyncHandler
+ )
}
given("아티클 조회 요청이 온 상황에서") {
@@ -52,6 +64,10 @@ class ReadArticleUseCaseTest : BehaviorSpec({
every { articleDao.selectArticleRecord(any()) } returns record
every { readArticleWriterRecordService.execute(any()) } returns writerSvcOutDto
every { browseArticleProblemsService.execute(any()) } returns probSvcOutDto
+ every { articleViewHisDao.countArticleViews(any()) } returns 1L
+ every { articleViewHisAsyncHandler.addArticleViewHis(any(), any()) } answers {
+ log.debug { "Inserting article view history asynchronously" }
+ }
then("아티클이 정상 조회된다") {
useCase.execute(useCaseIn)
@@ -59,6 +75,8 @@ class ReadArticleUseCaseTest : BehaviorSpec({
verify(exactly = 1) { articleDao.selectArticleRecord(any()) }
verify(exactly = 1) { readArticleWriterRecordService.execute(any()) }
verify(exactly = 1) { browseArticleProblemsService.execute(any()) }
+ verify(exactly = 1) { articleViewHisDao.countArticleViews(any()) }
+ verify(exactly = 1) { articleViewHisAsyncHandler.addArticleViewHis(any(), any()) }
}
}
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 cb781f828..0d2049086 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
@@ -6,6 +6,7 @@ import com.few.api.domain.subscription.service.dto.MemberIdOutDto
import com.few.api.domain.subscription.usecase.dto.SubscribeWorkbookUseCaseIn
import com.few.api.repo.dao.subscription.SubscriptionDao
import com.few.api.repo.dao.subscription.record.WorkbookSubscriptionStatus
+import io.github.oshai.kotlinlogging.KotlinLogging
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.BehaviorSpec
import io.mockk.every
@@ -16,6 +17,7 @@ import io.mockk.Runs
import org.springframework.context.ApplicationEventPublisher
class SubscribeWorkbookUseCaseTest : BehaviorSpec({
+ val log = KotlinLogging.logger {}
lateinit var subscriptionDao: SubscriptionDao
lateinit var memberService: MemberService
@@ -41,7 +43,7 @@ class SubscribeWorkbookUseCaseTest : BehaviorSpec({
every { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } returns null
every { subscriptionDao.insertWorkbookSubscription(any()) } just Runs
every { applicationEventPublisher.publishEvent(event) } answers {
- println("Mocking applicationEventPublisher.publishEvent(any()) was called")
+ log.debug { "Mocking applicationEventPublisher.publishEvent(any()) was called" }
}
then("신규 구독을 추가한다") {
@@ -68,7 +70,7 @@ class SubscribeWorkbookUseCaseTest : BehaviorSpec({
every { subscriptionDao.countWorkbookMappedArticles(any()) } returns lastDay
every { subscriptionDao.reSubscribeWorkbookSubscription(any()) } just Runs
every { applicationEventPublisher.publishEvent(event) } answers {
- println("Mocking applicationEventPublisher.publishEvent(any()) was called")
+ log.debug { "Mocking applicationEventPublisher.publishEvent(any()) was called" }
}
then("재구독한다") {
@@ -95,7 +97,7 @@ class SubscribeWorkbookUseCaseTest : BehaviorSpec({
every { subscriptionDao.countWorkbookMappedArticles(any()) } returns lastDay
every { subscriptionDao.reSubscribeWorkbookSubscription(any()) } just Runs
every { applicationEventPublisher.publishEvent(event) } answers {
- println("Mocking applicationEventPublisher.publishEvent(any()) was called")
+ log.debug { "Mocking applicationEventPublisher.publishEvent(any()) was called" }
}
then("예외가 발생한다") {
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 f545f9f13..3c1059b58 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
@@ -69,7 +69,8 @@ class ArticleControllerTest : ControllerTestSpec() {
val uri = UriComponentsBuilder.newInstance().path("$BASE_URL/{articleId}").build().toUriString()
// set usecase mock
val articleId = 1L
- `when`(readArticleUseCase.execute(ReadArticleUseCaseIn(articleId))).thenReturn(
+ val memberId = 0L
+ `when`(readArticleUseCase.execute(ReadArticleUseCaseIn(articleId, memberId))).thenReturn(
ReadArticleUseCaseOut(
id = 1L,
writer = WriterDetail(
@@ -81,7 +82,8 @@ class ArticleControllerTest : ControllerTestSpec() {
content = CategoryType.fromCode(0)!!.name,
problemIds = listOf(1L, 2L, 3L),
category = "경제",
- createdAt = LocalDateTime.now()
+ createdAt = LocalDateTime.now(),
+ views = 1L
)
)
@@ -119,7 +121,9 @@ class ArticleControllerTest : ControllerTestSpec() {
PayloadDocumentation.fieldWithPath("data.category")
.fieldWithString("아티클 카테고리"),
PayloadDocumentation.fieldWithPath("data.createdAt")
- .fieldWithString("아티클 생성일")
+ .fieldWithString("아티클 생성일"),
+ PayloadDocumentation.fieldWithPath("data.views")
+ .fieldWithNumber("아티클 조회수")
)
)
).build()
diff --git a/api/src/test/resources/application-test.yml b/api/src/test/resources/application-test.yml
index 684314968..387ac0443 100644
--- a/api/src/test/resources/application-test.yml
+++ b/api/src/test/resources/application-test.yml
@@ -64,3 +64,11 @@ discord:
queue-capacity: 30
wait-for-tasks-to-complete-on-shutdown: true
await-termination-seconds: 60
+
+database:
+ thread-pool:
+ core-pool-size: 10
+ max-pool-size: 30
+ queue-capacity: 70
+ wait-for-tasks-to-complete-on-shutdown: true
+ await-termination-seconds: 60
diff --git a/batch/.gitignore b/batch/.gitignore
deleted file mode 100644
index aabab5468..000000000
--- a/batch/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-# DB schema migration path
-**/*.sql
diff --git a/data/.gitignore b/data/.gitignore
deleted file mode 100644
index aabab5468..000000000
--- a/data/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-# DB schema migration path
-**/*.sql
diff --git a/data/db/migration/entity/V1.00.0.7__article_view_his_table.sql b/data/db/migration/entity/V1.00.0.7__article_view_his_table.sql
new file mode 100644
index 000000000..2ca763a0e
--- /dev/null
+++ b/data/db/migration/entity/V1.00.0.7__article_view_his_table.sql
@@ -0,0 +1,13 @@
+-- 아티클 조회수 저장 테이블
+CREATE TABLE ARTICLE_VIEW_HIS
+(
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ article_mst_id BIGINT NOT NULL,
+ member_id BIGINT NOT NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ deleted_at TIMESTAMP NULL DEFAULT NULL,
+ PRIMARY KEY (id)
+);
+
+-- [인덱스 추가] --
+CREATE INDEX article_view_his_idx1 ON ARTICLE_VIEW_HIS (article_mst_id);
diff --git a/email/.gitignore b/email/.gitignore
deleted file mode 100644
index aabab5468..000000000
--- a/email/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-# DB schema migration path
-**/*.sql
diff --git a/storage/.gitignore b/storage/.gitignore
deleted file mode 100644
index aabab5468..000000000
--- a/storage/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-# DB schema migration path
-**/*.sql
|