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