From 584286848fb6f13c17f5e66d1cd7aaa7799c97a4 Mon Sep 17 00:00:00 2001 From: belljun3395 <195850@jnu.ac.kr> Date: Tue, 17 Dec 2024 14:25:10 +0900 Subject: [PATCH] =?UTF-8?q?chore:=20pinterest=20ktlint=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/build.gradle.kts | 122 +- .../kotlin/com/few/api/config/ApiConfig.kt | 8 +- .../ApiDatabaseAccessThreadPoolConfig.kt | 29 +- .../com/few/api/config/ApiLocalCacheConfig.kt | 57 +- .../api/config/ApiMessageSourceAccessor.kt | 27 +- .../few/api/config/ApiMessageSourceConfig.kt | 23 +- .../com/few/api/config/ApiThreadPoolConfig.kt | 32 +- .../properties/ApiThreadPoolProperties.kt | 21 +- .../admin/controller/AdminController.kt | 97 +- .../controller/request/AddArticleRequest.kt | 1 + .../controller/response/AddArticleResponse.kt | 2 +- .../domain/admin/repo/document/DocumentDao.kt | 10 +- .../api/domain/admin/repo/image/ImageDao.kt | 10 +- .../service/AdminArticleMainCardService.kt | 22 +- .../admin/service/ConvertDocumentService.kt | 6 +- .../api/domain/admin/service/GetUrlService.kt | 16 +- .../domain/admin/service/dto/GetUrlInDto.kt | 9 +- .../domain/admin/usecase/AddArticleUseCase.kt | 116 +- .../admin/usecase/AddWorkbookUseCase.kt | 4 +- .../admin/usecase/ConvertContentUseCase.kt | 35 +- .../domain/admin/usecase/MapArticleUseCase.kt | 7 +- .../domain/admin/usecase/PutImageUseCase.kt | 71 +- .../admin/usecase/dto/AddArticleUseCaseIn.kt | 1 - .../domain/admin/utils/ObjectPathGenerator.kt | 5 +- .../article/controller/ArticleController.kt | 137 +- .../article/email/SendArticleEmailService.kt | 7 +- .../article/email/dto/SendArticleEmailArgs.kt | 2 +- .../article/event/ReadArticleEventListener.kt | 1 - .../handler/ArticleViewHisAsyncHandler.kt | 25 +- .../few/api/domain/article/repo/ArticleDao.kt | 195 +-- .../domain/article/repo/ArticleMainCardDao.kt | 115 +- .../article/repo/ArticleViewCountDao.kt | 208 +-- .../domain/article/repo/ArticleViewHisDao.kt | 29 +- .../repo/support/ArticleMainCardMapper.kt | 129 +- .../article/repo/support/CommonJsonMapper.kt | 4 +- .../article/service/ArticleLogService.kt | 14 +- .../article/service/ArticleMemberService.kt | 11 +- .../service/BrowseArticleProblemsService.kt | 7 +- .../service/ReadArticleWriterRecordService.kt | 9 +- .../article/usecase/BrowseArticlesUseCase.kt | 135 +- .../usecase/ReadArticleByEmailUseCase.kt | 9 +- .../article/usecase/ReadArticleUseCase.kt | 39 +- .../transaction/ArticleViewCountTxCase.kt | 6 +- .../ApiBatchSendArticleEmailService.kt | 1 - .../article/dto/WorkBookSubscriberItem.kt | 15 +- .../reader/WorkBookSubscriberReader.kt | 24 +- .../writer/WorkBookSubscriberWriter.kt | 65 +- .../writer/model/ReceiveLastArticleRecord.kt | 4 +- .../model/ReceiveLastArticleRecordFilter.kt | 15 +- .../model/UpdateProgressRecordFilter.kt | 8 +- .../service/BrowseArticleContentsService.kt | 34 +- .../service/BrowseMemberEmailService.kt | 13 +- .../BrowseMemberReceiveArticlesService.kt | 57 +- .../BrowseWorkbookLastDayColService.kt | 15 +- .../writer/support/MailSendRecorder.kt | 33 +- .../support/MailServiceArgsGenerator.kt | 45 +- .../batch/controller/BatchController.kt | 5 +- .../batch/log/ApiBatchCallExecutionService.kt | 8 +- .../batch/log/ApiBatchServiceLogAspect.kt | 4 +- .../exception/ExternalIntegrationException.kt | 4 +- .../common/exception/InsertException.kt | 4 +- .../common/exception/NotFoundException.kt | 4 +- .../api/domain/common/lock/ApiLockAspect.kt | 17 +- .../common/repo/client/ApiRepoClient.kt | 50 +- .../repo/event/ApiSlowQueryEventListener.kt | 14 +- .../few/api/domain/common/vo/CategoryType.kt | 29 +- .../com/few/api/domain/common/vo/DayCode.kt | 10 +- .../api/domain/common/vo/EmailLogEventType.kt | 13 +- .../few/api/domain/common/vo/MemberType.kt | 9 +- .../few/api/domain/common/vo/SendEventType.kt | 13 +- .../com/few/api/domain/common/vo/SendType.kt | 8 +- .../few/api/domain/common/vo/ViewCategory.kt | 8 +- .../api/domain/common/vo/WorkBookCategory.kt | 16 +- .../api/domain/common/vo/WorkBookStatus.kt | 4 +- .../common/web/config/ApiWebConfigurer.kt | 1 - .../web/config/converter/DayCodeConverter.kt | 4 +- .../converter/EmailLogEventTypeConverter.kt | 5 +- .../web/config/converter/SendTypeConverter.kt | 4 +- .../web/config/converter/ViewConverter.kt | 5 +- .../converter/WorkBookCategoryConverter.kt | 5 +- .../domain/log/controller/ApiLogController.kt | 27 +- .../com/few/api/domain/log/repo/LogIfoDao.kt | 10 +- .../log/repo/SendArticleEventHistoryDao.kt | 46 +- .../domain/log/usecase/AddEmailLogUseCase.kt | 34 +- .../member/controller/MemberController.kt | 55 +- .../member/email/SendAuthEmailService.kt | 5 +- .../exception/NotValidTokenException.kt | 4 +- .../domain/member/repo/MemberCacheManager.kt | 1 - .../few/api/domain/member/repo/MemberDao.kt | 243 ++-- .../support/WriterDescriptionJsonMapper.kt | 8 +- .../service/MemberSubscriptionService.kt | 5 +- .../member/usecase/DeleteMemberUseCase.kt | 12 +- .../member/usecase/SaveMemberUseCase.kt | 57 +- .../api/domain/member/usecase/TokenUseCase.kt | 63 +- .../usecase/transaction/SaveMemberTxCase.kt | 67 +- .../problem/controller/ProblemController.kt | 79 +- .../few/api/domain/problem/repo/ProblemDao.kt | 90 +- .../domain/problem/repo/SubmitHistoryDao.kt | 29 +- .../repo/support/ContentsJsonMapper.kt | 5 +- .../problem/service/ProblemArticleService.kt | 19 +- .../service/ProblemSubscriptionService.kt | 8 +- .../problem/usecase/BrowseProblemsUseCase.kt | 8 +- .../usecase/BrowseUndoneProblemsUseCase.kt | 61 +- .../problem/usecase/CheckProblemUseCase.kt | 9 +- .../problem/usecase/ReadProblemUseCase.kt | 27 +- .../client/ApiSubscriptionClient.kt | 59 +- .../controller/SubscriptionController.kt | 86 +- .../response/SubscribeWorkbooksResponse.kt | 32 +- ...ubscriptionAfterCompletionEventListener.kt | 3 +- .../WorkbookSubscriptionEventListener.kt | 1 - .../SendWorkbookArticleAsyncHandler.kt | 153 +- .../WorkbookSubscriptionClientAsyncHandler.kt | 11 +- .../SubscribeIllegalArgumentException.kt | 4 +- .../subscription/repo/SubscriptionDao.kt | 268 ++-- .../service/SubscriptionArticleService.kt | 22 +- .../service/SubscriptionEmailService.kt | 16 +- .../service/SubscriptionLogService.kt | 9 +- .../service/SubscriptionMemberService.kt | 18 +- .../service/SubscriptionWorkbookService.kt | 21 +- .../BrowseSubscribeWorkbooksUseCase.kt | 190 +-- .../usecase/SubscribeWorkbookUseCase.kt | 100 +- .../usecase/UnsubscribeAllUseCase.kt | 7 +- .../usecase/UnsubscribeWorkbookUseCase.kt | 5 +- .../usecase/UpdateSubscriptionDayUseCase.kt | 23 +- .../usecase/UpdateSubscriptionTimeUseCase.kt | 24 +- .../dto/BrowseSubscribeWorkbooksUseCaseOut.kt | 32 +- .../CancelledWorkbookSubscriptionHistory.kt | 9 +- .../model/WorkbookSubscriptionHistory.kt | 3 +- .../controller/WorkBookArticleController.kt | 26 +- .../response/ReadWorkBookArticleResponse.kt | 13 +- .../usecase/ReadWorkBookArticleUseCase.kt | 45 +- .../workbook/controller/WorkBookController.kt | 50 +- .../response/ReadWorkBookResponse.kt | 2 +- .../workbook/repo/WorkBookCacheManager.kt | 1 - .../api/domain/workbook/repo/WorkbookDao.kt | 139 +- .../service/WorkbookArticleService.kt | 46 +- .../workbook/service/WorkbookMemberService.kt | 19 +- .../service/WorkbookSubscribeService.kt | 16 +- .../usecase/BrowseWorkbooksUseCase.kt | 175 +-- .../workbook/usecase/ReadWorkbookUseCase.kt | 53 +- .../AuthMainViewWorkbookOrderDelegator.kt | 9 +- .../model/order/WorkbookOrderDelegator.kt | 1 - .../web/controller/ApiControllerTestSpec.kt | 9 +- .../ApiTestTokenUserDetailsService.kt | 14 +- .../controller/AdminApiControllerTest.kt | 277 ++-- .../controller/ArticleApiControllerTest.kt | 237 +-- .../article/usecase/ReadArticleUseCaseTest.kt | 256 ++-- .../log/controller/ApiLogApiControllerTest.kt | 57 +- .../controller/MemberApiControllerTest.kt | 137 +- .../member/usecase/SaveMemberUseCaseTest.kt | 231 +-- .../domain/member/usecase/TokenUseCaseTest.kt | 280 ++-- .../controller/ProblemApiControllerTest.kt | 173 ++- .../usecase/BrowseProblemsUseCaseTest.kt | 53 +- .../BrowseUndoneProblemsUseCaseTest.kt | 162 ++- .../usecase/CheckProblemUseCaseTest.kt | 127 +- .../problem/usecase/ReadProblemUseCaseTest.kt | 91 +- .../SubscriptionApiControllerTest.kt | 433 +++--- .../BrowseSubscribeWorkbooksUseCaseTest.kt | 577 ++++---- .../usecase/SubscribeWorkbookUseCaseTest.kt | 294 ++-- .../usecase/UnsubscribeAllUseCaseTest.kt | 66 +- .../usecase/UnsubscribeWorkbookUseCaseTest.kt | 65 +- .../model/WorkbookSubscriptionHistoryTest.kt | 125 +- .../WorkBookArticleApiControllerTest.kt | 75 +- .../controller/WorkBookApiControllerTest.kt | 270 ++-- .../usecase/BrowseWorkbooksUseCaseTest.kt | 413 +++--- .../usecase/ReadWorkbookUseCaseTest.kt | 159 ++- .../AuthMainViewWorkbookOrderDelegatorTest.kt | 204 +-- buildSrc/build.gradle.kts | 2 +- email/src/main/kotlin/email/EmailContext.kt | 9 +- email/src/main/kotlin/email/EmailSender.kt | 8 +- .../kotlin/email/EmailTemplateProcessor.kt | 8 +- .../kotlin/email/config/MailSenderConfig.kt | 14 +- .../ArticleAwsSESEmailSendProvider.kt | 10 +- .../email/provider/AwsSESEmailSendProvider.kt | 30 +- .../email/provider/EmailSendProvider.kt | 7 +- .../email/provider/JavaEmailSendProvider.kt | 8 +- .../kotlin/repo/config/DataSourceConfig.kt | 9 +- .../main/kotlin/repo/config/FlywayConfig.kt | 28 +- .../src/main/kotlin/repo/config/JooqConfig.kt | 8 +- .../flyway/support/ExceptionTranslator.kt | 3 +- .../flyway/support/PerformanceListener.kt | 1 + .../main/kotlin/security/AuthorityUtils.kt | 1 - .../src/main/kotlin/security/Encryptor.kt | 1 - security/src/main/kotlin/security/Roles.kt | 4 +- .../src/main/kotlin/security/TokenClaim.kt | 4 +- .../main/kotlin/security/TokenGenerator.kt | 7 +- .../src/main/kotlin/security/TokenResolver.kt | 3 + .../main/kotlin/security/TokenUserDetails.kt | 28 +- .../src/main/kotlin/security/UserAuthority.kt | 4 +- .../authentication/token/TokenAuthProvider.kt | 31 +- .../token/TokenUserDetailsService.kt | 45 +- .../kotlin/security/config/SecurityConfig.kt | 4 +- .../kotlin/security/encryptor/IdEncryptor.kt | 28 +- .../SecurityAccessTokenInvalidException.kt | 4 +- .../security/token/SecurityTokenGenerator.kt | 27 +- .../security/token/SecurityTokenResolver.kt | 21 +- .../storage/GetPreSignedObjectUrlProvider.kt | 1 - .../main/kotlin/storage/PutObjectProvider.kt | 6 +- .../storage/config/StorageClientConfig.kt | 42 +- .../storage/document/PutDocumentProvider.kt | 5 +- .../document/client/S3DocumentStoreClient.kt | 14 +- .../document/client/dto/DocumentObjectArgs.kt | 13 +- .../client/util/DocumentArgsGenerator.kt | 32 +- .../document/config/DocumentStorageConfig.kt | 2 +- .../document/config/S3DocumentStoreConfig.kt | 14 +- .../s3/S3GetPreSignedDocumentUrlProvider.kt | 2 +- .../provider/s3/S3PutDocumentProvider.kt | 7 +- .../kotlin/storage/image/PutImageProvider.kt | 5 +- .../storage/image/client/ImageStoreClient.kt | 1 - .../image/client/S3ImageStoreClient.kt | 17 +- .../image/client/dto/ImageObjectArgs.kt | 13 +- .../image/client/util/ImageArgsGenerator.kt | 41 +- .../image/config/ImageStorageConfig.kt | 6 +- .../image/config/S3ImageStoreConfig.kt | 17 +- .../s3/S3GetPreSignedImageUrlProvider.kt | 2 +- .../image/provider/s3/S3PutImageProvider.kt | 7 +- .../provider/s3/S3RemoveImageProvider.kt | 2 +- web/src/main/kotlin/web/ApiResponse.kt | 4 +- .../main/kotlin/web/ApiResponseGenerator.kt | 40 +- web/src/main/kotlin/web/ExceptionMessage.kt | 5 +- web/src/main/kotlin/web/MessageCode.kt | 5 +- .../kotlin/web/client/config/ClientConfig.kt | 6 +- web/src/main/kotlin/web/config/WebConfig.kt | 14 +- .../main/kotlin/web/config/WebConfigurer.kt | 7 +- .../main/kotlin/web/filter/MDCLogFilter.kt | 4 +- .../web/handler/ControllerExceptionHandler.kt | 13 +- .../main/kotlin/web/handler/LoggingHandler.kt | 1272 +++++++++-------- .../web/security/UserArgumentDetails.kt | 8 +- ...erArgumentHandlerMethodArgumentResolver.kt | 52 +- .../AbstractDelegatedSecurityConfigurer.kt | 1 - .../LocalDelegatedSecurityConfigurer.kt | 37 +- .../config/ProdDelegatedSecurityConfigurer.kt | 37 +- .../web/security/config/WebSecurityConfig.kt | 85 +- .../security/config/WebSecurityConfigurer.kt | 1 - .../WebTokenInvalidExceptionHandlerFilter.kt | 9 +- .../filter/token/TokenAuthenticationFilter.kt | 8 +- .../handler/DelegatedAccessDeniedHandler.kt | 5 +- .../DelegatedAuthenticationEntryPoint.kt | 1 - .../kotlin/web/description/Description.kt | 25 +- .../web/helper/ApiDefinitionExtension.kt | 20 +- .../helper/PayloadDocumentationExtension.kt | 25 +- 241 files changed, 6282 insertions(+), 5842 deletions(-) diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 1b9df922a..377d6405d 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -48,16 +48,18 @@ dependencies { } tasks.withType(OpenApi3Task::class.java) { - val multipartformdataPaths = listOf( - "/api/v1/admin/utilities/conversion/image", - "/api/v1/admin/utilities/conversion/content" - ) + val multipartformdataPaths = + listOf( + "/api/v1/admin/utilities/conversion/image", + "/api/v1/admin/utilities/conversion/content", + ) doLast { val input: InputStream = FileInputStream(File("$projectDir/src/main/resources/static/openapi3.yaml")) - val options = DumperOptions().apply { - defaultFlowStyle = DumperOptions.FlowStyle.BLOCK - isPrettyFlow = true - } + val options = + DumperOptions().apply { + defaultFlowStyle = DumperOptions.FlowStyle.BLOCK + isPrettyFlow = true + } val yaml = Yaml(options) val yamlData = yaml.loadAll(input) for (data in yamlData) { @@ -83,53 +85,63 @@ tasks.withType(OpenApi3Task::class.java) { if (methods.containsKey("post")) { val post = methods["post"] as MutableMap if (!post.containsKey("requestBody")) { - post["requestBody"] = mutableMapOf( - "content" to mutableMapOf( - "multipart/form-data" to mutableMapOf( - "schema" to mutableMapOf( - "type" to "object", - "properties" to mutableMapOf( - "source" to mutableMapOf( - "type" to "string", - "format" to "binary" - ) - ) - ) - ) + post["requestBody"] = + mutableMapOf( + "content" to + mutableMapOf( + "multipart/form-data" to + mutableMapOf( + "schema" to + mutableMapOf( + "type" to "object", + "properties" to + mutableMapOf( + "source" to + mutableMapOf( + "type" to "string", + "format" to "binary", + ), + ), + ), + ), + ), ) - ) } } } } val components = content["components"] as MutableMap> - components["securitySchemes"] = mutableMapOf( - "bearerAuth" to mutableMapOf( - "type" to "http", - "scheme" to "bearer", - "bearerFormat" to "JWT" + components["securitySchemes"] = + mutableMapOf( + "bearerAuth" to + mutableMapOf( + "type" to "http", + "scheme" to "bearer", + "bearerFormat" to "JWT", + ), ) - ) val output = File("$projectDir/src/main/resources/static/openapi3.yaml") yaml.dump(content, FileWriter(output)) } } } -val imageName = project.hasProperty("imageName").let { - if (it) { - project.property("imageName") as String - } else { - "fewletter/api" +val imageName = + project.hasProperty("imageName").let { + if (it) { + project.property("imageName") as String + } else { + "fewletter/api" + } } -} -val releaseVersion = project.hasProperty("releaseVersion").let { - if (it) { - project.property("releaseVersion") as String - } else { - Random().nextInt(90000) + 10000 +val releaseVersion = + project.hasProperty("releaseVersion").let { + if (it) { + project.property("releaseVersion") as String + } else { + Random().nextInt(90000) + 10000 + } } -} tasks.register("buildDockerImage") { dependsOn("bootJar") @@ -148,16 +160,32 @@ tasks.register("buildDockerImage") { exec { workingDir(".") commandLine( - "docker", "buildx", "build", "--platform=linux/amd64,linux/arm64", "-t", - "$imageName:latest", "--build-arg", "RELEASE_VERSION=$releaseVersion", ".", "--push" + "docker", + "buildx", + "build", + "--platform=linux/amd64,linux/arm64", + "-t", + "$imageName:latest", + "--build-arg", + "RELEASE_VERSION=$releaseVersion", + ".", + "--push", ) } exec { workingDir(".") commandLine( - "docker", "buildx", "build", "--platform=linux/amd64,linux/arm64", "-t", - "$imageName:$releaseVersion", "--build-arg", "RELEASE_VERSION=$releaseVersion", ".", "--push" + "docker", + "buildx", + "build", + "--platform=linux/amd64,linux/arm64", + "-t", + "$imageName:$releaseVersion", + "--build-arg", + "RELEASE_VERSION=$releaseVersion", + ".", + "--push", ) } } @@ -176,7 +204,7 @@ tasks.register("buildEcsDockerImage") { imageName, "--build-arg", "RELEASE_VERSION=$releaseVersion", - '.' + '.', ) } } @@ -197,7 +225,7 @@ tasks.register("buildPinpointEcsDockerImageDev") { "RELEASE_VERSION=$releaseVersion", "-f", "Dockerfile.dev.pinpoint", - '.' + '.', ) } } @@ -218,7 +246,7 @@ tasks.register("buildPinpointEcsDockerImagePrd") { "RELEASE_VERSION=$releaseVersion", "-f", "Dockerfile.prd.pinpoint", - '.' + '.', ) } } 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 176c8369f..b4b647c8d 100644 --- a/api/src/main/kotlin/com/few/api/config/ApiConfig.kt +++ b/api/src/main/kotlin/com/few/api/config/ApiConfig.kt @@ -1,14 +1,14 @@ package com.few.api.config import email.config.MailConfig -import repo.config.RepoConfig -import storage.document.config.DocumentStorageConfig -import storage.image.config.ImageStorageConfig import org.springframework.boot.context.properties.ConfigurationPropertiesScan import org.springframework.context.annotation.ComponentScan import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Import +import repo.config.RepoConfig import security.config.SecurityConfig +import storage.document.config.DocumentStorageConfig +import storage.image.config.ImageStorageConfig import web.config.WebConfig @Configuration @@ -19,7 +19,7 @@ import web.config.WebConfig ImageStorageConfig::class, DocumentStorageConfig::class, WebConfig::class, - SecurityConfig::class + SecurityConfig::class, ) @ConfigurationPropertiesScan(basePackages = [ApiConfig.BASE_PACKAGE]) class ApiConfig { diff --git a/api/src/main/kotlin/com/few/api/config/ApiDatabaseAccessThreadPoolConfig.kt b/api/src/main/kotlin/com/few/api/config/ApiDatabaseAccessThreadPoolConfig.kt index bdd490ede..641e16f9a 100644 --- a/api/src/main/kotlin/com/few/api/config/ApiDatabaseAccessThreadPoolConfig.kt +++ b/api/src/main/kotlin/com/few/api/config/ApiDatabaseAccessThreadPoolConfig.kt @@ -17,22 +17,21 @@ class ApiDatabaseAccessThreadPoolConfig { @Bean @ConfigurationProperties(prefix = "thread-pool.database") - fun databaseAccessThreadPoolProperties(): ApiThreadPoolProperties { - return ApiThreadPoolProperties() - } + fun databaseAccessThreadPoolProperties(): ApiThreadPoolProperties = ApiThreadPoolProperties() @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" } + 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() } - initialize() - } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/config/ApiLocalCacheConfig.kt b/api/src/main/kotlin/com/few/api/config/ApiLocalCacheConfig.kt index b8ed7839c..08343bca7 100644 --- a/api/src/main/kotlin/com/few/api/config/ApiLocalCacheConfig.kt +++ b/api/src/main/kotlin/com/few/api/config/ApiLocalCacheConfig.kt @@ -1,5 +1,6 @@ package com.few.api.config +import com.few.api.domain.common.repo.event.ApiLocalCacheEventListener import io.github.oshai.kotlinlogging.KotlinLogging import org.ehcache.config.builders.CacheConfigurationBuilder import org.ehcache.config.builders.ResourcePoolsBuilder @@ -13,7 +14,6 @@ import org.springframework.cache.annotation.EnableCaching import org.springframework.cache.jcache.JCacheCacheManager import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration -import com.few.api.domain.common.repo.event.ApiLocalCacheEventListener @Configuration @EnableCaching @@ -29,34 +29,39 @@ class ApiLocalCacheConfig { @Bean(LOCAL_CM) fun localCacheManager(): CacheManager { - val cacheEventListenerConfigurationConfig = DefaultCacheEventListenerConfiguration( - setOf( - EventType.CREATED, - EventType.EXPIRED, - EventType.REMOVED, - EventType.UPDATED - ), - ApiLocalCacheEventListener::class.java - ) + val cacheEventListenerConfigurationConfig = + DefaultCacheEventListenerConfiguration( + setOf( + EventType.CREATED, + EventType.EXPIRED, + EventType.REMOVED, + EventType.UPDATED, + ), + ApiLocalCacheEventListener::class.java, + ) val cacheManager = EhcacheCachingProvider().cacheManager - val cache10Configuration = CacheConfigurationBuilder.newCacheConfigurationBuilder( - Any::class.java, - Any::class.java, - ResourcePoolsBuilder.newResourcePoolsBuilder() - .heap(10, EntryUnit.ENTRIES) - ) - .withService(cacheEventListenerConfigurationConfig) - .build() + val cache10Configuration = + CacheConfigurationBuilder + .newCacheConfigurationBuilder( + Any::class.java, + Any::class.java, + ResourcePoolsBuilder + .newResourcePoolsBuilder() + .heap(10, EntryUnit.ENTRIES), + ).withService(cacheEventListenerConfigurationConfig) + .build() - val cache5Configuration = CacheConfigurationBuilder.newCacheConfigurationBuilder( - Any::class.java, - Any::class.java, - ResourcePoolsBuilder.newResourcePoolsBuilder() - .heap(5, EntryUnit.ENTRIES) - ) - .withService(cacheEventListenerConfigurationConfig) - .build() + val cache5Configuration = + CacheConfigurationBuilder + .newCacheConfigurationBuilder( + Any::class.java, + Any::class.java, + ResourcePoolsBuilder + .newResourcePoolsBuilder() + .heap(5, EntryUnit.ENTRIES), + ).withService(cacheEventListenerConfigurationConfig) + .build() val selectArticleRecordCacheConfig: javax.cache.configuration.Configuration = Eh107Configuration.fromEhcacheCacheConfiguration(cache10Configuration) diff --git a/api/src/main/kotlin/com/few/api/config/ApiMessageSourceAccessor.kt b/api/src/main/kotlin/com/few/api/config/ApiMessageSourceAccessor.kt index ec36b5c07..15cbd6c2f 100644 --- a/api/src/main/kotlin/com/few/api/config/ApiMessageSourceAccessor.kt +++ b/api/src/main/kotlin/com/few/api/config/ApiMessageSourceAccessor.kt @@ -9,25 +9,28 @@ import java.util.* @Component class ApiMessageSourceAccessor : MessageSourceAware { override fun setMessageSource(messageSource: MessageSource) { - messageSourceAccessor = MessageSourceAccessor( - messageSource, - Locale.getDefault() - ) + messageSourceAccessor = + MessageSourceAccessor( + messageSource, + Locale.getDefault(), + ) } companion object { private var messageSourceAccessor: MessageSourceAccessor? = null - fun getMessage(code: String?): String { - return messageSourceAccessor!!.getMessage( - code!! + + fun getMessage(code: String?): String = + messageSourceAccessor!!.getMessage( + code!!, ) - } - fun getMessage(code: String?, vararg args: Any?): String { - return messageSourceAccessor!!.getMessage( + fun getMessage( + code: String?, + vararg args: Any?, + ): String = + messageSourceAccessor!!.getMessage( code!!, - args + args, ) - } } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/config/ApiMessageSourceConfig.kt b/api/src/main/kotlin/com/few/api/config/ApiMessageSourceConfig.kt index 1f967246f..491cc10cc 100644 --- a/api/src/main/kotlin/com/few/api/config/ApiMessageSourceConfig.kt +++ b/api/src/main/kotlin/com/few/api/config/ApiMessageSourceConfig.kt @@ -18,16 +18,17 @@ class ApiMessageSourceConfig { } companion object { - private val MESSAGE_SOURCE_CLASSPATH_LIST = listOf( - "classpath:messages/api/article", - "classpath:messages/api/document", - "classpath:messages/api/external", - "classpath:messages/api/image", - "classpath:messages/api/member", - "classpath:messages/api/problem", - "classpath:messages/api/submit", - "classpath:messages/api/subscribe", - "classpath:messages/api/workbook" - ) + private val MESSAGE_SOURCE_CLASSPATH_LIST = + listOf( + "classpath:messages/api/article", + "classpath:messages/api/document", + "classpath:messages/api/external", + "classpath:messages/api/image", + "classpath:messages/api/member", + "classpath:messages/api/problem", + "classpath:messages/api/submit", + "classpath:messages/api/subscribe", + "classpath:messages/api/workbook", + ) } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/config/ApiThreadPoolConfig.kt b/api/src/main/kotlin/com/few/api/config/ApiThreadPoolConfig.kt index 972d8bb1f..19a2edc47 100644 --- a/api/src/main/kotlin/com/few/api/config/ApiThreadPoolConfig.kt +++ b/api/src/main/kotlin/com/few/api/config/ApiThreadPoolConfig.kt @@ -10,7 +10,6 @@ import web.config.ClonedTaskDecorator @Configuration class ApiThreadPoolConfig { - private val log = KotlinLogging.logger {} companion object { @@ -19,23 +18,22 @@ class ApiThreadPoolConfig { @Bean @ConfigurationProperties(prefix = "thread-pool.discord") - fun disCordThreadPoolProperties(): ApiThreadPoolProperties { - return ApiThreadPoolProperties() - } + fun disCordThreadPoolProperties(): ApiThreadPoolProperties = ApiThreadPoolProperties() @Bean(DISCORD_HOOK_EVENT_POOL) - fun discordHookThreadPool() = ThreadPoolTaskExecutor().apply { - val properties = disCordThreadPoolProperties() - corePoolSize = properties.getCorePoolSize() - maxPoolSize = properties.getMaxPoolSize() - queueCapacity = properties.getQueueCapacity() - setWaitForTasksToCompleteOnShutdown(properties.getWaitForTasksToCompleteOnShutdown()) - setAwaitTerminationSeconds(properties.getAwaitTerminationSeconds()) - setThreadNamePrefix("discordHookThreadPool-") - setRejectedExecutionHandler { r, _ -> - log.warn { "Discord Hook Event Task Rejected: $r" } + fun discordHookThreadPool() = + ThreadPoolTaskExecutor().apply { + val properties = disCordThreadPoolProperties() + corePoolSize = properties.getCorePoolSize() + maxPoolSize = properties.getMaxPoolSize() + queueCapacity = properties.getQueueCapacity() + setWaitForTasksToCompleteOnShutdown(properties.getWaitForTasksToCompleteOnShutdown()) + setAwaitTerminationSeconds(properties.getAwaitTerminationSeconds()) + setThreadNamePrefix("discordHookThreadPool-") + setRejectedExecutionHandler { r, _ -> + log.warn { "Discord Hook Event Task Rejected: $r" } + } + setTaskDecorator(ClonedTaskDecorator()) + initialize() } - setTaskDecorator(ClonedTaskDecorator()) - initialize() - } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/config/properties/ApiThreadPoolProperties.kt b/api/src/main/kotlin/com/few/api/config/properties/ApiThreadPoolProperties.kt index f500fa7e0..a9d344209 100644 --- a/api/src/main/kotlin/com/few/api/config/properties/ApiThreadPoolProperties.kt +++ b/api/src/main/kotlin/com/few/api/config/properties/ApiThreadPoolProperties.kt @@ -9,23 +9,14 @@ data class ApiThreadPoolProperties( var waitForTasksToCompleteOnShutdown: Boolean? = null, var awaitTerminationSeconds: Int? = null, ) { - fun getCorePoolSize(): Int { - return corePoolSize ?: throw IllegalStateException("core pool size") - } + fun getCorePoolSize(): Int = corePoolSize ?: throw IllegalStateException("core pool size") - fun getMaxPoolSize(): Int { - return maxPoolSize ?: throw IllegalStateException("max pool size") - } + fun getMaxPoolSize(): Int = maxPoolSize ?: throw IllegalStateException("max pool size") - fun getQueueCapacity(): Int { - return queueCapacity ?: throw IllegalStateException("queue capacity") - } + fun getQueueCapacity(): Int = queueCapacity ?: throw IllegalStateException("queue capacity") - fun getWaitForTasksToCompleteOnShutdown(): Boolean { - return waitForTasksToCompleteOnShutdown ?: throw IllegalStateException("waitForTasksToCompleteOnShutdown") - } + fun getWaitForTasksToCompleteOnShutdown(): Boolean = + waitForTasksToCompleteOnShutdown ?: throw IllegalStateException("waitForTasksToCompleteOnShutdown") - fun getAwaitTerminationSeconds(): Int { - return awaitTerminationSeconds ?: throw IllegalStateException("awaitTerminationSeconds") - } + fun getAwaitTerminationSeconds(): Int = awaitTerminationSeconds ?: throw IllegalStateException("awaitTerminationSeconds") } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/admin/controller/AdminController.kt b/api/src/main/kotlin/com/few/api/domain/admin/controller/AdminController.kt index 578f0cf02..525b1684e 100644 --- a/api/src/main/kotlin/com/few/api/domain/admin/controller/AdminController.kt +++ b/api/src/main/kotlin/com/few/api/domain/admin/controller/AdminController.kt @@ -3,11 +3,11 @@ package com.few.api.domain.admin.controller import com.few.api.domain.admin.controller.request.AddArticleRequest import com.few.api.domain.admin.controller.request.AddWorkbookRequest import com.few.api.domain.admin.controller.request.ConvertContentRequest +import com.few.api.domain.admin.controller.request.ImageSourceRequest import com.few.api.domain.admin.controller.request.MapArticleRequest import com.few.api.domain.admin.controller.response.AddArticleResponse import com.few.api.domain.admin.controller.response.AddWorkbookResponse import com.few.api.domain.admin.controller.response.ConvertContentResponse -import com.few.api.domain.admin.controller.request.ImageSourceRequest import com.few.api.domain.admin.controller.response.ImageSourceResponse import com.few.api.domain.admin.usecase.* import com.few.api.domain.admin.usecase.dto.* @@ -33,15 +33,18 @@ class AdminController( private val putImageUseCase: PutImageUseCase, ) { @PostMapping("/workbooks") - fun addWorkbook(@RequestBody request: AddWorkbookRequest): ApiResponse> { - val useCaseOut = AddWorkbookUseCaseIn( - title = request.title, - mainImageUrl = request.mainImageUrl, - category = request.category, - description = request.description - ).let { - addWorkbookUseCase.execute(it) - } + fun addWorkbook( + @RequestBody request: AddWorkbookRequest, + ): ApiResponse> { + val useCaseOut = + AddWorkbookUseCaseIn( + title = request.title, + mainImageUrl = request.mainImageUrl, + category = request.category, + description = request.description, + ).let { + addWorkbookUseCase.execute(it) + } return AddWorkbookResponse(useCaseOut.workbookId).let { ApiResponseGenerator.success(it, HttpStatus.OK) @@ -52,29 +55,33 @@ class AdminController( fun addArticle( @RequestBody request: AddArticleRequest, ): ApiResponse> { - val useCaseOut = AddArticleUseCaseIn( - writerEmail = request.writerEmail, - articleImageUrl = request.articleImageUrl, - title = request.title, - category = request.category, - contentType = request.contentType, - contentSource = request.contentSource, - problems = request.problemData.map { datum -> - ProblemDetail( - title = datum.title, - contents = datum.contents.map { detail -> - ProblemContentDetail( - number = detail.number, - content = detail.content - ) - }, - answer = datum.answer, - explanation = datum.explanation - ) - }.toList() - ).let { useCaseIn -> - addArticleUseCase.execute(useCaseIn) - } + val useCaseOut = + AddArticleUseCaseIn( + writerEmail = request.writerEmail, + articleImageUrl = request.articleImageUrl, + title = request.title, + category = request.category, + contentType = request.contentType, + contentSource = request.contentSource, + problems = + request.problemData + .map { datum -> + ProblemDetail( + title = datum.title, + contents = + datum.contents.map { detail -> + ProblemContentDetail( + number = detail.number, + content = detail.content, + ) + }, + answer = datum.answer, + explanation = datum.explanation, + ) + }.toList(), + ).let { useCaseIn -> + addArticleUseCase.execute(useCaseIn) + } return AddArticleResponse(useCaseOut).let { ApiResponseGenerator.success(it, HttpStatus.OK) @@ -82,11 +89,13 @@ class AdminController( } @PostMapping("/relations/articles") - fun mapArticle(@RequestBody request: MapArticleRequest): ApiResponse { + fun mapArticle( + @RequestBody request: MapArticleRequest, + ): ApiResponse { MapArticleUseCaseIn( workbookId = request.workbookId, articleId = request.articleId, - dayCol = request.dayCol + dayCol = request.dayCol, ).let { mapArticleUseCase.execute(it) } @@ -95,12 +104,11 @@ class AdminController( } @PostMapping("/utilities/conversion/content", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE]) - fun convertContent( - request: ConvertContentRequest, - ): ApiResponse> { - val useCaseOut = ConvertContentUseCaseIn(request.content).let { - convertContentUseCase.execute(it) - } + fun convertContent(request: ConvertContentRequest): ApiResponse> { + val useCaseOut = + ConvertContentUseCaseIn(request.content).let { + convertContentUseCase.execute(it) + } ConvertContentResponse(useCaseOut.content, useCaseOut.originDownLoadUrl).let { return ApiResponseGenerator.success(it, HttpStatus.OK) @@ -109,9 +117,10 @@ class AdminController( @PostMapping("/utilities/conversion/image", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE]) fun putImage(request: ImageSourceRequest): ApiResponse> { - val useCaseOut = PutImageUseCaseIn(request.source).let { useCaseIn: PutImageUseCaseIn -> - putImageUseCase.execute(useCaseIn) - } + val useCaseOut = + PutImageUseCaseIn(request.source).let { useCaseIn: PutImageUseCaseIn -> + putImageUseCase.execute(useCaseIn) + } return ImageSourceResponse(useCaseOut.url, useCaseOut.supportSuffix).let { ApiResponseGenerator.success(it, HttpStatus.OK) diff --git a/api/src/main/kotlin/com/few/api/domain/admin/controller/request/AddArticleRequest.kt b/api/src/main/kotlin/com/few/api/domain/admin/controller/request/AddArticleRequest.kt index e9fd4d565..edb2656bf 100644 --- a/api/src/main/kotlin/com/few/api/domain/admin/controller/request/AddArticleRequest.kt +++ b/api/src/main/kotlin/com/few/api/domain/admin/controller/request/AddArticleRequest.kt @@ -30,6 +30,7 @@ data class ProblemDto( @field:NotBlank(message = "{problem.explanation.notblank}") val explanation: String, ) + data class ProblemContentDto( @field:NotBlank(message = "{min.problem.number}") val number: Long, diff --git a/api/src/main/kotlin/com/few/api/domain/admin/controller/response/AddArticleResponse.kt b/api/src/main/kotlin/com/few/api/domain/admin/controller/response/AddArticleResponse.kt index c83a3ace3..fc86b550a 100644 --- a/api/src/main/kotlin/com/few/api/domain/admin/controller/response/AddArticleResponse.kt +++ b/api/src/main/kotlin/com/few/api/domain/admin/controller/response/AddArticleResponse.kt @@ -6,6 +6,6 @@ data class AddArticleResponse( val articleId: Long, ) { constructor(useCaseOut: AddArticleUseCaseOut) : this( - articleId = useCaseOut.articleId + articleId = useCaseOut.articleId, ) } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/admin/repo/document/DocumentDao.kt b/api/src/main/kotlin/com/few/api/domain/admin/repo/document/DocumentDao.kt index 61ba8d2b9..ace996ce3 100644 --- a/api/src/main/kotlin/com/few/api/domain/admin/repo/document/DocumentDao.kt +++ b/api/src/main/kotlin/com/few/api/domain/admin/repo/document/DocumentDao.kt @@ -9,13 +9,13 @@ import org.springframework.stereotype.Repository class DocumentDao( private val dslContext: DSLContext, ) { - - fun insertDocumentIfo(command: InsertDocumentIfoCommand): Long? { - return dslContext.insertInto(DocumentIfo.DOCUMENT_IFO) + fun insertDocumentIfo(command: InsertDocumentIfoCommand): Long? = + dslContext + .insertInto(DocumentIfo.DOCUMENT_IFO) .set(DocumentIfo.DOCUMENT_IFO.PATH, command.path) .set(DocumentIfo.DOCUMENT_IFO.URL, command.url.toString()) .set(DocumentIfo.DOCUMENT_IFO.ALIAS, command.alias) .returning(DocumentIfo.DOCUMENT_IFO.ID) - .fetchOne()?.id - } + .fetchOne() + ?.id } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/admin/repo/image/ImageDao.kt b/api/src/main/kotlin/com/few/api/domain/admin/repo/image/ImageDao.kt index 9db7fb551..9fd2c75df 100644 --- a/api/src/main/kotlin/com/few/api/domain/admin/repo/image/ImageDao.kt +++ b/api/src/main/kotlin/com/few/api/domain/admin/repo/image/ImageDao.kt @@ -9,14 +9,14 @@ import org.springframework.stereotype.Repository class ImageDao( private val dslContext: DSLContext, ) { - // todo test - fun insertImageIfo(command: InsertImageIfoCommand): Long? { - return dslContext.insertInto(ImageIfo.IMAGE_IFO) + fun insertImageIfo(command: InsertImageIfoCommand): Long? = + dslContext + .insertInto(ImageIfo.IMAGE_IFO) .set(ImageIfo.IMAGE_IFO.PATH, command.imagePath) .set(ImageIfo.IMAGE_IFO.URL, command.url.toString()) .set(ImageIfo.IMAGE_IFO.ALIAS, command.alias) .returning(ImageIfo.IMAGE_IFO.ID) - .fetchOne()?.id - } + .fetchOne() + ?.id } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/admin/service/AdminArticleMainCardService.kt b/api/src/main/kotlin/com/few/api/domain/admin/service/AdminArticleMainCardService.kt index f5afdcbc9..f30bc29b8 100644 --- a/api/src/main/kotlin/com/few/api/domain/admin/service/AdminArticleMainCardService.kt +++ b/api/src/main/kotlin/com/few/api/domain/admin/service/AdminArticleMainCardService.kt @@ -2,12 +2,12 @@ package com.few.api.domain.admin.service import com.few.api.domain.admin.service.dto.AppendWorkbookToArticleMainCardInDto import com.few.api.domain.admin.service.dto.InitializeArticleMainCardInDto -import com.few.api.domain.common.exception.NotFoundException import com.few.api.domain.article.repo.ArticleMainCardDao import com.few.api.domain.article.repo.command.ArticleMainCardExcludeWorkbookCommand import com.few.api.domain.article.repo.command.UpdateArticleMainCardWorkbookCommand import com.few.api.domain.article.repo.command.WorkbookCommand import com.few.api.domain.article.repo.record.ArticleMainCardRecord +import com.few.api.domain.common.exception.NotFoundException import com.few.api.domain.workbook.repo.WorkbookDao import com.few.api.domain.workbook.repo.query.SelectWorkBookRecordQuery import org.springframework.stereotype.Service @@ -17,7 +17,6 @@ class AdminArticleMainCardService( val articleMainCardDao: ArticleMainCardDao, val workbookDao: WorkbookDao, ) { - fun initialize(inDto: InitializeArticleMainCardInDto) { articleMainCardDao.insertArticleMainCard( ArticleMainCardExcludeWorkbookCommand( @@ -30,31 +29,34 @@ class AdminArticleMainCardService( writerEmail = inDto.writerEmail, writerName = inDto.writerName, writerUrl = inDto.writerUrl, - writerImgUrl = inDto.writerImgUrl - ) + writerImgUrl = inDto.writerImgUrl, + ), ) } fun appendWorkbook(inDto: AppendWorkbookToArticleMainCardInDto) { - val workbookRecord = workbookDao.selectWorkBook(SelectWorkBookRecordQuery(inDto.workbookId)) - ?: throw NotFoundException("workbook.notfound.id") + val workbookRecord = + workbookDao.selectWorkBook(SelectWorkBookRecordQuery(inDto.workbookId)) + ?: throw NotFoundException("workbook.notfound.id") val toBeAddedWorkbook = WorkbookCommand(inDto.workbookId, workbookRecord.title) val articleMainCardRecord: ArticleMainCardRecord = - articleMainCardDao.selectArticleMainCardsRecord(setOf(inDto.articleId)) + articleMainCardDao + .selectArticleMainCardsRecord(setOf(inDto.articleId)) .firstOrNull() ?: throw NotFoundException("article.notfound.id") val workbookCommands = - articleMainCardRecord.workbooks.map { WorkbookCommand(it.id!!, it.title!!) } + articleMainCardRecord.workbooks + .map { WorkbookCommand(it.id!!, it.title!!) } .toMutableList() .apply { add(toBeAddedWorkbook) } articleMainCardDao.updateArticleMainCardSetWorkbook( UpdateArticleMainCardWorkbookCommand( articleId = inDto.articleId, - workbooks = workbookCommands - ) + workbooks = workbookCommands, + ), ) } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/admin/service/ConvertDocumentService.kt b/api/src/main/kotlin/com/few/api/domain/admin/service/ConvertDocumentService.kt index 4240752b3..22a89e24a 100644 --- a/api/src/main/kotlin/com/few/api/domain/admin/service/ConvertDocumentService.kt +++ b/api/src/main/kotlin/com/few/api/domain/admin/service/ConvertDocumentService.kt @@ -7,7 +7,6 @@ import org.springframework.stereotype.Service @Service class ConvertDocumentService { - companion object { val parser = Parser.builder().build()!! val htmlRenderer = HtmlRenderer.builder().build()!! @@ -28,7 +27,10 @@ class ConvertDocumentService { it.attr("style", "font-size: 20px; line-height: 140%; font-weight: 600") } html.getElementsByTag("img").forEach { - it.attr("style", "max-height: 280px; object-fit: contain; max-width: 480px; margin-left: auto; margin-right: auto; width: 100%;") + it.attr( + "style", + "max-height: 280px; object-fit: contain; max-width: 480px; margin-left: auto; margin-right: auto; width: 100%;", + ) } html.getElementsByTag("article").forEach { it.attr("style", "max-width: 480px; font-size: 15px; line-height: 170%; font-weight: 400;") diff --git a/api/src/main/kotlin/com/few/api/domain/admin/service/GetUrlService.kt b/api/src/main/kotlin/com/few/api/domain/admin/service/GetUrlService.kt index 21829d21c..9def82cd4 100644 --- a/api/src/main/kotlin/com/few/api/domain/admin/service/GetUrlService.kt +++ b/api/src/main/kotlin/com/few/api/domain/admin/service/GetUrlService.kt @@ -4,10 +4,10 @@ import com.few.api.domain.admin.service.dto.GetUrlInDto import com.few.api.domain.admin.service.dto.GetUrlOutDto import com.few.api.domain.admin.service.dto.getPreSignedUrlServiceKey import com.few.api.domain.common.exception.ExternalIntegrationException -import storage.GetPreSignedObjectUrlProvider -import storage.image.config.properties.CdnProperty import org.springframework.context.annotation.Profile import org.springframework.stereotype.Service +import storage.GetPreSignedObjectUrlProvider +import storage.image.config.properties.CdnProperty import java.net.URL import java.util.* @@ -21,9 +21,11 @@ class GetLocalUrlService( private val services: Map, ) : GetUrlService { override fun execute(query: GetUrlInDto): GetUrlOutDto { - val service = services.keys.firstOrNull { key -> - key.lowercase(Locale.getDefault()).contains(query.getPreSignedUrlServiceKey()) - }?.let { services[it] } ?: throw IllegalArgumentException("Cannot find service for ${query.getPreSignedUrlServiceKey()}") + val service = + services.keys + .firstOrNull { key -> + key.lowercase(Locale.getDefault()).contains(query.getPreSignedUrlServiceKey()) + }?.let { services[it] } ?: throw IllegalArgumentException("Cannot find service for ${query.getPreSignedUrlServiceKey()}") return service.execute(query.`object`)?.let { GetUrlOutDto(URL(it)) @@ -36,7 +38,5 @@ class GetLocalUrlService( class GetCdnUrlService( private val cdnProperty: CdnProperty, ) : GetUrlService { - override fun execute(query: GetUrlInDto): GetUrlOutDto { - return GetUrlOutDto(URL(cdnProperty.url + "/" + query.`object`)) - } + override fun execute(query: GetUrlInDto): GetUrlOutDto = GetUrlOutDto(URL(cdnProperty.url + "/" + query.`object`)) } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/admin/service/dto/GetUrlInDto.kt b/api/src/main/kotlin/com/few/api/domain/admin/service/dto/GetUrlInDto.kt index 936b82c49..a32b42c0a 100644 --- a/api/src/main/kotlin/com/few/api/domain/admin/service/dto/GetUrlInDto.kt +++ b/api/src/main/kotlin/com/few/api/domain/admin/service/dto/GetUrlInDto.kt @@ -3,9 +3,12 @@ package com.few.api.domain.admin.service.dto import java.util.* /** query.object example: images/2024-07-01/14789db.png */ -fun GetUrlInDto.getPreSignedUrlServiceKey(): String { - return this.`object`.split("/")[0].lowercase(Locale.getDefault()).replace("s", "") -} +fun GetUrlInDto.getPreSignedUrlServiceKey(): String = + this.`object` + .split("/")[0] + .lowercase(Locale.getDefault()) + .replace("s", "") + data class GetUrlInDto( val `object`: String, ) \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/admin/usecase/AddArticleUseCase.kt b/api/src/main/kotlin/com/few/api/domain/admin/usecase/AddArticleUseCase.kt index 2eb4068c2..1ed3eaa77 100644 --- a/api/src/main/kotlin/com/few/api/domain/admin/usecase/AddArticleUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/admin/usecase/AddArticleUseCase.kt @@ -1,5 +1,7 @@ package com.few.api.domain.admin.usecase +import com.few.api.domain.admin.repo.document.DocumentDao +import com.few.api.domain.admin.repo.document.command.InsertDocumentIfoCommand import com.few.api.domain.admin.service.AdminArticleMainCardService import com.few.api.domain.admin.service.GetUrlService import com.few.api.domain.admin.service.dto.GetUrlInDto @@ -7,24 +9,22 @@ import com.few.api.domain.admin.service.dto.InitializeArticleMainCardInDto import com.few.api.domain.admin.usecase.dto.AddArticleUseCaseIn import com.few.api.domain.admin.usecase.dto.AddArticleUseCaseOut import com.few.api.domain.admin.utils.ObjectPathGenerator -import com.few.api.domain.common.vo.CategoryType -import com.few.api.domain.common.exception.ExternalIntegrationException -import com.few.api.domain.common.exception.NotFoundException import com.few.api.domain.article.repo.ArticleDao import com.few.api.domain.article.repo.ArticleViewCountDao import com.few.api.domain.article.repo.command.InsertFullArticleRecordCommand import com.few.api.domain.article.repo.query.ArticleViewCountQuery -import com.few.api.domain.admin.repo.document.DocumentDao -import com.few.api.domain.admin.repo.document.command.InsertDocumentIfoCommand +import com.few.api.domain.common.exception.ExternalIntegrationException +import com.few.api.domain.common.exception.NotFoundException +import com.few.api.domain.common.vo.CategoryType import com.few.api.domain.member.repo.MemberDao import com.few.api.domain.member.repo.query.SelectMemberByEmailQuery import com.few.api.domain.problem.repo.ProblemDao import com.few.api.domain.problem.repo.command.InsertProblemsCommand import com.few.api.domain.problem.repo.support.Content import com.few.api.domain.problem.repo.support.Contents -import storage.document.PutDocumentProvider import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional +import storage.document.PutDocumentProvider import java.io.File import java.time.LocalDateTime import java.util.* @@ -43,63 +43,71 @@ class AddArticleUseCase( ) { @Transactional fun execute(useCaseIn: AddArticleUseCaseIn): AddArticleUseCaseOut { - val writerRecord = memberDao.selectMemberByEmail(SelectMemberByEmailQuery(useCaseIn.writerEmail)) - ?: throw NotFoundException("member.notfound.id") + val writerRecord = + memberDao.selectMemberByEmail(SelectMemberByEmailQuery(useCaseIn.writerEmail)) + ?: throw NotFoundException("member.notfound.id") - val htmlSource = when (useCaseIn.contentType.lowercase(Locale.getDefault())) { - "md" -> { - val mdSource = useCaseIn.contentSource - val htmlSource = convertDocumentService.mdToHtml(mdSource) + val htmlSource = + when (useCaseIn.contentType.lowercase(Locale.getDefault())) { + "md" -> { + val mdSource = useCaseIn.contentSource + val htmlSource = convertDocumentService.mdToHtml(mdSource) - val document = File.createTempFile("temp", ".md").apply { - writeText(mdSource) - } - val documentName = ObjectPathGenerator.documentPath("md") + val document = + File.createTempFile("temp", ".md").apply { + writeText(mdSource) + } + val documentName = ObjectPathGenerator.documentPath("md") - val url = putDocumentService.execute(documentName, document)?.let { res -> - val source = res.`object` - getUrlService.execute(GetUrlInDto(source)).url - } ?: throw ExternalIntegrationException("external.putfail.document") + val url = + putDocumentService.execute(documentName, document)?.let { res -> + val source = res.`object` + getUrlService.execute(GetUrlInDto(source)).url + } ?: throw ExternalIntegrationException("external.putfail.document") - documentDao.insertDocumentIfo(InsertDocumentIfoCommand(path = documentName, url = url)) - htmlSource + documentDao.insertDocumentIfo(InsertDocumentIfoCommand(path = documentName, url = url)) + htmlSource + } + "html" -> useCaseIn.contentSource + else -> throw IllegalArgumentException("Unsupported content type: ${useCaseIn.contentType}") } - "html" -> useCaseIn.contentSource - else -> throw IllegalArgumentException("Unsupported content type: ${useCaseIn.contentType}") - } - val category = CategoryType.fromName(useCaseIn.category) - ?: throw NotFoundException("article.invalid.category") + val category = + CategoryType.fromName(useCaseIn.category) + ?: throw NotFoundException("article.invalid.category") - val articleMstId = articleDao.insertFullArticleRecord( - InsertFullArticleRecordCommand( - writerId = writerRecord.memberId, - mainImageURL = useCaseIn.articleImageUrl, - title = useCaseIn.title, - category = category.code, - content = htmlSource - ) - ) - - useCaseIn.problems.map { problemDatum -> - InsertProblemsCommand( - articleId = articleMstId, - createrId = 0L, // todo fix - title = problemDatum.title, - contents = Contents( - problemDatum.contents.map { detail -> - Content(detail.number, detail.content) - } + val articleMstId = + articleDao.insertFullArticleRecord( + InsertFullArticleRecordCommand( + writerId = writerRecord.memberId, + mainImageURL = useCaseIn.articleImageUrl, + title = useCaseIn.title, + category = category.code, + content = htmlSource, ), - answer = problemDatum.answer, - explanation = problemDatum.explanation ) - }.also { commands -> - problemDao.insertProblems(commands) - } + + useCaseIn.problems + .map { problemDatum -> + InsertProblemsCommand( + articleId = articleMstId, + createrId = 0L, // todo fix + title = problemDatum.title, + contents = + Contents( + problemDatum.contents.map { detail -> + Content(detail.number, detail.content) + }, + ), + answer = problemDatum.answer, + explanation = problemDatum.explanation, + ) + }.also { commands -> + problemDao.insertProblems(commands) + } articleViewCountDao.insertArticleViewCountToZero( - ArticleViewCountQuery(articleMstId, category) + ArticleViewCountQuery(articleMstId, category), ) adminArticleMainCardService.initialize( @@ -113,8 +121,8 @@ class AddArticleUseCase( writerEmail = useCaseIn.writerEmail, writerName = writerRecord.writerName ?: throw NotFoundException("article.writer.name"), writerUrl = writerRecord.url ?: throw NotFoundException("article.writer.url"), - writerImgUrl = writerRecord.imageUrl ?: throw NotFoundException("article.writer.url") - ) + writerImgUrl = writerRecord.imageUrl ?: throw NotFoundException("article.writer.url"), + ), ) return AddArticleUseCaseOut(articleMstId) diff --git a/api/src/main/kotlin/com/few/api/domain/admin/usecase/AddWorkbookUseCase.kt b/api/src/main/kotlin/com/few/api/domain/admin/usecase/AddWorkbookUseCase.kt index cd35aa96c..4700b7ae4 100644 --- a/api/src/main/kotlin/com/few/api/domain/admin/usecase/AddWorkbookUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/admin/usecase/AddWorkbookUseCase.kt @@ -20,8 +20,8 @@ class AddWorkbookUseCase( title = useCaseIn.title, mainImageUrl = useCaseIn.mainImageUrl, category = useCaseIn.category, - description = useCaseIn.description - ) + description = useCaseIn.description, + ), ) ?: throw InsertException("workbook.insertfail.record") return AddWorkbookUseCaseOut(workbookId) diff --git a/api/src/main/kotlin/com/few/api/domain/admin/usecase/ConvertContentUseCase.kt b/api/src/main/kotlin/com/few/api/domain/admin/usecase/ConvertContentUseCase.kt index 5d800cb1b..9eda387df 100644 --- a/api/src/main/kotlin/com/few/api/domain/admin/usecase/ConvertContentUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/admin/usecase/ConvertContentUseCase.kt @@ -1,5 +1,7 @@ package com.few.api.domain.admin.usecase +import com.few.api.domain.admin.repo.document.DocumentDao +import com.few.api.domain.admin.repo.document.command.InsertDocumentIfoCommand import com.few.api.domain.admin.service.GetUrlService import com.few.api.domain.admin.service.dto.GetUrlInDto import com.few.api.domain.admin.usecase.dto.ConvertContentUseCaseIn @@ -7,11 +9,9 @@ import com.few.api.domain.admin.usecase.dto.ConvertContentUseCaseOut import com.few.api.domain.admin.utils.ObjectPathGenerator import com.few.api.domain.common.exception.ExternalIntegrationException import com.few.api.domain.common.exception.InsertException -import com.few.api.domain.admin.repo.document.DocumentDao -import com.few.api.domain.admin.repo.document.command.InsertDocumentIfoCommand -import storage.document.PutDocumentProvider import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional +import storage.document.PutDocumentProvider import java.io.File @Component @@ -26,25 +26,28 @@ class ConvertContentUseCase( val contentSource = useCaseIn.content val documentSuffix = contentSource.originalFilename?.substringAfterLast(".") ?: "md" - val document = File.createTempFile("temp", ".$documentSuffix").apply { - contentSource.transferTo(this) - } + val document = + File.createTempFile("temp", ".$documentSuffix").apply { + contentSource.transferTo(this) + } val documentName = ObjectPathGenerator.documentPath(documentSuffix) val originDownloadUrl = - putDocumentService.execute(documentName, document) + putDocumentService + .execute(documentName, document) ?.`object` ?.let { source -> - getUrlService.execute(GetUrlInDto(source)).also { dto -> - documentDao.insertDocumentIfo( - InsertDocumentIfoCommand( - path = documentName, - url = dto.url - ) - ) ?: throw InsertException("document.insertfail.record") - } - .let { savedDocument -> + getUrlService + .execute(GetUrlInDto(source)) + .also { dto -> + documentDao.insertDocumentIfo( + InsertDocumentIfoCommand( + path = documentName, + url = dto.url, + ), + ) ?: throw InsertException("document.insertfail.record") + }.let { savedDocument -> savedDocument.url } } ?: throw ExternalIntegrationException("external.document.presignedfail") diff --git a/api/src/main/kotlin/com/few/api/domain/admin/usecase/MapArticleUseCase.kt b/api/src/main/kotlin/com/few/api/domain/admin/usecase/MapArticleUseCase.kt index 625615dda..1eda89f30 100644 --- a/api/src/main/kotlin/com/few/api/domain/admin/usecase/MapArticleUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/admin/usecase/MapArticleUseCase.kt @@ -19,13 +19,12 @@ class MapArticleUseCase( MapWorkBookToArticleCommand( useCaseIn.workbookId, useCaseIn.articleId, - useCaseIn.dayCol - ) - + useCaseIn.dayCol, + ), ) adminArticleMainCardService.appendWorkbook( - AppendWorkbookToArticleMainCardInDto(useCaseIn.articleId, useCaseIn.workbookId) + AppendWorkbookToArticleMainCardInDto(useCaseIn.articleId, useCaseIn.workbookId), ) } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/admin/usecase/PutImageUseCase.kt b/api/src/main/kotlin/com/few/api/domain/admin/usecase/PutImageUseCase.kt index 3813a50c8..ba8e02e60 100644 --- a/api/src/main/kotlin/com/few/api/domain/admin/usecase/PutImageUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/admin/usecase/PutImageUseCase.kt @@ -1,5 +1,7 @@ package com.few.api.domain.admin.usecase +import com.few.api.domain.admin.repo.image.ImageDao +import com.few.api.domain.admin.repo.image.command.InsertImageIfoCommand import com.few.api.domain.admin.service.GetUrlService import com.few.api.domain.admin.service.dto.GetUrlInDto import com.few.api.domain.admin.usecase.dto.PutImageUseCaseIn @@ -7,13 +9,11 @@ import com.few.api.domain.admin.usecase.dto.PutImageUseCaseOut import com.few.api.domain.admin.utils.ObjectPathGenerator import com.few.api.domain.common.exception.ExternalIntegrationException import com.few.api.domain.common.exception.InsertException -import com.few.api.domain.admin.repo.image.ImageDao -import com.few.api.domain.admin.repo.image.command.InsertImageIfoCommand -import storage.image.PutImageProvider import com.sksamuel.scrimage.ImmutableImage import com.sksamuel.scrimage.webp.WebpWriter import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional +import storage.image.PutImageProvider import java.io.File @Component @@ -22,43 +22,52 @@ class PutImageUseCase( private val putImageService: PutImageProvider, private val getUrlService: GetUrlService, ) { - @Transactional fun execute(useCaseIn: PutImageUseCaseIn): PutImageUseCaseOut { val imageSource = useCaseIn.source val suffix = imageSource.originalFilename?.substringAfterLast(".") ?: "jpg" val imageName = ObjectPathGenerator.imagePath(suffix) - val originImage = File.createTempFile("temp", ".$suffix").apply { - imageSource.transferTo(this) - } + val originImage = + File.createTempFile("temp", ".$suffix").apply { + imageSource.transferTo(this) + } - val webpImage = ImmutableImage.loader().fromFile(originImage) - .output(WebpWriter.DEFAULT, File.createTempFile("temp", ".webp")) + val webpImage = + ImmutableImage + .loader() + .fromFile(originImage) + .output(WebpWriter.DEFAULT, File.createTempFile("temp", ".webp")) - val url = putImageService.execute(imageName, originImage) - ?.`object` - ?.let { source -> - getUrlService.execute(GetUrlInDto(source)).also { dto -> - imageDao.insertImageIfo(InsertImageIfoCommand(source, dto.url)) - ?: throw InsertException("image.insertfail.record") - } - .let { savedImage -> - savedImage.url - } - } ?: throw ExternalIntegrationException("external.presignedfail.image") + val url = + putImageService + .execute(imageName, originImage) + ?.`object` + ?.let { source -> + getUrlService + .execute(GetUrlInDto(source)) + .also { dto -> + imageDao.insertImageIfo(InsertImageIfoCommand(source, dto.url)) + ?: throw InsertException("image.insertfail.record") + }.let { savedImage -> + savedImage.url + } + } ?: throw ExternalIntegrationException("external.presignedfail.image") - val webpUrl = putImageService.execute(imageName, webpImage) - ?.`object` - ?.let { source -> - getUrlService.execute(GetUrlInDto(source)).also { dto -> - imageDao.insertImageIfo(InsertImageIfoCommand(source, dto.url)) - ?: throw InsertException("image.insertfail.record") - } - .let { savedImage -> - savedImage.url - } - } ?: throw ExternalIntegrationException("external.presignedfail.image") + val webpUrl = + putImageService + .execute(imageName, webpImage) + ?.`object` + ?.let { source -> + getUrlService + .execute(GetUrlInDto(source)) + .also { dto -> + imageDao.insertImageIfo(InsertImageIfoCommand(source, dto.url)) + ?: throw InsertException("image.insertfail.record") + }.let { savedImage -> + savedImage.url + } + } ?: throw ExternalIntegrationException("external.presignedfail.image") return PutImageUseCaseOut(url, listOf(suffix, "webp")) } diff --git a/api/src/main/kotlin/com/few/api/domain/admin/usecase/dto/AddArticleUseCaseIn.kt b/api/src/main/kotlin/com/few/api/domain/admin/usecase/dto/AddArticleUseCaseIn.kt index 9a612b8a7..e6964a661 100644 --- a/api/src/main/kotlin/com/few/api/domain/admin/usecase/dto/AddArticleUseCaseIn.kt +++ b/api/src/main/kotlin/com/few/api/domain/admin/usecase/dto/AddArticleUseCaseIn.kt @@ -12,7 +12,6 @@ data class AddArticleUseCaseIn( val contentType: String, val contentSource: String, val problems: List, - ) data class ProblemDetail( diff --git a/api/src/main/kotlin/com/few/api/domain/admin/utils/ObjectPathGenerator.kt b/api/src/main/kotlin/com/few/api/domain/admin/utils/ObjectPathGenerator.kt index 93de1cffb..d3f8a654b 100644 --- a/api/src/main/kotlin/com/few/api/domain/admin/utils/ObjectPathGenerator.kt +++ b/api/src/main/kotlin/com/few/api/domain/admin/utils/ObjectPathGenerator.kt @@ -4,7 +4,6 @@ import java.time.LocalDate import kotlin.random.Random object ObjectPathGenerator { - fun imagePath(suffix: String): String { val dateDir = LocalDate.now().toString() return "images/$dateDir/${generateImageName()}" + ".$suffix" @@ -15,9 +14,7 @@ object ObjectPathGenerator { return "documents/$dateDir/${generateImageName()}" + ".$suffix" } - private fun generateImageName(): String { - return randomString() - } + private fun generateImageName(): String = randomString() private fun randomString(): String { val charPool: List = ('a'..'z') + ('A'..'Z') + ('0'..'9') diff --git a/api/src/main/kotlin/com/few/api/domain/article/controller/ArticleController.kt b/api/src/main/kotlin/com/few/api/domain/article/controller/ArticleController.kt index fbf7263f8..576904530 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/controller/ArticleController.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/controller/ArticleController.kt @@ -1,28 +1,28 @@ package com.few.api.domain.article.controller -import com.few.api.domain.article.usecase.ReadArticleUseCase +import com.few.api.domain.article.controller.request.ReadArticleByEmailRequest +import com.few.api.domain.article.controller.response.ReadArticleResponse +import com.few.api.domain.article.controller.response.ReadArticlesResponse +import com.few.api.domain.article.controller.response.WorkbookInfo +import com.few.api.domain.article.controller.response.WriterInfo import com.few.api.domain.article.usecase.BrowseArticlesUseCase import com.few.api.domain.article.usecase.ReadArticleByEmailUseCase +import com.few.api.domain.article.usecase.ReadArticleUseCase import com.few.api.domain.article.usecase.dto.ReadArticleByEmailUseCaseIn import com.few.api.domain.article.usecase.dto.ReadArticleUseCaseIn import com.few.api.domain.article.usecase.dto.ReadArticlesUseCaseIn import com.few.api.domain.common.vo.CategoryType -import com.few.api.domain.article.controller.request.ReadArticleByEmailRequest -import com.few.api.domain.article.controller.response.ReadArticleResponse -import com.few.api.domain.article.controller.response.ReadArticlesResponse -import com.few.api.domain.article.controller.response.WorkbookInfo -import com.few.api.domain.article.controller.response.WriterInfo import com.few.api.domain.common.vo.EmailLogEventType import com.few.api.domain.common.vo.SendType -import web.ApiResponse -import web.ApiResponseGenerator -import web.security.UserArgument -import web.security.UserArgumentDetails import jakarta.validation.constraints.Min import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.validation.annotation.Validated import org.springframework.web.bind.annotation.* +import web.ApiResponse +import web.ApiResponseGenerator +import web.security.UserArgument +import web.security.UserArgumentDetails @Validated @RestController @@ -32,7 +32,6 @@ class ArticleController( private val browseArticlesUseCase: BrowseArticlesUseCase, private val readArticleByEmailUseCase: ReadArticleByEmailUseCase, ) { - @GetMapping("/{articleId}") fun readArticle( @UserArgument userArgumentDetails: UserArgumentDetails, @@ -42,29 +41,32 @@ class ArticleController( ): ApiResponse> { val memberId = userArgumentDetails.id.toLong() - val useCaseOut = ReadArticleUseCaseIn( - articleId = articleId, - memberId = memberId - ).let { useCaseIn: ReadArticleUseCaseIn -> - readArticleUseCase.execute(useCaseIn) - } + val useCaseOut = + ReadArticleUseCaseIn( + articleId = articleId, + memberId = memberId, + ).let { useCaseIn: ReadArticleUseCaseIn -> + readArticleUseCase.execute(useCaseIn) + } - val response = ReadArticleResponse( - id = useCaseOut.id, - title = useCaseOut.title, - writer = WriterInfo( - id = useCaseOut.writer.id, - name = useCaseOut.writer.name, - url = useCaseOut.writer.url, - imageUrl = useCaseOut.writer.imageUrl - ), - mainImageUrl = useCaseOut.mainImageUrl, - content = useCaseOut.content, - problemIds = useCaseOut.problemIds, - category = useCaseOut.category, - createdAt = useCaseOut.createdAt, - views = useCaseOut.views - ) + val response = + ReadArticleResponse( + id = useCaseOut.id, + title = useCaseOut.title, + writer = + WriterInfo( + id = useCaseOut.writer.id, + name = useCaseOut.writer.name, + url = useCaseOut.writer.url, + imageUrl = useCaseOut.writer.imageUrl, + ), + mainImageUrl = useCaseOut.mainImageUrl, + content = useCaseOut.content, + problemIds = useCaseOut.problemIds, + category = useCaseOut.category, + createdAt = useCaseOut.createdAt, + views = useCaseOut.views, + ) return ApiResponseGenerator.success(response, HttpStatus.OK) } @@ -73,35 +75,38 @@ class ArticleController( fun readArticles( @RequestParam( required = false, - defaultValue = "0" + defaultValue = "0", ) prevArticleId: Long, @RequestParam( required = false, - defaultValue = "-1" + defaultValue = "-1", ) categoryCd: Byte, ): ApiResponse> { val useCaseOut = browseArticlesUseCase.execute(ReadArticlesUseCaseIn(prevArticleId, categoryCd)) - val articles: List = useCaseOut.articles.map { a -> - ReadArticleResponse( - id = a.id, - title = a.title, - writer = WriterInfo( - id = a.writer.id, - name = a.writer.name, - url = a.writer.url, - imageUrl = a.writer.imageUrl - ), - mainImageUrl = a.mainImageUrl, - content = a.content, - problemIds = a.problemIds, - category = a.category, - createdAt = a.createdAt, - views = a.views, - workbooks = a.workbooks.map { WorkbookInfo(it.id, it.title) } - ) - }.toList() + val articles: List = + useCaseOut.articles + .map { a -> + ReadArticleResponse( + id = a.id, + title = a.title, + writer = + WriterInfo( + id = a.writer.id, + name = a.writer.name, + url = a.writer.url, + imageUrl = a.writer.imageUrl, + ), + mainImageUrl = a.mainImageUrl, + content = a.content, + problemIds = a.problemIds, + category = a.category, + createdAt = a.createdAt, + views = a.views, + workbooks = a.workbooks.map { WorkbookInfo(it.id, it.title) }, + ) + }.toList() val response = ReadArticlesResponse(articles, useCaseOut.isLast) @@ -109,19 +114,19 @@ class ArticleController( } @GetMapping("/categories") - fun browseArticleCategories(): ApiResponse> { - return ApiResponseGenerator.success( + fun browseArticleCategories(): ApiResponse> = + ApiResponseGenerator.success( mapOf( - "categories" to CategoryType.entries.map { - mapOf( - "code" to it.code, - "name" to it.displayName - ) - } + "categories" to + CategoryType.entries.map { + mapOf( + "code" to it.code, + "name" to it.displayName, + ) + }, ), - HttpStatus.OK + HttpStatus.OK, ) - } @PostMapping("/views") fun readArticleByEmail( @@ -133,8 +138,8 @@ class ArticleController( destination = request.destination, messageId = request.messageId, eventType = EmailLogEventType.OPEN, - sendType = type - ) + sendType = type, + ), ) return ApiResponseGenerator.success(HttpStatus.OK) } diff --git a/api/src/main/kotlin/com/few/api/domain/article/email/SendArticleEmailService.kt b/api/src/main/kotlin/com/few/api/domain/article/email/SendArticleEmailService.kt index 1ff1baf10..2b0e26507 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/email/SendArticleEmailService.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/email/SendArticleEmailService.kt @@ -16,7 +16,6 @@ class SendArticleEmailService( emailSendProvider: ArticleAwsSESEmailSendProvider, private val emailTemplateProcessor: EmailTemplateProcessor, ) : EmailSender(mailProperties, emailSendProvider) { - override fun getHtml(args: SendArticleEmailArgs): String { val context = EmailContext() context.setVariable("articleLink", args.content.articleLink.toString() + "?fromEmail=true") @@ -24,9 +23,9 @@ class SendArticleEmailService( "currentDate", args.content.currentDate.format( DateTimeFormatter.ofPattern("yyyy/MM/dd EEEE").withLocale( - Locale.KOREA - ) - ) + Locale.KOREA, + ), + ), ) context.setVariable("category", args.content.category) context.setVariable("articleDay", "Day" + args.content.articleDay) diff --git a/api/src/main/kotlin/com/few/api/domain/article/email/dto/SendArticleEmailArgs.kt b/api/src/main/kotlin/com/few/api/domain/article/email/dto/SendArticleEmailArgs.kt index f8b46a701..fd07a16d4 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/email/dto/SendArticleEmailArgs.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/email/dto/SendArticleEmailArgs.kt @@ -46,7 +46,7 @@ data class Content( writerLink, articleContent, problemLink = URL("https://www.fewletter.com/problem?articleId=$articleId"), - unsubscribeLink = URL("https://www.fewletter.com/unsubscribe?user=$memberEmail&workbookId=$workbookId&articleId=$articleId") + unsubscribeLink = URL("https://www.fewletter.com/unsubscribe?user=$memberEmail&workbookId=$workbookId&articleId=$articleId"), ) } } \ No newline at end of file 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 index 07184f87c..5358ba960 100644 --- 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 @@ -9,7 +9,6 @@ import org.springframework.stereotype.Component class ReadArticleEventListener( private val articleViewHisAsyncHandler: ArticleViewHisAsyncHandler, ) { - @EventListener fun handleEvent(event: ReadArticleEvent) { articleViewHisAsyncHandler.addArticleViewHis(event.articleId, event.memberId, event.category) diff --git a/api/src/main/kotlin/com/few/api/domain/article/event/handler/ArticleViewHisAsyncHandler.kt b/api/src/main/kotlin/com/few/api/domain/article/event/handler/ArticleViewHisAsyncHandler.kt index af559f429..f0b3a81cb 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/event/handler/ArticleViewHisAsyncHandler.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/event/handler/ArticleViewHisAsyncHandler.kt @@ -1,11 +1,11 @@ package com.few.api.domain.article.event.handler import com.few.api.config.ApiDatabaseAccessThreadPoolConfig.Companion.DATABASE_ACCESS_POOL -import com.few.api.domain.common.vo.CategoryType import com.few.api.domain.article.repo.ArticleViewCountDao import com.few.api.domain.article.repo.ArticleViewHisDao import com.few.api.domain.article.repo.command.ArticleViewHisCommand import com.few.api.domain.article.repo.query.ArticleViewCountQuery +import com.few.api.domain.common.vo.CategoryType import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.scheduling.annotation.Async import org.springframework.stereotype.Component @@ -20,16 +20,21 @@ class ArticleViewHisAsyncHandler( @Async(value = DATABASE_ACCESS_POOL) @Transactional - fun addArticleViewHis(articleId: Long, memberId: Long, categoryType: CategoryType) { + fun addArticleViewHis( + articleId: Long, + memberId: Long, + categoryType: CategoryType, + ) { runCatching { - articleViewHisDao.insertArticleViewHis( - ArticleViewHisCommand( - articleId, - memberId - ) - ).also { - log.debug { "Successfully inserted article view history for articleId: $articleId and memberId: $memberId" } - } + articleViewHisDao + .insertArticleViewHis( + ArticleViewHisCommand( + articleId, + memberId, + ), + ).also { + log.debug { "Successfully inserted article view history for articleId: $articleId and memberId: $memberId" } + } articleViewCountDao.upsertArticleViewCount(ArticleViewCountQuery(articleId, categoryType)).also { log.debug { "Successfully upserted article view count for articleId: $articleId and categoryType: $categoryType" } diff --git a/api/src/main/kotlin/com/few/api/domain/article/repo/ArticleDao.kt b/api/src/main/kotlin/com/few/api/domain/article/repo/ArticleDao.kt index 80e0a55f1..60ac23d99 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/repo/ArticleDao.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/repo/ArticleDao.kt @@ -1,11 +1,11 @@ package com.few.api.domain.article.repo -import com.few.api.domain.article.repo.query.* -import com.few.api.domain.article.repo.record.* -import com.few.api.domain.common.vo.MemberType import com.few.api.config.ApiLocalCacheConfig.Companion.LOCAL_CM import com.few.api.config.ApiLocalCacheConfig.Companion.SELECT_ARTICLE_RECORD_CACHE import com.few.api.domain.article.repo.command.InsertFullArticleRecordCommand +import com.few.api.domain.article.repo.query.* +import com.few.api.domain.article.repo.record.* +import com.few.api.domain.common.vo.MemberType import jooq.jooq_dsl.tables.* import jooq.jooq_dsl.tables.MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE import org.jooq.* @@ -17,44 +17,44 @@ import org.springframework.stereotype.Repository class ArticleDao( private val dslContext: DSLContext, ) { - @Cacheable(key = "#query.articleId", cacheManager = LOCAL_CM, cacheNames = [SELECT_ARTICLE_RECORD_CACHE]) - fun selectArticleRecord(query: SelectArticleRecordQuery): SelectArticleRecord? { - return selectArticleRecordQuery(query) + fun selectArticleRecord(query: SelectArticleRecordQuery): SelectArticleRecord? = + selectArticleRecordQuery(query) .fetchOneInto(SelectArticleRecord::class.java) - } - fun selectArticleRecordQuery(query: SelectArticleRecordQuery) = dslContext.select( - ArticleMst.ARTICLE_MST.ID.`as`(SelectArticleRecord::articleId.name), - ArticleMst.ARTICLE_MST.MEMBER_ID.`as`(SelectArticleRecord::writerId.name), - ArticleMst.ARTICLE_MST.MAIN_IMAGE_URL.`as`(SelectArticleRecord::mainImageURL.name), - ArticleMst.ARTICLE_MST.TITLE.`as`(SelectArticleRecord::title.name), - ArticleMst.ARTICLE_MST.CATEGORY_CD.`as`(SelectArticleRecord::category.name), - ArticleIfo.ARTICLE_IFO.CONTENT.`as`(SelectArticleRecord::content.name), - ArticleMst.ARTICLE_MST.CREATED_AT.`as`(SelectArticleRecord::createdAt.name) - ).from(ArticleMst.ARTICLE_MST) - .join(ArticleIfo.ARTICLE_IFO) - .on(ArticleMst.ARTICLE_MST.ID.eq(ArticleIfo.ARTICLE_IFO.ARTICLE_MST_ID)) - .where(ArticleMst.ARTICLE_MST.ID.eq(query.articleId)) - .and(ArticleMst.ARTICLE_MST.DELETED_AT.isNull) - .query - - fun selectWorkBookArticleRecord(query: SelectWorkBookArticleRecordQuery): SelectWorkBookArticleRecord? { - return selectWorkBookArticleRecordQuery(query) + fun selectArticleRecordQuery(query: SelectArticleRecordQuery) = + dslContext + .select( + ArticleMst.ARTICLE_MST.ID.`as`(SelectArticleRecord::articleId.name), + ArticleMst.ARTICLE_MST.MEMBER_ID.`as`(SelectArticleRecord::writerId.name), + ArticleMst.ARTICLE_MST.MAIN_IMAGE_URL.`as`(SelectArticleRecord::mainImageURL.name), + ArticleMst.ARTICLE_MST.TITLE.`as`(SelectArticleRecord::title.name), + ArticleMst.ARTICLE_MST.CATEGORY_CD.`as`(SelectArticleRecord::category.name), + ArticleIfo.ARTICLE_IFO.CONTENT.`as`(SelectArticleRecord::content.name), + ArticleMst.ARTICLE_MST.CREATED_AT.`as`(SelectArticleRecord::createdAt.name), + ).from(ArticleMst.ARTICLE_MST) + .join(ArticleIfo.ARTICLE_IFO) + .on(ArticleMst.ARTICLE_MST.ID.eq(ArticleIfo.ARTICLE_IFO.ARTICLE_MST_ID)) + .where(ArticleMst.ARTICLE_MST.ID.eq(query.articleId)) + .and(ArticleMst.ARTICLE_MST.DELETED_AT.isNull) + .query + + fun selectWorkBookArticleRecord(query: SelectWorkBookArticleRecordQuery): SelectWorkBookArticleRecord? = + selectWorkBookArticleRecordQuery(query) .fetchOneInto(SelectWorkBookArticleRecord::class.java) - } fun selectWorkBookArticleRecordQuery(query: SelectWorkBookArticleRecordQuery) = - dslContext.select( - ArticleMst.ARTICLE_MST.ID.`as`(SelectWorkBookArticleRecord::articleId.name), - ArticleMst.ARTICLE_MST.MEMBER_ID.`as`(SelectWorkBookArticleRecord::writerId.name), - ArticleMst.ARTICLE_MST.MAIN_IMAGE_URL.`as`(SelectWorkBookArticleRecord::mainImageURL.name), - ArticleMst.ARTICLE_MST.TITLE.`as`(SelectWorkBookArticleRecord::title.name), - ArticleMst.ARTICLE_MST.CATEGORY_CD.`as`(SelectWorkBookArticleRecord::category.name), - ArticleIfo.ARTICLE_IFO.CONTENT.`as`(SelectWorkBookArticleRecord::content.name), - ArticleMst.ARTICLE_MST.CREATED_AT.`as`(SelectWorkBookArticleRecord::createdAt.name), - MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DAY_COL.`as`(SelectWorkBookArticleRecord::day.name) - ).from(ArticleMst.ARTICLE_MST) + dslContext + .select( + ArticleMst.ARTICLE_MST.ID.`as`(SelectWorkBookArticleRecord::articleId.name), + ArticleMst.ARTICLE_MST.MEMBER_ID.`as`(SelectWorkBookArticleRecord::writerId.name), + ArticleMst.ARTICLE_MST.MAIN_IMAGE_URL.`as`(SelectWorkBookArticleRecord::mainImageURL.name), + ArticleMst.ARTICLE_MST.TITLE.`as`(SelectWorkBookArticleRecord::title.name), + ArticleMst.ARTICLE_MST.CATEGORY_CD.`as`(SelectWorkBookArticleRecord::category.name), + ArticleIfo.ARTICLE_IFO.CONTENT.`as`(SelectWorkBookArticleRecord::content.name), + ArticleMst.ARTICLE_MST.CREATED_AT.`as`(SelectWorkBookArticleRecord::createdAt.name), + MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DAY_COL.`as`(SelectWorkBookArticleRecord::day.name), + ).from(ArticleMst.ARTICLE_MST) .join(ArticleIfo.ARTICLE_IFO) .on(ArticleMst.ARTICLE_MST.ID.eq(ArticleIfo.ARTICLE_IFO.ARTICLE_MST_ID)) .join(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE) @@ -64,41 +64,43 @@ class ArticleDao( .and(ArticleMst.ARTICLE_MST.DELETED_AT.isNull) .query - fun selectWorkbookMappedArticleRecords(query: SelectWorkbookMappedArticleRecordsQuery): List { - return selectWorkbookMappedArticleRecordsQuery(query) + fun selectWorkbookMappedArticleRecords(query: SelectWorkbookMappedArticleRecordsQuery): List = + selectWorkbookMappedArticleRecordsQuery(query) .fetchInto(SelectWorkBookMappedArticleRecord::class.java) - } - fun selectWorkbookMappedArticleRecordsQuery(query: SelectWorkbookMappedArticleRecordsQuery) = dslContext.select( - ArticleMst.ARTICLE_MST.ID.`as`(SelectWorkBookArticleRecord::articleId.name), - ArticleMst.ARTICLE_MST.MEMBER_ID.`as`(SelectWorkBookArticleRecord::writerId.name), - ArticleMst.ARTICLE_MST.MAIN_IMAGE_URL.`as`(SelectWorkBookArticleRecord::mainImageURL.name), - ArticleMst.ARTICLE_MST.TITLE.`as`(SelectWorkBookArticleRecord::title.name), - ArticleMst.ARTICLE_MST.CATEGORY_CD.`as`(SelectWorkBookArticleRecord::category.name), - ArticleIfo.ARTICLE_IFO.CONTENT.`as`(SelectWorkBookArticleRecord::content.name), - ArticleMst.ARTICLE_MST.CREATED_AT.`as`(SelectWorkBookArticleRecord::createdAt.name), - MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DAY_COL.`as`(SelectWorkBookArticleRecord::day.name) - ) - .from(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE) - .join(ArticleMst.ARTICLE_MST) - .on(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.ARTICLE_ID.eq(ArticleMst.ARTICLE_MST.ID)) - .join(ArticleIfo.ARTICLE_IFO) - .on(ArticleMst.ARTICLE_MST.ID.eq(ArticleIfo.ARTICLE_IFO.ARTICLE_MST_ID)) - .where(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID.eq(query.workbookId)) - .and(ArticleMst.ARTICLE_MST.DELETED_AT.isNull) - .orderBy(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DAY_COL) - .query + fun selectWorkbookMappedArticleRecordsQuery(query: SelectWorkbookMappedArticleRecordsQuery) = + dslContext + .select( + ArticleMst.ARTICLE_MST.ID.`as`(SelectWorkBookArticleRecord::articleId.name), + ArticleMst.ARTICLE_MST.MEMBER_ID.`as`(SelectWorkBookArticleRecord::writerId.name), + ArticleMst.ARTICLE_MST.MAIN_IMAGE_URL.`as`(SelectWorkBookArticleRecord::mainImageURL.name), + ArticleMst.ARTICLE_MST.TITLE.`as`(SelectWorkBookArticleRecord::title.name), + ArticleMst.ARTICLE_MST.CATEGORY_CD.`as`(SelectWorkBookArticleRecord::category.name), + ArticleIfo.ARTICLE_IFO.CONTENT.`as`(SelectWorkBookArticleRecord::content.name), + ArticleMst.ARTICLE_MST.CREATED_AT.`as`(SelectWorkBookArticleRecord::createdAt.name), + MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DAY_COL.`as`(SelectWorkBookArticleRecord::day.name), + ).from(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE) + .join(ArticleMst.ARTICLE_MST) + .on(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.ARTICLE_ID.eq(ArticleMst.ARTICLE_MST.ID)) + .join(ArticleIfo.ARTICLE_IFO) + .on(ArticleMst.ARTICLE_MST.ID.eq(ArticleIfo.ARTICLE_IFO.ARTICLE_MST_ID)) + .where(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID.eq(query.workbookId)) + .and(ArticleMst.ARTICLE_MST.DELETED_AT.isNull) + .orderBy(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DAY_COL) + .query fun insertFullArticleRecord(command: InsertFullArticleRecordCommand): Long { - val mstId = insertArticleMstCommand(command) - .returning(ArticleMst.ARTICLE_MST.ID) - .fetchOne() + val mstId = + insertArticleMstCommand(command) + .returning(ArticleMst.ARTICLE_MST.ID) + .fetchOne() insertArticleIfoCommand(mstId!!.getValue(ArticleMst.ARTICLE_MST.ID), command).execute() return mstId.getValue(ArticleMst.ARTICLE_MST.ID) } fun insertArticleMstCommand(command: InsertFullArticleRecordCommand) = - dslContext.insertInto(ArticleMst.ARTICLE_MST) + dslContext + .insertInto(ArticleMst.ARTICLE_MST) .set(ArticleMst.ARTICLE_MST.MEMBER_ID, command.writerId) .set(ArticleMst.ARTICLE_MST.MAIN_IMAGE_URL, command.mainImageURL.toString()) .set(ArticleMst.ARTICLE_MST.TITLE, command.title) @@ -107,19 +109,20 @@ class ArticleDao( fun insertArticleIfoCommand( mstId: Long, command: InsertFullArticleRecordCommand, - ) = dslContext.insertInto(ArticleIfo.ARTICLE_IFO) + ) = dslContext + .insertInto(ArticleIfo.ARTICLE_IFO) .set(ArticleIfo.ARTICLE_IFO.ARTICLE_MST_ID, mstId) .set(ArticleIfo.ARTICLE_IFO.CONTENT, command.content) - fun selectArticleIdByWorkbookIdAndDay(query: SelectArticleIdByWorkbookIdAndDayQuery): Long? { - return selectArticleIdByWorkbookIdAndDayQuery(query) + fun selectArticleIdByWorkbookIdAndDay(query: SelectArticleIdByWorkbookIdAndDayQuery): Long? = + selectArticleIdByWorkbookIdAndDayQuery(query) .fetchOneInto(Long::class.java) - } fun selectArticleIdByWorkbookIdAndDayQuery(query: SelectArticleIdByWorkbookIdAndDayQuery) = - dslContext.select( - MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.ARTICLE_ID - ).from(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE) + dslContext + .select( + MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.ARTICLE_ID, + ).from(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE) .where(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID.eq(query.workbookId)) .and(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DAY_COL.eq(query.day)) .query @@ -128,46 +131,48 @@ class ArticleDao( selectArticleContentsQuery(articleIds) .fetchInto(SelectArticleContentsRecord::class.java) - fun selectArticleContentsQuery(articleIds: Set) = dslContext.select( - ArticleIfo.ARTICLE_IFO.ARTICLE_MST_ID.`as`(SelectArticleContentsRecord::articleId.name), - ArticleIfo.ARTICLE_IFO.CONTENT.`as`(SelectArticleContentsRecord::content.name) - ).from(ArticleIfo.ARTICLE_IFO) - .where(ArticleIfo.ARTICLE_IFO.ARTICLE_MST_ID.`in`(articleIds)) - .and(ArticleIfo.ARTICLE_IFO.DELETED_AT.isNull) + fun selectArticleContentsQuery(articleIds: Set) = + dslContext + .select( + ArticleIfo.ARTICLE_IFO.ARTICLE_MST_ID.`as`(SelectArticleContentsRecord::articleId.name), + ArticleIfo.ARTICLE_IFO.CONTENT.`as`(SelectArticleContentsRecord::content.name), + ).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) + fun selectArticleContent(query: SelectArticleContentQuery): ArticleContentRecord? = + 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) + 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)) + 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) - fun selectArticleIdsByWorkbookIdLimitDay(query: SelectAritlceIdByWorkbookIdAndDayQuery): ArticleIdRecord { - return selectArticleIdByWorkbookIdLimitDayQuery(query) + fun selectArticleIdsByWorkbookIdLimitDay(query: SelectAritlceIdByWorkbookIdAndDayQuery): ArticleIdRecord = + selectArticleIdByWorkbookIdLimitDayQuery(query) .fetch() .map { it[MAPPING_WORKBOOK_ARTICLE.ARTICLE_ID] } .let { ArticleIdRecord(it) } - } fun selectArticleIdByWorkbookIdLimitDayQuery(query: SelectAritlceIdByWorkbookIdAndDayQuery) = dslContext diff --git a/api/src/main/kotlin/com/few/api/domain/article/repo/ArticleMainCardDao.kt b/api/src/main/kotlin/com/few/api/domain/article/repo/ArticleMainCardDao.kt index e4715a32c..52b696060 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/repo/ArticleMainCardDao.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/repo/ArticleMainCardDao.kt @@ -3,8 +3,8 @@ package com.few.api.domain.article.repo import com.few.api.domain.article.repo.command.ArticleMainCardExcludeWorkbookCommand import com.few.api.domain.article.repo.command.UpdateArticleMainCardWorkbookCommand import com.few.api.domain.article.repo.record.ArticleMainCardRecord -import com.few.api.domain.article.repo.support.CommonJsonMapper import com.few.api.domain.article.repo.support.ArticleMainCardMapper +import com.few.api.domain.article.repo.support.CommonJsonMapper import jooq.jooq_dsl.tables.ArticleMainCard.ARTICLE_MAIN_CARD import org.jooq.* import org.jooq.impl.DSL.* @@ -16,73 +16,74 @@ class ArticleMainCardDao( private val commonJsonMapper: CommonJsonMapper, private val articleMainCardMapper: ArticleMainCardMapper, ) { - - fun selectArticleMainCardsRecord(articleIds: Set): Set { - return selectArticleMainCardsRecordQuery(articleIds) + fun selectArticleMainCardsRecord(articleIds: Set): Set = + selectArticleMainCardsRecordQuery(articleIds) .fetch(articleMainCardMapper) .toSet() - } - fun selectArticleMainCardsRecordQuery(articleIds: Set) = dslContext.select( - ARTICLE_MAIN_CARD.ID.`as`(ArticleMainCardRecord::articleId.name), - ARTICLE_MAIN_CARD.TITLE.`as`(ArticleMainCardRecord::articleTitle.name), - ARTICLE_MAIN_CARD.MAIN_IMAGE_URL.`as`(ArticleMainCardRecord::mainImageUrl.name), - ARTICLE_MAIN_CARD.CATEGORY_CD.`as`(ArticleMainCardRecord::categoryCd.name), - ARTICLE_MAIN_CARD.CREATED_AT.`as`(ArticleMainCardRecord::createdAt.name), - ARTICLE_MAIN_CARD.WRITER_ID.`as`(ArticleMainCardRecord::writerId.name), - ARTICLE_MAIN_CARD.WRITER_EMAIL.`as`(ArticleMainCardRecord::writerEmail.name), - jsonGetAttributeAsText( - ARTICLE_MAIN_CARD.WRITER_DESCRIPTION, - "name" - ).`as`(ArticleMainCardRecord::writerName.name), - jsonGetAttribute(ARTICLE_MAIN_CARD.WRITER_DESCRIPTION, "url").`as`(ArticleMainCardRecord::writerUrl.name), - jsonGetAttribute(ARTICLE_MAIN_CARD.WRITER_DESCRIPTION, "imageUrl").`as`(ArticleMainCardRecord::writerImgUrl.name), - ARTICLE_MAIN_CARD.WORKBOOKS.`as`(ArticleMainCardRecord::workbooks.name) - ).from(ARTICLE_MAIN_CARD) - .where(ARTICLE_MAIN_CARD.ID.`in`(articleIds)) - .query + fun selectArticleMainCardsRecordQuery(articleIds: Set) = + dslContext + .select( + ARTICLE_MAIN_CARD.ID.`as`(ArticleMainCardRecord::articleId.name), + ARTICLE_MAIN_CARD.TITLE.`as`(ArticleMainCardRecord::articleTitle.name), + ARTICLE_MAIN_CARD.MAIN_IMAGE_URL.`as`(ArticleMainCardRecord::mainImageUrl.name), + ARTICLE_MAIN_CARD.CATEGORY_CD.`as`(ArticleMainCardRecord::categoryCd.name), + ARTICLE_MAIN_CARD.CREATED_AT.`as`(ArticleMainCardRecord::createdAt.name), + ARTICLE_MAIN_CARD.WRITER_ID.`as`(ArticleMainCardRecord::writerId.name), + ARTICLE_MAIN_CARD.WRITER_EMAIL.`as`(ArticleMainCardRecord::writerEmail.name), + jsonGetAttributeAsText( + ARTICLE_MAIN_CARD.WRITER_DESCRIPTION, + "name", + ).`as`(ArticleMainCardRecord::writerName.name), + jsonGetAttribute(ARTICLE_MAIN_CARD.WRITER_DESCRIPTION, "url").`as`(ArticleMainCardRecord::writerUrl.name), + jsonGetAttribute(ARTICLE_MAIN_CARD.WRITER_DESCRIPTION, "imageUrl").`as`(ArticleMainCardRecord::writerImgUrl.name), + ARTICLE_MAIN_CARD.WORKBOOKS.`as`(ArticleMainCardRecord::workbooks.name), + ).from(ARTICLE_MAIN_CARD) + .where(ARTICLE_MAIN_CARD.ID.`in`(articleIds)) + .query /** * NOTE - The query performed in this function do not save the workbook. */ - fun insertArticleMainCard(command: ArticleMainCardExcludeWorkbookCommand) = - insertArticleMainCardCommand(command).execute() + fun insertArticleMainCard(command: ArticleMainCardExcludeWorkbookCommand) = insertArticleMainCardCommand(command).execute() - fun insertArticleMainCardCommand(command: ArticleMainCardExcludeWorkbookCommand) = dslContext - .insertInto( - ARTICLE_MAIN_CARD, - ARTICLE_MAIN_CARD.ID, - ARTICLE_MAIN_CARD.TITLE, - ARTICLE_MAIN_CARD.MAIN_IMAGE_URL, - ARTICLE_MAIN_CARD.CATEGORY_CD, - ARTICLE_MAIN_CARD.CREATED_AT, - ARTICLE_MAIN_CARD.WRITER_ID, - ARTICLE_MAIN_CARD.WRITER_EMAIL, - ARTICLE_MAIN_CARD.WRITER_DESCRIPTION - ).values( - command.articleId, - command.articleTitle, - command.mainImageUrl.toString(), - command.categoryCd, - command.createdAt, - command.writerId, - command.writerEmail, - JSON.valueOf( - commonJsonMapper.toJsonStr( - mapOf( - "name" to command.writerName, - "url" to command.writerUrl, - "imageUrl" to command.writerImgUrl - ) - ) + fun insertArticleMainCardCommand(command: ArticleMainCardExcludeWorkbookCommand) = + dslContext + .insertInto( + ARTICLE_MAIN_CARD, + ARTICLE_MAIN_CARD.ID, + ARTICLE_MAIN_CARD.TITLE, + ARTICLE_MAIN_CARD.MAIN_IMAGE_URL, + ARTICLE_MAIN_CARD.CATEGORY_CD, + ARTICLE_MAIN_CARD.CREATED_AT, + ARTICLE_MAIN_CARD.WRITER_ID, + ARTICLE_MAIN_CARD.WRITER_EMAIL, + ARTICLE_MAIN_CARD.WRITER_DESCRIPTION, + ).values( + command.articleId, + command.articleTitle, + command.mainImageUrl.toString(), + command.categoryCd, + command.createdAt, + command.writerId, + command.writerEmail, + JSON.valueOf( + commonJsonMapper.toJsonStr( + mapOf( + "name" to command.writerName, + "url" to command.writerUrl, + "imageUrl" to command.writerImgUrl, + ), + ), + ), ) - ) fun updateArticleMainCardSetWorkbook(command: UpdateArticleMainCardWorkbookCommand) = updateArticleMainCardSetWorkbookCommand(command).execute() - fun updateArticleMainCardSetWorkbookCommand(command: UpdateArticleMainCardWorkbookCommand) = dslContext - .update(ARTICLE_MAIN_CARD) - .set(ARTICLE_MAIN_CARD.WORKBOOKS, JSON.valueOf(articleMainCardMapper.toJsonStr(command.workbooks))) - .where(ARTICLE_MAIN_CARD.ID.eq(command.articleId)) + fun updateArticleMainCardSetWorkbookCommand(command: UpdateArticleMainCardWorkbookCommand) = + dslContext + .update(ARTICLE_MAIN_CARD) + .set(ARTICLE_MAIN_CARD.WORKBOOKS, JSON.valueOf(articleMainCardMapper.toJsonStr(command.workbooks))) + .where(ARTICLE_MAIN_CARD.ID.eq(command.articleId)) } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/article/repo/ArticleViewCountDao.kt b/api/src/main/kotlin/com/few/api/domain/article/repo/ArticleViewCountDao.kt index 0976f1f4d..d6c4b45cd 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/repo/ArticleViewCountDao.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/repo/ArticleViewCountDao.kt @@ -1,6 +1,5 @@ package com.few.api.domain.article.repo -import com.few.api.domain.common.vo.CategoryType import com.few.api.domain.article.repo.TempTable.ARTICLE_ID_COLUMN import com.few.api.domain.article.repo.TempTable.ARTICLE_VIEW_COUNT_OFFSET_TABLE import com.few.api.domain.article.repo.TempTable.ARTICLE_VIEW_COUNT_OFFSET_TABLE_ARTICLE_ID @@ -22,6 +21,7 @@ import com.few.api.domain.article.repo.query.ArticleViewCountQuery import com.few.api.domain.article.repo.query.SelectArticlesOrderByViewsQuery import com.few.api.domain.article.repo.query.SelectRankByViewsQuery import com.few.api.domain.article.repo.record.SelectArticleViewsRecord +import com.few.api.domain.common.vo.CategoryType import jooq.jooq_dsl.tables.ArticleViewCount.ARTICLE_VIEW_COUNT import jooq.jooq_dsl.tables.SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY import org.jooq.DSLContext @@ -56,14 +56,14 @@ object TempTable { class ArticleViewCountDao( private val dslContext: DSLContext, ) { - fun upsertArticleViewCount(query: ArticleViewCountQuery) { upsertArticleViewCountQuery(query) .execute() } fun upsertArticleViewCountQuery(query: ArticleViewCountQuery) = - dslContext.insertInto(ARTICLE_VIEW_COUNT) + dslContext + .insertInto(ARTICLE_VIEW_COUNT) .set(ARTICLE_VIEW_COUNT.ARTICLE_ID, query.articleId) .set(ARTICLE_VIEW_COUNT.VIEW_COUNT, 1) .set(ARTICLE_VIEW_COUNT.CATEGORY_CD, query.categoryType.code) @@ -72,109 +72,119 @@ class ArticleViewCountDao( fun insertArticleViewCountToZero(query: ArticleViewCountQuery) = insertArticleViewCountToZeroQuery(query).execute() - fun insertArticleViewCountToZeroQuery(query: ArticleViewCountQuery) = dslContext.insertInto(ARTICLE_VIEW_COUNT) - .set(ARTICLE_VIEW_COUNT.ARTICLE_ID, query.articleId) - .set(ARTICLE_VIEW_COUNT.VIEW_COUNT, 0) - .set(ARTICLE_VIEW_COUNT.CATEGORY_CD, query.categoryType.code) + fun insertArticleViewCountToZeroQuery(query: ArticleViewCountQuery) = + dslContext + .insertInto(ARTICLE_VIEW_COUNT) + .set(ARTICLE_VIEW_COUNT.ARTICLE_ID, query.articleId) + .set(ARTICLE_VIEW_COUNT.VIEW_COUNT, 0) + .set(ARTICLE_VIEW_COUNT.CATEGORY_CD, query.categoryType.code) - fun selectArticleViewCount(command: ArticleViewCountCommand): Long? { - return selectArticleViewCountQuery(command).fetchOneInto(Long::class.java) - } + fun selectArticleViewCount(command: ArticleViewCountCommand): Long? = + selectArticleViewCountQuery(command).fetchOneInto(Long::class.java) - fun selectArticleViewCountQuery(command: ArticleViewCountCommand) = dslContext.select( - ARTICLE_VIEW_COUNT.VIEW_COUNT - ).from(ARTICLE_VIEW_COUNT) - .where(ARTICLE_VIEW_COUNT.ARTICLE_ID.eq(command.articleId)) - .and(ARTICLE_VIEW_COUNT.DELETED_AT.isNull) - .query + fun selectArticleViewCountQuery(command: ArticleViewCountCommand) = + dslContext + .select( + ARTICLE_VIEW_COUNT.VIEW_COUNT, + ).from(ARTICLE_VIEW_COUNT) + .where(ARTICLE_VIEW_COUNT.ARTICLE_ID.eq(command.articleId)) + .and(ARTICLE_VIEW_COUNT.DELETED_AT.isNull) + .query - fun selectRankByViews(query: SelectRankByViewsQuery): Long? { - return selectRankByViewsQuery(query) + fun selectRankByViews(query: SelectRankByViewsQuery): Long? = + selectRankByViewsQuery(query) .fetchOneInto(Long::class.java) - } - fun selectRankByViewsQuery(query: SelectRankByViewsQuery) = dslContext - .select(field(ROW_RANK_TABLE_OFFSET, Long::class.java)) - .from( - dslContext.select( - field(TOTAL_VIEW_COUNT_TABLE_ARTICLE_ID, Long::class.java).`as`(ARTICLE_ID_COLUMN), - rowNumber().over( - orderBy( - field(TOTAL_VIEW_COUNT_TABLE_VIEW_COUNT).desc(), - field(TOTAL_VIEW_COUNT_TABLE_ARTICLE_ID).desc() - ) - ).`as`(OFFSET_COLUMN) + fun selectRankByViewsQuery(query: SelectRankByViewsQuery) = + dslContext + .select(field(ROW_RANK_TABLE_OFFSET, Long::class.java)) + .from( + dslContext + .select( + field(TOTAL_VIEW_COUNT_TABLE_ARTICLE_ID, Long::class.java).`as`(ARTICLE_ID_COLUMN), + rowNumber() + .over( + orderBy( + field(TOTAL_VIEW_COUNT_TABLE_VIEW_COUNT).desc(), + field(TOTAL_VIEW_COUNT_TABLE_ARTICLE_ID).desc(), + ), + ).`as`(OFFSET_COLUMN), + ).from( + dslContext + .select( + ARTICLE_VIEW_COUNT.ARTICLE_ID, + ARTICLE_VIEW_COUNT.VIEW_COUNT + .plus( + ifnull(field(EMAIL_VIEW_COUNT_TABLE_VIEW_COUNT, Long::class.java), 0), + ).`as`(VIEW_COUNT_COLUMN), + ).from(ARTICLE_VIEW_COUNT) + .leftJoin( + dslContext + .select( + SEND_ARTICLE_EVENT_HISTORY.ARTICLE_ID, + count(SEND_ARTICLE_EVENT_HISTORY.ARTICLE_ID).`as`(VIEW_COUNT_COLUMN), + ).from( + SEND_ARTICLE_EVENT_HISTORY, + ).groupBy( + SEND_ARTICLE_EVENT_HISTORY.ARTICLE_ID, + ).asTable(EMAIL_VIEW_COUNT_TABLE), + ).on( + ARTICLE_VIEW_COUNT.ARTICLE_ID.eq( + field(EMAIL_VIEW_COUNT_TABLE_ARTICLE_ID, Long::class.java), + ), + ).asTable(TOTAL_VIEW_COUNT_TABLE), + ).asTable(ROW_RANK_TABLE), + ).where(field(ROW_RANK_TABLE_ARTICLE_ID).eq(query.articleId)) + .query + + fun selectArticlesOrderByViews(query: SelectArticlesOrderByViewsQuery): List = + selectArticlesOrderByViewsQuery(query) + .fetchInto(SelectArticleViewsRecord::class.java) + + fun selectArticlesOrderByViewsQuery(query: SelectArticlesOrderByViewsQuery) = + dslContext + .select( + field(ARTICLE_VIEW_COUNT_OFFSET_TABLE_ARTICLE_ID).`as`(SelectArticleViewsRecord::articleId.name), + field(ARTICLE_VIEW_COUNT_OFFSET_TABLE_VIEW_COUNT).`as`(SelectArticleViewsRecord::views.name), ).from( - dslContext.select( - ARTICLE_VIEW_COUNT.ARTICLE_ID, - ARTICLE_VIEW_COUNT.VIEW_COUNT.plus( - ifnull(field(EMAIL_VIEW_COUNT_TABLE_VIEW_COUNT, Long::class.java), 0) - ).`as`(VIEW_COUNT_COLUMN) - ).from(ARTICLE_VIEW_COUNT) + dslContext + .select( + ARTICLE_VIEW_COUNT.ARTICLE_ID, + ARTICLE_VIEW_COUNT.VIEW_COUNT + .plus( + ifnull(field(EMAIL_VIEW_COUNT_TABLE_VIEW_COUNT, Long::class.java), 0), + ).`as`(VIEW_COUNT_COLUMN), + ARTICLE_VIEW_COUNT.CATEGORY_CD, + ).from(ARTICLE_VIEW_COUNT) .leftJoin( - dslContext.select( - SEND_ARTICLE_EVENT_HISTORY.ARTICLE_ID, - count(SEND_ARTICLE_EVENT_HISTORY.ARTICLE_ID).`as`(VIEW_COUNT_COLUMN) - ).from( - SEND_ARTICLE_EVENT_HISTORY - ).groupBy( - SEND_ARTICLE_EVENT_HISTORY.ARTICLE_ID - ).asTable(EMAIL_VIEW_COUNT_TABLE) + dslContext + .select( + SEND_ARTICLE_EVENT_HISTORY.ARTICLE_ID, + count(SEND_ARTICLE_EVENT_HISTORY.ARTICLE_ID).`as`(VIEW_COUNT_COLUMN), + ).from( + SEND_ARTICLE_EVENT_HISTORY, + ).groupBy( + SEND_ARTICLE_EVENT_HISTORY.ARTICLE_ID, + ).asTable( + EMAIL_VIEW_COUNT_TABLE, + ), ).on( ARTICLE_VIEW_COUNT.ARTICLE_ID.eq( - field(EMAIL_VIEW_COUNT_TABLE_ARTICLE_ID, Long::class.java) - ) - ) - .asTable(TOTAL_VIEW_COUNT_TABLE) - ).asTable(ROW_RANK_TABLE) - ).where(field(ROW_RANK_TABLE_ARTICLE_ID).eq(query.articleId)) - .query - - fun selectArticlesOrderByViews(query: SelectArticlesOrderByViewsQuery): List { - return selectArticlesOrderByViewsQuery(query) - .fetchInto(SelectArticleViewsRecord::class.java) - } - - fun selectArticlesOrderByViewsQuery(query: SelectArticlesOrderByViewsQuery) = dslContext - .select( - field(ARTICLE_VIEW_COUNT_OFFSET_TABLE_ARTICLE_ID).`as`(SelectArticleViewsRecord::articleId.name), - field(ARTICLE_VIEW_COUNT_OFFSET_TABLE_VIEW_COUNT).`as`(SelectArticleViewsRecord::views.name) - ).from( - dslContext.select( - ARTICLE_VIEW_COUNT.ARTICLE_ID, - ARTICLE_VIEW_COUNT.VIEW_COUNT.plus( - ifnull(field(EMAIL_VIEW_COUNT_TABLE_VIEW_COUNT, Long::class.java), 0) - ).`as`(VIEW_COUNT_COLUMN), - ARTICLE_VIEW_COUNT.CATEGORY_CD - ).from(ARTICLE_VIEW_COUNT) - .leftJoin( - dslContext.select( - SEND_ARTICLE_EVENT_HISTORY.ARTICLE_ID, - count(SEND_ARTICLE_EVENT_HISTORY.ARTICLE_ID).`as`(VIEW_COUNT_COLUMN) - ).from( - SEND_ARTICLE_EVENT_HISTORY - ).groupBy( - SEND_ARTICLE_EVENT_HISTORY.ARTICLE_ID - ).asTable( - EMAIL_VIEW_COUNT_TABLE - ) - ).on( - ARTICLE_VIEW_COUNT.ARTICLE_ID.eq( - field( - EMAIL_VIEW_COUNT_TABLE_ARTICLE_ID, - Long::class.java - ) - ) - ).orderBy( - field(VIEW_COUNT_COLUMN, Long::class.java).desc(), - ARTICLE_VIEW_COUNT.ARTICLE_ID.desc() - ).limit(query.offset, Long.MAX_VALUE) - .asTable(ARTICLE_VIEW_COUNT_OFFSET_TABLE) - ).where( - when { - (query.category == CategoryType.All) -> noCondition() - else -> field(ARTICLE_VIEW_COUNT_OFFSET_TABLE_CATEGORY_CD).eq(query.category.code) - } - ).limit(11) - .query + field( + EMAIL_VIEW_COUNT_TABLE_ARTICLE_ID, + Long::class.java, + ), + ), + ).orderBy( + field(VIEW_COUNT_COLUMN, Long::class.java).desc(), + ARTICLE_VIEW_COUNT.ARTICLE_ID.desc(), + ).limit(query.offset, Long.MAX_VALUE) + .asTable(ARTICLE_VIEW_COUNT_OFFSET_TABLE), + ).where( + when { + (query.category == CategoryType.All) -> noCondition() + else -> field(ARTICLE_VIEW_COUNT_OFFSET_TABLE_CATEGORY_CD).eq(query.category.code) + }, + ).limit(11) + .query } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/article/repo/ArticleViewHisDao.kt b/api/src/main/kotlin/com/few/api/domain/article/repo/ArticleViewHisDao.kt index 85a5b4abc..a938e12f3 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/repo/ArticleViewHisDao.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/repo/ArticleViewHisDao.kt @@ -10,28 +10,29 @@ import org.springframework.stereotype.Repository class ArticleViewHisDao( private val dslContext: DSLContext, ) { - fun insertArticleViewHis(command: ArticleViewHisCommand) { insertArticleViewHisCommand(command).execute() } fun insertArticleViewHisCommand(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 - ) + dslContext + .insertInto( + ArticleViewHis.ARTICLE_VIEW_HIS, + ArticleViewHis.ARTICLE_VIEW_HIS.ARTICLE_MST_ID, + ArticleViewHis.ARTICLE_VIEW_HIS.MEMBER_ID, + ).values( + command.articleId, + command.memberId, + ) - fun countArticleViews(query: ArticleViewHisCountQuery): Long? { - return countArticleViewsQuery(query) + fun countArticleViews(query: ArticleViewHisCountQuery): Long? = + countArticleViewsQuery(query) .fetchOne(0, Long::class.java) - } fun countArticleViewsQuery(query: ArticleViewHisCountQuery) = - dslContext.selectCount() + dslContext + .selectCount() .from(ArticleViewHis.ARTICLE_VIEW_HIS) - .where(ArticleViewHis.ARTICLE_VIEW_HIS.ARTICLE_MST_ID.eq(query.articleId)).query + .where(ArticleViewHis.ARTICLE_VIEW_HIS.ARTICLE_MST_ID.eq(query.articleId)) + .query } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/article/repo/support/ArticleMainCardMapper.kt b/api/src/main/kotlin/com/few/api/domain/article/repo/support/ArticleMainCardMapper.kt index f59173c8f..a0a4f753f 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/repo/support/ArticleMainCardMapper.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/repo/support/ArticleMainCardMapper.kt @@ -6,8 +6,8 @@ import com.few.api.domain.article.repo.command.WorkbookCommand import com.few.api.domain.article.repo.record.ArticleMainCardRecord import com.few.api.domain.article.repo.record.WorkbookRecord import org.jooq.JSON -import org.jooq.RecordMapper import org.jooq.Record +import org.jooq.RecordMapper import org.springframework.stereotype.Component import java.net.URL import java.time.LocalDateTime @@ -16,62 +16,77 @@ import java.time.LocalDateTime class ArticleMainCardMapper( private val objectMapper: ObjectMapper, ) : RecordMapper { - override fun map(record: Record) = ArticleMainCardRecord( - articleId = record.get( - ArticleMainCardRecord::articleId.name, - Long::class.java - ), - articleTitle = record.get( - ArticleMainCardRecord::articleTitle.name, - String::class.java - ), - mainImageUrl = record.get( - ArticleMainCardRecord::mainImageUrl.name, - URL::class.java - ), - categoryCd = record.get( - ArticleMainCardRecord::categoryCd.name, - Byte::class.java - ), - createdAt = record.get( - ArticleMainCardRecord::createdAt.name, - LocalDateTime::class.java - ), - writerId = record.get( - ArticleMainCardRecord::writerId.name, - Long::class.java - ), - writerEmail = record.get( - ArticleMainCardRecord::writerEmail.name, - String::class.java - ), - writerName = record.get( - ArticleMainCardRecord::writerName.name, - String::class.java - ), - writerUrl = record.get( - ArticleMainCardRecord::writerUrl.name, - URL::class.java - ), - writerImgUrl = record.get( - ArticleMainCardRecord::writerImgUrl.name, - URL::class.java - ), - workbooks = record.get( - ArticleMainCardRecord::workbooks.name, - JSON::class.java - )?.data()?.let { - if ("{}".equals(it)) { - emptyList() - } else { - val workbookRecords = objectMapper.readValue>(it) - workbookRecords.filter { w -> w.id != null && w.title != null } - .toList() - } - } ?: run { - emptyList() - } - ) + override fun map(record: Record) = + ArticleMainCardRecord( + articleId = + record.get( + ArticleMainCardRecord::articleId.name, + Long::class.java, + ), + articleTitle = + record.get( + ArticleMainCardRecord::articleTitle.name, + String::class.java, + ), + mainImageUrl = + record.get( + ArticleMainCardRecord::mainImageUrl.name, + URL::class.java, + ), + categoryCd = + record.get( + ArticleMainCardRecord::categoryCd.name, + Byte::class.java, + ), + createdAt = + record.get( + ArticleMainCardRecord::createdAt.name, + LocalDateTime::class.java, + ), + writerId = + record.get( + ArticleMainCardRecord::writerId.name, + Long::class.java, + ), + writerEmail = + record.get( + ArticleMainCardRecord::writerEmail.name, + String::class.java, + ), + writerName = + record.get( + ArticleMainCardRecord::writerName.name, + String::class.java, + ), + writerUrl = + record.get( + ArticleMainCardRecord::writerUrl.name, + URL::class.java, + ), + writerImgUrl = + record.get( + ArticleMainCardRecord::writerImgUrl.name, + URL::class.java, + ), + workbooks = + record + .get( + ArticleMainCardRecord::workbooks.name, + JSON::class.java, + )?.data() + ?.let { + if ("{}".equals(it)) { + emptyList() + } else { + val workbookRecords = objectMapper.readValue>(it) + workbookRecords + .filter { w -> w.id != null && w.title != null } + .toList() + } + } ?: run { + emptyList() + }, + ) fun toJsonStr(workbooks: List) = objectMapper.writeValueAsString(workbooks) } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/article/repo/support/CommonJsonMapper.kt b/api/src/main/kotlin/com/few/api/domain/article/repo/support/CommonJsonMapper.kt index b9d28e95f..8fe93235e 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/repo/support/CommonJsonMapper.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/repo/support/CommonJsonMapper.kt @@ -7,7 +7,5 @@ import org.springframework.stereotype.Component class CommonJsonMapper( // TODO: common 성 패키지 위치로 이동 private val objectMapper: ObjectMapper, ) { - fun toJsonStr(map: Map): String { - return objectMapper.writeValueAsString(map) - } + fun toJsonStr(map: Map): String = objectMapper.writeValueAsString(map) } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/article/service/ArticleLogService.kt b/api/src/main/kotlin/com/few/api/domain/article/service/ArticleLogService.kt index 877c8f393..1d1496cd9 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/service/ArticleLogService.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/service/ArticleLogService.kt @@ -12,15 +12,13 @@ import org.springframework.stereotype.Service class ArticleLogService( private val sendArticleEventHistoryDao: SendArticleEventHistoryDao, ) { - - fun selectDeliveryEventByMessageId(dto: SelectDeliveryEventByMessageIdDto): SendArticleEventHistoryRecord? { - return sendArticleEventHistoryDao.selectEventByMessageId( + fun selectDeliveryEventByMessageId(dto: SelectDeliveryEventByMessageIdDto): SendArticleEventHistoryRecord? = + sendArticleEventHistoryDao.selectEventByMessageId( SelectEventByMessageIdAndEventTypeQuery( dto.messageId, - dto.eventType - ) + dto.eventType, + ), ) - } fun insertOpenEvent(dto: InsertOpenEventDto) { sendArticleEventHistoryDao.insertEvent( @@ -29,8 +27,8 @@ class ArticleLogService( articleId = dto.articleId, messageId = dto.messageId, eventType = dto.eventType, - sendType = dto.sendType - ) + sendType = dto.sendType, + ), ) } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/article/service/ArticleMemberService.kt b/api/src/main/kotlin/com/few/api/domain/article/service/ArticleMemberService.kt index 27cd4b2d5..d1ae211fa 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/service/ArticleMemberService.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/service/ArticleMemberService.kt @@ -9,10 +9,9 @@ import org.springframework.stereotype.Service class ArticleMemberService( private val memberDao: MemberDao, ) { - - fun readMemberByEmail(dto: ReadMemberByEmailDto): Long? { - return memberDao.selectMemberByEmail( - SelectMemberByEmailQuery(dto.email) - )?.memberId - } + fun readMemberByEmail(dto: ReadMemberByEmailDto): Long? = + memberDao + .selectMemberByEmail( + SelectMemberByEmailQuery(dto.email), + )?.memberId } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/article/service/BrowseArticleProblemsService.kt b/api/src/main/kotlin/com/few/api/domain/article/service/BrowseArticleProblemsService.kt index fea41ca6a..49df6b30f 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/service/BrowseArticleProblemsService.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/service/BrowseArticleProblemsService.kt @@ -12,10 +12,9 @@ import org.springframework.stereotype.Service class BrowseArticleProblemsService( private val problemDao: ProblemDao, ) { - - fun execute(query: BrowseArticleProblemIdsInDto): BrowseArticleProblemsOutDto { - return problemDao.selectProblemsByArticleId(SelectProblemsByArticleIdQuery(query.articleId)) + fun execute(query: BrowseArticleProblemIdsInDto): BrowseArticleProblemsOutDto = + problemDao + .selectProblemsByArticleId(SelectProblemsByArticleIdQuery(query.articleId)) ?.let { BrowseArticleProblemsOutDto(it.problemIds) } ?: throw NotFoundException("problem.notfound.articleId") - } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/article/service/ReadArticleWriterRecordService.kt b/api/src/main/kotlin/com/few/api/domain/article/service/ReadArticleWriterRecordService.kt index f3b190fab..07ccf85fa 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/service/ReadArticleWriterRecordService.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/service/ReadArticleWriterRecordService.kt @@ -11,16 +11,15 @@ import org.springframework.stereotype.Service class ReadArticleWriterRecordService( private val memberDao: MemberDao, ) { - - fun execute(query: ReadWriterRecordInDto): ReadWriterOutDto? { - return memberDao.selectWriter(SelectWriterQuery(query.writerId)) + fun execute(query: ReadWriterRecordInDto): ReadWriterOutDto? = + memberDao + .selectWriter(SelectWriterQuery(query.writerId)) ?.let { ReadWriterOutDto( writerId = it.writerId, name = it.name, url = it.url, - imageUrl = it.imageUrl + imageUrl = it.imageUrl, ) } - } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/article/usecase/BrowseArticlesUseCase.kt b/api/src/main/kotlin/com/few/api/domain/article/usecase/BrowseArticlesUseCase.kt index e8f6d1095..c15994c13 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/usecase/BrowseArticlesUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/usecase/BrowseArticlesUseCase.kt @@ -1,8 +1,5 @@ package com.few.api.domain.article.usecase -import com.few.api.domain.article.usecase.dto.* -import com.few.api.domain.common.vo.CategoryType -import com.few.api.domain.common.exception.NotFoundException import com.few.api.domain.article.repo.ArticleDao import com.few.api.domain.article.repo.ArticleMainCardDao import com.few.api.domain.article.repo.ArticleViewCountDao @@ -11,6 +8,9 @@ import com.few.api.domain.article.repo.query.SelectRankByViewsQuery import com.few.api.domain.article.repo.record.ArticleMainCardRecord import com.few.api.domain.article.repo.record.SelectArticleContentsRecord import com.few.api.domain.article.repo.record.SelectArticleViewsRecord +import com.few.api.domain.article.usecase.dto.* +import com.few.api.domain.common.exception.NotFoundException +import com.few.api.domain.common.vo.CategoryType import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional import java.util.* @@ -22,40 +22,43 @@ class BrowseArticlesUseCase( private val articleMainCardDao: ArticleMainCardDao, private val articleDao: ArticleDao, ) { - @Transactional(readOnly = true) fun execute(useCaseIn: ReadArticlesUseCaseIn): ReadArticlesUseCaseOut { /** * 아티클 조회수 테이블에서 마지막 읽은 아티클 아이디, 카테고리를 기반으로 Offset(테이블 row 순위)을 구함 */ - val offset = if (useCaseIn.prevArticleId <= 0) { - 0L - } else { - articleViewCountDao.selectRankByViews( - SelectRankByViewsQuery(useCaseIn.prevArticleId) - ) ?: 0 - } + val offset = + if (useCaseIn.prevArticleId <= 0) { + 0L + } else { + articleViewCountDao.selectRankByViews( + SelectRankByViewsQuery(useCaseIn.prevArticleId), + ) ?: 0 + } /** * 구한 Offset을 기준으로 이번 스크롤에서 보여줄 아티클 11개를 뽑아옴 * 카테고리 별, 조회수 순 11개. 조회수가 같을 경우 최신 아티클이 우선순위를 가짐 */ - val articleViewsRecords: MutableList = articleViewCountDao.selectArticlesOrderByViews( - SelectArticlesOrderByViewsQuery( - offset, - CategoryType.fromCode(useCaseIn.categoryCd) ?: CategoryType.All - ) - ).toMutableList() + val articleViewsRecords: MutableList = + articleViewCountDao + .selectArticlesOrderByViews( + SelectArticlesOrderByViewsQuery( + offset, + CategoryType.fromCode(useCaseIn.categoryCd) ?: CategoryType.All, + ), + ).toMutableList() /** * 11개를 조회한 상황에서 11개가 조회되지 않았다면 마지막 스크롤로 판단 */ - val isLast = if (articleViewsRecords.size == 11) { - articleViewsRecords.removeAt(10) - false - } else { - true - } + val isLast = + if (articleViewsRecords.size == 11) { + articleViewsRecords.removeAt(10) + false + } else { + true + } /** * ARTICLE_MAIN_CARD 테이블에서 이번 스크롤에서 보여줄 10개 아티클 조회 (TODO: 캐싱 적용) @@ -75,27 +78,32 @@ class BrowseArticlesUseCase( */ val sortedArticles = updateAndSortArticleViews(articleMainCardRecords, articleViewsRecords) - val articleUseCaseOuts: List = sortedArticles.map { a -> - ReadArticleUseCaseOut( - id = a.articleId, - writer = WriterDetail( - id = a.writerId, - name = a.writerName, - imageUrl = a.writerImgUrl, - url = a.writerUrl - ), - mainImageUrl = a.mainImageUrl, - title = a.articleTitle, - content = a.content, - problemIds = emptyList(), - category = CategoryType.fromCode(a.categoryCd)?.displayName - ?: throw NotFoundException("article.invalid.category"), - createdAt = a.createdAt, - views = a.views, - workbooks = a.workbooks - .map { WorkbookDetail(it.id!!, it.title!!) } - ) - }.toList() + val articleUseCaseOuts: List = + sortedArticles + .map { a -> + ReadArticleUseCaseOut( + id = a.articleId, + writer = + WriterDetail( + id = a.writerId, + name = a.writerName, + imageUrl = a.writerImgUrl, + url = a.writerUrl, + ), + mainImageUrl = a.mainImageUrl, + title = a.articleTitle, + content = a.content, + problemIds = emptyList(), + category = + CategoryType.fromCode(a.categoryCd)?.displayName + ?: throw NotFoundException("article.invalid.category"), + createdAt = a.createdAt, + views = a.views, + workbooks = + a.workbooks + .map { WorkbookDetail(it.id!!, it.title!!) }, + ) + }.toList() return ReadArticlesUseCaseOut(articleUseCaseOuts, isLast) } @@ -104,25 +112,26 @@ class BrowseArticlesUseCase( articleRecords: Set, articleViewsRecords: List, ): Set { - val sortedSet = TreeSet( - Comparator { a1, a2 -> - // views 값이 null일 경우 0으로 간주 - val views1 = a1.views ?: 0 - val views2 = a2.views ?: 0 - - // views 내림차순 정렬 - val viewComparison = views2.compareTo(views1) - - if (viewComparison != 0) { - viewComparison - } else { - // views가 같을 경우 articleId 내림차순 정렬(최신글) - val articleId1 = a1.articleId - val articleId2 = a2.articleId - articleId2.compareTo(articleId1) - } - } - ) + val sortedSet = + TreeSet( + Comparator { a1, a2 -> + // views 값이 null일 경우 0으로 간주 + val views1 = a1.views ?: 0 + val views2 = a2.views ?: 0 + + // views 내림차순 정렬 + val viewComparison = views2.compareTo(views1) + + if (viewComparison != 0) { + viewComparison + } else { + // views가 같을 경우 articleId 내림차순 정렬(최신글) + val articleId1 = a1.articleId + val articleId2 = a2.articleId + articleId2.compareTo(articleId1) + } + }, + ) val viewsMap = articleViewsRecords.associateBy({ it.articleId }, { it.views }) diff --git a/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleByEmailUseCase.kt b/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleByEmailUseCase.kt index 1f0492a88..09747d066 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleByEmailUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleByEmailUseCase.kt @@ -16,7 +16,6 @@ class ReadArticleByEmailUseCase( private val memberService: ArticleMemberService, private val articleLogService: ArticleLogService, ) { - @Transactional fun execute(useCaseIn: ReadArticleByEmailUseCaseIn) { val memberId = @@ -27,8 +26,8 @@ class ReadArticleByEmailUseCase( articleLogService.selectDeliveryEventByMessageId( SelectDeliveryEventByMessageIdDto( useCaseIn.messageId, - EmailLogEventType.DELIVERY.code - ) + EmailLogEventType.DELIVERY.code, + ), ) ?: throw IllegalStateException("event is not found") @@ -38,8 +37,8 @@ class ReadArticleByEmailUseCase( articleId = record.articleId, messageId = record.messageId, eventType = EmailLogEventType.OPEN.code, - sendType = record.sendType - ) + sendType = record.sendType, + ), ) } } \ 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 2ab81022a..0c151e739 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,18 +1,18 @@ package com.few.api.domain.article.usecase import com.few.api.domain.article.event.dto.ReadArticleEvent -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 +import com.few.api.domain.article.repo.ArticleDao +import com.few.api.domain.article.repo.query.SelectArticleRecordQuery 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 import com.few.api.domain.article.service.dto.ReadWriterRecordInDto -import com.few.api.domain.common.vo.CategoryType -import com.few.api.domain.common.exception.NotFoundException -import com.few.api.domain.article.repo.ArticleDao -import com.few.api.domain.article.repo.query.SelectArticleRecordQuery +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 import com.few.api.domain.article.usecase.transaction.ArticleViewCountTxCase +import com.few.api.domain.common.exception.NotFoundException +import com.few.api.domain.common.vo.CategoryType import org.springframework.context.ApplicationEventPublisher import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional @@ -25,7 +25,6 @@ class ReadArticleUseCase( private val articleViewCountTxCase: ArticleViewCountTxCase, private val applicationEventPublisher: ApplicationEventPublisher, ) { - @Transactional(readOnly = true) fun execute(useCaseIn: ReadArticleUseCaseIn): ReadArticleUseCaseOut { val articleRecord = @@ -44,27 +43,29 @@ class ReadArticleUseCase( ReadArticleEvent( articleId = useCaseIn.articleId, memberId = useCaseIn.memberId, - category = CategoryType.fromCode(articleRecord.category) ?: throw NotFoundException( - "article.invalid.category" - ) - ) + category = + CategoryType.fromCode(articleRecord.category) ?: throw NotFoundException( + "article.invalid.category", + ), + ), ) return ReadArticleUseCaseOut( id = articleRecord.articleId, - writer = WriterDetail( - id = writerRecord.writerId, - name = writerRecord.name, - url = writerRecord.url, - imageUrl = writerRecord.imageUrl - ), + writer = + WriterDetail( + id = writerRecord.writerId, + name = writerRecord.name, + url = writerRecord.url, + imageUrl = writerRecord.imageUrl, + ), mainImageUrl = articleRecord.mainImageURL, title = articleRecord.title, content = articleRecord.content, problemIds = problemIds.problemIds, category = CategoryType.convertToDisplayName(articleRecord.category), createdAt = articleRecord.createdAt, - views = views + views = views, ) } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/article/usecase/transaction/ArticleViewCountTxCase.kt b/api/src/main/kotlin/com/few/api/domain/article/usecase/transaction/ArticleViewCountTxCase.kt index 712f15f9d..edc661de4 100644 --- a/api/src/main/kotlin/com/few/api/domain/article/usecase/transaction/ArticleViewCountTxCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/article/usecase/transaction/ArticleViewCountTxCase.kt @@ -11,9 +11,7 @@ import org.springframework.transaction.annotation.Transactional class ArticleViewCountTxCase( private val articleViewCountDao: ArticleViewCountDao, ) { - @Transactional(isolation = Isolation.READ_UNCOMMITTED, propagation = Propagation.REQUIRES_NEW) - fun browseArticleViewCount(articleId: Long): Long { - return (articleViewCountDao.selectArticleViewCount(ArticleViewCountCommand(articleId)) ?: 0L) + 1L - } + fun browseArticleViewCount(articleId: Long): Long = + (articleViewCountDao.selectArticleViewCount(ArticleViewCountCommand(articleId)) ?: 0L) + 1L } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/batch/article/ApiBatchSendArticleEmailService.kt b/api/src/main/kotlin/com/few/api/domain/batch/article/ApiBatchSendArticleEmailService.kt index ac139ee16..0a36e5515 100644 --- a/api/src/main/kotlin/com/few/api/domain/batch/article/ApiBatchSendArticleEmailService.kt +++ b/api/src/main/kotlin/com/few/api/domain/batch/article/ApiBatchSendArticleEmailService.kt @@ -14,7 +14,6 @@ class ApiBatchSendArticleEmailService( private val batchCallExecutionService: ApiBatchCallExecutionService, private val objectMapper: ObjectMapper, ) { - @Transactional fun execute() { val startTime = System.currentTimeMillis() diff --git a/api/src/main/kotlin/com/few/api/domain/batch/article/dto/WorkBookSubscriberItem.kt b/api/src/main/kotlin/com/few/api/domain/batch/article/dto/WorkBookSubscriberItem.kt index 1b477761e..77b9a35ab 100644 --- a/api/src/main/kotlin/com/few/api/domain/batch/article/dto/WorkBookSubscriberItem.kt +++ b/api/src/main/kotlin/com/few/api/domain/batch/article/dto/WorkBookSubscriberItem.kt @@ -1,16 +1,12 @@ package com.few.api.domain.batch.article.dto -fun List.toMemberIds(): Set { - return this.map { it.memberId }.toSet() -} +fun List.toMemberIds(): Set = this.map { it.memberId }.toSet() -fun List.toTargetWorkBookIds(): Set { - return this.map { it.targetWorkBookId }.toSet() -} +fun List.toTargetWorkBookIds(): Set = this.map { it.targetWorkBookId }.toSet() /** key: 구독자들이 구독한 학습지 ID, value: 구독자들의 학습지 구독 진행률 */ -fun List.toTargetWorkBookProgress(): Map> { - return this.stream().collect( +fun List.toTargetWorkBookProgress(): Map> = + this.stream().collect( { mutableMapOf>() }, { map, dto -> if (map.containsKey(dto.targetWorkBookId)) { @@ -27,9 +23,8 @@ fun List.toTargetWorkBookProgress(): Map { @@ -31,12 +30,12 @@ class WorkBookSubscriberReader( DayCode.MON_TUE_WED_THU_FRI } - return dslContext.select( - SUBSCRIPTION.MEMBER_ID.`as`(WorkBookSubscriberItem::memberId.name), - SUBSCRIPTION.TARGET_WORKBOOK_ID.`as`(WorkBookSubscriberItem::targetWorkBookId.name), - SUBSCRIPTION.PROGRESS.`as`(WorkBookSubscriberItem::progress.name) - ) - .from(SUBSCRIPTION) + return dslContext + .select( + SUBSCRIPTION.MEMBER_ID.`as`(WorkBookSubscriberItem::memberId.name), + SUBSCRIPTION.TARGET_WORKBOOK_ID.`as`(WorkBookSubscriberItem::targetWorkBookId.name), + SUBSCRIPTION.PROGRESS.`as`(WorkBookSubscriberItem::progress.name), + ).from(SUBSCRIPTION) .where(SUBSCRIPTION.SEND_TIME.eq(time)) .and(sendDayCondition(SUBSCRIPTION.SEND_DAY, sendDay)) .and(SUBSCRIPTION.TARGET_MEMBER_ID.isNull) @@ -44,11 +43,15 @@ class WorkBookSubscriberReader( .fetchInto(WorkBookSubscriberItem::class.java) } - private fun sendDayCondition(sendDayField: TableField, sendDayCode: DayCode): Condition { - return when (sendDayCode) { + private fun sendDayCondition( + sendDayField: TableField, + sendDayCode: DayCode, + ): Condition = + when (sendDayCode) { /** 평일인 경우 매일을 포함하여 전송한다 */ DayCode.MON_TUE_WED_THU_FRI -> { - sendDayField.eq(DayCode.MON_TUE_WED_THU_FRI.code) + sendDayField + .eq(DayCode.MON_TUE_WED_THU_FRI.code) .or(sendDayField.eq(DayCode.MON_TUE_WED_THU_FRI_SAT_SUN.code)) } /** 매일의 경우 매일만 전송한다 */ @@ -59,5 +62,4 @@ class WorkBookSubscriberReader( throw IllegalArgumentException("Invalid sendDayCode: $sendDayCode") } } - } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/batch/article/writer/WorkBookSubscriberWriter.kt b/api/src/main/kotlin/com/few/api/domain/batch/article/writer/WorkBookSubscriberWriter.kt index ebbf970ba..e95e6e66d 100644 --- a/api/src/main/kotlin/com/few/api/domain/batch/article/writer/WorkBookSubscriberWriter.kt +++ b/api/src/main/kotlin/com/few/api/domain/batch/article/writer/WorkBookSubscriberWriter.kt @@ -1,7 +1,5 @@ package com.few.api.domain.batch.article.writer -import com.few.api.domain.common.vo.SendEventType -import com.few.api.domain.common.vo.SendType import com.few.api.domain.article.email.SendArticleEmailService import com.few.api.domain.batch.article.dto.WorkBookSubscriberItem import com.few.api.domain.batch.article.dto.toMemberIds @@ -14,6 +12,8 @@ import com.few.api.domain.batch.article.writer.service.BrowseMemberReceiveArticl import com.few.api.domain.batch.article.writer.service.BrowseWorkbookLastDayColService import com.few.api.domain.batch.article.writer.support.MailSendRecorder import com.few.api.domain.batch.article.writer.support.MailServiceArgsGenerator +import com.few.api.domain.common.vo.SendEventType +import com.few.api.domain.common.vo.SendType import jooq.jooq_dsl.tables.* import org.jooq.DSLContext import org.jooq.InsertSetMoreStep @@ -26,15 +26,12 @@ import java.time.LocalDateTime @Component class WorkBookSubscriberWriter( private val dslContext: DSLContext, - private val browseMemberEmailService: BrowseMemberEmailService, private val browseMemberReceiveArticlesService: BrowseMemberReceiveArticlesService, private val browseArticleContentsService: BrowseArticleContentsService, private val browseWorkbookLastDayColService: BrowseWorkbookLastDayColService, - private val sendArticleEmailService: SendArticleEmailService, ) { - /** * 구독자들에게 이메일을 전송하고 진행률을 업데이트한다. */ @@ -52,13 +49,14 @@ class WorkBookSubscriberWriter( val articleContents = browseArticleContentsService.execute(memberReceiveArticles.getArticleIds()) /** 조회한 데이터를 이용하여 이메일 전송을 위한 인자 생성 */ - val emailServiceArgs = MailServiceArgsGenerator( - LocalDate.now(), - items, - memberEmailRecords, - memberReceiveArticles, - articleContents - ).generate() + val emailServiceArgs = + MailServiceArgsGenerator( + LocalDate.now(), + items, + memberEmailRecords, + memberReceiveArticles, + articleContents, + ).generate() /** 이메일 전송 */ val mailSendRecorder = MailSendRecorder(emailServiceArgs) @@ -67,18 +65,19 @@ class WorkBookSubscriberWriter( try { val messageId = sendArticleEmailService.send(it.sendArticleEmailArgs) insertSendArticleQueries.add( - dslContext.insertInto(SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY) + dslContext + .insertInto(SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY) .set(SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY.MEMBER_ID, it.memberId) .set(SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY.ARTICLE_ID, it.articleId) .set(SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY.MESSAGE_ID, messageId) .set(SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY.EVENT_TYPE_CD, SendEventType.SEND.code) - .set(SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY.SEND_TYPE_CD, SendType.AWSSES.code) + .set(SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY.SEND_TYPE_CD, SendType.AWSSES.code), ) } catch (e: Exception) { mailSendRecorder.recordFail( it.memberId, it.workbookId, - e.message ?: "Unknown Error" + e.message ?: "Unknown Error", ) } } @@ -86,33 +85,36 @@ class WorkBookSubscriberWriter( /** 이메일 전송 결과에 따라 진행률 업데이트 및 구독 해지 처리를 위한 데이터 생성 */ val receiveLastDayRecords = - ReceiveLastArticleRecordFilter(items, workbooksMappedLastDayCol).filter() + ReceiveLastArticleRecordFilter(items, workbooksMappedLastDayCol) + .filter() .map { ReceiveLastArticleRecord(it.memberId, it.targetWorkBookId) } - val updateTargetMemberRecords = UpdateProgressRecordFilter( - items, - mailSendRecorder.getSuccessMemberIds(), - receiveLastDayRecords.getMemberIds() - ).filter().map { - UpdateProgressRecord( - it.memberId, - it.targetWorkBookId, - it.progress - ) - } + val updateTargetMemberRecords = + UpdateProgressRecordFilter( + items, + mailSendRecorder.getSuccessMemberIds(), + receiveLastDayRecords.getMemberIds(), + ).filter().map { + UpdateProgressRecord( + it.memberId, + it.targetWorkBookId, + it.progress, + ) + } /** 진행률 업데이트 */ val updateQueries = mutableListOf>() for (updateTargetMemberRecord in updateTargetMemberRecords) { updateQueries.add( - dslContext.update(Subscription.SUBSCRIPTION) + dslContext + .update(Subscription.SUBSCRIPTION) .set(Subscription.SUBSCRIPTION.PROGRESS, updateTargetMemberRecord.updatedProgress) .set(Subscription.SUBSCRIPTION.MODIFIED_AT, LocalDateTime.now()) .set(Subscription.SUBSCRIPTION.SEND_AT, LocalDateTime.now()) .where(Subscription.SUBSCRIPTION.MEMBER_ID.eq(updateTargetMemberRecord.memberId)) - .and(Subscription.SUBSCRIPTION.TARGET_WORKBOOK_ID.eq(updateTargetMemberRecord.targetWorkBookId)) + .and(Subscription.SUBSCRIPTION.TARGET_WORKBOOK_ID.eq(updateTargetMemberRecord.targetWorkBookId)), ) } dslContext.batch(updateQueries).execute() @@ -121,13 +123,14 @@ class WorkBookSubscriberWriter( val receiveLastDayQueries = mutableListOf>() for (receiveLastDayMember in receiveLastDayRecords) { receiveLastDayQueries.add( - dslContext.update(Subscription.SUBSCRIPTION) + dslContext + .update(Subscription.SUBSCRIPTION) .set(Subscription.SUBSCRIPTION.DELETED_AT, LocalDateTime.now()) .set(Subscription.SUBSCRIPTION.MODIFIED_AT, LocalDateTime.now()) .set(Subscription.SUBSCRIPTION.SEND_AT, LocalDateTime.now()) .set(Subscription.SUBSCRIPTION.UNSUBS_OPINION, "receive.all") .where(Subscription.SUBSCRIPTION.MEMBER_ID.eq(receiveLastDayMember.memberId)) - .and(Subscription.SUBSCRIPTION.TARGET_WORKBOOK_ID.eq(receiveLastDayMember.targetWorkBookId)) + .and(Subscription.SUBSCRIPTION.TARGET_WORKBOOK_ID.eq(receiveLastDayMember.targetWorkBookId)), ) } dslContext.batch(receiveLastDayQueries).execute() diff --git a/api/src/main/kotlin/com/few/api/domain/batch/article/writer/model/ReceiveLastArticleRecord.kt b/api/src/main/kotlin/com/few/api/domain/batch/article/writer/model/ReceiveLastArticleRecord.kt index fe4519dc4..4bedd86cc 100644 --- a/api/src/main/kotlin/com/few/api/domain/batch/article/writer/model/ReceiveLastArticleRecord.kt +++ b/api/src/main/kotlin/com/few/api/domain/batch/article/writer/model/ReceiveLastArticleRecord.kt @@ -1,8 +1,6 @@ package com.few.api.domain.batch.article.writer.model -fun List.getMemberIds(): Set { - return this.map { it.memberId }.toSet() -} +fun List.getMemberIds(): Set = this.map { it.memberId }.toSet() /** 학습지의 마지막 아티클을 받은 구독자 정보 */ data class ReceiveLastArticleRecord( diff --git a/api/src/main/kotlin/com/few/api/domain/batch/article/writer/model/ReceiveLastArticleRecordFilter.kt b/api/src/main/kotlin/com/few/api/domain/batch/article/writer/model/ReceiveLastArticleRecordFilter.kt index 4acbe199b..f0fa98177 100644 --- a/api/src/main/kotlin/com/few/api/domain/batch/article/writer/model/ReceiveLastArticleRecordFilter.kt +++ b/api/src/main/kotlin/com/few/api/domain/batch/article/writer/model/ReceiveLastArticleRecordFilter.kt @@ -9,12 +9,11 @@ class ReceiveLastArticleRecordFilter( private val items: List, private val workbooksMappedLastDayCol: Map, ) { - - fun filter(): List { - return items.filter { - it.targetWorkBookId in workbooksMappedLastDayCol.keys - }.filter { - (it.progress.toInt() + 1) == workbooksMappedLastDayCol[it.targetWorkBookId] - } - } + fun filter(): List = + items + .filter { + it.targetWorkBookId in workbooksMappedLastDayCol.keys + }.filter { + (it.progress.toInt() + 1) == workbooksMappedLastDayCol[it.targetWorkBookId] + } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/batch/article/writer/model/UpdateProgressRecordFilter.kt b/api/src/main/kotlin/com/few/api/domain/batch/article/writer/model/UpdateProgressRecordFilter.kt index b501f573e..b0d1e1e10 100644 --- a/api/src/main/kotlin/com/few/api/domain/batch/article/writer/model/UpdateProgressRecordFilter.kt +++ b/api/src/main/kotlin/com/few/api/domain/batch/article/writer/model/UpdateProgressRecordFilter.kt @@ -12,7 +12,9 @@ class UpdateProgressRecordFilter( private val successMemberIds: Set, private val receiveLastDayArticleRecordMemberIds: Set, ) { - fun filter(): List { - return items.filter { it.memberId in successMemberIds }.filterNot { it.memberId in receiveLastDayArticleRecordMemberIds } - } + fun filter(): List = + items + .filter { + it.memberId in successMemberIds + }.filterNot { it.memberId in receiveLastDayArticleRecordMemberIds } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/batch/article/writer/service/BrowseArticleContentsService.kt b/api/src/main/kotlin/com/few/api/domain/batch/article/writer/service/BrowseArticleContentsService.kt index c545de05b..08e06c76d 100644 --- a/api/src/main/kotlin/com/few/api/domain/batch/article/writer/service/BrowseArticleContentsService.kt +++ b/api/src/main/kotlin/com/few/api/domain/batch/article/writer/service/BrowseArticleContentsService.kt @@ -18,36 +18,34 @@ data class ArticleContent( val writerLink: URL, ) -fun List.peek(articleId: Long): ArticleContent { - return this.find { +fun List.peek(articleId: Long): ArticleContent = + this.find { it.id == articleId } ?: throw IllegalArgumentException("Cannot find article by articleId: $articleId") -} @Service class BrowseArticleContentsService( private val dslContext: DSLContext, ) { /** 구독자들이 받을 아티클 정보를 조회한다 */ - fun execute(articleIds: List): List { - return dslContext.select( - ArticleIfo.ARTICLE_IFO.ARTICLE_MST_ID.`as`(ArticleContent::id.name), - ArticleIfo.ARTICLE_IFO.CONTENT.`as`(ArticleContent::articleContent.name), - ArticleMst.ARTICLE_MST.TITLE.`as`(ArticleContent::articleTitle.name), - ArticleMst.ARTICLE_MST.CATEGORY_CD.`as`(ArticleContent::category.name), - DSL.jsonGetAttributeAsText(Member.MEMBER.DESCRIPTION, "name").`as`(ArticleContent::writerName.name), - DSL.jsonGetAttribute(Member.MEMBER.DESCRIPTION, "url").`as`(ArticleContent::writerLink.name) - ) - .from(ArticleIfo.ARTICLE_IFO) + fun execute(articleIds: List): List = + dslContext + .select( + ArticleIfo.ARTICLE_IFO.ARTICLE_MST_ID.`as`(ArticleContent::id.name), + ArticleIfo.ARTICLE_IFO.CONTENT.`as`(ArticleContent::articleContent.name), + ArticleMst.ARTICLE_MST.TITLE.`as`(ArticleContent::articleTitle.name), + ArticleMst.ARTICLE_MST.CATEGORY_CD.`as`(ArticleContent::category.name), + DSL.jsonGetAttributeAsText(Member.MEMBER.DESCRIPTION, "name").`as`(ArticleContent::writerName.name), + DSL.jsonGetAttribute(Member.MEMBER.DESCRIPTION, "url").`as`(ArticleContent::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.`in`(articleIds)) + 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.`in`(articleIds)) .and(ArticleIfo.ARTICLE_IFO.DELETED_AT.isNull) .fetchInto(ArticleContent::class.java) - } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/batch/article/writer/service/BrowseMemberEmailService.kt b/api/src/main/kotlin/com/few/api/domain/batch/article/writer/service/BrowseMemberEmailService.kt index fe98a3aac..0fca60c79 100644 --- a/api/src/main/kotlin/com/few/api/domain/batch/article/writer/service/BrowseMemberEmailService.kt +++ b/api/src/main/kotlin/com/few/api/domain/batch/article/writer/service/BrowseMemberEmailService.kt @@ -9,15 +9,14 @@ class BrowseMemberEmailService( private val dslContext: DSLContext, ) { /** 회원 ID를 기준으로 이메일을 조회한다.*/ - fun execute(memberIds: Set): Map { - return dslContext.select( - Member.MEMBER.ID, - Member.MEMBER.EMAIL - ) - .from(Member.MEMBER) + fun execute(memberIds: Set): Map = + dslContext + .select( + Member.MEMBER.ID, + Member.MEMBER.EMAIL, + ).from(Member.MEMBER) .where(Member.MEMBER.ID.`in`(memberIds)) .and(Member.MEMBER.DELETED_AT.isNull) .fetch() .intoMap(Member.MEMBER.ID, Member.MEMBER.EMAIL) - } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/batch/article/writer/service/BrowseMemberReceiveArticlesService.kt b/api/src/main/kotlin/com/few/api/domain/batch/article/writer/service/BrowseMemberReceiveArticlesService.kt index 646113948..f54b57bd8 100644 --- a/api/src/main/kotlin/com/few/api/domain/batch/article/writer/service/BrowseMemberReceiveArticlesService.kt +++ b/api/src/main/kotlin/com/few/api/domain/batch/article/writer/service/BrowseMemberReceiveArticlesService.kt @@ -13,17 +13,18 @@ data class MemberReceiveArticle( data class MemberReceiveArticles( val articles: List, ) { - fun getByWorkBookIdAndDayCol(workbookId: Long, dayCol: Long): MemberReceiveArticle { - return articles.find { + fun getByWorkBookIdAndDayCol( + workbookId: Long, + dayCol: Long, + ): MemberReceiveArticle = + articles.find { it.workbookId == workbookId && it.dayCol == dayCol } ?: throw IllegalArgumentException("Cannot find article by workbookId: $workbookId, dayCol: $dayCol") - } - fun getArticleIds(): List { - return articles.map { + fun getArticleIds(): List = + articles.map { it.articleId } - } } @Service @@ -31,25 +32,27 @@ class BrowseMemberReceiveArticlesService( private val dslContext: DSLContext, ) { /** 회원들이 구독한 학습지와 학습 진행 상태를 기준으로 회원들이 받아야 하는 아티클 정보를 조회한다 */ - fun execute(workbooks: Map>): MemberReceiveArticles { - return workbooks.entries.stream().map { (workbookId, progress) -> - val dayCols = progress.stream().map { it + 1L }.toList() - dslContext.select( - MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID.`as`( - MemberReceiveArticle::workbookId.name - ), - MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.ARTICLE_ID.`as`(MemberReceiveArticle::articleId.name), - MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DAY_COL.`as`(MemberReceiveArticle::dayCol.name) - ) - .from(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE) - .where( - MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID.eq(workbookId) - ) - .and(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DAY_COL.`in`(dayCols)) - .and(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DELETED_AT.isNull) - .fetchInto(MemberReceiveArticle::class.java) - }.flatMap { it.stream() }.toList().let { - MemberReceiveArticles(it) - } - } + fun execute(workbooks: Map>): MemberReceiveArticles = + workbooks.entries + .stream() + .map { (workbookId, progress) -> + val dayCols = progress.stream().map { it + 1L }.toList() + dslContext + .select( + MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID.`as`( + MemberReceiveArticle::workbookId.name, + ), + MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.ARTICLE_ID.`as`(MemberReceiveArticle::articleId.name), + MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DAY_COL.`as`(MemberReceiveArticle::dayCol.name), + ).from(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE) + .where( + MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID.eq(workbookId), + ).and(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DAY_COL.`in`(dayCols)) + .and(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DELETED_AT.isNull) + .fetchInto(MemberReceiveArticle::class.java) + }.flatMap { it.stream() } + .toList() + .let { + MemberReceiveArticles(it) + } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/batch/article/writer/service/BrowseWorkbookLastDayColService.kt b/api/src/main/kotlin/com/few/api/domain/batch/article/writer/service/BrowseWorkbookLastDayColService.kt index 95c5465bf..993dfbee6 100644 --- a/api/src/main/kotlin/com/few/api/domain/batch/article/writer/service/BrowseWorkbookLastDayColService.kt +++ b/api/src/main/kotlin/com/few/api/domain/batch/article/writer/service/BrowseWorkbookLastDayColService.kt @@ -10,19 +10,18 @@ class BrowseWorkbookLastDayColService( private val dslContext: DSLContext, ) { /** 워크북의 마지막 아티클의 day_col을 조회한다 */ - fun execute(workbookIds: Set): Map { - return dslContext.select( - MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID, - DSL.max(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DAY_COL) - ) - .from(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE) + fun execute(workbookIds: Set): Map = + dslContext + .select( + MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID, + DSL.max(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DAY_COL), + ).from(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE) .where(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID.`in`(workbookIds)) .and(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DELETED_AT.isNull) .groupBy(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID) .fetch() .intoMap( MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID, - DSL.max(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DAY_COL) + DSL.max(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DAY_COL), ) - } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/batch/article/writer/support/MailSendRecorder.kt b/api/src/main/kotlin/com/few/api/domain/batch/article/writer/support/MailSendRecorder.kt index 73cd7d541..aa3f927a1 100644 --- a/api/src/main/kotlin/com/few/api/domain/batch/article/writer/support/MailSendRecorder.kt +++ b/api/src/main/kotlin/com/few/api/domain/batch/article/writer/support/MailSendRecorder.kt @@ -6,26 +6,37 @@ private data class MemberMappedWorkBook( ) /** 이메일 전송 성공/실패 기록을 위한 정보 기록 클래스 */ -class MailSendRecorder(mailServiceArgs: List) { - +class MailSendRecorder( + mailServiceArgs: List, +) { private var records: MutableMap = mailServiceArgs.associate { MemberMappedWorkBook(it.memberId, it.workbookId) to true }.toMutableMap() private var failRecords: MutableMap> = mutableMapOf() - fun recordFail(memberId: Long, workbookId: Long, reason: String) { + fun recordFail( + memberId: Long, + workbookId: Long, + reason: String, + ) { records[MemberMappedWorkBook(memberId, workbookId)] = false - failRecords[reason] = failRecords.getOrDefault(reason, arrayListOf()).apply { - add(MemberMappedWorkBook(memberId, workbookId)) - } + failRecords[reason] = + failRecords.getOrDefault(reason, arrayListOf()).apply { + add(MemberMappedWorkBook(memberId, workbookId)) + } } - fun getSuccessMemberIds(): Set { - return records.filter { it.value }.keys.map { it.memberId }.toSet() - } + fun getSuccessMemberIds(): Set = + records + .filter { it.value } + .keys + .map { it.memberId } + .toSet() fun getExecutionResult(): Map { - val executionRecords = records.keys.groupBy { it.memberId } - .mapValues { it -> it.value.associate { it.workbookId to records[it] } } + val executionRecords = + records.keys + .groupBy { it.memberId } + .mapValues { it -> it.value.associate { it.workbookId to records[it] } } return if (failRecords.isNotEmpty()) { mapOf("records" to executionRecords, "fail" to failRecords) } else { diff --git a/api/src/main/kotlin/com/few/api/domain/batch/article/writer/support/MailServiceArgsGenerator.kt b/api/src/main/kotlin/com/few/api/domain/batch/article/writer/support/MailServiceArgsGenerator.kt index 9e6b775da..050fd9d2d 100644 --- a/api/src/main/kotlin/com/few/api/domain/batch/article/writer/support/MailServiceArgsGenerator.kt +++ b/api/src/main/kotlin/com/few/api/domain/batch/article/writer/support/MailServiceArgsGenerator.kt @@ -1,12 +1,12 @@ package com.few.api.domain.batch.article.writer.support -import com.few.api.domain.common.vo.CategoryType import com.few.api.domain.article.email.dto.Content import com.few.api.domain.article.email.dto.SendArticleEmailArgs import com.few.api.domain.batch.article.dto.WorkBookSubscriberItem import com.few.api.domain.batch.article.writer.service.ArticleContent import com.few.api.domain.batch.article.writer.service.MemberReceiveArticles import com.few.api.domain.batch.article.writer.service.peek +import com.few.api.domain.common.vo.CategoryType import java.net.URL import java.time.LocalDate @@ -32,25 +32,29 @@ class MailServiceArgsGenerator( /** * 이메일 전송을 위한 인자 생성 */ - fun generate(): List { - return items.map { + fun generate(): List = + items.map { val toEmail = memberEmailRecords[it.memberId]!! val memberArticle = memberReceiveArticles.getByWorkBookIdAndDayCol(it.targetWorkBookId, it.progress + 1) - val articleContent = articleContents.peek(memberArticle.articleId).let { article -> - Content( - articleLink = URL("https://www.fewletter.com/article/${memberArticle.articleId}"), - currentDate = date, - category = CategoryType.convertToDisplayName(article.category.toByte()), - articleDay = memberArticle.dayCol.toInt(), - articleTitle = article.articleTitle, - writerName = article.writerName, - writerLink = article.writerLink, - articleContent = article.articleContent, - problemLink = URL("https://www.fewletter.com/problem?articleId=${memberArticle.articleId}"), - unsubscribeLink = URL("https://www.fewletter.com/unsubscribe?user=${memberEmailRecords[it.memberId]}&workbookId=${it.targetWorkBookId}&articleId=${memberArticle.articleId}") - ) - } + val articleContent = + articleContents.peek(memberArticle.articleId).let { article -> + Content( + articleLink = URL("https://www.fewletter.com/article/${memberArticle.articleId}"), + currentDate = date, + category = CategoryType.convertToDisplayName(article.category.toByte()), + articleDay = memberArticle.dayCol.toInt(), + articleTitle = article.articleTitle, + writerName = article.writerName, + writerLink = article.writerLink, + articleContent = article.articleContent, + problemLink = URL("https://www.fewletter.com/problem?articleId=${memberArticle.articleId}"), + unsubscribeLink = + URL( + "https://www.fewletter.com/unsubscribe?user=${memberEmailRecords[it.memberId]}&workbookId=${it.targetWorkBookId}&articleId=${memberArticle.articleId}", + ), + ) + } MailServiceArg( it.memberId, @@ -60,12 +64,11 @@ class MailServiceArgsGenerator( toEmail, ARTICLE_SUBJECT_TEMPLATE.format( memberArticle.dayCol, - articleContent.articleTitle + articleContent.articleTitle, ), ARTICLE_TEMPLATE, - articleContent - ) + articleContent, + ), ) } - } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/batch/controller/BatchController.kt b/api/src/main/kotlin/com/few/api/domain/batch/controller/BatchController.kt index 4ec05dc9e..7ba473701 100644 --- a/api/src/main/kotlin/com/few/api/domain/batch/controller/BatchController.kt +++ b/api/src/main/kotlin/com/few/api/domain/batch/controller/BatchController.kt @@ -15,9 +15,10 @@ class BatchController( private val batchSendArticleEmailService: ApiBatchSendArticleEmailService, @Value("\${auth.batch}") private val auth: String, ) { - @PostMapping("/article") - fun batchArticle(@RequestParam(value = "auth") auth: String) { + fun batchArticle( + @RequestParam(value = "auth") auth: String, + ) { if (this.auth != auth) { throw IllegalAccessException("Invalid Permission") } diff --git a/api/src/main/kotlin/com/few/api/domain/batch/log/ApiBatchCallExecutionService.kt b/api/src/main/kotlin/com/few/api/domain/batch/log/ApiBatchCallExecutionService.kt index 60163a618..914a6593a 100644 --- a/api/src/main/kotlin/com/few/api/domain/batch/log/ApiBatchCallExecutionService.kt +++ b/api/src/main/kotlin/com/few/api/domain/batch/log/ApiBatchCallExecutionService.kt @@ -9,8 +9,12 @@ import org.springframework.stereotype.Service class ApiBatchCallExecutionService( private val dslContext: DSLContext, ) { - fun execute(status: Boolean, jsonDescription: String) { - dslContext.insertInto(BATCH_CALL_EXECUTION) + fun execute( + status: Boolean, + jsonDescription: String, + ) { + dslContext + .insertInto(BATCH_CALL_EXECUTION) .set(BATCH_CALL_EXECUTION.STATUS, status) .set(BATCH_CALL_EXECUTION.DESCRIPTION, JSON.valueOf(jsonDescription)) .execute() diff --git a/api/src/main/kotlin/com/few/api/domain/batch/log/ApiBatchServiceLogAspect.kt b/api/src/main/kotlin/com/few/api/domain/batch/log/ApiBatchServiceLogAspect.kt index c173ea8ad..951a4b7b2 100644 --- a/api/src/main/kotlin/com/few/api/domain/batch/log/ApiBatchServiceLogAspect.kt +++ b/api/src/main/kotlin/com/few/api/domain/batch/log/ApiBatchServiceLogAspect.kt @@ -21,7 +21,9 @@ class ApiBatchServiceLogAspect { fun requestLogging(joinPoint: ProceedingJoinPoint): Any? { val signature = joinPoint.signature val splitByDot = - signature.declaringTypeName.split(regex = "\\.".toRegex()).dropLastWhile { it.isEmpty() } + signature.declaringTypeName + .split(regex = "\\.".toRegex()) + .dropLastWhile { it.isEmpty() } .toTypedArray() val serviceName = splitByDot[splitByDot.size - 1] val args = joinPoint.args diff --git a/api/src/main/kotlin/com/few/api/domain/common/exception/ExternalIntegrationException.kt b/api/src/main/kotlin/com/few/api/domain/common/exception/ExternalIntegrationException.kt index 9764a02ca..1e6bf4dc6 100644 --- a/api/src/main/kotlin/com/few/api/domain/common/exception/ExternalIntegrationException.kt +++ b/api/src/main/kotlin/com/few/api/domain/common/exception/ExternalIntegrationException.kt @@ -18,9 +18,9 @@ class ExternalIntegrationException : IllegalStateException { constructor(code: String, cause: Throwable?) : super( ApiMessageSourceAccessor.getMessage( - code + code, ), - cause + cause, ) { this.code = code } diff --git a/api/src/main/kotlin/com/few/api/domain/common/exception/InsertException.kt b/api/src/main/kotlin/com/few/api/domain/common/exception/InsertException.kt index eff735d52..d7b1ce9af 100644 --- a/api/src/main/kotlin/com/few/api/domain/common/exception/InsertException.kt +++ b/api/src/main/kotlin/com/few/api/domain/common/exception/InsertException.kt @@ -18,9 +18,9 @@ class InsertException : IllegalArgumentException { constructor(code: String, cause: Throwable?) : super( ApiMessageSourceAccessor.getMessage( - code + code, ), - cause + cause, ) { this.code = code } diff --git a/api/src/main/kotlin/com/few/api/domain/common/exception/NotFoundException.kt b/api/src/main/kotlin/com/few/api/domain/common/exception/NotFoundException.kt index e115dcaa2..dae1d865c 100644 --- a/api/src/main/kotlin/com/few/api/domain/common/exception/NotFoundException.kt +++ b/api/src/main/kotlin/com/few/api/domain/common/exception/NotFoundException.kt @@ -18,9 +18,9 @@ class NotFoundException : IllegalArgumentException { constructor(code: String, cause: Throwable?) : super( ApiMessageSourceAccessor.getMessage( - code + code, ), - cause + cause, ) { this.code = code } diff --git a/api/src/main/kotlin/com/few/api/domain/common/lock/ApiLockAspect.kt b/api/src/main/kotlin/com/few/api/domain/common/lock/ApiLockAspect.kt index 10a321f0b..e4fe80c90 100644 --- a/api/src/main/kotlin/com/few/api/domain/common/lock/ApiLockAspect.kt +++ b/api/src/main/kotlin/com/few/api/domain/common/lock/ApiLockAspect.kt @@ -1,11 +1,11 @@ package com.few.api.domain.common.lock -import com.few.api.domain.subscription.usecase.dto.SubscribeWorkbookUseCaseIn import com.few.api.domain.subscription.repo.SubscriptionDao +import com.few.api.domain.subscription.usecase.dto.SubscribeWorkbookUseCaseIn import io.github.oshai.kotlinlogging.KotlinLogging +import org.aspectj.lang.JoinPoint import org.aspectj.lang.annotation.AfterReturning import org.aspectj.lang.annotation.AfterThrowing -import org.aspectj.lang.JoinPoint import org.aspectj.lang.annotation.Aspect import org.aspectj.lang.annotation.Before import org.aspectj.lang.annotation.Pointcut @@ -48,7 +48,10 @@ class ApiLockAspect( getSubscriptionMemberIdAndWorkBookIdLock(useCaseIn.memberId, useCaseIn.workbookId) } - private fun getSubscriptionMemberIdAndWorkBookIdLock(memberId: Long, workbookId: Long) { + private fun getSubscriptionMemberIdAndWorkBookIdLock( + memberId: Long, + workbookId: Long, + ) { subscriptionDao.getLock(memberId, workbookId).run { if (!this) { throw IllegalStateException("Already in progress for $memberId's subscription to $workbookId") @@ -79,8 +82,7 @@ class ApiLockAspect( } } - private fun getLockFor(joinPoint: JoinPoint) = - (joinPoint.signature as MethodSignature).method.getAnnotation(ApiLockFor::class.java) + private fun getLockFor(joinPoint: JoinPoint) = (joinPoint.signature as MethodSignature).method.getAnnotation(ApiLockFor::class.java) private fun releaseSubscriptionMemberIdAndWorkBookIdLockCase(joinPoint: JoinPoint) { if (joinPoint.args[0] is SubscribeWorkbookUseCaseIn) { @@ -97,7 +99,10 @@ class ApiLockAspect( releaseSubscriptionMemberIdAndWorkBookIdLock(useCaseIn.memberId, useCaseIn.workbookId) } - private fun releaseSubscriptionMemberIdAndWorkBookIdLock(memberId: Long, workbookId: Long) { + private fun releaseSubscriptionMemberIdAndWorkBookIdLock( + memberId: Long, + workbookId: Long, + ) { subscriptionDao.releaseLock(memberId, workbookId) log.debug { "Lock released for $memberId's subscription to $workbookId" } } diff --git a/api/src/main/kotlin/com/few/api/domain/common/repo/client/ApiRepoClient.kt b/api/src/main/kotlin/com/few/api/domain/common/repo/client/ApiRepoClient.kt index e4af637f5..f825cb7ba 100644 --- a/api/src/main/kotlin/com/few/api/domain/common/repo/client/ApiRepoClient.kt +++ b/api/src/main/kotlin/com/few/api/domain/common/repo/client/ApiRepoClient.kt @@ -1,7 +1,5 @@ package com.few.api.domain.common.repo.client -import web.client.DiscordBodyProperty -import web.client.Embed import com.few.api.domain.common.repo.client.dto.RepoAlterArgs import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.beans.factory.annotation.Value @@ -9,6 +7,8 @@ import org.springframework.http.HttpEntity import org.springframework.http.HttpMethod import org.springframework.stereotype.Service import org.springframework.web.client.RestTemplate +import web.client.DiscordBodyProperty +import web.client.Embed @Service class ApiRepoClient( @@ -18,20 +18,21 @@ class ApiRepoClient( private val log = KotlinLogging.logger {} fun announceRepoAlter(args: RepoAlterArgs) { - val embedsList = mutableListOf( - Embed( - title = "Exception", - description = "Slow Query Detected" + val embedsList = + mutableListOf( + Embed( + title = "Exception", + description = "Slow Query Detected", + ), ) - ) args.let { arg -> arg.requestURL.let { requestURL -> embedsList.add( Embed( title = "Request URL", - description = requestURL - ) + description = requestURL, + ), ) } @@ -39,24 +40,25 @@ class ApiRepoClient( embedsList.add( Embed( title = "Slow Query Detected", - description = query - ) + description = query, + ), ) } } - restTemplate.exchange( - discordWebhook, - HttpMethod.POST, - HttpEntity( - DiscordBodyProperty( - content = "😭 DB 이상 발생", - embeds = embedsList - ) - ), - String::class.java - ).let { res -> - log.info { "Discord webhook response: ${res.statusCode}" } - } + restTemplate + .exchange( + discordWebhook, + HttpMethod.POST, + HttpEntity( + DiscordBodyProperty( + content = "😭 DB 이상 발생", + embeds = embedsList, + ), + ), + String::class.java, + ).let { res -> + log.info { "Discord webhook response: ${res.statusCode}" } + } } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/common/repo/event/ApiSlowQueryEventListener.kt b/api/src/main/kotlin/com/few/api/domain/common/repo/event/ApiSlowQueryEventListener.kt index c3ced8a92..a0f5c3ccf 100644 --- a/api/src/main/kotlin/com/few/api/domain/common/repo/event/ApiSlowQueryEventListener.kt +++ b/api/src/main/kotlin/com/few/api/domain/common/repo/event/ApiSlowQueryEventListener.kt @@ -1,26 +1,26 @@ package com.few.api.domain.common.repo.event +import com.few.api.config.ApiThreadPoolConfig.Companion.DISCORD_HOOK_EVENT_POOL import com.few.api.domain.common.repo.client.ApiRepoClient import com.few.api.domain.common.repo.client.dto.RepoAlterArgs -import com.few.api.config.ApiThreadPoolConfig.Companion.DISCORD_HOOK_EVENT_POOL -import repo.event.SlowQueryEvent import org.slf4j.MDC import org.springframework.context.event.EventListener import org.springframework.scheduling.annotation.Async import org.springframework.stereotype.Component +import repo.event.SlowQueryEvent @Component class ApiSlowQueryEventListener( private val apiRepoClient: ApiRepoClient, ) { - @Async(value = DISCORD_HOOK_EVENT_POOL) @EventListener fun handleSlowQueryEvent(event: SlowQueryEvent) { - val args = RepoAlterArgs( - requestURL = MDC.get("Request-URL") ?: "", - query = event.slowQuery - ) + val args = + RepoAlterArgs( + requestURL = MDC.get("Request-URL") ?: "", + query = event.slowQuery, + ) apiRepoClient.announceRepoAlter(args) } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/common/vo/CategoryType.kt b/api/src/main/kotlin/com/few/api/domain/common/vo/CategoryType.kt index a46691fcf..92174b1a4 100644 --- a/api/src/main/kotlin/com/few/api/domain/common/vo/CategoryType.kt +++ b/api/src/main/kotlin/com/few/api/domain/common/vo/CategoryType.kt @@ -1,6 +1,9 @@ package com.few.api.domain.common.vo -enum class CategoryType(val code: Byte, val displayName: String) { +enum class CategoryType( + val code: Byte, + val displayName: String, +) { All(-1, "전체"), // Should not be stored in the DB ECONOMY(0, "경제"), IT(10, "IT"), @@ -11,20 +14,20 @@ enum class CategoryType(val code: Byte, val displayName: String) { ; companion object { - fun fromCode(code: Byte): CategoryType? { - return entries.find { it.code == code } - } + fun fromCode(code: Byte): CategoryType? = entries.find { it.code == code } - fun fromName(displayName: String): CategoryType? { - return entries.find { it.displayName.equals(displayName) } - } + fun fromName(displayName: String): CategoryType? = entries.find { it.displayName.equals(displayName) } - fun convertToCode(displayName: String): Byte { - return entries.find { it.name == displayName }?.code ?: throw IllegalArgumentException("Invalid category name $displayName") - } + fun convertToCode(displayName: String): Byte = + entries + .find { + it.name == displayName + }?.code ?: throw IllegalArgumentException("Invalid category name $displayName") - fun convertToDisplayName(code: Byte): String { - return entries.find { it.code == code }?.displayName ?: throw IllegalArgumentException("Invalid category code $code") - } + fun convertToDisplayName(code: Byte): String = + entries + .find { + it.code == code + }?.displayName ?: throw IllegalArgumentException("Invalid category code $code") } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/common/vo/DayCode.kt b/api/src/main/kotlin/com/few/api/domain/common/vo/DayCode.kt index af3956472..246315fd6 100644 --- a/api/src/main/kotlin/com/few/api/domain/common/vo/DayCode.kt +++ b/api/src/main/kotlin/com/few/api/domain/common/vo/DayCode.kt @@ -1,6 +1,9 @@ package com.few.api.domain.common.vo -enum class DayCode(val code: String, val days: String) { +enum class DayCode( + val code: String, + val days: String, +) { MON("0000001", "월"), TUE("0000010", "화"), MON_TUE("0000011", "월,화"), @@ -129,9 +132,8 @@ enum class DayCode(val code: String, val days: String) { TUE_WED_THU_FRI_SAT_SUN("1111110", "화,수,목,금,토,일"), MON_TUE_WED_THU_FRI_SAT_SUN("1111111", "월,화,수,목,금,토,일"), ; + companion object { - fun fromCode(code: String): DayCode { - return entries.first { it.code == code } - } + fun fromCode(code: String): DayCode = entries.first { it.code == code } } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/common/vo/EmailLogEventType.kt b/api/src/main/kotlin/com/few/api/domain/common/vo/EmailLogEventType.kt index ecfde6f1c..2a850858a 100644 --- a/api/src/main/kotlin/com/few/api/domain/common/vo/EmailLogEventType.kt +++ b/api/src/main/kotlin/com/few/api/domain/common/vo/EmailLogEventType.kt @@ -1,6 +1,9 @@ package com.few.api.domain.common.vo -enum class EmailLogEventType(val code: Byte, val type: String) { +enum class EmailLogEventType( + val code: Byte, + val type: String, +) { OPEN(0, "open"), DELIVERY(1, "delivery"), CLICK(2, "click"), @@ -9,12 +12,8 @@ enum class EmailLogEventType(val code: Byte, val type: String) { ; companion object { - fun fromType(type: String): EmailLogEventType? { - return entries.find { it.type == type.lowercase() } - } + fun fromType(type: String): EmailLogEventType? = entries.find { it.type == type.lowercase() } - fun fromCode(code: Byte): EmailLogEventType? { - return entries.find { it.code == code } - } + fun fromCode(code: Byte): EmailLogEventType? = entries.find { it.code == code } } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/common/vo/MemberType.kt b/api/src/main/kotlin/com/few/api/domain/common/vo/MemberType.kt index f0bd5b9be..0e5490a27 100644 --- a/api/src/main/kotlin/com/few/api/domain/common/vo/MemberType.kt +++ b/api/src/main/kotlin/com/few/api/domain/common/vo/MemberType.kt @@ -1,6 +1,9 @@ package com.few.api.domain.common.vo -enum class MemberType(val code: Byte, val displayName: String) { +enum class MemberType( + val code: Byte, + val displayName: String, +) { PREAUTH(30, "가입대기멤버"), NORMAL(60, "일반멤버"), ADMIN(0, "어드민멤버"), @@ -8,8 +11,6 @@ enum class MemberType(val code: Byte, val displayName: String) { ; companion object { - fun fromCode(code: Byte): MemberType? { - return entries.find { it.code == code } - } + fun fromCode(code: Byte): MemberType? = entries.find { it.code == code } } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/common/vo/SendEventType.kt b/api/src/main/kotlin/com/few/api/domain/common/vo/SendEventType.kt index cc8b6f520..5f87bca7e 100644 --- a/api/src/main/kotlin/com/few/api/domain/common/vo/SendEventType.kt +++ b/api/src/main/kotlin/com/few/api/domain/common/vo/SendEventType.kt @@ -1,6 +1,9 @@ package com.few.api.domain.common.vo -enum class SendEventType(val code: Byte, val type: String) { +enum class SendEventType( + val code: Byte, + val type: String, +) { OPEN(0, "open"), DELIVERY(1, "delivery"), CLICK(2, "click"), @@ -9,12 +12,8 @@ enum class SendEventType(val code: Byte, val type: String) { ; companion object { - fun fromType(type: String): SendEventType? { - return entries.find { it.type == type } - } + fun fromType(type: String): SendEventType? = entries.find { it.type == type } - fun fromCode(code: Byte): SendEventType? { - return entries.find { it.code == code } - } + fun fromCode(code: Byte): SendEventType? = entries.find { it.code == code } } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/common/vo/SendType.kt b/api/src/main/kotlin/com/few/api/domain/common/vo/SendType.kt index 318b21275..e3fa1a8df 100644 --- a/api/src/main/kotlin/com/few/api/domain/common/vo/SendType.kt +++ b/api/src/main/kotlin/com/few/api/domain/common/vo/SendType.kt @@ -1,13 +1,13 @@ package com.few.api.domain.common.vo -enum class SendType(val code: Byte) { +enum class SendType( + val code: Byte, +) { EMAIL(0), AWSSES(1), ; companion object { - fun fromCode(code: Byte): SendType { - return entries.first { it.code == code } - } + fun fromCode(code: Byte): SendType = entries.first { it.code == code } } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/common/vo/ViewCategory.kt b/api/src/main/kotlin/com/few/api/domain/common/vo/ViewCategory.kt index 6be015ed1..60bf79fde 100644 --- a/api/src/main/kotlin/com/few/api/domain/common/vo/ViewCategory.kt +++ b/api/src/main/kotlin/com/few/api/domain/common/vo/ViewCategory.kt @@ -1,13 +1,13 @@ package com.few.api.domain.common.vo -enum class ViewCategory(val viewName: String) { +enum class ViewCategory( + val viewName: String, +) { MAIN_CARD("mainCard"), MY_PAGE("myPage"), ; companion object { - fun fromViewName(viewName: String): ViewCategory? { - return ViewCategory.entries.find { it.viewName == viewName } - } + fun fromViewName(viewName: String): ViewCategory? = ViewCategory.entries.find { it.viewName == viewName } } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/common/vo/WorkBookCategory.kt b/api/src/main/kotlin/com/few/api/domain/common/vo/WorkBookCategory.kt index a5e96a8ba..5ce6e6d2d 100644 --- a/api/src/main/kotlin/com/few/api/domain/common/vo/WorkBookCategory.kt +++ b/api/src/main/kotlin/com/few/api/domain/common/vo/WorkBookCategory.kt @@ -1,6 +1,10 @@ package com.few.api.domain.common.vo -enum class WorkBookCategory(val code: Byte, val parameterName: String, val displayName: String) { +enum class WorkBookCategory( + val code: Byte, + val parameterName: String, + val displayName: String, +) { All(-1, "all", "전체"), ECONOMY(0, "economy", "경제"), IT(10, "it", "IT"), @@ -11,12 +15,10 @@ enum class WorkBookCategory(val code: Byte, val parameterName: String, val displ ; companion object { - fun fromCode(code: Byte): WorkBookCategory? { - return entries.find { it.code == code } - } + fun fromCode(code: Byte): WorkBookCategory? = entries.find { it.code == code } - fun convertToCode(parameterName: String): Byte { - return entries.find { it.parameterName == parameterName }?.code ?: throw IllegalArgumentException("Invalid category name $parameterName") - } + fun convertToCode(parameterName: String): Byte = + entries.find { it.parameterName == parameterName }?.code + ?: throw IllegalArgumentException("Invalid category name $parameterName") } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/common/vo/WorkBookStatus.kt b/api/src/main/kotlin/com/few/api/domain/common/vo/WorkBookStatus.kt index 235cdb147..29fff5708 100644 --- a/api/src/main/kotlin/com/few/api/domain/common/vo/WorkBookStatus.kt +++ b/api/src/main/kotlin/com/few/api/domain/common/vo/WorkBookStatus.kt @@ -9,8 +9,6 @@ enum class WorkBookStatus { /** * status ture -> ACTIVE, false -> DONE */ - fun fromStatus(status: Boolean): WorkBookStatus { - return if (status) ACTIVE else DONE - } + fun fromStatus(status: Boolean): WorkBookStatus = if (status) ACTIVE else DONE } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/common/web/config/ApiWebConfigurer.kt b/api/src/main/kotlin/com/few/api/domain/common/web/config/ApiWebConfigurer.kt index 828cbdf76..ba61778a5 100644 --- a/api/src/main/kotlin/com/few/api/domain/common/web/config/ApiWebConfigurer.kt +++ b/api/src/main/kotlin/com/few/api/domain/common/web/config/ApiWebConfigurer.kt @@ -7,7 +7,6 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer @Configuration class ApiWebConfigurer : WebMvcConfigurer { - override fun addFormatters(registry: FormatterRegistry) { registry.addConverter(WorkBookCategoryConverter()) registry.addConverter(ViewConverter()) diff --git a/api/src/main/kotlin/com/few/api/domain/common/web/config/converter/DayCodeConverter.kt b/api/src/main/kotlin/com/few/api/domain/common/web/config/converter/DayCodeConverter.kt index 4e4f2ea80..ba57bc347 100644 --- a/api/src/main/kotlin/com/few/api/domain/common/web/config/converter/DayCodeConverter.kt +++ b/api/src/main/kotlin/com/few/api/domain/common/web/config/converter/DayCodeConverter.kt @@ -4,7 +4,5 @@ import com.few.api.domain.common.vo.DayCode import org.springframework.core.convert.converter.Converter class DayCodeConverter : Converter { - override fun convert(source: String): DayCode { - return DayCode.fromCode(source) - } + override fun convert(source: String): DayCode = DayCode.fromCode(source) } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/common/web/config/converter/EmailLogEventTypeConverter.kt b/api/src/main/kotlin/com/few/api/domain/common/web/config/converter/EmailLogEventTypeConverter.kt index 8ccfcfde4..8b5a084ec 100644 --- a/api/src/main/kotlin/com/few/api/domain/common/web/config/converter/EmailLogEventTypeConverter.kt +++ b/api/src/main/kotlin/com/few/api/domain/common/web/config/converter/EmailLogEventTypeConverter.kt @@ -4,8 +4,7 @@ import com.few.api.domain.common.vo.EmailLogEventType import org.springframework.core.convert.converter.Converter class EmailLogEventTypeConverter : Converter { - override fun convert(source: String): EmailLogEventType { - return EmailLogEventType.fromType(source) + override fun convert(source: String): EmailLogEventType = + EmailLogEventType.fromType(source) ?: throw IllegalArgumentException("EmailLogEventType not found. type=$source") - } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/common/web/config/converter/SendTypeConverter.kt b/api/src/main/kotlin/com/few/api/domain/common/web/config/converter/SendTypeConverter.kt index 894a9e3ff..9562556ad 100644 --- a/api/src/main/kotlin/com/few/api/domain/common/web/config/converter/SendTypeConverter.kt +++ b/api/src/main/kotlin/com/few/api/domain/common/web/config/converter/SendTypeConverter.kt @@ -4,7 +4,5 @@ import com.few.api.domain.common.vo.SendType import org.springframework.core.convert.converter.Converter class SendTypeConverter : Converter { - override fun convert(source: String): SendType { - return SendType.fromCode(source.toByte()) - } + override fun convert(source: String): SendType = SendType.fromCode(source.toByte()) } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/common/web/config/converter/ViewConverter.kt b/api/src/main/kotlin/com/few/api/domain/common/web/config/converter/ViewConverter.kt index eb426ecc2..e9d74a7d7 100644 --- a/api/src/main/kotlin/com/few/api/domain/common/web/config/converter/ViewConverter.kt +++ b/api/src/main/kotlin/com/few/api/domain/common/web/config/converter/ViewConverter.kt @@ -4,8 +4,5 @@ import com.few.api.domain.common.vo.ViewCategory import org.springframework.core.convert.converter.Converter class ViewConverter : Converter { - - override fun convert(source: String): ViewCategory? { - return ViewCategory.fromViewName(source) - } + override fun convert(source: String): ViewCategory? = ViewCategory.fromViewName(source) } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/common/web/config/converter/WorkBookCategoryConverter.kt b/api/src/main/kotlin/com/few/api/domain/common/web/config/converter/WorkBookCategoryConverter.kt index c3a056d3d..8bac055e8 100644 --- a/api/src/main/kotlin/com/few/api/domain/common/web/config/converter/WorkBookCategoryConverter.kt +++ b/api/src/main/kotlin/com/few/api/domain/common/web/config/converter/WorkBookCategoryConverter.kt @@ -4,8 +4,5 @@ import com.few.api.domain.common.vo.WorkBookCategory import org.springframework.core.convert.converter.Converter class WorkBookCategoryConverter : Converter { - - override fun convert(source: String): WorkBookCategory? { - return WorkBookCategory.fromCode(source.toByte()) - } + override fun convert(source: String): WorkBookCategory? = WorkBookCategory.fromCode(source.toByte()) } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/log/controller/ApiLogController.kt b/api/src/main/kotlin/com/few/api/domain/log/controller/ApiLogController.kt index 6d2924b79..46dcfe7cc 100644 --- a/api/src/main/kotlin/com/few/api/domain/log/controller/ApiLogController.kt +++ b/api/src/main/kotlin/com/few/api/domain/log/controller/ApiLogController.kt @@ -1,20 +1,20 @@ package com.few.api.domain.log.controller +import com.few.api.domain.common.vo.EmailLogEventType +import com.few.api.domain.log.controller.request.ApiLogRequest import com.few.api.domain.log.controller.request.EmailLogRequest -import com.few.api.domain.log.usecase.AddApiLogUseCase -import com.few.api.domain.log.usecase.AddEmailLogUseCase import com.few.api.domain.log.dto.AddApiLogUseCaseIn import com.few.api.domain.log.dto.AddEmailLogUseCaseIn -import com.few.api.domain.common.vo.EmailLogEventType -import com.few.api.domain.log.controller.request.ApiLogRequest -import web.ApiResponse -import web.ApiResponseGenerator +import com.few.api.domain.log.usecase.AddApiLogUseCase +import com.few.api.domain.log.usecase.AddEmailLogUseCase import org.springframework.http.HttpStatus import org.springframework.validation.annotation.Validated import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController +import web.ApiResponse +import web.ApiResponseGenerator @Validated @RestController @@ -24,7 +24,9 @@ class ApiLogController( private val addEmailLogUseCase: AddEmailLogUseCase, ) { @PostMapping - fun addApiLog(@RequestBody request: ApiLogRequest): ApiResponse { + fun addApiLog( + @RequestBody request: ApiLogRequest, + ): ApiResponse { AddApiLogUseCaseIn(request.history).let { addApiLogUseCase.execute(it) } @@ -33,13 +35,16 @@ class ApiLogController( } @PostMapping("/email/articles") - fun addEmailLog(@RequestBody request: EmailLogRequest): ApiResponse { + fun addEmailLog( + @RequestBody request: EmailLogRequest, + ): ApiResponse { AddEmailLogUseCaseIn( - eventType = EmailLogEventType.fromType(request.eventType) - ?: throw IllegalArgumentException("EmailLogEventType not found. type=${request.eventType}"), + eventType = + EmailLogEventType.fromType(request.eventType) + ?: throw IllegalArgumentException("EmailLogEventType not found. type=${request.eventType}"), messageId = request.messageId, destination = request.destination, - mailTimestamp = request.mailTimestamp + mailTimestamp = request.mailTimestamp, ).let { addEmailLogUseCase.execute(it) } diff --git a/api/src/main/kotlin/com/few/api/domain/log/repo/LogIfoDao.kt b/api/src/main/kotlin/com/few/api/domain/log/repo/LogIfoDao.kt index f6a72e822..873db6501 100644 --- a/api/src/main/kotlin/com/few/api/domain/log/repo/LogIfoDao.kt +++ b/api/src/main/kotlin/com/few/api/domain/log/repo/LogIfoDao.kt @@ -10,11 +10,11 @@ import org.springframework.stereotype.Repository class LogIfoDao( private val dslContext: DSLContext, ) { - - fun insertLogIfo(command: InsertLogCommand): Long? { - return dslContext.insertInto(LogIfo.LOG_IFO) + fun insertLogIfo(command: InsertLogCommand): Long? = + dslContext + .insertInto(LogIfo.LOG_IFO) .set(LogIfo.LOG_IFO.HISTORY, JSON.valueOf(command.history)) .returning(LogIfo.LOG_IFO.ID) - .fetchOne()?.id - } + .fetchOne() + ?.id } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/log/repo/SendArticleEventHistoryDao.kt b/api/src/main/kotlin/com/few/api/domain/log/repo/SendArticleEventHistoryDao.kt index 483b8f26e..d24526ee7 100644 --- a/api/src/main/kotlin/com/few/api/domain/log/repo/SendArticleEventHistoryDao.kt +++ b/api/src/main/kotlin/com/few/api/domain/log/repo/SendArticleEventHistoryDao.kt @@ -11,9 +11,9 @@ import org.springframework.stereotype.Repository class SendArticleEventHistoryDao( private val dslContext: DSLContext, ) { - fun insertEvent(command: InsertEventCommand) { - dslContext.insertInto(SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY) + dslContext + .insertInto(SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY) .set(SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY.MEMBER_ID, command.memberId) .set(SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY.ARTICLE_ID, command.articleId) .set(SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY.MESSAGE_ID, command.messageId) @@ -22,27 +22,27 @@ class SendArticleEventHistoryDao( .execute() } - fun selectEventByMessageId(query: SelectEventByMessageIdAndEventTypeQuery): SendArticleEventHistoryRecord? { - return dslContext.select( - SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY.MEMBER_ID.`as`( - SendArticleEventHistoryRecord::memberId.name - ), - SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY.ARTICLE_ID.`as`( - SendArticleEventHistoryRecord::articleId.name - ), - SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY.MESSAGE_ID.`as`( - SendArticleEventHistoryRecord::messageId.name - ), - SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY.EVENT_TYPE_CD.`as`( - SendArticleEventHistoryRecord::eventType.name - ), - SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY.SEND_TYPE_CD.`as`( - SendArticleEventHistoryRecord::sendType.name - ) - ) - .from(SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY) + fun selectEventByMessageId(query: SelectEventByMessageIdAndEventTypeQuery): SendArticleEventHistoryRecord? = + dslContext + .select( + SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY.MEMBER_ID.`as`( + SendArticleEventHistoryRecord::memberId.name, + ), + SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY.ARTICLE_ID.`as`( + SendArticleEventHistoryRecord::articleId.name, + ), + SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY.MESSAGE_ID.`as`( + SendArticleEventHistoryRecord::messageId.name, + ), + SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY.EVENT_TYPE_CD.`as`( + SendArticleEventHistoryRecord::eventType.name, + ), + SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY.SEND_TYPE_CD.`as`( + SendArticleEventHistoryRecord::sendType.name, + ), + ).from(SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY) .where(SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY.MESSAGE_ID.eq(query.messageId)) .and(SendArticleEventHistory.SEND_ARTICLE_EVENT_HISTORY.EVENT_TYPE_CD.eq(query.eventType)) - .fetchOne()?.into(SendArticleEventHistoryRecord::class.java) - } + .fetchOne() + ?.into(SendArticleEventHistoryRecord::class.java) } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/log/usecase/AddEmailLogUseCase.kt b/api/src/main/kotlin/com/few/api/domain/log/usecase/AddEmailLogUseCase.kt index 985e9a736..73ec14f6b 100644 --- a/api/src/main/kotlin/com/few/api/domain/log/usecase/AddEmailLogUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/log/usecase/AddEmailLogUseCase.kt @@ -1,13 +1,13 @@ package com.few.api.domain.log.usecase import com.few.api.domain.common.exception.NotFoundException +import com.few.api.domain.common.vo.EmailLogEventType import com.few.api.domain.log.dto.AddEmailLogUseCaseIn import com.few.api.domain.log.repo.SendArticleEventHistoryDao import com.few.api.domain.log.repo.command.InsertEventCommand import com.few.api.domain.log.repo.query.SelectEventByMessageIdAndEventTypeQuery import com.few.api.domain.member.repo.MemberDao import com.few.api.domain.member.repo.query.SelectMemberByEmailQuery -import com.few.api.domain.common.vo.EmailLogEventType import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional @@ -18,29 +18,31 @@ class AddEmailLogUseCase( ) { @Transactional fun execute(useCaseIn: AddEmailLogUseCaseIn) { - val (memberId, _, _, _) = memberDao.selectMemberByEmail( - SelectMemberByEmailQuery(useCaseIn.destination[0]) - ) ?: throw NotFoundException("member.notfound.email") + val (memberId, _, _, _) = + memberDao.selectMemberByEmail( + SelectMemberByEmailQuery(useCaseIn.destination[0]), + ) ?: throw NotFoundException("member.notfound.email") val record = sendArticleEventHistoryDao.selectEventByMessageId( SelectEventByMessageIdAndEventTypeQuery( useCaseIn.messageId, - EmailLogEventType.SEND.code - ) + EmailLogEventType.SEND.code, + ), ) ?: throw IllegalStateException("event is not found") - sendArticleEventHistoryDao.insertEvent( - InsertEventCommand( - memberId = memberId, - articleId = record.articleId, - messageId = record.messageId, - eventType = useCaseIn.eventType.code, - sendType = record.sendType - ) - ).let { + sendArticleEventHistoryDao + .insertEvent( + InsertEventCommand( + memberId = memberId, + articleId = record.articleId, + messageId = record.messageId, + eventType = useCaseIn.eventType.code, + sendType = record.sendType, + ), + ).let { // TODO("다른 이벤트는 필요시 추가한다.") - } + } } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/member/controller/MemberController.kt b/api/src/main/kotlin/com/few/api/domain/member/controller/MemberController.kt index 3fc89d7b8..950916a78 100644 --- a/api/src/main/kotlin/com/few/api/domain/member/controller/MemberController.kt +++ b/api/src/main/kotlin/com/few/api/domain/member/controller/MemberController.kt @@ -1,17 +1,15 @@ package com.few.api.domain.member.controller +import com.few.api.domain.member.controller.request.SaveMemberRequest +import com.few.api.domain.member.controller.request.TokenRequest +import com.few.api.domain.member.controller.response.SaveMemberResponse +import com.few.api.domain.member.controller.response.TokenResponse import com.few.api.domain.member.usecase.DeleteMemberUseCase import com.few.api.domain.member.usecase.SaveMemberUseCase import com.few.api.domain.member.usecase.TokenUseCase import com.few.api.domain.member.usecase.dto.DeleteMemberUseCaseIn import com.few.api.domain.member.usecase.dto.SaveMemberUseCaseIn import com.few.api.domain.member.usecase.dto.TokenUseCaseIn -import com.few.api.domain.member.controller.request.SaveMemberRequest -import com.few.api.domain.member.controller.request.TokenRequest -import com.few.api.domain.member.controller.response.SaveMemberResponse -import com.few.api.domain.member.controller.response.TokenResponse -import web.ApiResponse -import web.ApiResponseGenerator import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.security.core.annotation.AuthenticationPrincipal @@ -23,6 +21,8 @@ import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController import security.TokenUserDetails +import web.ApiResponse +import web.ApiResponseGenerator @Validated @RestController @@ -36,14 +36,15 @@ class MemberController( fun saveMember( @RequestBody request: SaveMemberRequest, ): ApiResponse> { - val useCaseOut = SaveMemberUseCaseIn( - email = request.email - ).let { - saveMemberUseCase.execute(it) - } + val useCaseOut = + SaveMemberUseCaseIn( + email = request.email, + ).let { + saveMemberUseCase.execute(it) + } SaveMemberResponse( - isSendAuth = useCaseOut.isSendAuthEmail + isSendAuth = useCaseOut.isSendAuthEmail, ).let { return ApiResponseGenerator.success(it, HttpStatus.OK) } @@ -54,11 +55,12 @@ class MemberController( @AuthenticationPrincipal userDetails: TokenUserDetails, ): ApiResponse { val memberId = userDetails.username.toLong() - val useCaseOut = DeleteMemberUseCaseIn( - memberId = memberId - ).let { - deleteMemberUseCase.execute(it) - } + val useCaseOut = + DeleteMemberUseCaseIn( + memberId = memberId, + ).let { + deleteMemberUseCase.execute(it) + } return ApiResponseGenerator.success(HttpStatus.OK) } @@ -70,19 +72,20 @@ class MemberController( @RequestParam(value = "rt", required = false) rt: Long?, @RequestBody request: TokenRequest?, ): ApiResponse> { - val useCaseOut = TokenUseCaseIn( - token = token, - at = at, - rt = rt, - refreshToken = request?.refreshToken - ).let { - tokenUseCase.execute(it) - } + val useCaseOut = + TokenUseCaseIn( + token = token, + at = at, + rt = rt, + refreshToken = request?.refreshToken, + ).let { + tokenUseCase.execute(it) + } TokenResponse( accessToken = useCaseOut.accessToken, refreshToken = useCaseOut.refreshToken, - isLogin = useCaseOut.isLogin + isLogin = useCaseOut.isLogin, ).let { return ApiResponseGenerator.success(it, HttpStatus.OK) } diff --git a/api/src/main/kotlin/com/few/api/domain/member/email/SendAuthEmailService.kt b/api/src/main/kotlin/com/few/api/domain/member/email/SendAuthEmailService.kt index 2a84a3818..1417db7bd 100644 --- a/api/src/main/kotlin/com/few/api/domain/member/email/SendAuthEmailService.kt +++ b/api/src/main/kotlin/com/few/api/domain/member/email/SendAuthEmailService.kt @@ -1,10 +1,10 @@ package com.few.api.domain.member.email -import email.EmailSender -import email.provider.EmailSendProvider import com.few.api.domain.member.email.dto.SendAuthEmailArgs import email.EmailContext +import email.EmailSender import email.EmailTemplateProcessor +import email.provider.EmailSendProvider import org.springframework.boot.autoconfigure.mail.MailProperties import org.springframework.stereotype.Component @@ -14,7 +14,6 @@ class SendAuthEmailService( emailSendProvider: EmailSendProvider, private val emailTemplateProcessor: EmailTemplateProcessor, ) : EmailSender(mailProperties, emailSendProvider) { - override fun getHtml(args: SendAuthEmailArgs): String { val context = EmailContext() context.setVariable("email", args.content.email) diff --git a/api/src/main/kotlin/com/few/api/domain/member/exception/NotValidTokenException.kt b/api/src/main/kotlin/com/few/api/domain/member/exception/NotValidTokenException.kt index 445ac4dfe..7cdef1299 100644 --- a/api/src/main/kotlin/com/few/api/domain/member/exception/NotValidTokenException.kt +++ b/api/src/main/kotlin/com/few/api/domain/member/exception/NotValidTokenException.kt @@ -18,9 +18,9 @@ class NotValidTokenException : IllegalStateException { constructor(code: String, cause: Throwable?) : super( ApiMessageSourceAccessor.getMessage( - code + code, ), - cause + cause, ) { this.code = code } diff --git a/api/src/main/kotlin/com/few/api/domain/member/repo/MemberCacheManager.kt b/api/src/main/kotlin/com/few/api/domain/member/repo/MemberCacheManager.kt index ee3b3a336..e14fafd43 100644 --- a/api/src/main/kotlin/com/few/api/domain/member/repo/MemberCacheManager.kt +++ b/api/src/main/kotlin/com/few/api/domain/member/repo/MemberCacheManager.kt @@ -11,7 +11,6 @@ import javax.cache.Cache class MemberCacheManager( private val cacheManager: CacheManager, ) { - private var selectWriterCache: Cache = cacheManager.getCache(SELECT_WRITER_CACHE)?.nativeCache as Cache fun getAllWriterValues(): List { diff --git a/api/src/main/kotlin/com/few/api/domain/member/repo/MemberDao.kt b/api/src/main/kotlin/com/few/api/domain/member/repo/MemberDao.kt index a8bebe4c8..2b73e4ff9 100644 --- a/api/src/main/kotlin/com/few/api/domain/member/repo/MemberDao.kt +++ b/api/src/main/kotlin/com/few/api/domain/member/repo/MemberDao.kt @@ -1,18 +1,18 @@ package com.few.api.domain.member.repo -import com.few.api.domain.common.vo.MemberType import com.few.api.config.ApiLocalCacheConfig.Companion.LOCAL_CM import com.few.api.config.ApiLocalCacheConfig.Companion.SELECT_WRITER_CACHE +import com.few.api.domain.common.vo.MemberType import com.few.api.domain.member.repo.command.DeleteMemberCommand import com.few.api.domain.member.repo.command.InsertMemberCommand import com.few.api.domain.member.repo.command.UpdateDeletedMemberTypeCommand import com.few.api.domain.member.repo.command.UpdateMemberTypeCommand import com.few.api.domain.member.repo.query.* +import com.few.api.domain.member.repo.record.MemberEmailAndTypeRecord import com.few.api.domain.member.repo.record.MemberIdAndIsDeletedRecord import com.few.api.domain.member.repo.record.MemberRecord -import com.few.api.domain.member.repo.record.MemberEmailAndTypeRecord -import com.few.api.domain.member.repo.record.WriterRecordMappedWorkbook import com.few.api.domain.member.repo.record.WriterRecord +import com.few.api.domain.member.repo.record.WriterRecordMappedWorkbook import jooq.jooq_dsl.tables.ArticleMst import jooq.jooq_dsl.tables.MappingWorkbookArticle import jooq.jooq_dsl.tables.Member @@ -29,23 +29,22 @@ class MemberDao( private val dslContext: DSLContext, private val cacheManager: MemberCacheManager, ) { - @Cacheable(key = "#query.writerId", cacheManager = LOCAL_CM, cacheNames = [SELECT_WRITER_CACHE]) - fun selectWriter(query: SelectWriterQuery): WriterRecord? { - return selectWriterQuery(query) + fun selectWriter(query: SelectWriterQuery): WriterRecord? = + selectWriterQuery(query) .fetchOneInto(WriterRecord::class.java) - } - fun selectWriterQuery(query: SelectWriterQuery) = dslContext.select( - Member.MEMBER.ID.`as`(WriterRecord::writerId.name), - DSL.jsonGetAttributeAsText(Member.MEMBER.DESCRIPTION, "name").`as`(WriterRecord::name.name), - DSL.jsonGetAttribute(Member.MEMBER.DESCRIPTION, "url").`as`(WriterRecord::url.name), - DSL.jsonGetAttribute(Member.MEMBER.DESCRIPTION, "imageUrl").`as`(WriterRecord::imageUrl.name) - ) - .from(Member.MEMBER) - .where(Member.MEMBER.ID.eq(query.writerId)) - .and(Member.MEMBER.TYPE_CD.eq(MemberType.WRITER.code)) - .and(Member.MEMBER.DELETED_AT.isNull) + fun selectWriterQuery(query: SelectWriterQuery) = + dslContext + .select( + Member.MEMBER.ID.`as`(WriterRecord::writerId.name), + DSL.jsonGetAttributeAsText(Member.MEMBER.DESCRIPTION, "name").`as`(WriterRecord::name.name), + DSL.jsonGetAttribute(Member.MEMBER.DESCRIPTION, "url").`as`(WriterRecord::url.name), + DSL.jsonGetAttribute(Member.MEMBER.DESCRIPTION, "imageUrl").`as`(WriterRecord::imageUrl.name), + ).from(Member.MEMBER) + .where(Member.MEMBER.ID.eq(query.writerId)) + .and(Member.MEMBER.TYPE_CD.eq(MemberType.WRITER.code)) + .and(Member.MEMBER.DELETED_AT.isNull) /** * 작가 목록 조회 쿼리 @@ -58,164 +57,173 @@ class MemberDao( val cachedIdS = cachedValues.map { it.writerId } val notCachedIds = query.writerIds.filter { it !in cachedIdS } - val fetchedValue = selectWritersQuery(notCachedIds) - .fetchInto(WriterRecord::class.java).let { - cacheManager.addSelectWorkBookCache(it) - return@let it - } + val fetchedValue = + selectWritersQuery(notCachedIds) + .fetchInto(WriterRecord::class.java) + .let { + cacheManager.addSelectWorkBookCache(it) + return@let it + } return cachedValues + fetchedValue } - fun selectWritersQuery(notCachedIds: List) = dslContext.select( - Member.MEMBER.ID.`as`(WriterRecord::writerId.name), - DSL.jsonGetAttributeAsText(Member.MEMBER.DESCRIPTION, "name") - .`as`(WriterRecord::name.name), - DSL.jsonGetAttribute(Member.MEMBER.DESCRIPTION, "url").`as`(WriterRecord::url.name), - DSL.jsonGetAttribute(Member.MEMBER.DESCRIPTION, "imageUrl").`as`(WriterRecord::imageUrl.name) - ) - .from(Member.MEMBER) - .where(Member.MEMBER.ID.`in`(notCachedIds)) - .and(Member.MEMBER.TYPE_CD.eq(MemberType.WRITER.code)) - .and(Member.MEMBER.DELETED_AT.isNull) - .orderBy(Member.MEMBER.ID.asc()) - - fun selectWriters(query: BrowseWorkbookWritersQuery): List { - return selectWritersQuery(query) + fun selectWritersQuery(notCachedIds: List) = + dslContext + .select( + Member.MEMBER.ID.`as`(WriterRecord::writerId.name), + DSL + .jsonGetAttributeAsText(Member.MEMBER.DESCRIPTION, "name") + .`as`(WriterRecord::name.name), + DSL.jsonGetAttribute(Member.MEMBER.DESCRIPTION, "url").`as`(WriterRecord::url.name), + DSL.jsonGetAttribute(Member.MEMBER.DESCRIPTION, "imageUrl").`as`(WriterRecord::imageUrl.name), + ).from(Member.MEMBER) + .where(Member.MEMBER.ID.`in`(notCachedIds)) + .and(Member.MEMBER.TYPE_CD.eq(MemberType.WRITER.code)) + .and(Member.MEMBER.DELETED_AT.isNull) + .orderBy(Member.MEMBER.ID.asc()) + + fun selectWriters(query: BrowseWorkbookWritersQuery): List = + selectWritersQuery(query) .fetchInto(WriterRecordMappedWorkbook::class.java) - } fun selectWritersQuery(query: BrowseWorkbookWritersQuery) = /** workbookId를 기준으로 중복된 writer를 제거하기 위해 distinct를 사용한다. */ - dslContext.selectDistinct( - DSL.field("article_mapping.${MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID.name}") - .`as`(WriterRecordMappedWorkbook::workbookId.name) - ).select( - Member.MEMBER.ID.`as`(WriterRecordMappedWorkbook::writerId.name), - DSL.jsonGetAttributeAsText(Member.MEMBER.DESCRIPTION, "name") - .`as`(WriterRecordMappedWorkbook::name.name), - DSL.jsonGetAttribute(Member.MEMBER.DESCRIPTION, "url") - .`as`(WriterRecordMappedWorkbook::url.name) - ) - .from(Member.MEMBER) + dslContext + .selectDistinct( + DSL + .field("article_mapping.${MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID.name}") + .`as`(WriterRecordMappedWorkbook::workbookId.name), + ).select( + Member.MEMBER.ID.`as`(WriterRecordMappedWorkbook::writerId.name), + DSL + .jsonGetAttributeAsText(Member.MEMBER.DESCRIPTION, "name") + .`as`(WriterRecordMappedWorkbook::name.name), + DSL + .jsonGetAttribute(Member.MEMBER.DESCRIPTION, "url") + .`as`(WriterRecordMappedWorkbook::url.name), + ).from(Member.MEMBER) .join( /** 조회 workbookId에 포함된 articleId 및 writerId를 조회하기 위해 article_mapping을 사용한다. */ - dslContext.select( - ArticleMst.ARTICLE_MST.MEMBER_ID.`as`(ArticleMst.ARTICLE_MST.MEMBER_ID.name), - MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID.`as`( - MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID.name - ) - ) - .from(ArticleMst.ARTICLE_MST) + dslContext + .select( + ArticleMst.ARTICLE_MST.MEMBER_ID.`as`(ArticleMst.ARTICLE_MST.MEMBER_ID.name), + MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID.`as`( + MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID.name, + ), + ).from(ArticleMst.ARTICLE_MST) .join(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE) .on(ArticleMst.ARTICLE_MST.ID.eq(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.ARTICLE_ID)) .where(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID.`in`(query.workbookIds)) .and(ArticleMst.ARTICLE_MST.DELETED_AT.isNull) - .asTable("article_mapping") - ) - .on( + .asTable("article_mapping"), + ).on( Member.MEMBER.ID.eq( DSL.field( "article_mapping.${ArticleMst.ARTICLE_MST.MEMBER_ID.name}", - Long::class.java - ) - ) - ) - .where(Member.MEMBER.TYPE_CD.eq(MemberType.WRITER.code)) + Long::class.java, + ), + ), + ).where(Member.MEMBER.TYPE_CD.eq(MemberType.WRITER.code)) .and(Member.MEMBER.DELETED_AT.isNull) - fun selectMemberByEmail(query: SelectMemberByEmailQuery): MemberRecord? { - return selectMemberByEmailQuery(query) + fun selectMemberByEmail(query: SelectMemberByEmailQuery): MemberRecord? = + selectMemberByEmailQuery(query) .fetchOneInto(MemberRecord::class.java) - } - fun selectMemberByEmailQuery(query: SelectMemberByEmailQuery) = dslContext.select( - Member.MEMBER.ID.`as`(MemberRecord::memberId.name), - // writer only(nullable) - jsonGetAttributeAsText(Member.MEMBER.DESCRIPTION, "name").`as`(MemberRecord::writerName.name), - // writer only(nullable) - jsonGetAttribute(Member.MEMBER.DESCRIPTION, "url").`as`(MemberRecord::url.name), - // writer only(nullable) - jsonGetAttribute(Member.MEMBER.DESCRIPTION, "imageUrl").`as`(MemberRecord::imageUrl.name) - ) - .from(Member.MEMBER) - .where(Member.MEMBER.EMAIL.eq(query.email)) - .and(Member.MEMBER.DELETED_AT.isNull) - - fun selectMemberByEmail(query: SelectMemberByEmailNotConsiderDeletedAtQuery): MemberIdAndIsDeletedRecord? { - return selectMemberByEmailQuery(query) + fun selectMemberByEmailQuery(query: SelectMemberByEmailQuery) = + dslContext + .select( + Member.MEMBER.ID.`as`(MemberRecord::memberId.name), + // writer only(nullable) + jsonGetAttributeAsText(Member.MEMBER.DESCRIPTION, "name").`as`(MemberRecord::writerName.name), + // writer only(nullable) + jsonGetAttribute(Member.MEMBER.DESCRIPTION, "url").`as`(MemberRecord::url.name), + // writer only(nullable) + jsonGetAttribute(Member.MEMBER.DESCRIPTION, "imageUrl").`as`(MemberRecord::imageUrl.name), + ).from(Member.MEMBER) + .where(Member.MEMBER.EMAIL.eq(query.email)) + .and(Member.MEMBER.DELETED_AT.isNull) + + fun selectMemberByEmail(query: SelectMemberByEmailNotConsiderDeletedAtQuery): MemberIdAndIsDeletedRecord? = + selectMemberByEmailQuery(query) .fetchOneInto(MemberIdAndIsDeletedRecord::class.java) - } - fun selectMemberByEmailQuery(query: SelectMemberByEmailNotConsiderDeletedAtQuery) = dslContext.select( - Member.MEMBER.ID.`as`(MemberIdAndIsDeletedRecord::memberId.name), - Member.MEMBER.DELETED_AT.isNotNull.`as`(MemberIdAndIsDeletedRecord::isDeleted.name) - ) - .from(Member.MEMBER) - .where(Member.MEMBER.EMAIL.eq(query.email)) + fun selectMemberByEmailQuery(query: SelectMemberByEmailNotConsiderDeletedAtQuery) = + dslContext + .select( + Member.MEMBER.ID.`as`(MemberIdAndIsDeletedRecord::memberId.name), + Member.MEMBER.DELETED_AT.isNotNull + .`as`(MemberIdAndIsDeletedRecord::isDeleted.name), + ).from(Member.MEMBER) + .where(Member.MEMBER.EMAIL.eq(query.email)) fun insertMember(command: InsertMemberCommand): Long? { - val result = insertMemberCommand(command) - .returning(Member.MEMBER.ID) - .fetchOne() + val result = + insertMemberCommand(command) + .returning(Member.MEMBER.ID) + .fetchOne() return result?.getValue(Member.MEMBER.ID) } fun insertMemberCommand(command: InsertMemberCommand) = - dslContext.insertInto(Member.MEMBER) + dslContext + .insertInto(Member.MEMBER) .set(Member.MEMBER.EMAIL, command.email) .set(Member.MEMBER.TYPE_CD, command.memberType.code) - fun selectMemberEmail(query: SelectMemberEmailQuery): String? { - return selectMemberEmailQuery(query) + fun selectMemberEmail(query: SelectMemberEmailQuery): String? = + 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 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) + fun selectMemberEmailAndType(memberId: Long): MemberEmailAndTypeRecord? = + selectMemberIdAndTypeQuery(memberId) .fetchOne() ?.map { MemberEmailAndTypeRecord( email = it[MemberEmailAndTypeRecord::email.name] as String, - memberType = MemberType.fromCode(it[MemberEmailAndTypeRecord::memberType.name] as Byte)!! + memberType = MemberType.fromCode(it[MemberEmailAndTypeRecord::memberType.name] as Byte)!!, ) } - } - fun selectMemberIdAndTypeQuery(memberId: Long) = dslContext.select( - Member.MEMBER.EMAIL.`as`(MemberEmailAndTypeRecord::email.name), - Member.MEMBER.TYPE_CD.`as`(MemberEmailAndTypeRecord::memberType.name) - ) - .from(Member.MEMBER) - .where(Member.MEMBER.ID.eq(memberId)) - .and(Member.MEMBER.DELETED_AT.isNull) + fun selectMemberIdAndTypeQuery(memberId: Long) = + dslContext + .select( + Member.MEMBER.EMAIL.`as`(MemberEmailAndTypeRecord::email.name), + Member.MEMBER.TYPE_CD.`as`(MemberEmailAndTypeRecord::memberType.name), + ).from(Member.MEMBER) + .where(Member.MEMBER.ID.eq(memberId)) + .and(Member.MEMBER.DELETED_AT.isNull) fun updateMemberType(command: UpdateMemberTypeCommand) { updateMemberTypeCommand(command).execute() } fun updateMemberTypeCommand(command: UpdateMemberTypeCommand) = - dslContext.update(Member.MEMBER) + dslContext + .update(Member.MEMBER) .set(Member.MEMBER.TYPE_CD, command.memberType.code) .where(Member.MEMBER.ID.eq(command.id)) .and(Member.MEMBER.DELETED_AT.isNull) - fun updateMemberType(command: UpdateDeletedMemberTypeCommand): Long? { - return updateMemberTypeCommand(command) + fun updateMemberType(command: UpdateDeletedMemberTypeCommand): Long? = + updateMemberTypeCommand(command) .returning(Member.MEMBER.ID) .execute() .toLong() - } fun updateMemberTypeCommand(command: UpdateDeletedMemberTypeCommand) = - dslContext.update(Member.MEMBER) + dslContext + .update(Member.MEMBER) .set(Member.MEMBER.TYPE_CD, command.memberType.code) .set(Member.MEMBER.DELETED_AT, DSL.`val`(null, Member.MEMBER.DELETED_AT.dataType)) .where(Member.MEMBER.ID.eq(command.id)) @@ -226,7 +234,8 @@ class MemberDao( } fun deleteMemberCommand(command: DeleteMemberCommand) = - dslContext.update(Member.MEMBER) + dslContext + .update(Member.MEMBER) .set(Member.MEMBER.DELETED_AT, LocalDateTime.now()) .where(Member.MEMBER.ID.eq(command.memberId)) .and(Member.MEMBER.DELETED_AT.isNull) diff --git a/api/src/main/kotlin/com/few/api/domain/member/repo/support/WriterDescriptionJsonMapper.kt b/api/src/main/kotlin/com/few/api/domain/member/repo/support/WriterDescriptionJsonMapper.kt index 548fa4328..8bded9ece 100644 --- a/api/src/main/kotlin/com/few/api/domain/member/repo/support/WriterDescriptionJsonMapper.kt +++ b/api/src/main/kotlin/com/few/api/domain/member/repo/support/WriterDescriptionJsonMapper.kt @@ -12,11 +12,7 @@ class WriterDescriptionJsonMapper( objectMapper.registerKotlinModule() } - fun toJson(writerDescription: WriterDescription): String { - return objectMapper.writeValueAsString(writerDescription) - } + fun toJson(writerDescription: WriterDescription): String = objectMapper.writeValueAsString(writerDescription) - fun toObject(value: String): WriterDescription { - return objectMapper.readValue(value, WriterDescription::class.java) - } + fun toObject(value: String): WriterDescription = objectMapper.readValue(value, WriterDescription::class.java) } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/member/service/MemberSubscriptionService.kt b/api/src/main/kotlin/com/few/api/domain/member/service/MemberSubscriptionService.kt index 3cbdd6b52..108fcd645 100644 --- a/api/src/main/kotlin/com/few/api/domain/member/service/MemberSubscriptionService.kt +++ b/api/src/main/kotlin/com/few/api/domain/member/service/MemberSubscriptionService.kt @@ -10,14 +10,13 @@ import org.springframework.transaction.annotation.Transactional class MemberSubscriptionService( private val subscriptionDao: SubscriptionDao, ) { - @Transactional fun deleteSubscription(dto: DeleteSubscriptionDto) { subscriptionDao.updateDeletedAtInAllSubscription( UpdateDeletedAtInAllSubscriptionCommand( memberId = dto.memberId, - opinion = dto.opinion - ) + opinion = dto.opinion, + ), ) } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/member/usecase/DeleteMemberUseCase.kt b/api/src/main/kotlin/com/few/api/domain/member/usecase/DeleteMemberUseCase.kt index 5ecbc6713..b7199fa03 100644 --- a/api/src/main/kotlin/com/few/api/domain/member/usecase/DeleteMemberUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/member/usecase/DeleteMemberUseCase.kt @@ -1,11 +1,11 @@ package com.few.api.domain.member.usecase +import com.few.api.domain.member.repo.MemberDao +import com.few.api.domain.member.repo.command.DeleteMemberCommand import com.few.api.domain.member.service.MemberSubscriptionService import com.few.api.domain.member.service.dto.DeleteSubscriptionDto import com.few.api.domain.member.usecase.dto.DeleteMemberUseCaseIn import com.few.api.domain.member.usecase.dto.DeleteMemberUseCaseOut -import com.few.api.domain.member.repo.MemberDao -import com.few.api.domain.member.repo.command.DeleteMemberCommand import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional @@ -18,14 +18,14 @@ class DeleteMemberUseCase( fun execute(useCaseIn: DeleteMemberUseCaseIn): DeleteMemberUseCaseOut { memberDao.deleteMember( DeleteMemberCommand( - memberId = useCaseIn.memberId - ) + memberId = useCaseIn.memberId, + ), ) memberSubscriptionService.deleteSubscription( DeleteSubscriptionDto( - memberId = useCaseIn.memberId - ) + memberId = useCaseIn.memberId, + ), ) return DeleteMemberUseCaseOut(true) } diff --git a/api/src/main/kotlin/com/few/api/domain/member/usecase/SaveMemberUseCase.kt b/api/src/main/kotlin/com/few/api/domain/member/usecase/SaveMemberUseCase.kt index 1bdfde657..66eff6476 100644 --- a/api/src/main/kotlin/com/few/api/domain/member/usecase/SaveMemberUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/member/usecase/SaveMemberUseCase.kt @@ -1,14 +1,14 @@ package com.few.api.domain.member.usecase +import com.few.api.domain.member.email.SendAuthEmailService +import com.few.api.domain.member.email.dto.Content +import com.few.api.domain.member.email.dto.SendAuthEmailArgs +import com.few.api.domain.member.repo.MemberDao +import com.few.api.domain.member.repo.query.SelectMemberByEmailNotConsiderDeletedAtQuery import com.few.api.domain.member.usecase.dto.SaveMemberTxCaseIn import com.few.api.domain.member.usecase.dto.SaveMemberUseCaseIn import com.few.api.domain.member.usecase.dto.SaveMemberUseCaseOut import com.few.api.domain.member.usecase.transaction.SaveMemberTxCase -import com.few.api.domain.member.repo.MemberDao -import com.few.api.domain.member.repo.query.SelectMemberByEmailNotConsiderDeletedAtQuery -import com.few.api.domain.member.email.SendAuthEmailService -import com.few.api.domain.member.email.dto.Content -import com.few.api.domain.member.email.dto.SendAuthEmailArgs import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional import security.encryptor.IdEncryptor @@ -24,19 +24,21 @@ class SaveMemberUseCase( @Transactional fun execute(useCaseIn: SaveMemberUseCaseIn): SaveMemberUseCaseOut { /** email을 통해 가입 이력이 있는지 확인 */ - val (headComment, subComment, memberId) = memberDao.selectMemberByEmail( - SelectMemberByEmailNotConsiderDeletedAtQuery( - email = useCaseIn.email - ) - ).let { - /** 가입 이력 여부를 기준으로 가입 처리 */ - saveMemberTxCase.execute( - SaveMemberTxCaseIn( - record = it, - email = useCaseIn.email - ) - ) - } + val (headComment, subComment, memberId) = + memberDao + .selectMemberByEmail( + SelectMemberByEmailNotConsiderDeletedAtQuery( + email = useCaseIn.email, + ), + ).let { + /** 가입 이력 여부를 기준으로 가입 처리 */ + saveMemberTxCase.execute( + SaveMemberTxCaseIn( + record = it, + email = useCaseIn.email, + ), + ) + } /** 회원 ID를 암호화하여 토큰으로 사용 */ val token = idEncryption.encrypt(memberId.toString()) @@ -47,22 +49,23 @@ class SaveMemberUseCase( to = useCaseIn.email, subject = "[FEW] 인증 이메일 주소를 확인해주세요.", template = "auth", - content = Content( - headComment = headComment, - subComment = subComment, - email = useCaseIn.email, - confirmLink = URL("https://www.fewletter.com/auth/validation/complete?auth_token=$token") - ) - ) + content = + Content( + headComment = headComment, + subComment = subComment, + email = useCaseIn.email, + confirmLink = URL("https://www.fewletter.com/auth/validation/complete?auth_token=$token"), + ), + ), ) }.onFailure { return SaveMemberUseCaseOut( - isSendAuthEmail = false + isSendAuthEmail = false, ) } return SaveMemberUseCaseOut( - isSendAuthEmail = true + isSendAuthEmail = true, ) } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/member/usecase/TokenUseCase.kt b/api/src/main/kotlin/com/few/api/domain/member/usecase/TokenUseCase.kt index 0f357e6f4..50e32e156 100644 --- a/api/src/main/kotlin/com/few/api/domain/member/usecase/TokenUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/member/usecase/TokenUseCase.kt @@ -2,17 +2,17 @@ package com.few.api.domain.member.usecase import com.few.api.domain.common.exception.NotFoundException import com.few.api.domain.common.vo.MemberType -import security.encryptor.IdEncryptor -import com.few.api.domain.member.usecase.dto.TokenUseCaseIn -import com.few.api.domain.member.usecase.dto.TokenUseCaseOut import com.few.api.domain.member.exception.NotValidTokenException import com.few.api.domain.member.repo.MemberDao import com.few.api.domain.member.repo.command.UpdateMemberTypeCommand +import com.few.api.domain.member.usecase.dto.TokenUseCaseIn +import com.few.api.domain.member.usecase.dto.TokenUseCaseOut import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional import security.Roles import security.TokenGenerator import security.TokenResolver +import security.encryptor.IdEncryptor @Component class TokenUseCase( @@ -21,7 +21,6 @@ class TokenUseCase( private val memberDao: MemberDao, private val idEncryption: IdEncryptor, ) { - @Transactional fun execute(useCaseIn: TokenUseCaseIn): TokenUseCaseOut { var isLogin = true @@ -35,17 +34,18 @@ class TokenUseCase( _memberId = tokenResolver.resolveId(token) _memberEmail = tokenResolver.resolveEmail(token) }.onSuccess { - tokenGenerator.generateAuthToken( - memberId = _memberId, - memberEmail = _memberEmail, - memberRoles = listOf(Roles.ROLE_USER) - ).let { token -> - return TokenUseCaseOut( - accessToken = token.accessToken, - refreshToken = token.refreshToken, - isLogin = true - ) - } + tokenGenerator + .generateAuthToken( + memberId = _memberId, + memberEmail = _memberEmail, + memberRoles = listOf(Roles.ROLE_USER), + ).let { token -> + return TokenUseCaseOut( + accessToken = token.accessToken, + refreshToken = token.refreshToken, + isLogin = true, + ) + } }.onFailure { throw NotValidTokenException("member.notvalid.token") } @@ -53,36 +53,39 @@ class TokenUseCase( val accessTokenValidTime: Long? = useCaseIn.at val refreshTokenValidTime: Long? = useCaseIn.rt - val memberId = useCaseIn.token?.let { - idEncryption.decrypt(it).toLong() - } ?: throw IllegalStateException("member.notvalid.fromEmailId") + val memberId = + useCaseIn.token?.let { + idEncryption.decrypt(it).toLong() + } ?: throw IllegalStateException("member.notvalid.fromEmailId") - val memberEmailAndTypeRecord = memberDao.selectMemberEmailAndType(memberId) - ?: throw NotFoundException("member.notfound.id") + val memberEmailAndTypeRecord = + memberDao.selectMemberEmailAndType(memberId) + ?: throw NotFoundException("member.notfound.id") if (memberEmailAndTypeRecord.memberType == MemberType.PREAUTH) { isLogin = false memberDao.updateMemberType( UpdateMemberTypeCommand( id = memberId, - memberType = MemberType.NORMAL - ) + memberType = MemberType.NORMAL, + ), ) } /** id가 요청에 포함되어 있으면 id를 통해 새로운 토큰을 발급 */ - val token = tokenGenerator.generateAuthToken( - memberId = memberId, - memberEmail = memberEmailAndTypeRecord.email, - memberRoles = listOf(Roles.ROLE_USER), - accessTokenValidTime = accessTokenValidTime, - refreshTokenValidTime = refreshTokenValidTime - ) + val token = + tokenGenerator.generateAuthToken( + memberId = memberId, + memberEmail = memberEmailAndTypeRecord.email, + memberRoles = listOf(Roles.ROLE_USER), + accessTokenValidTime = accessTokenValidTime, + refreshTokenValidTime = refreshTokenValidTime, + ) return TokenUseCaseOut( accessToken = token.accessToken, refreshToken = token.refreshToken, - isLogin = isLogin + isLogin = isLogin, ) } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/member/usecase/transaction/SaveMemberTxCase.kt b/api/src/main/kotlin/com/few/api/domain/member/usecase/transaction/SaveMemberTxCase.kt index 0774c746f..0f58c83b0 100644 --- a/api/src/main/kotlin/com/few/api/domain/member/usecase/transaction/SaveMemberTxCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/member/usecase/transaction/SaveMemberTxCase.kt @@ -1,13 +1,13 @@ package com.few.api.domain.member.usecase.transaction -import com.few.api.domain.common.vo.MemberType -import com.few.api.domain.member.usecase.dto.SaveMemberTxCaseIn -import com.few.api.domain.member.usecase.dto.SaveMemberTxCaseOut import com.few.api.domain.common.exception.InsertException +import com.few.api.domain.common.vo.MemberType import com.few.api.domain.member.repo.MemberDao import com.few.api.domain.member.repo.command.InsertMemberCommand import com.few.api.domain.member.repo.command.UpdateDeletedMemberTypeCommand import com.few.api.domain.member.repo.record.MemberIdAndIsDeletedRecord +import com.few.api.domain.member.usecase.dto.SaveMemberTxCaseIn +import com.few.api.domain.member.usecase.dto.SaveMemberTxCaseOut import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional @@ -23,53 +23,52 @@ class SaveMemberTxCase( } @Transactional - fun execute(dto: SaveMemberTxCaseIn): SaveMemberTxCaseOut { - return dto.record?.let { signUpBeforeMember -> + fun execute(dto: SaveMemberTxCaseIn): SaveMemberTxCaseOut = + dto.record?.let { signUpBeforeMember -> signUpBeforeMember.takeIf { it.isDeleted }?.let { ifDeletedMember(it.memberId) } ?: ifMember(signUpBeforeMember) } ?: ifNewMember(dto.email) - } private fun ifDeletedMember(memberId: Long): SaveMemberTxCaseOut { - memberDao.updateMemberType( - UpdateDeletedMemberTypeCommand( - id = memberId, - memberType = MemberType.PREAUTH - ) - ).also { - if (it != 1L) { - throw InsertException("member.updatefail.record") + memberDao + .updateMemberType( + UpdateDeletedMemberTypeCommand( + id = memberId, + memberType = MemberType.PREAUTH, + ), + ).also { + if (it != 1L) { + throw InsertException("member.updatefail.record") + } } - } return SaveMemberTxCaseOut( headComment = AUTH_HEAD_COMMENT, subComment = AUTH_SUB_COMMENT, - memberId = memberId + memberId = memberId, ) } - private fun ifMember(signUpBeforeMember: MemberIdAndIsDeletedRecord): SaveMemberTxCaseOut { - return SaveMemberTxCaseOut( + private fun ifMember(signUpBeforeMember: MemberIdAndIsDeletedRecord): SaveMemberTxCaseOut = + SaveMemberTxCaseOut( headComment = AUTH_HEAD_COMMENT, subComment = AUTH_SUB_COMMENT, - memberId = signUpBeforeMember.memberId + memberId = signUpBeforeMember.memberId, ) - } - private fun ifNewMember(email: String): SaveMemberTxCaseOut { - return memberDao.insertMember( - InsertMemberCommand( - email = email, - memberType = MemberType.PREAUTH - ) - )?.let { - SaveMemberTxCaseOut( - headComment = SIGNUP_HEAD_COMMENT, - subComment = SIGNUP_SUB_COMMENT, - memberId = it - ) - } ?: throw InsertException("member.insertfail.record") - } + private fun ifNewMember(email: String): SaveMemberTxCaseOut = + memberDao + .insertMember( + InsertMemberCommand( + email = email, + memberType = MemberType.PREAUTH, + ), + )?.let { + SaveMemberTxCaseOut( + headComment = SIGNUP_HEAD_COMMENT, + subComment = SIGNUP_SUB_COMMENT, + memberId = it, + ) + } ?: throw InsertException("member.insertfail.record") } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/problem/controller/ProblemController.kt b/api/src/main/kotlin/com/few/api/domain/problem/controller/ProblemController.kt index d794087f2..1038d4fc9 100644 --- a/api/src/main/kotlin/com/few/api/domain/problem/controller/ProblemController.kt +++ b/api/src/main/kotlin/com/few/api/domain/problem/controller/ProblemController.kt @@ -1,28 +1,28 @@ package com.few.api.domain.problem.controller -import com.few.api.domain.problem.usecase.BrowseProblemsUseCase -import com.few.api.domain.problem.usecase.BrowseUndoneProblemsUseCase import com.few.api.domain.problem.controller.request.CheckProblemRequest +import com.few.api.domain.problem.controller.response.BrowseProblemsResponse import com.few.api.domain.problem.controller.response.CheckProblemResponse import com.few.api.domain.problem.controller.response.ProblemContents import com.few.api.domain.problem.controller.response.ReadProblemResponse -import web.ApiResponse -import web.ApiResponseGenerator +import com.few.api.domain.problem.usecase.BrowseProblemsUseCase +import com.few.api.domain.problem.usecase.BrowseUndoneProblemsUseCase import com.few.api.domain.problem.usecase.CheckProblemUseCase import com.few.api.domain.problem.usecase.ReadProblemUseCase import com.few.api.domain.problem.usecase.dto.BrowseProblemsUseCaseIn import com.few.api.domain.problem.usecase.dto.BrowseUndoneProblemsUseCaseIn import com.few.api.domain.problem.usecase.dto.CheckProblemUseCaseIn import com.few.api.domain.problem.usecase.dto.ReadProblemUseCaseIn -import com.few.api.domain.problem.controller.response.BrowseProblemsResponse -import web.security.UserArgument -import web.security.UserArgumentDetails import jakarta.validation.Valid import jakarta.validation.constraints.Min import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.validation.annotation.Validated import org.springframework.web.bind.annotation.* +import web.ApiResponse +import web.ApiResponseGenerator +import web.security.UserArgument +import web.security.UserArgumentDetails import java.util.* @Validated @@ -34,13 +34,15 @@ class ProblemController( private val checkProblemUseCase: CheckProblemUseCase, private val browseUndoneProblemsUseCase: BrowseUndoneProblemsUseCase, ) { - @GetMapping - fun browseProblems(@RequestParam(value = "articleId", required = false) articleId: Long?): ApiResponse> { + fun browseProblems( + @RequestParam(value = "articleId", required = false) articleId: Long?, + ): ApiResponse> { articleId?.let { id -> - val useCaseOut = BrowseProblemsUseCaseIn(id).let { useCaseIn -> - browseProblemsUseCase.execute(useCaseIn) - } + val useCaseOut = + BrowseProblemsUseCaseIn(id).let { useCaseIn -> + browseProblemsUseCase.execute(useCaseIn) + } val response = BrowseProblemsResponse(useCaseOut.problemIds, useCaseOut.problemIds.size) @@ -58,14 +60,16 @@ class ProblemController( ): ApiResponse> { val useCaseOut = readProblemUseCase.execute(ReadProblemUseCaseIn(problemId)) - val response = ReadProblemResponse( - id = useCaseOut.id, - title = useCaseOut.title, - contents = useCaseOut.contents - .map { c -> ProblemContents(c.number, c.content) } - .toCollection(LinkedList()), - articleId = useCaseOut.articleId - ) + val response = + ReadProblemResponse( + id = useCaseOut.id, + title = useCaseOut.title, + contents = + useCaseOut.contents + .map { c -> ProblemContents(c.number, c.content) } + .toCollection(LinkedList()), + articleId = useCaseOut.articleId, + ) return ApiResponseGenerator.success(response, HttpStatus.OK) } @@ -80,30 +84,35 @@ class ProblemController( body: CheckProblemRequest, ): ApiResponse> { val memberId = userArgumentDetails.id.toLong() - val useCaseOut = checkProblemUseCase.execute( - CheckProblemUseCaseIn( - memberId, - problemId, - body.sub + val useCaseOut = + checkProblemUseCase.execute( + CheckProblemUseCaseIn( + memberId, + problemId, + body.sub, + ), ) - ) - val response = CheckProblemResponse( - explanation = useCaseOut.explanation, - answer = useCaseOut.answer, - isSolved = useCaseOut.isSolved - ) + val response = + CheckProblemResponse( + explanation = useCaseOut.explanation, + answer = useCaseOut.answer, + isSolved = useCaseOut.isSolved, + ) return ApiResponseGenerator.success(response, HttpStatus.OK) } @GetMapping("/unsubmitted") - fun browseUndoneProblems(@UserArgument userArgumentDetails: UserArgumentDetails): ApiResponse> { + fun browseUndoneProblems( + @UserArgument userArgumentDetails: UserArgumentDetails, + ): ApiResponse> { val memberId = userArgumentDetails.id.toLong() - val useCaseOut = BrowseUndoneProblemsUseCaseIn(memberId).let { useCaseIn -> - browseUndoneProblemsUseCase.execute(useCaseIn) - } + val useCaseOut = + BrowseUndoneProblemsUseCaseIn(memberId).let { useCaseIn -> + browseUndoneProblemsUseCase.execute(useCaseIn) + } val response = BrowseProblemsResponse(useCaseOut.problemIds, useCaseOut.problemIds.size) diff --git a/api/src/main/kotlin/com/few/api/domain/problem/repo/ProblemDao.kt b/api/src/main/kotlin/com/few/api/domain/problem/repo/ProblemDao.kt index 8f00b767f..1f9f4e4f3 100644 --- a/api/src/main/kotlin/com/few/api/domain/problem/repo/ProblemDao.kt +++ b/api/src/main/kotlin/com/few/api/domain/problem/repo/ProblemDao.kt @@ -21,59 +21,62 @@ class ProblemDao( private val dslContext: DSLContext, private val contentsJsonMapper: ContentsJsonMapper, ) { - fun selectProblemContents(query: SelectProblemQuery): SelectProblemRecord? { - return selectProblemContentsQuery(query) + fun selectProblemContents(query: SelectProblemQuery): SelectProblemRecord? = + selectProblemContentsQuery(query) .fetchOneInto(SelectProblemRecord::class.java) - } + fun selectProblemContentsQuery(query: SelectProblemQuery) = - dslContext.select( - Problem.PROBLEM.ID.`as`(SelectProblemRecord::id.name), - Problem.PROBLEM.TITLE.`as`(SelectProblemRecord::title.name), - DSL.field("JSON_UNQUOTE({0})", String::class.java, Problem.PROBLEM.CONTENTS) - .`as`(SelectProblemRecord::contents.name), - Problem.PROBLEM.ARTICLE_ID.`as`(SelectProblemRecord::articleId.name) - ) - .from(Problem.PROBLEM) + dslContext + .select( + Problem.PROBLEM.ID.`as`(SelectProblemRecord::id.name), + Problem.PROBLEM.TITLE.`as`(SelectProblemRecord::title.name), + DSL + .field("JSON_UNQUOTE({0})", String::class.java, Problem.PROBLEM.CONTENTS) + .`as`(SelectProblemRecord::contents.name), + Problem.PROBLEM.ARTICLE_ID.`as`(SelectProblemRecord::articleId.name), + ).from(Problem.PROBLEM) .where(Problem.PROBLEM.ID.eq(query.problemId)) .and(Problem.PROBLEM.DELETED_AT.isNull) - fun selectProblemAnswer(query: SelectProblemAnswerQuery): SelectProblemAnswerRecord? { - return selectProblemAnswerQuery(query) + fun selectProblemAnswer(query: SelectProblemAnswerQuery): SelectProblemAnswerRecord? = + selectProblemAnswerQuery(query) .fetchOneInto(SelectProblemAnswerRecord::class.java) - } fun selectProblemAnswerQuery(query: SelectProblemAnswerQuery) = - dslContext.select( - Problem.PROBLEM.ID.`as`(SelectProblemAnswerRecord::id.name), - Problem.PROBLEM.ANSWER.`as`(SelectProblemAnswerRecord::answer.name), - Problem.PROBLEM.EXPLANATION.`as`(SelectProblemAnswerRecord::explanation.name) - ) - .from(Problem.PROBLEM) + dslContext + .select( + Problem.PROBLEM.ID.`as`(SelectProblemAnswerRecord::id.name), + Problem.PROBLEM.ANSWER.`as`(SelectProblemAnswerRecord::answer.name), + Problem.PROBLEM.EXPLANATION.`as`(SelectProblemAnswerRecord::explanation.name), + ).from(Problem.PROBLEM) .where(Problem.PROBLEM.ID.eq(query.problemId)) .and(Problem.PROBLEM.DELETED_AT.isNull) - fun selectProblemsByArticleId(query: SelectProblemsByArticleIdQuery): ProblemIdsRecord? { - return selectProblemsByArticleIdQuery(query) + fun selectProblemsByArticleId(query: SelectProblemsByArticleIdQuery): ProblemIdsRecord? = + selectProblemsByArticleIdQuery(query) .fetch() .map { it[Problem.PROBLEM.ID] } .let { ProblemIdsRecord(it) } - } - fun selectProblemsByArticleIdQuery(query: SelectProblemsByArticleIdQuery) = dslContext.select() - .from(Problem.PROBLEM) - .where(Problem.PROBLEM.ARTICLE_ID.eq(query.articleId)) - .and(Problem.PROBLEM.DELETED_AT.isNull) + fun selectProblemsByArticleIdQuery(query: SelectProblemsByArticleIdQuery) = + dslContext + .select() + .from(Problem.PROBLEM) + .where(Problem.PROBLEM.ARTICLE_ID.eq(query.articleId)) + .and(Problem.PROBLEM.DELETED_AT.isNull) fun insertProblems(command: List) { - dslContext.batch( - command.map { - insertProblemCommand(it) - } - ).execute() + dslContext + .batch( + command.map { + insertProblemCommand(it) + }, + ).execute() } fun insertProblemCommand(it: InsertProblemsCommand) = - dslContext.insertInto(Problem.PROBLEM) + dslContext + .insertInto(Problem.PROBLEM) .set(Problem.PROBLEM.ARTICLE_ID, it.articleId) .set(Problem.PROBLEM.CREATOR_ID, it.createrId) .set(Problem.PROBLEM.TITLE, it.title) @@ -81,17 +84,16 @@ class ProblemDao( .set(Problem.PROBLEM.ANSWER, it.answer) .set(Problem.PROBLEM.EXPLANATION, it.explanation) - fun selectProblemIdByArticleIds(query: SelectProblemIdByArticleIdsQuery): List { - return selectProblemIdByArticleIdsQuery(query) + fun selectProblemIdByArticleIds(query: SelectProblemIdByArticleIdsQuery): List = + selectProblemIdByArticleIdsQuery(query) .fetchInto(ProblemIdAndArticleIdRecord::class.java) - } - fun selectProblemIdByArticleIdsQuery(query: SelectProblemIdByArticleIdsQuery) = dslContext - .select( - Problem.PROBLEM.ID.`as`(ProblemIdAndArticleIdRecord::problemId.name), - Problem.PROBLEM.ARTICLE_ID.`as`(ProblemIdAndArticleIdRecord::articleId.name) - ) - .from(Problem.PROBLEM) - .where(Problem.PROBLEM.ARTICLE_ID.`in`(query.articleIds)) - .and(Problem.PROBLEM.DELETED_AT.isNull) + fun selectProblemIdByArticleIdsQuery(query: SelectProblemIdByArticleIdsQuery) = + dslContext + .select( + Problem.PROBLEM.ID.`as`(ProblemIdAndArticleIdRecord::problemId.name), + Problem.PROBLEM.ARTICLE_ID.`as`(ProblemIdAndArticleIdRecord::articleId.name), + ).from(Problem.PROBLEM) + .where(Problem.PROBLEM.ARTICLE_ID.`in`(query.articleIds)) + .and(Problem.PROBLEM.DELETED_AT.isNull) } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/problem/repo/SubmitHistoryDao.kt b/api/src/main/kotlin/com/few/api/domain/problem/repo/SubmitHistoryDao.kt index c1e0e3887..e6e79dacd 100644 --- a/api/src/main/kotlin/com/few/api/domain/problem/repo/SubmitHistoryDao.kt +++ b/api/src/main/kotlin/com/few/api/domain/problem/repo/SubmitHistoryDao.kt @@ -11,33 +11,34 @@ import org.springframework.stereotype.Repository class SubmitHistoryDao( private val dslContext: DSLContext, ) { - fun insertSubmitHistory(command: InsertSubmitHistoryCommand): Long? { - val result = insertSubmitCommand(command) - .returning(SUBMIT_HISTORY.ID) - .fetchOne() + val result = + insertSubmitCommand(command) + .returning(SUBMIT_HISTORY.ID) + .fetchOne() return result?.getValue(SUBMIT_HISTORY.ID) } fun insertSubmitCommand(command: InsertSubmitHistoryCommand) = - dslContext.insertInto(SUBMIT_HISTORY) + dslContext + .insertInto(SUBMIT_HISTORY) .set(SUBMIT_HISTORY.PROBLEM_ID, command.problemId) .set(SUBMIT_HISTORY.MEMBER_ID, command.memberId) .set(SUBMIT_HISTORY.SUBMIT_ANS, command.submitAns) .set(SUBMIT_HISTORY.IS_SOLVED, command.isSolved) - fun selectProblemIdByProblemIds(query: SelectSubmittedProblemIdsQuery): SubmittedProblemIdsRecord { - return selectProblemIdByProblemIdsQuery(query) + fun selectProblemIdByProblemIds(query: SelectSubmittedProblemIdsQuery): SubmittedProblemIdsRecord = + selectProblemIdByProblemIdsQuery(query) .fetch() .map { it[SUBMIT_HISTORY.PROBLEM_ID] } .let { SubmittedProblemIdsRecord(it) } - } - fun selectProblemIdByProblemIdsQuery(query: SelectSubmittedProblemIdsQuery) = dslContext - .select(SUBMIT_HISTORY.PROBLEM_ID) - .from(SUBMIT_HISTORY) - .where(SUBMIT_HISTORY.PROBLEM_ID.`in`(query.problemIds)) - .and(SUBMIT_HISTORY.MEMBER_ID.eq(query.memberId)) - .and(SUBMIT_HISTORY.DELETED_AT.isNull) + fun selectProblemIdByProblemIdsQuery(query: SelectSubmittedProblemIdsQuery) = + dslContext + .select(SUBMIT_HISTORY.PROBLEM_ID) + .from(SUBMIT_HISTORY) + .where(SUBMIT_HISTORY.PROBLEM_ID.`in`(query.problemIds)) + .and(SUBMIT_HISTORY.MEMBER_ID.eq(query.memberId)) + .and(SUBMIT_HISTORY.DELETED_AT.isNull) } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/problem/repo/support/ContentsJsonMapper.kt b/api/src/main/kotlin/com/few/api/domain/problem/repo/support/ContentsJsonMapper.kt index b8e7cb948..5537df52b 100644 --- a/api/src/main/kotlin/com/few/api/domain/problem/repo/support/ContentsJsonMapper.kt +++ b/api/src/main/kotlin/com/few/api/domain/problem/repo/support/ContentsJsonMapper.kt @@ -7,10 +7,7 @@ import org.springframework.stereotype.Component class ContentsJsonMapper( private val objectMapper: ObjectMapper, ) { - - fun toJson(contents: Contents): String { - return objectMapper.writeValueAsString(contents) - } + fun toJson(contents: Contents): String = objectMapper.writeValueAsString(contents) fun toObject(value: String): Contents { val contents = objectMapper.readTree(value).get("contents") diff --git a/api/src/main/kotlin/com/few/api/domain/problem/service/ProblemArticleService.kt b/api/src/main/kotlin/com/few/api/domain/problem/service/ProblemArticleService.kt index ca03fddc9..a419931e5 100644 --- a/api/src/main/kotlin/com/few/api/domain/problem/service/ProblemArticleService.kt +++ b/api/src/main/kotlin/com/few/api/domain/problem/service/ProblemArticleService.kt @@ -1,21 +1,20 @@ package com.few.api.domain.problem.service -import com.few.api.domain.problem.service.dto.BrowseArticleIdInDto import com.few.api.domain.article.repo.ArticleDao import com.few.api.domain.article.repo.query.SelectAritlceIdByWorkbookIdAndDayQuery +import com.few.api.domain.problem.service.dto.BrowseArticleIdInDto import org.springframework.stereotype.Service @Service class ProblemArticleService( private val articleDao: ArticleDao, ) { - - fun browseArticleIdByWorkbookIdLimitDay(inDto: BrowseArticleIdInDto): List { - return articleDao.selectArticleIdsByWorkbookIdLimitDay( - SelectAritlceIdByWorkbookIdAndDayQuery( - inDto.workbookId, - inDto.day - ) - ).articleIds - } + fun browseArticleIdByWorkbookIdLimitDay(inDto: BrowseArticleIdInDto): List = + articleDao + .selectArticleIdsByWorkbookIdLimitDay( + SelectAritlceIdByWorkbookIdAndDayQuery( + inDto.workbookId, + inDto.day, + ), + ).articleIds } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/problem/service/ProblemSubscriptionService.kt b/api/src/main/kotlin/com/few/api/domain/problem/service/ProblemSubscriptionService.kt index 3dbec5a0b..b93228f8e 100644 --- a/api/src/main/kotlin/com/few/api/domain/problem/service/ProblemSubscriptionService.kt +++ b/api/src/main/kotlin/com/few/api/domain/problem/service/ProblemSubscriptionService.kt @@ -10,11 +10,11 @@ import org.springframework.stereotype.Component class ProblemSubscriptionService( private val subscriptionDao: SubscriptionDao, ) { - fun browseWorkbookIdAndProgress(inDto: BrowseWorkbookIdAndProgressInDto): List { - val subscriptionProgresses = subscriptionDao.selectWorkbookIdAndProgressByMember( - SelectSubscriptionSendStatusQuery(inDto.memberId) - ) + val subscriptionProgresses = + subscriptionDao.selectWorkbookIdAndProgressByMember( + SelectSubscriptionSendStatusQuery(inDto.memberId), + ) return subscriptionProgresses.map { SubscriptionProgressOutDto(it.workbookId, it.day) } } diff --git a/api/src/main/kotlin/com/few/api/domain/problem/usecase/BrowseProblemsUseCase.kt b/api/src/main/kotlin/com/few/api/domain/problem/usecase/BrowseProblemsUseCase.kt index f22d6a2a3..036dfb420 100644 --- a/api/src/main/kotlin/com/few/api/domain/problem/usecase/BrowseProblemsUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/problem/usecase/BrowseProblemsUseCase.kt @@ -1,10 +1,10 @@ package com.few.api.domain.problem.usecase -import com.few.api.domain.problem.usecase.dto.BrowseProblemsUseCaseIn -import com.few.api.domain.problem.usecase.dto.BrowseProblemsUseCaseOut import com.few.api.domain.common.exception.NotFoundException import com.few.api.domain.problem.repo.ProblemDao import com.few.api.domain.problem.repo.query.SelectProblemsByArticleIdQuery +import com.few.api.domain.problem.usecase.dto.BrowseProblemsUseCaseIn +import com.few.api.domain.problem.usecase.dto.BrowseProblemsUseCaseOut import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional @@ -12,10 +12,10 @@ import org.springframework.transaction.annotation.Transactional class BrowseProblemsUseCase( private val problemDao: ProblemDao, ) { - @Transactional(readOnly = true) fun execute(useCaseIn: BrowseProblemsUseCaseIn): BrowseProblemsUseCaseOut { - problemDao.selectProblemsByArticleId(SelectProblemsByArticleIdQuery(useCaseIn.articleId)) + problemDao + .selectProblemsByArticleId(SelectProblemsByArticleIdQuery(useCaseIn.articleId)) ?.let { return BrowseProblemsUseCaseOut(it.problemIds) } ?: throw NotFoundException("problem.notfound.articleId") diff --git a/api/src/main/kotlin/com/few/api/domain/problem/usecase/BrowseUndoneProblemsUseCase.kt b/api/src/main/kotlin/com/few/api/domain/problem/usecase/BrowseUndoneProblemsUseCase.kt index ab7eda110..0d9eac243 100644 --- a/api/src/main/kotlin/com/few/api/domain/problem/usecase/BrowseUndoneProblemsUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/problem/usecase/BrowseUndoneProblemsUseCase.kt @@ -1,16 +1,16 @@ package com.few.api.domain.problem.usecase +import com.few.api.domain.common.exception.NotFoundException +import com.few.api.domain.problem.repo.ProblemDao +import com.few.api.domain.problem.repo.SubmitHistoryDao +import com.few.api.domain.problem.repo.query.SelectProblemIdByArticleIdsQuery +import com.few.api.domain.problem.repo.query.SelectSubmittedProblemIdsQuery import com.few.api.domain.problem.service.ProblemArticleService import com.few.api.domain.problem.service.ProblemSubscriptionService import com.few.api.domain.problem.service.dto.BrowseArticleIdInDto import com.few.api.domain.problem.service.dto.BrowseWorkbookIdAndProgressInDto import com.few.api.domain.problem.usecase.dto.BrowseProblemsUseCaseOut import com.few.api.domain.problem.usecase.dto.BrowseUndoneProblemsUseCaseIn -import com.few.api.domain.common.exception.NotFoundException -import com.few.api.domain.problem.repo.ProblemDao -import com.few.api.domain.problem.repo.SubmitHistoryDao -import com.few.api.domain.problem.repo.query.SelectProblemIdByArticleIdsQuery -import com.few.api.domain.problem.repo.query.SelectSubmittedProblemIdsQuery import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional @@ -21,49 +21,56 @@ class BrowseUndoneProblemsUseCase( private val problemArticleService: ProblemArticleService, private val submitHistoryDao: SubmitHistoryDao, ) { - @Transactional(readOnly = true) fun execute(useCaseIn: BrowseUndoneProblemsUseCaseIn): BrowseProblemsUseCaseOut { /** * 유저가 구독한 워크북들에 속한 아티클 개수를 조회함 * 이때 아티클 개수는 현 시점 기준으로 이메일이 전송된 아티클 개수까지만 조회함 */ - val subscriptionProgresses = problemSubscriptionService.browseWorkbookIdAndProgress( - BrowseWorkbookIdAndProgressInDto(useCaseIn.memberId) - ).takeIf { it.isNotEmpty() } ?: throw NotFoundException("subscribe.workbook.notexist") + val subscriptionProgresses = + problemSubscriptionService + .browseWorkbookIdAndProgress( + BrowseWorkbookIdAndProgressInDto(useCaseIn.memberId), + ).takeIf { it.isNotEmpty() } ?: throw NotFoundException("subscribe.workbook.notexist") /** * 위에서 조회한 워크부에 속한 아티클 개수에 대해 article_id 들을 조회함 */ - val sentArticleIds = subscriptionProgresses.flatMap { subscriptionProgress -> - problemArticleService.browseArticleIdByWorkbookIdLimitDay( - BrowseArticleIdInDto( - subscriptionProgress.workbookId, - subscriptionProgress.day - ) - ) - }.toSet() + val sentArticleIds = + subscriptionProgresses + .flatMap { subscriptionProgress -> + problemArticleService.browseArticleIdByWorkbookIdLimitDay( + BrowseArticleIdInDto( + subscriptionProgress.workbookId, + subscriptionProgress.day, + ), + ) + }.toSet() /** * 위에서 구한 아티클에 속한 모든 problem_id, article_id 조합을 조회함 */ - val allProblemIdsAndArticleIdsToBeSolved = problemDao.selectProblemIdByArticleIds( - SelectProblemIdByArticleIdsQuery(sentArticleIds) - ) + val allProblemIdsAndArticleIdsToBeSolved = + problemDao.selectProblemIdByArticleIds( + SelectProblemIdByArticleIdsQuery(sentArticleIds), + ) /** * 위에서 구한 문제들에 대해 풀이 이력이 존재하는 problem_id만 추출 후 * 유저가 풀어야 할 전체 problem_id에 대해 여집합 연산 */ val allProblemIdsToBeSolved = allProblemIdsAndArticleIdsToBeSolved.map { it.problemId } - val submittedProblemIds = submitHistoryDao.selectProblemIdByProblemIds( - SelectSubmittedProblemIdsQuery(useCaseIn.memberId, allProblemIdsToBeSolved) - ).problemIds + val submittedProblemIds = + submitHistoryDao + .selectProblemIdByProblemIds( + SelectSubmittedProblemIdsQuery(useCaseIn.memberId, allProblemIdsToBeSolved), + ).problemIds - val unsubmittedProblemIdAndArticleIds: Map> = allProblemIdsAndArticleIdsToBeSolved - .filter { it.problemId !in submittedProblemIds } - .groupBy { it.articleId } - .mapValues { entry -> entry.value.map { it.problemId } } + val unsubmittedProblemIdAndArticleIds: Map> = + allProblemIdsAndArticleIdsToBeSolved + .filter { it.problemId !in submittedProblemIds } + .groupBy { it.articleId } + .mapValues { entry -> entry.value.map { it.problemId } } /** * 결과를 article_id를 기준으로 랜덤화한 뒤 problem_id를 순차적으로 리턴함 diff --git a/api/src/main/kotlin/com/few/api/domain/problem/usecase/CheckProblemUseCase.kt b/api/src/main/kotlin/com/few/api/domain/problem/usecase/CheckProblemUseCase.kt index a49d81ed6..e7c19da70 100644 --- a/api/src/main/kotlin/com/few/api/domain/problem/usecase/CheckProblemUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/problem/usecase/CheckProblemUseCase.kt @@ -1,13 +1,13 @@ package com.few.api.domain.problem.usecase +import com.few.api.domain.common.exception.InsertException +import com.few.api.domain.common.exception.NotFoundException import com.few.api.domain.problem.repo.ProblemDao import com.few.api.domain.problem.repo.SubmitHistoryDao import com.few.api.domain.problem.repo.command.InsertSubmitHistoryCommand import com.few.api.domain.problem.repo.query.SelectProblemAnswerQuery import com.few.api.domain.problem.usecase.dto.CheckProblemUseCaseIn import com.few.api.domain.problem.usecase.dto.CheckProblemUseCaseOut -import com.few.api.domain.common.exception.InsertException -import com.few.api.domain.common.exception.NotFoundException import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional @@ -16,7 +16,6 @@ class CheckProblemUseCase( private val problemDao: ProblemDao, private val submitHistoryDao: SubmitHistoryDao, ) { - @Transactional fun execute(useCaseIn: CheckProblemUseCaseIn): CheckProblemUseCaseOut { val memberId = useCaseIn.memberId @@ -27,13 +26,13 @@ class CheckProblemUseCase( val isSolved = record.answer == submitAns submitHistoryDao.insertSubmitHistory( - InsertSubmitHistoryCommand(problemId, memberId, submitAns, isSolved) + InsertSubmitHistoryCommand(problemId, memberId, submitAns, isSolved), ) ?: throw InsertException("submit.insertfail.record") return CheckProblemUseCaseOut( explanation = record.explanation, isSolved = isSolved, - answer = record.answer + answer = record.answer, ) } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/problem/usecase/ReadProblemUseCase.kt b/api/src/main/kotlin/com/few/api/domain/problem/usecase/ReadProblemUseCase.kt index 38a73515b..f10b122da 100644 --- a/api/src/main/kotlin/com/few/api/domain/problem/usecase/ReadProblemUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/problem/usecase/ReadProblemUseCase.kt @@ -1,12 +1,12 @@ package com.few.api.domain.problem.usecase +import com.few.api.domain.common.exception.NotFoundException import com.few.api.domain.problem.repo.ProblemDao import com.few.api.domain.problem.repo.query.SelectProblemQuery -import com.few.api.domain.problem.usecase.dto.ReadProblemUseCaseIn +import com.few.api.domain.problem.repo.support.ContentsJsonMapper import com.few.api.domain.problem.usecase.dto.ReadProblemContentsUseCaseOutDetail +import com.few.api.domain.problem.usecase.dto.ReadProblemUseCaseIn import com.few.api.domain.problem.usecase.dto.ReadProblemUseCaseOut -import com.few.api.domain.common.exception.NotFoundException -import com.few.api.domain.problem.repo.support.ContentsJsonMapper import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional @@ -15,26 +15,27 @@ class ReadProblemUseCase( private val problemDao: ProblemDao, private val contentsJsonMapper: ContentsJsonMapper, ) { - @Transactional(readOnly = true) fun execute(useCaseIn: ReadProblemUseCaseIn): ReadProblemUseCaseOut { val problemId = useCaseIn.problemId - val record = problemDao.selectProblemContents(SelectProblemQuery(problemId)) - ?: throw NotFoundException("problem.notfound.id") + val record = + problemDao.selectProblemContents(SelectProblemQuery(problemId)) + ?: throw NotFoundException("problem.notfound.id") - val contents: List = contentsJsonMapper.toObject(record.contents).contents.map { - ReadProblemContentsUseCaseOutDetail( - number = it.number, - content = it.content - ) - } + val contents: List = + contentsJsonMapper.toObject(record.contents).contents.map { + ReadProblemContentsUseCaseOutDetail( + number = it.number, + content = it.content, + ) + } return ReadProblemUseCaseOut( id = record.id, title = record.title, contents = contents, - articleId = record.articleId + articleId = record.articleId, ) } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/client/ApiSubscriptionClient.kt b/api/src/main/kotlin/com/few/api/domain/subscription/client/ApiSubscriptionClient.kt index 04594d61f..b420222cd 100644 --- a/api/src/main/kotlin/com/few/api/domain/subscription/client/ApiSubscriptionClient.kt +++ b/api/src/main/kotlin/com/few/api/domain/subscription/client/ApiSubscriptionClient.kt @@ -1,7 +1,5 @@ package com.few.api.domain.subscription.client -import web.client.DiscordBodyProperty -import web.client.Embed import com.few.api.domain.subscription.client.dto.WorkbookSubscriptionArgs import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.beans.factory.annotation.Value @@ -9,6 +7,8 @@ import org.springframework.http.HttpEntity import org.springframework.http.HttpMethod import org.springframework.stereotype.Service import org.springframework.web.client.RestTemplate +import web.client.DiscordBodyProperty +import web.client.Embed @Service class ApiSubscriptionClient( @@ -18,33 +18,36 @@ class ApiSubscriptionClient( private val log = KotlinLogging.logger {} fun announceWorkbookSubscription(args: WorkbookSubscriptionArgs) { - val body = args.let { arg -> - DiscordBodyProperty( - content = "🎉 신규 구독 알림 ", - embeds = listOf( - Embed( - title = "Total Subscriptions", - description = arg.totalSubscriptions.toString() - ), - Embed( - title = "Active Subscriptions", - description = arg.activeSubscriptions.toString() - ), - Embed( - title = "Workbook Title", - description = arg.workbookTitle - ) + val body = + args.let { arg -> + DiscordBodyProperty( + content = "🎉 신규 구독 알림 ", + embeds = + listOf( + Embed( + title = "Total Subscriptions", + description = arg.totalSubscriptions.toString(), + ), + Embed( + title = "Active Subscriptions", + description = arg.activeSubscriptions.toString(), + ), + Embed( + title = "Workbook Title", + description = arg.workbookTitle, + ), + ), ) - ) - } + } - restTemplate.exchange( - discordWebhook, - HttpMethod.POST, - HttpEntity(body), - String::class.java - ).let { res -> - log.info { "Discord webhook response: ${res.statusCode}" } - } + restTemplate + .exchange( + discordWebhook, + HttpMethod.POST, + HttpEntity(body), + String::class.java, + ).let { res -> + log.info { "Discord webhook response: ${res.statusCode}" } + } } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/controller/SubscriptionController.kt b/api/src/main/kotlin/com/few/api/domain/subscription/controller/SubscriptionController.kt index c9e257563..e6e983198 100644 --- a/api/src/main/kotlin/com/few/api/domain/subscription/controller/SubscriptionController.kt +++ b/api/src/main/kotlin/com/few/api/domain/subscription/controller/SubscriptionController.kt @@ -1,18 +1,16 @@ package com.few.api.domain.subscription.controller -import com.few.api.domain.subscription.usecase.* -import com.few.api.domain.subscription.controller.request.UnsubscribeWorkbookRequest -import web.ApiResponse -import web.ApiResponseGenerator -import com.few.api.domain.subscription.usecase.dto.* +import com.few.api.domain.common.vo.DayCode +import com.few.api.domain.common.vo.ViewCategory import com.few.api.domain.subscription.controller.request.UnsubscribeAllRequest +import com.few.api.domain.subscription.controller.request.UnsubscribeWorkbookRequest import com.few.api.domain.subscription.controller.request.UpdateSubscriptionDayRequest import com.few.api.domain.subscription.controller.request.UpdateSubscriptionTimeRequest -import com.few.api.domain.common.vo.DayCode -import com.few.api.domain.common.vo.ViewCategory import com.few.api.domain.subscription.controller.response.MainCardSubscribeWorkbookInfo import com.few.api.domain.subscription.controller.response.MyPageSubscribeWorkbookInfo import com.few.api.domain.subscription.controller.response.SubscribeWorkbooksResponse +import com.few.api.domain.subscription.usecase.* +import com.few.api.domain.subscription.usecase.dto.* import jakarta.validation.Valid import jakarta.validation.constraints.Min import org.springframework.http.HttpStatus @@ -21,6 +19,8 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.validation.annotation.Validated import org.springframework.web.bind.annotation.* import security.TokenUserDetails +import web.ApiResponse +import web.ApiResponseGenerator import java.lang.IllegalStateException @Validated @@ -34,13 +34,12 @@ class SubscriptionController( private val updateSubscriptionDayUseCase: UpdateSubscriptionDayUseCase, private val updateSubscriptionTimeUseCase: UpdateSubscriptionTimeUseCase, ) { - @GetMapping("/subscriptions/workbooks") fun browseSubscribeWorkbooks( @AuthenticationPrincipal userDetails: TokenUserDetails, @RequestParam( value = "view", - required = false + required = false, ) view: ViewCategory?, ): ApiResponse> { val memberId = userDetails.username.toLong() @@ -51,33 +50,36 @@ class SubscriptionController( // todo fix to facade usecase return SubscribeWorkbooksResponse( - workbooks = when (useCaseOut.clazz) { - MainCardSubscribeWorkbookDetail::class.java -> useCaseOut.workbooks.map { it as MainCardSubscribeWorkbookDetail }.map { - MainCardSubscribeWorkbookInfo( - id = it.workbookId, - status = it.isActiveSub.name, - totalDay = it.totalDay, - currentDay = it.currentDay, - rank = it.rank, - totalSubscriber = it.totalSubscriber, - subscription = it.subscription, - articleInfo = it.articleInfo - ) - } - MyPageSubscribeWorkbookDetail::class.java -> useCaseOut.workbooks.map { it as MyPageSubscribeWorkbookDetail }.map { - MyPageSubscribeWorkbookInfo( - id = it.workbookId, - status = it.isActiveSub.name, - totalDay = it.totalDay, - currentDay = it.currentDay, - rank = it.rank, - totalSubscriber = it.totalSubscriber, - subscription = it.subscription, - workbookInfo = it.workbookInfo - ) - } - else -> throw IllegalStateException("Invalid class type") - } + workbooks = + when (useCaseOut.clazz) { + MainCardSubscribeWorkbookDetail::class.java -> + useCaseOut.workbooks.map { it as MainCardSubscribeWorkbookDetail }.map { + MainCardSubscribeWorkbookInfo( + id = it.workbookId, + status = it.isActiveSub.name, + totalDay = it.totalDay, + currentDay = it.currentDay, + rank = it.rank, + totalSubscriber = it.totalSubscriber, + subscription = it.subscription, + articleInfo = it.articleInfo, + ) + } + MyPageSubscribeWorkbookDetail::class.java -> + useCaseOut.workbooks.map { it as MyPageSubscribeWorkbookDetail }.map { + MyPageSubscribeWorkbookInfo( + id = it.workbookId, + status = it.isActiveSub.name, + totalDay = it.totalDay, + currentDay = it.currentDay, + rank = it.rank, + totalSubscriber = it.totalSubscriber, + subscription = it.subscription, + workbookInfo = it.workbookInfo, + ) + } + else -> throw IllegalStateException("Invalid class type") + }, ).let { ApiResponseGenerator.success(it, HttpStatus.OK) } @@ -93,7 +95,7 @@ class SubscriptionController( ): ApiResponse { val memberId = userDetails.username.toLong() subscribeWorkbookUseCase.execute( - SubscribeWorkbookUseCaseIn(workbookId = workbookId, memberId = memberId) + SubscribeWorkbookUseCaseIn(workbookId = workbookId, memberId = memberId), ) return ApiResponseGenerator.success(HttpStatus.OK) @@ -114,8 +116,8 @@ class SubscriptionController( UnsubscribeWorkbookUseCaseIn( workbookId = workbookId, memberId = memberId, - opinion = body?.opinion ?: "cancel" - ) + opinion = body?.opinion ?: "cancel", + ), ) return ApiResponseGenerator.success(HttpStatus.OK) @@ -129,7 +131,7 @@ class SubscriptionController( ): ApiResponse { val memberId = userDetails.username.toLong() unsubscribeAllUseCase.execute( - UnsubscribeAllUseCaseIn(memberId = memberId, opinion = body?.opinion ?: "cancel") + UnsubscribeAllUseCaseIn(memberId = memberId, opinion = body?.opinion ?: "cancel"), ) return ApiResponseGenerator.success(HttpStatus.OK) @@ -144,7 +146,7 @@ class SubscriptionController( UpdateSubscriptionTimeUseCaseIn( memberId = userDetails.username.toLong(), time = body.time, - workbookId = body.workbookId + workbookId = body.workbookId, ).let { updateSubscriptionTimeUseCase.execute(it) } @@ -166,7 +168,7 @@ class SubscriptionController( UpdateSubscriptionDayUseCaseIn( memberId = userDetails.username.toLong(), dayCode = dayCode, - workbookId = body.workbookId + workbookId = body.workbookId, ).let { updateSubscriptionDayUseCase.execute(it) } diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/controller/response/SubscribeWorkbooksResponse.kt b/api/src/main/kotlin/com/few/api/domain/subscription/controller/response/SubscribeWorkbooksResponse.kt index ea12f1881..421ef9e60 100644 --- a/api/src/main/kotlin/com/few/api/domain/subscription/controller/response/SubscribeWorkbooksResponse.kt +++ b/api/src/main/kotlin/com/few/api/domain/subscription/controller/response/SubscribeWorkbooksResponse.kt @@ -26,14 +26,14 @@ class MainCardSubscribeWorkbookInfo( subscription: Subscription, val articleInfo: String, ) : SubscribeWorkbookInfo( - id = id, - status = status, - totalDay = totalDay, - currentDay = currentDay, - rank = rank, - totalSubscriber = totalSubscriber, - subscription = subscription -) + id = id, + status = status, + totalDay = totalDay, + currentDay = currentDay, + rank = rank, + totalSubscriber = totalSubscriber, + subscription = subscription, + ) class MyPageSubscribeWorkbookInfo( id: Long, @@ -45,11 +45,11 @@ class MyPageSubscribeWorkbookInfo( subscription: Subscription, val workbookInfo: String, ) : SubscribeWorkbookInfo( - id = id, - status = status, - totalDay = totalDay, - currentDay = currentDay, - rank = rank, - totalSubscriber = totalSubscriber, - subscription = subscription -) \ No newline at end of file + id = id, + status = status, + totalDay = totalDay, + currentDay = currentDay, + rank = rank, + totalSubscriber = totalSubscriber, + subscription = subscription, + ) \ 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 index db4596037..ea661b619 100644 --- 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 @@ -10,13 +10,12 @@ import org.springframework.transaction.event.TransactionalEventListener class WorkbookSubscriptionAfterCompletionEventListener( private val sendWorkbookArticleAsyncHandler: SendWorkbookArticleAsyncHandler, ) { - @TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION) fun handleEvent(event: WorkbookSubscriptionEvent) { sendWorkbookArticleAsyncHandler.sendWorkbookArticle( event.memberId, event.workbookId, - event.articleDayCol.toByte() + 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 c1ab57d57..2d0e916e3 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 @@ -9,7 +9,6 @@ import org.springframework.stereotype.Component class WorkbookSubscriptionEventListener( private val workbookSubscriptionClientAsyncHandler: WorkbookSubscriptionClientAsyncHandler, ) { - @EventListener fun handleEvent(event: WorkbookSubscriptionEvent) { workbookSubscriptionClientAsyncHandler.sendSubscriptionEvent(event.workbookId) diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/event/handler/SendWorkbookArticleAsyncHandler.kt b/api/src/main/kotlin/com/few/api/domain/subscription/event/handler/SendWorkbookArticleAsyncHandler.kt index 4fae23ff6..6bdbb3407 100644 --- a/api/src/main/kotlin/com/few/api/domain/subscription/event/handler/SendWorkbookArticleAsyncHandler.kt +++ b/api/src/main/kotlin/com/few/api/domain/subscription/event/handler/SendWorkbookArticleAsyncHandler.kt @@ -1,17 +1,17 @@ package com.few.api.domain.subscription.event.handler import com.few.api.config.ApiDatabaseAccessThreadPoolConfig.Companion.DATABASE_ACCESS_POOL +import com.few.api.domain.common.exception.NotFoundException import com.few.api.domain.common.lock.ApiLockFor import com.few.api.domain.common.lock.ApiLockIdentifier import com.few.api.domain.common.vo.CategoryType -import com.few.api.domain.subscription.service.* -import com.few.api.domain.subscription.service.dto.* -import com.few.api.domain.common.exception.NotFoundException +import com.few.api.domain.common.vo.SendType import com.few.api.domain.subscription.repo.SubscriptionDao import com.few.api.domain.subscription.repo.command.UpdateArticleProgressCommand import com.few.api.domain.subscription.repo.command.UpdateLastArticleProgressCommand import com.few.api.domain.subscription.repo.query.SelectSubscriptionQuery -import com.few.api.domain.common.vo.SendType +import com.few.api.domain.subscription.service.* +import com.few.api.domain.subscription.service.dto.* import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.scheduling.annotation.Async import org.springframework.stereotype.Component @@ -33,89 +33,98 @@ class SendWorkbookArticleAsyncHandler( @Async(value = DATABASE_ACCESS_POOL) @ApiLockFor(identifier = ApiLockIdentifier.SUBSCRIPTION_MEMBER_ID_WORKBOOK_ID) @Transactional(propagation = Propagation.REQUIRES_NEW) - fun sendWorkbookArticle(memberId: Long, workbookId: Long, articleDayCol: Byte) { + fun sendWorkbookArticle( + memberId: Long, + workbookId: Long, + articleDayCol: Byte, + ) { val date = LocalDate.now() - subscriptionDao.selectSubscriptionTimeRecord( - SelectSubscriptionQuery( - memberId = memberId, - workbookId = workbookId - ) - )?.let { - if (it.sendAt?.isAfter(date.atStartOfDay()) == true) { - return + subscriptionDao + .selectSubscriptionTimeRecord( + SelectSubscriptionQuery( + memberId = memberId, + workbookId = workbookId, + ), + )?.let { + if (it.sendAt?.isAfter(date.atStartOfDay()) == true) { + return + } } - } - val memberEmail = memberService.readMemberEmail(ReadMemberEmailInDto(memberId))?.email - ?: throw NotFoundException("member.notfound.id") - val article = articleService.readArticleIdByWorkbookIdAndDay( - ReadArticleIdByWorkbookIdAndDayDto( - workbookId, - articleDayCol.toInt() - ) - )?.let { articleId -> - articleService.readArticleContent(ReadArticleContentInDto(articleId)) - } ?: throw NotFoundException("article.notfound.id") + val memberEmail = + memberService.readMemberEmail(ReadMemberEmailInDto(memberId))?.email + ?: throw NotFoundException("member.notfound.id") + val article = + articleService + .readArticleIdByWorkbookIdAndDay( + ReadArticleIdByWorkbookIdAndDayDto( + workbookId, + articleDayCol.toInt(), + ), + )?.let { articleId -> + articleService.readArticleContent(ReadArticleContentInDto(articleId)) + } ?: throw NotFoundException("article.notfound.id") - val sendArticleInDto = SendArticleInDto( - memberId = memberId, - workbookId = workbookId, - toEmail = memberEmail, - articleDayCol = articleDayCol, - articleTitle = article.articleTitle, - articleContent = ContentDto( - memberEmail = memberEmail, + val sendArticleInDto = + SendArticleInDto( + memberId = memberId, workbookId = workbookId, - articleId = article.id, - currentDate = date, - category = CategoryType.convertToDisplayName(article.category.toByte()), - articleDay = articleDayCol.toInt(), + toEmail = memberEmail, + articleDayCol = articleDayCol, articleTitle = article.articleTitle, - writerName = article.writerName, - writerLink = article.writerLink, - articleContent = article.articleContent + articleContent = + ContentDto( + 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, + ), ) - ) runCatching { emailService.sendArticleEmail(sendArticleInDto) - } - .onSuccess { - subscriptionLogService.insertSendEvent( - InsertSendEventDto( - memberId = memberId, - articleId = article.id, - messageId = it, - sendType = SendType.AWSSES.code - ) - ) + }.onSuccess { + subscriptionLogService.insertSendEvent( + InsertSendEventDto( + memberId = memberId, + articleId = article.id, + messageId = it, + sendType = SendType.AWSSES.code, + ), + ) - val lastDayArticleId = - workbookService.readWorkbookLastArticleId( + val lastDayArticleId = + workbookService + .readWorkbookLastArticleId( ReadWorkbookLastArticleIdInDto( - workbookId - ) + workbookId, + ), )?.lastArticleId ?: throw NotFoundException("workbook.notfound.id") - if (article.id != lastDayArticleId) { - subscriptionDao.updateArticleProgress( - UpdateArticleProgressCommand( - memberId, - workbookId - ) - ) - } else { - subscriptionDao.updateLastArticleProgress( - UpdateLastArticleProgressCommand( - memberId, - workbookId - ) - ) - } - } - .onFailure { - log.error(it) { "Failed to send article email" } + if (article.id != lastDayArticleId) { + subscriptionDao.updateArticleProgress( + UpdateArticleProgressCommand( + memberId, + workbookId, + ), + ) + } else { + subscriptionDao.updateLastArticleProgress( + UpdateLastArticleProgressCommand( + memberId, + workbookId, + ), + ) } + }.onFailure { + log.error(it) { "Failed to send article email" } + } } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/event/handler/WorkbookSubscriptionClientAsyncHandler.kt b/api/src/main/kotlin/com/few/api/domain/subscription/event/handler/WorkbookSubscriptionClientAsyncHandler.kt index 93a80673b..eeafa1666 100644 --- a/api/src/main/kotlin/com/few/api/domain/subscription/event/handler/WorkbookSubscriptionClientAsyncHandler.kt +++ b/api/src/main/kotlin/com/few/api/domain/subscription/event/handler/WorkbookSubscriptionClientAsyncHandler.kt @@ -1,12 +1,12 @@ package com.few.api.domain.subscription.event.handler +import com.few.api.config.ApiThreadPoolConfig.Companion.DISCORD_HOOK_EVENT_POOL +import com.few.api.domain.common.exception.NotFoundException import com.few.api.domain.subscription.client.ApiSubscriptionClient import com.few.api.domain.subscription.client.dto.WorkbookSubscriptionArgs -import com.few.api.config.ApiThreadPoolConfig.Companion.DISCORD_HOOK_EVENT_POOL +import com.few.api.domain.subscription.repo.SubscriptionDao import com.few.api.domain.subscription.service.SubscriptionWorkbookService import com.few.api.domain.subscription.service.dto.ReadWorkbookTitleInDto -import com.few.api.domain.common.exception.NotFoundException -import com.few.api.domain.subscription.repo.SubscriptionDao import org.springframework.scheduling.annotation.Async import org.springframework.stereotype.Component @@ -16,7 +16,6 @@ class WorkbookSubscriptionClientAsyncHandler( private val apiSubscriptionClient: ApiSubscriptionClient, private val workbookService: SubscriptionWorkbookService, ) { - @Async(value = DISCORD_HOOK_EVENT_POOL) fun sendSubscriptionEvent(workbookId: Long) { val title = @@ -28,8 +27,8 @@ class WorkbookSubscriptionClientAsyncHandler( WorkbookSubscriptionArgs( totalSubscriptions = record.totalSubscriptions, activeSubscriptions = record.activeSubscriptions, - workbookTitle = title - ) + workbookTitle = title, + ), ) } } diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/exception/SubscribeIllegalArgumentException.kt b/api/src/main/kotlin/com/few/api/domain/subscription/exception/SubscribeIllegalArgumentException.kt index 14a893839..27552f023 100644 --- a/api/src/main/kotlin/com/few/api/domain/subscription/exception/SubscribeIllegalArgumentException.kt +++ b/api/src/main/kotlin/com/few/api/domain/subscription/exception/SubscribeIllegalArgumentException.kt @@ -18,9 +18,9 @@ class SubscribeIllegalArgumentException : IllegalStateException { constructor(code: String, cause: Throwable?) : super( ApiMessageSourceAccessor.getMessage( - code + code, ), - cause + cause, ) { this.code = code } diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/repo/SubscriptionDao.kt b/api/src/main/kotlin/com/few/api/domain/subscription/repo/SubscriptionDao.kt index 85eab4eef..9602098e0 100644 --- a/api/src/main/kotlin/com/few/api/domain/subscription/repo/SubscriptionDao.kt +++ b/api/src/main/kotlin/com/few/api/domain/subscription/repo/SubscriptionDao.kt @@ -16,21 +16,30 @@ import java.time.LocalDateTime class SubscriptionDao( private val dslContext: DSLContext, ) { - fun getLock(memberId: Long, workbookId: Long, timeout: Int = 5): Boolean { - return dslContext.fetch( - """ + fun getLock( + memberId: Long, + workbookId: Long, + timeout: Int = 5, + ): Boolean = + dslContext + .fetch( + """ SELECT GET_LOCK(CONCAT('subscription_', $memberId, '_', $workbookId), $timeout); - """ - ).into(Int::class.java).first() == 1 - } - - fun releaseLock(memberId: Long, workbookId: Long): Boolean { - return dslContext.fetch( - """ + """, + ).into(Int::class.java) + .first() == 1 + + fun releaseLock( + memberId: Long, + workbookId: Long, + ): Boolean = + dslContext + .fetch( + """ SELECT RELEASE_LOCK(CONCAT('subscription_', $memberId, '_', $workbookId)); - """ - ).into(Int::class.java).first() == 1 - } + """, + ).into(Int::class.java) + .first() == 1 fun insertWorkbookSubscription(command: InsertWorkbookSubscriptionCommand) { insertWorkbookSubscriptionCommand(command) @@ -39,7 +48,8 @@ class SubscriptionDao( } fun insertWorkbookSubscriptionCommand(command: InsertWorkbookSubscriptionCommand) = - dslContext.insertInto(SUBSCRIPTION) + dslContext + .insertInto(SUBSCRIPTION) .set(SUBSCRIPTION.MEMBER_ID, command.memberId) .set(SUBSCRIPTION.TARGET_WORKBOOK_ID, command.workbookId) .set(SUBSCRIPTION.SEND_DAY, command.sendDay) @@ -51,7 +61,8 @@ class SubscriptionDao( } fun reSubscribeWorkBookSubscriptionCommand(command: InsertWorkbookSubscriptionCommand) = - dslContext.update(SUBSCRIPTION) + dslContext + .update(SUBSCRIPTION) .set(SUBSCRIPTION.DELETED_AT, null as LocalDateTime?) .set(SUBSCRIPTION.UNSUBS_OPINION, null as String?) .set(SUBSCRIPTION.MODIFIED_AT, LocalDateTime.now()) @@ -66,45 +77,51 @@ class SubscriptionDao( } fun updateDeletedAtInWorkbookSubscriptionCommand(command: UpdateDeletedAtInWorkbookSubscriptionCommand) = - dslContext.update(SUBSCRIPTION) + dslContext + .update(SUBSCRIPTION) .set(SUBSCRIPTION.DELETED_AT, LocalDateTime.now()) .set(Subscription.SUBSCRIPTION.MODIFIED_AT, LocalDateTime.now()) .set(SUBSCRIPTION.UNSUBS_OPINION, command.opinion) .where(SUBSCRIPTION.MEMBER_ID.eq(command.memberId)) .and(SUBSCRIPTION.TARGET_WORKBOOK_ID.eq(command.workbookId)) - fun selectTopWorkbookSubscriptionStatus(query: SelectAllWorkbookSubscriptionStatusNotConsiderDeletedAtQuery): WorkbookSubscriptionStatus? { - return selectTopWorkbookSubscriptionStatusQuery(query) + fun selectTopWorkbookSubscriptionStatus( + query: SelectAllWorkbookSubscriptionStatusNotConsiderDeletedAtQuery, + ): WorkbookSubscriptionStatus? = + selectTopWorkbookSubscriptionStatusQuery(query) .fetchOneInto(WorkbookSubscriptionStatus::class.java) - } fun selectTopWorkbookSubscriptionStatusQuery(query: SelectAllWorkbookSubscriptionStatusNotConsiderDeletedAtQuery) = - dslContext.select( - SUBSCRIPTION.TARGET_WORKBOOK_ID.`as`(WorkbookSubscriptionStatus::workbookId.name), - SUBSCRIPTION.DELETED_AT.isNull.`as`(WorkbookSubscriptionStatus::isActiveSub.name), - SUBSCRIPTION.PROGRESS.add(1).`as`(WorkbookSubscriptionStatus::day.name) - ) - .from(SUBSCRIPTION) + dslContext + .select( + SUBSCRIPTION.TARGET_WORKBOOK_ID.`as`(WorkbookSubscriptionStatus::workbookId.name), + SUBSCRIPTION.DELETED_AT.isNull.`as`(WorkbookSubscriptionStatus::isActiveSub.name), + SUBSCRIPTION.PROGRESS.add(1).`as`(WorkbookSubscriptionStatus::day.name), + ).from(SUBSCRIPTION) .where(SUBSCRIPTION.MEMBER_ID.eq(query.memberId)) .and(SUBSCRIPTION.TARGET_WORKBOOK_ID.eq(query.workbookId)) .orderBy(SUBSCRIPTION.CREATED_AT.desc()) .limit(1) - fun selectAllInActiveWorkbookSubscriptionStatus(query: SelectAllMemberWorkbookInActiveSubscriptionQuery): List { - return selectAllWorkbookInActiveSubscriptionStatusQuery(query) + fun selectAllInActiveWorkbookSubscriptionStatus( + query: SelectAllMemberWorkbookInActiveSubscriptionQuery, + ): List = + selectAllWorkbookInActiveSubscriptionStatusQuery(query) .fetchInto(MemberWorkbookSubscriptionStatusRecord::class.java) - } fun selectAllWorkbookInActiveSubscriptionStatusQuery(query: SelectAllMemberWorkbookInActiveSubscriptionQuery) = - dslContext.select( - SUBSCRIPTION.TARGET_WORKBOOK_ID.`as`(MemberWorkbookSubscriptionStatusRecord::workbookId.name), - SUBSCRIPTION.DELETED_AT.isNull.`as`(MemberWorkbookSubscriptionStatusRecord::isActiveSub.name), - DSL.max(SUBSCRIPTION.PROGRESS).add(1) - .`as`(MemberWorkbookSubscriptionStatusRecord::currentDay.name), - DSL.max(MAPPING_WORKBOOK_ARTICLE.DAY_COL) - .`as`(MemberWorkbookSubscriptionStatusRecord::totalDay.name) - ) - .from(SUBSCRIPTION) + dslContext + .select( + SUBSCRIPTION.TARGET_WORKBOOK_ID.`as`(MemberWorkbookSubscriptionStatusRecord::workbookId.name), + SUBSCRIPTION.DELETED_AT.isNull.`as`(MemberWorkbookSubscriptionStatusRecord::isActiveSub.name), + DSL + .max(SUBSCRIPTION.PROGRESS) + .add(1) + .`as`(MemberWorkbookSubscriptionStatusRecord::currentDay.name), + DSL + .max(MAPPING_WORKBOOK_ARTICLE.DAY_COL) + .`as`(MemberWorkbookSubscriptionStatusRecord::totalDay.name), + ).from(SUBSCRIPTION) .join(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE) .on(SUBSCRIPTION.TARGET_WORKBOOK_ID.eq(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID)) .where(SUBSCRIPTION.MEMBER_ID.eq(query.memberId)) @@ -114,19 +131,20 @@ class SubscriptionDao( .orderBy(SUBSCRIPTION.PROGRESS) .query - fun selectAllActiveWorkbookSubscriptionStatus(query: SelectAllMemberWorkbookActiveSubscriptionQuery): List { - return selectAllWorkbookActiveSubscriptionStatusQuery(query) + fun selectAllActiveWorkbookSubscriptionStatus( + query: SelectAllMemberWorkbookActiveSubscriptionQuery, + ): List = + selectAllWorkbookActiveSubscriptionStatusQuery(query) .fetchInto(MemberWorkbookSubscriptionStatusRecord::class.java) - } fun selectAllWorkbookActiveSubscriptionStatusQuery(query: SelectAllMemberWorkbookActiveSubscriptionQuery) = - dslContext.select( - SUBSCRIPTION.TARGET_WORKBOOK_ID.`as`(MemberWorkbookSubscriptionStatusRecord::workbookId.name), - SUBSCRIPTION.DELETED_AT.isNull.`as`(MemberWorkbookSubscriptionStatusRecord::isActiveSub.name), - DSL.max(SUBSCRIPTION.PROGRESS).add(1).`as`(MemberWorkbookSubscriptionStatusRecord::currentDay.name), - DSL.max(MAPPING_WORKBOOK_ARTICLE.DAY_COL).`as`(MemberWorkbookSubscriptionStatusRecord::totalDay.name) - ) - .from(SUBSCRIPTION) + dslContext + .select( + SUBSCRIPTION.TARGET_WORKBOOK_ID.`as`(MemberWorkbookSubscriptionStatusRecord::workbookId.name), + SUBSCRIPTION.DELETED_AT.isNull.`as`(MemberWorkbookSubscriptionStatusRecord::isActiveSub.name), + DSL.max(SUBSCRIPTION.PROGRESS).add(1).`as`(MemberWorkbookSubscriptionStatusRecord::currentDay.name), + DSL.max(MAPPING_WORKBOOK_ARTICLE.DAY_COL).`as`(MemberWorkbookSubscriptionStatusRecord::totalDay.name), + ).from(SUBSCRIPTION) .join(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE) .on(SUBSCRIPTION.TARGET_WORKBOOK_ID.eq(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID)) .where(SUBSCRIPTION.MEMBER_ID.eq(query.memberId)) @@ -142,30 +160,35 @@ class SubscriptionDao( } fun updateDeletedAtInAllSubscriptionCommand(command: UpdateDeletedAtInAllSubscriptionCommand) = - dslContext.update(SUBSCRIPTION) + dslContext + .update(SUBSCRIPTION) .set(SUBSCRIPTION.DELETED_AT, LocalDateTime.now()) .set(SUBSCRIPTION.MODIFIED_AT, LocalDateTime.now()) .set(SUBSCRIPTION.UNSUBS_OPINION, command.opinion) // TODO: opinion row 마다 중복 해결 .where(SUBSCRIPTION.MEMBER_ID.eq(command.memberId)) - fun countWorkbookMappedArticles(query: CountWorkbookMappedArticlesQuery): Int? { - return countWorkbookMappedArticlesQuery(query) + fun countWorkbookMappedArticles(query: CountWorkbookMappedArticlesQuery): Int? = + countWorkbookMappedArticlesQuery(query) .fetchOne(0, Int::class.java) - } fun countWorkbookMappedArticlesQuery(query: CountWorkbookMappedArticlesQuery) = - dslContext.selectCount() + dslContext + .selectCount() .from(MAPPING_WORKBOOK_ARTICLE) .where(MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID.eq(query.workbookId)) fun countAllSubscriptionStatus(): CountAllSubscriptionStatusRecord { - val total = dslContext.selectCount() - .from(SUBSCRIPTION) - .fetchOne(0, Int::class.java)!! - val active = dslContext.selectCount() - .from(SUBSCRIPTION) - .where(SUBSCRIPTION.DELETED_AT.isNull) - .fetchOne(0, Int::class.java)!! + val total = + dslContext + .selectCount() + .from(SUBSCRIPTION) + .fetchOne(0, Int::class.java)!! + val active = + dslContext + .selectCount() + .from(SUBSCRIPTION) + .where(SUBSCRIPTION.DELETED_AT.isNull) + .fetchOne(0, Int::class.java)!! return CountAllSubscriptionStatusRecord(total.toLong(), active.toLong()) } @@ -173,33 +196,33 @@ class SubscriptionDao( * key: workbookId * value: workbook 구독 전체 기록 수 */ - fun countAllWorkbookSubscription(query: CountAllWorkbooksSubscriptionQuery): Map { - return countAllWorkbookSubscriptionQuery() + fun countAllWorkbookSubscription(query: CountAllWorkbooksSubscriptionQuery): Map = + countAllWorkbookSubscriptionQuery() .fetch() .intoMap(SUBSCRIPTION.TARGET_WORKBOOK_ID, DSL.count(SUBSCRIPTION.TARGET_WORKBOOK_ID)) - } - fun countAllWorkbookSubscriptionQuery() = dslContext.select( - SUBSCRIPTION.TARGET_WORKBOOK_ID, - DSL.count(SUBSCRIPTION.TARGET_WORKBOOK_ID) - ) - .from(SUBSCRIPTION) - .groupBy(SUBSCRIPTION.TARGET_WORKBOOK_ID) - .query + fun countAllWorkbookSubscriptionQuery() = + dslContext + .select( + SUBSCRIPTION.TARGET_WORKBOOK_ID, + DSL.count(SUBSCRIPTION.TARGET_WORKBOOK_ID), + ).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)) - .set(SUBSCRIPTION.MODIFIED_AT, LocalDateTime.now()) - .set(SUBSCRIPTION.SEND_AT, LocalDateTime.now()) - .where(SUBSCRIPTION.MEMBER_ID.eq(command.memberId)) - .and(SUBSCRIPTION.TARGET_WORKBOOK_ID.eq(command.workbookId)) + fun updateArticleProgressCommand(command: UpdateArticleProgressCommand) = + dslContext + .update(SUBSCRIPTION) + .set(SUBSCRIPTION.PROGRESS, SUBSCRIPTION.PROGRESS.add(1)) + .set(SUBSCRIPTION.MODIFIED_AT, LocalDateTime.now()) + .set(SUBSCRIPTION.SEND_AT, LocalDateTime.now()) + .where(SUBSCRIPTION.MEMBER_ID.eq(command.memberId)) + .and(SUBSCRIPTION.TARGET_WORKBOOK_ID.eq(command.workbookId)) fun updateLastArticleProgress(command: UpdateLastArticleProgressCommand) { updateLastArticleProgressCommand(command) @@ -207,7 +230,8 @@ class SubscriptionDao( } fun updateLastArticleProgressCommand(command: UpdateLastArticleProgressCommand) = - dslContext.update(SUBSCRIPTION) + dslContext + .update(SUBSCRIPTION) .set(SUBSCRIPTION.DELETED_AT, LocalDateTime.now()) .set(SUBSCRIPTION.MODIFIED_AT, LocalDateTime.now()) .set(SUBSCRIPTION.SEND_AT, LocalDateTime.now()) @@ -215,30 +239,29 @@ class SubscriptionDao( .where(SUBSCRIPTION.MEMBER_ID.eq(command.memberId)) .and(SUBSCRIPTION.TARGET_WORKBOOK_ID.eq(command.workbookId)) - fun selectAllSubscriptionSendStatus(query: SelectAllSubscriptionSendStatusQuery): List { - return selectAllSubscriptionSendStatusQuery(query) + fun selectAllSubscriptionSendStatus(query: SelectAllSubscriptionSendStatusQuery): List = + selectAllSubscriptionSendStatusQuery(query) .fetchInto( - SubscriptionSendStatusRecord::class.java + SubscriptionSendStatusRecord::class.java, ) - } fun selectAllSubscriptionSendStatusQuery(query: SelectAllSubscriptionSendStatusQuery) = - dslContext.select( - SUBSCRIPTION.TARGET_WORKBOOK_ID.`as`(SubscriptionSendStatusRecord::workbookId.name), - SUBSCRIPTION.SEND_TIME.`as`(SubscriptionSendStatusRecord::sendTime.name), - SUBSCRIPTION.SEND_DAY.`as`(SubscriptionSendStatusRecord::sendDay.name) - ) - .from(SUBSCRIPTION) + dslContext + .select( + SUBSCRIPTION.TARGET_WORKBOOK_ID.`as`(SubscriptionSendStatusRecord::workbookId.name), + SUBSCRIPTION.SEND_TIME.`as`(SubscriptionSendStatusRecord::sendTime.name), + SUBSCRIPTION.SEND_DAY.`as`(SubscriptionSendStatusRecord::sendDay.name), + ).from(SUBSCRIPTION) .where(SUBSCRIPTION.MEMBER_ID.eq(query.memberId)) .and(SUBSCRIPTION.TARGET_WORKBOOK_ID.`in`(query.workbookIds)) - fun selectAllActiveSubscriptionWorkbookIds(query: SelectAllActiveSubscriptionWorkbookIdsQuery): List { - return dslContext.select(SUBSCRIPTION.TARGET_WORKBOOK_ID) + fun selectAllActiveSubscriptionWorkbookIds(query: SelectAllActiveSubscriptionWorkbookIdsQuery): List = + dslContext + .select(SUBSCRIPTION.TARGET_WORKBOOK_ID) .from(SUBSCRIPTION) .where(SUBSCRIPTION.MEMBER_ID.eq(query.memberId)) .and(SUBSCRIPTION.DELETED_AT.isNull) .fetchInto(Long::class.java) - } data class SelectAllActiveSubscriptionWorkbookIdsQuery( val memberId: Long, @@ -250,7 +273,8 @@ class SubscriptionDao( } fun bulkUpdateSubscriptionSendTimeCommand(command: BulkUpdateSubscriptionSendTimeCommand) = - dslContext.update(SUBSCRIPTION) + dslContext + .update(SUBSCRIPTION) .set(SUBSCRIPTION.SEND_TIME, command.time) .set(SUBSCRIPTION.MODIFIED_AT, LocalDateTime.now()) .where(SUBSCRIPTION.MEMBER_ID.eq(command.memberId)) @@ -262,58 +286,54 @@ class SubscriptionDao( } fun bulkUpdateSubscriptionSendDayCommand(command: BulkUpdateSubscriptionSendDayCommand) = - dslContext.update(SUBSCRIPTION) + dslContext + .update(SUBSCRIPTION) .set(SUBSCRIPTION.SEND_DAY, command.day) .set(SUBSCRIPTION.MODIFIED_AT, LocalDateTime.now()) .where(SUBSCRIPTION.MEMBER_ID.eq(command.memberId)) .and(SUBSCRIPTION.TARGET_WORKBOOK_ID.`in`(command.workbookIds)) - fun selectSubscriptionTimeRecord( - query: SelectSubscriptionQuery, - ): SubscriptionTimeRecord? { - return selectSubscriptionTimeRecordQuery(query) + fun selectSubscriptionTimeRecord(query: SelectSubscriptionQuery): SubscriptionTimeRecord? = + selectSubscriptionTimeRecordQuery(query) .fetchOneInto(SubscriptionTimeRecord::class.java) - } fun selectSubscriptionTimeRecordQuery(query: SelectSubscriptionQuery) = - dslContext.select( - SUBSCRIPTION.MEMBER_ID.`as`(SubscriptionTimeRecord::memberId.name), - SUBSCRIPTION.TARGET_WORKBOOK_ID.`as`(SubscriptionTimeRecord::workbookId.name), - SUBSCRIPTION.CREATED_AT.`as`(SubscriptionTimeRecord::createdAt.name), - SUBSCRIPTION.MODIFIED_AT.`as`(SubscriptionTimeRecord::modifiedAt.name), - SUBSCRIPTION.SEND_AT.`as`(SubscriptionTimeRecord::sendAt.name) - ) - .from(SUBSCRIPTION) + dslContext + .select( + SUBSCRIPTION.MEMBER_ID.`as`(SubscriptionTimeRecord::memberId.name), + SUBSCRIPTION.TARGET_WORKBOOK_ID.`as`(SubscriptionTimeRecord::workbookId.name), + SUBSCRIPTION.CREATED_AT.`as`(SubscriptionTimeRecord::createdAt.name), + SUBSCRIPTION.MODIFIED_AT.`as`(SubscriptionTimeRecord::modifiedAt.name), + SUBSCRIPTION.SEND_AT.`as`(SubscriptionTimeRecord::sendAt.name), + ).from(SUBSCRIPTION) .where(SUBSCRIPTION.MEMBER_ID.eq(query.memberId)) .and(SUBSCRIPTION.TARGET_WORKBOOK_ID.eq(query.workbookId)) - fun selectSubscriptionSendStatus(query: SelectSubscriptionSendStatusQuery): List { - return selectSubscriptionSendStatusQuery(query) + fun selectSubscriptionSendStatus(query: SelectSubscriptionSendStatusQuery): List = + selectSubscriptionSendStatusQuery(query) .fetchInto(SubscriptionSendStatus::class.java) - } fun selectSubscriptionSendStatusQuery(query: SelectSubscriptionSendStatusQuery) = - dslContext.select( - SUBSCRIPTION.MEMBER_ID, - SUBSCRIPTION.TARGET_WORKBOOK_ID, - SUBSCRIPTION.SEND_TIME, - SUBSCRIPTION.SEND_DAY - ) - .from(SUBSCRIPTION) + dslContext + .select( + SUBSCRIPTION.MEMBER_ID, + SUBSCRIPTION.TARGET_WORKBOOK_ID, + SUBSCRIPTION.SEND_TIME, + SUBSCRIPTION.SEND_DAY, + ).from(SUBSCRIPTION) .where(SUBSCRIPTION.MEMBER_ID.eq(query.memberId)) .and(SUBSCRIPTION.DELETED_AT.isNull) - fun selectWorkbookIdAndProgressByMember(query: SelectSubscriptionSendStatusQuery): List { - return selectWorkbookIdAndProgressByMemberQuery(query) + fun selectWorkbookIdAndProgressByMember(query: SelectSubscriptionSendStatusQuery): List = + selectWorkbookIdAndProgressByMemberQuery(query) .fetchInto(SubscriptionProgressRecord::class.java) - } fun selectWorkbookIdAndProgressByMemberQuery(query: SelectSubscriptionSendStatusQuery) = - dslContext.select( - SUBSCRIPTION.TARGET_WORKBOOK_ID.`as`(SubscriptionProgressRecord::workbookId.name), - SUBSCRIPTION.PROGRESS.add(1).`as`(SubscriptionProgressRecord::day.name) - ) - .from(SUBSCRIPTION) + dslContext + .select( + SUBSCRIPTION.TARGET_WORKBOOK_ID.`as`(SubscriptionProgressRecord::workbookId.name), + SUBSCRIPTION.PROGRESS.add(1).`as`(SubscriptionProgressRecord::day.name), + ).from(SUBSCRIPTION) .where(SUBSCRIPTION.MEMBER_ID.eq(query.memberId)) .and(SUBSCRIPTION.DELETED_AT.isNull) } \ 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 7bbb86547..dfb80a778 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,36 +1,34 @@ 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.domain.article.repo.ArticleDao import com.few.api.domain.article.repo.query.SelectArticleContentQuery import com.few.api.domain.article.repo.query.SelectArticleIdByWorkbookIdAndDayQuery +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 org.springframework.stereotype.Service @Service class SubscriptionArticleService( private val articleDao: ArticleDao, ) { - fun readArticleIdByWorkbookIdAndDay(dto: ReadArticleIdByWorkbookIdAndDayDto): Long? { - return articleDao.selectArticleIdByWorkbookIdAndDay( + fun readArticleIdByWorkbookIdAndDay(dto: ReadArticleIdByWorkbookIdAndDayDto): Long? = + articleDao.selectArticleIdByWorkbookIdAndDay( SelectArticleIdByWorkbookIdAndDayQuery( dto.workbookId, - dto.day - ) + dto.day, + ), ) - } - fun readArticleContent(dto: ReadArticleContentInDto): ReadArticleContentOutDto? { - return articleDao.selectArticleContent(SelectArticleContentQuery(dto.articleId))?.let { + fun readArticleContent(dto: ReadArticleContentInDto): ReadArticleContentOutDto? = + articleDao.selectArticleContent(SelectArticleContentQuery(dto.articleId))?.let { ReadArticleContentOutDto( it.id, it.category, it.articleTitle, it.articleContent, it.writerName, - it.writerLink + 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 index 3b44b2154..bc485db23 100644 --- 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 @@ -1,28 +1,27 @@ package com.few.api.domain.subscription.service -import com.few.api.domain.subscription.service.dto.SendArticleInDto import com.few.api.domain.article.email.SendArticleEmailService import com.few.api.domain.article.email.dto.Content import com.few.api.domain.article.email.dto.SendArticleEmailArgs +import com.few.api.domain.subscription.service.dto.SendArticleInDto 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): String { - return sendArticleEmailService.send( + fun sendArticleEmail(dto: SendArticleInDto): String = + sendArticleEmailService.send( SendArticleEmailArgs( dto.toEmail, ARTICLE_SUBJECT_TEMPLATE.format( dto.articleDayCol, - dto.articleTitle + dto.articleTitle, ), ARTICLE_TEMPLATE, Content.create( @@ -35,9 +34,8 @@ class SubscriptionEmailService( dto.articleContent.articleTitle, dto.articleContent.writerName, dto.articleContent.writerLink, - dto.articleContent.articleContent - ) - ) + dto.articleContent.articleContent, + ), + ), ) - } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionLogService.kt b/api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionLogService.kt index 0caa2ea3e..798d14892 100644 --- a/api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionLogService.kt +++ b/api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionLogService.kt @@ -1,16 +1,15 @@ package com.few.api.domain.subscription.service -import com.few.api.domain.subscription.service.dto.InsertSendEventDto +import com.few.api.domain.common.vo.EmailLogEventType import com.few.api.domain.log.repo.SendArticleEventHistoryDao import com.few.api.domain.log.repo.command.InsertEventCommand -import com.few.api.domain.common.vo.EmailLogEventType +import com.few.api.domain.subscription.service.dto.InsertSendEventDto import org.springframework.stereotype.Service @Service class SubscriptionLogService( private val sendArticleEventHistoryDao: SendArticleEventHistoryDao, ) { - fun insertSendEvent(dto: InsertSendEventDto) { sendArticleEventHistoryDao.insertEvent( InsertEventCommand( @@ -18,8 +17,8 @@ class SubscriptionLogService( articleId = dto.articleId, messageId = dto.messageId, eventType = EmailLogEventType.SEND.code, - sendType = dto.sendType - ) + sendType = dto.sendType, + ), ) } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionMemberService.kt b/api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionMemberService.kt index e7064b297..7cb04b6ae 100644 --- a/api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionMemberService.kt +++ b/api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionMemberService.kt @@ -1,30 +1,26 @@ package com.few.api.domain.subscription.service -import com.few.api.domain.subscription.service.dto.* import com.few.api.domain.common.exception.InsertException import com.few.api.domain.member.repo.MemberDao import com.few.api.domain.member.repo.command.InsertMemberCommand import com.few.api.domain.member.repo.query.SelectMemberByEmailQuery import com.few.api.domain.member.repo.query.SelectMemberEmailQuery +import com.few.api.domain.subscription.service.dto.* import org.springframework.stereotype.Service @Service class SubscriptionMemberService( private val memberDao: MemberDao, ) { + fun readMemberId(dto: ReadMemberIdInDto): MemberIdOutDto? = + memberDao.selectMemberByEmail(SelectMemberByEmailQuery(dto.email))?.let { MemberIdOutDto(it.memberId) } - fun readMemberId(dto: ReadMemberIdInDto): MemberIdOutDto? { - return memberDao.selectMemberByEmail(SelectMemberByEmailQuery(dto.email))?.let { MemberIdOutDto(it.memberId) } - } - - fun insertMember(dto: InsertMemberInDto): MemberIdOutDto { - return memberDao.insertMember(InsertMemberCommand(dto.email, dto.memberType))?.let { MemberIdOutDto(it) } + fun insertMember(dto: InsertMemberInDto): MemberIdOutDto = + memberDao.insertMember(InsertMemberCommand(dto.email, dto.memberType))?.let { MemberIdOutDto(it) } ?: throw InsertException("member.insertfail.record") - } - fun readMemberEmail(dto: ReadMemberEmailInDto): ReadMemberEmailOutDto? { - return memberDao.selectMemberEmail(SelectMemberEmailQuery(dto.memberId))?.let { + fun readMemberEmail(dto: ReadMemberEmailInDto): ReadMemberEmailOutDto? = + memberDao.selectMemberEmail(SelectMemberEmailQuery(dto.memberId))?.let { ReadMemberEmailOutDto(it) } - } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionWorkbookService.kt b/api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionWorkbookService.kt index e1bf4b834..bc0cd66cd 100644 --- a/api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionWorkbookService.kt +++ b/api/src/main/kotlin/com/few/api/domain/subscription/service/SubscriptionWorkbookService.kt @@ -11,30 +11,29 @@ import org.springframework.stereotype.Service class SubscriptionWorkbookService( private val workbookDao: WorkbookDao, ) { - - fun readWorkbookTitle(dto: ReadWorkbookTitleInDto): ReadWorkbookTitleOutDto? { - return workbookDao.selectWorkBook(SelectWorkBookRecordQuery(dto.workbookId)) + fun readWorkbookTitle(dto: ReadWorkbookTitleInDto): ReadWorkbookTitleOutDto? = + workbookDao + .selectWorkBook(SelectWorkBookRecordQuery(dto.workbookId)) ?.title ?.let { ReadWorkbookTitleOutDto( - it + it, ) } - } /** * key: workbookId * value: title */ - fun readAllWorkbookTitle(dto: ReadAllWorkbookTitleInDto): Map { - return workbookDao.selectAllWorkbookTitle(SelectAllWorkbookTitleQuery(dto.workbookIds)) + fun readAllWorkbookTitle(dto: ReadAllWorkbookTitleInDto): Map = + workbookDao + .selectAllWorkbookTitle(SelectAllWorkbookTitleQuery(dto.workbookIds)) .associateBy({ it.workbookId }, { it.title }) - } - fun readWorkbookLastArticleId(dto: ReadWorkbookLastArticleIdInDto): ReadWorkbookLastArticleIdOutDto? { - return workbookDao.selectWorkBookLastArticleId(SelectWorkBookLastArticleIdQuery(dto.workbookId)) + fun readWorkbookLastArticleId(dto: ReadWorkbookLastArticleIdInDto): ReadWorkbookLastArticleIdOutDto? = + workbookDao + .selectWorkBookLastArticleId(SelectWorkBookLastArticleIdQuery(dto.workbookId)) ?.let { ReadWorkbookLastArticleIdOutDto(it) } - } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/BrowseSubscribeWorkbooksUseCase.kt b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/BrowseSubscribeWorkbooksUseCase.kt index 639edd5fa..0b74b2569 100644 --- a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/BrowseSubscribeWorkbooksUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/BrowseSubscribeWorkbooksUseCase.kt @@ -2,18 +2,18 @@ package com.few.api.domain.subscription.usecase import com.fasterxml.jackson.databind.ObjectMapper import com.few.api.domain.common.exception.NotFoundException -import com.few.api.domain.subscription.service.SubscriptionArticleService -import com.few.api.domain.subscription.service.SubscriptionWorkbookService -import com.few.api.domain.subscription.service.dto.ReadAllWorkbookTitleInDto -import com.few.api.domain.subscription.service.dto.ReadArticleIdByWorkbookIdAndDayDto -import com.few.api.domain.subscription.usecase.dto.* +import com.few.api.domain.common.vo.ViewCategory +import com.few.api.domain.common.vo.WorkBookStatus import com.few.api.domain.subscription.repo.SubscriptionDao import com.few.api.domain.subscription.repo.query.CountAllWorkbooksSubscriptionQuery import com.few.api.domain.subscription.repo.query.SelectAllMemberWorkbookActiveSubscriptionQuery import com.few.api.domain.subscription.repo.query.SelectAllMemberWorkbookInActiveSubscriptionQuery import com.few.api.domain.subscription.repo.query.SelectAllSubscriptionSendStatusQuery -import com.few.api.domain.common.vo.ViewCategory -import com.few.api.domain.common.vo.WorkBookStatus +import com.few.api.domain.subscription.service.SubscriptionArticleService +import com.few.api.domain.subscription.service.SubscriptionWorkbookService +import com.few.api.domain.subscription.service.dto.ReadAllWorkbookTitleInDto +import com.few.api.domain.subscription.service.dto.ReadArticleIdByWorkbookIdAndDayDto +import com.few.api.domain.subscription.usecase.dto.* import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional import java.lang.IllegalStateException @@ -52,108 +52,124 @@ class BrowseSubscribeWorkbooksUseCase( ) { @Transactional fun execute(useCaseIn: BrowseSubscribeWorkbooksUseCaseIn): BrowseSubscribeWorkbooksUseCaseOut { - val strategy = when (useCaseIn.view) { - ViewCategory.MAIN_CARD -> SUBSCRIBE_WORKBOOK_STRATEGY.MAIN_CARD - ViewCategory.MY_PAGE -> SUBSCRIBE_WORKBOOK_STRATEGY.MY_PAGE - } + val strategy = + when (useCaseIn.view) { + ViewCategory.MAIN_CARD -> SUBSCRIBE_WORKBOOK_STRATEGY.MAIN_CARD + ViewCategory.MY_PAGE -> SUBSCRIBE_WORKBOOK_STRATEGY.MY_PAGE + } - val workbookStatusRecords = when (strategy) { - SUBSCRIBE_WORKBOOK_STRATEGY.MAIN_CARD -> { - val activeSubscriptionRecords = subscriptionDao.selectAllActiveWorkbookSubscriptionStatus( - SelectAllMemberWorkbookActiveSubscriptionQuery(useCaseIn.memberId) - ) - val inActiveSubscriptionRecords = - subscriptionDao.selectAllInActiveWorkbookSubscriptionStatus( - SelectAllMemberWorkbookInActiveSubscriptionQuery(useCaseIn.memberId) + val workbookStatusRecords = + when (strategy) { + SUBSCRIBE_WORKBOOK_STRATEGY.MAIN_CARD -> { + val activeSubscriptionRecords = + subscriptionDao.selectAllActiveWorkbookSubscriptionStatus( + SelectAllMemberWorkbookActiveSubscriptionQuery(useCaseIn.memberId), + ) + val inActiveSubscriptionRecords = + subscriptionDao.selectAllInActiveWorkbookSubscriptionStatus( + SelectAllMemberWorkbookInActiveSubscriptionQuery(useCaseIn.memberId), + ) + activeSubscriptionRecords + inActiveSubscriptionRecords + } + SUBSCRIBE_WORKBOOK_STRATEGY.MY_PAGE -> { + subscriptionDao.selectAllActiveWorkbookSubscriptionStatus( + SelectAllMemberWorkbookActiveSubscriptionQuery(useCaseIn.memberId), ) - activeSubscriptionRecords + inActiveSubscriptionRecords - } - SUBSCRIBE_WORKBOOK_STRATEGY.MY_PAGE -> { - subscriptionDao.selectAllActiveWorkbookSubscriptionStatus( - SelectAllMemberWorkbookActiveSubscriptionQuery(useCaseIn.memberId) - ) + } } - } val subscriptionWorkbookIds = workbookStatusRecords.map { it.workbookId } - val subscriptionWorkbookCountRecords = subscriptionDao.countAllWorkbookSubscription( - CountAllWorkbooksSubscriptionQuery(subscriptionWorkbookIds) - ) - val subscriptionWorkbookSendStatusRecords = subscriptionDao.selectAllSubscriptionSendStatus( - SelectAllSubscriptionSendStatusQuery(useCaseIn.memberId, subscriptionWorkbookIds) - ).associateBy { it.workbookId } - - val workbookDetails = workbookStatusRecords.map { - SubscribeWorkbookDetail( - workbookId = it.workbookId, - isActiveSub = WorkBookStatus.fromStatus(it.isActiveSub), - currentDay = it.currentDay, - totalDay = it.totalDay, - totalSubscriber = subscriptionWorkbookCountRecords[it.workbookId]?.toLong() ?: 0, - subscription = subscriptionWorkbookSendStatusRecords[it.workbookId]?.let { record -> - Subscription( - time = record.sendTime, - dateTimeCode = record.sendDay - ) - } ?: throw IllegalStateException("${it.workbookId}'s subscription send status is null") + val subscriptionWorkbookCountRecords = + subscriptionDao.countAllWorkbookSubscription( + CountAllWorkbooksSubscriptionQuery(subscriptionWorkbookIds), ) - } + val subscriptionWorkbookSendStatusRecords = + subscriptionDao + .selectAllSubscriptionSendStatus( + SelectAllSubscriptionSendStatusQuery(useCaseIn.memberId, subscriptionWorkbookIds), + ).associateBy { it.workbookId } + + val workbookDetails = + workbookStatusRecords.map { + SubscribeWorkbookDetail( + workbookId = it.workbookId, + isActiveSub = WorkBookStatus.fromStatus(it.isActiveSub), + currentDay = it.currentDay, + totalDay = it.totalDay, + totalSubscriber = subscriptionWorkbookCountRecords[it.workbookId]?.toLong() ?: 0, + subscription = + subscriptionWorkbookSendStatusRecords[it.workbookId]?.let { record -> + Subscription( + time = record.sendTime, + dateTimeCode = record.sendDay, + ) + } ?: throw IllegalStateException("${it.workbookId}'s subscription send status is null"), + ) + } return when (strategy) { SUBSCRIBE_WORKBOOK_STRATEGY.MAIN_CARD -> { - val workbookSubscriptionCurrentArticleIdRecords = workbookStatusRecords.associate { record -> - val articleId = subscriptionArticleService.readArticleIdByWorkbookIdAndDay( - ReadArticleIdByWorkbookIdAndDayDto(record.workbookId, record.currentDay) - ) ?: throw NotFoundException("article.notfound.workbookIdAndCurrentDay") + val workbookSubscriptionCurrentArticleIdRecords = + workbookStatusRecords.associate { record -> + val articleId = + subscriptionArticleService.readArticleIdByWorkbookIdAndDay( + ReadArticleIdByWorkbookIdAndDayDto(record.workbookId, record.currentDay), + ) ?: throw NotFoundException("article.notfound.workbookIdAndCurrentDay") - record.workbookId to articleId - } + record.workbookId to articleId + } BrowseSubscribeWorkbooksUseCaseOut( clazz = MainCardSubscribeWorkbookDetail::class.java, - workbooks = workbookDetails.map { - MainCardSubscribeWorkbookDetail( - workbookId = it.workbookId, - isActiveSub = it.isActiveSub, - currentDay = it.currentDay, - totalDay = it.totalDay, - totalSubscriber = it.totalSubscriber, - subscription = it.subscription, - articleInfo = objectMapper.writeValueAsString( - ArticleInfo( - workbookSubscriptionCurrentArticleIdRecords[it.workbookId] - ?: throw IllegalStateException("${it.workbookId}'s articleId is null") - ) + workbooks = + workbookDetails.map { + MainCardSubscribeWorkbookDetail( + workbookId = it.workbookId, + isActiveSub = it.isActiveSub, + currentDay = it.currentDay, + totalDay = it.totalDay, + totalSubscriber = it.totalSubscriber, + subscription = it.subscription, + articleInfo = + objectMapper.writeValueAsString( + ArticleInfo( + workbookSubscriptionCurrentArticleIdRecords[it.workbookId] + ?: throw IllegalStateException("${it.workbookId}'s articleId is null"), + ), + ), ) - ) - } + }, ) } SUBSCRIBE_WORKBOOK_STRATEGY.MY_PAGE -> { - val workbookTitleRecords = subscriptionWorkbookService.readAllWorkbookTitle( - ReadAllWorkbookTitleInDto(subscriptionWorkbookIds) - ) + val workbookTitleRecords = + subscriptionWorkbookService.readAllWorkbookTitle( + ReadAllWorkbookTitleInDto(subscriptionWorkbookIds), + ) BrowseSubscribeWorkbooksUseCaseOut( clazz = MyPageSubscribeWorkbookDetail::class.java, - workbooks = workbookDetails.map { - MyPageSubscribeWorkbookDetail( - workbookId = it.workbookId, - isActiveSub = it.isActiveSub, - currentDay = it.currentDay, - totalDay = it.totalDay, - totalSubscriber = it.totalSubscriber, - subscription = it.subscription, - workbookInfo = objectMapper.writeValueAsString( - WorkbookInfo( - id = it.workbookId, - title = workbookTitleRecords[it.workbookId] - ?: throw IllegalStateException("${it.workbookId}'s title is null") - ) + workbooks = + workbookDetails.map { + MyPageSubscribeWorkbookDetail( + workbookId = it.workbookId, + isActiveSub = it.isActiveSub, + currentDay = it.currentDay, + totalDay = it.totalDay, + totalSubscriber = it.totalSubscriber, + subscription = it.subscription, + workbookInfo = + objectMapper.writeValueAsString( + WorkbookInfo( + id = it.workbookId, + title = + workbookTitleRecords[it.workbookId] + ?: throw IllegalStateException("${it.workbookId}'s title is null"), + ), + ), ) - ) - } + }, ) } } 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 27367c451..3f621605f 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 @@ -1,19 +1,19 @@ package com.few.api.domain.subscription.usecase +import com.few.api.domain.common.exception.NotFoundException import com.few.api.domain.common.lock.ApiLockFor import com.few.api.domain.common.lock.ApiLockIdentifier import com.few.api.domain.subscription.event.dto.WorkbookSubscriptionEvent +import com.few.api.domain.subscription.exception.SubscribeIllegalArgumentException import com.few.api.domain.subscription.repo.SubscriptionDao import com.few.api.domain.subscription.repo.command.InsertWorkbookSubscriptionCommand +import com.few.api.domain.subscription.repo.query.CountWorkbookMappedArticlesQuery import com.few.api.domain.subscription.repo.query.SelectAllWorkbookSubscriptionStatusNotConsiderDeletedAtQuery +import com.few.api.domain.subscription.repo.query.SelectSubscriptionSendStatusQuery 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.domain.common.exception.NotFoundException -import com.few.api.domain.subscription.exception.SubscribeIllegalArgumentException -import com.few.api.domain.subscription.repo.query.CountWorkbookMappedArticlesQuery -import com.few.api.domain.subscription.repo.query.SelectSubscriptionSendStatusQuery import org.springframework.context.ApplicationEventPublisher import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional @@ -23,50 +23,53 @@ class SubscribeWorkbookUseCase( private val subscriptionDao: SubscriptionDao, private val applicationEventPublisher: ApplicationEventPublisher, ) { - @ApiLockFor(ApiLockIdentifier.SUBSCRIPTION_MEMBER_ID_WORKBOOK_ID) @Transactional fun execute(useCaseIn: SubscribeWorkbookUseCaseIn) { val subTargetWorkbookId = useCaseIn.workbookId val memberId = useCaseIn.memberId - val command = subscriptionDao.selectSubscriptionSendStatus( - SelectSubscriptionSendStatusQuery( - memberId = memberId - ) - ).takeIf { - it.isNotEmpty() - }?.let { - /** 현재 구독 중인 정보가 있다면 해당 정보를 통해 구독 정보를 생성 */ - InsertWorkbookSubscriptionCommand( - memberId = memberId, - workbookId = subTargetWorkbookId, - sendDay = it[0].sendDay, - sendTime = it[0].sendTime - ) - } ?: run { - /** 현재 구독 중인 정보가 없다면 기본 정보로 구독 정보를 생성 */ - InsertWorkbookSubscriptionCommand( - memberId = memberId, - workbookId = subTargetWorkbookId - ) - } - - val workbookSubscriptionHistory = subscriptionDao.selectTopWorkbookSubscriptionStatus( - SelectAllWorkbookSubscriptionStatusNotConsiderDeletedAtQuery( - memberId = memberId, - workbookId = subTargetWorkbookId - ) - )?.let { - WorkbookSubscriptionHistory( - false, - WorkbookSubscriptionStatus( - workbookId = it.workbookId, - isActiveSub = it.isActiveSub, - day = it.day + val command = + subscriptionDao + .selectSubscriptionSendStatus( + SelectSubscriptionSendStatusQuery( + memberId = memberId, + ), + ).takeIf { + it.isNotEmpty() + }?.let { + /** 현재 구독 중인 정보가 있다면 해당 정보를 통해 구독 정보를 생성 */ + InsertWorkbookSubscriptionCommand( + memberId = memberId, + workbookId = subTargetWorkbookId, + sendDay = it[0].sendDay, + sendTime = it[0].sendTime, + ) + } ?: run { + /** 현재 구독 중인 정보가 없다면 기본 정보로 구독 정보를 생성 */ + InsertWorkbookSubscriptionCommand( + memberId = memberId, + workbookId = subTargetWorkbookId, ) - ) - } ?: WorkbookSubscriptionHistory(true) + } + + val workbookSubscriptionHistory = + subscriptionDao + .selectTopWorkbookSubscriptionStatus( + SelectAllWorkbookSubscriptionStatusNotConsiderDeletedAtQuery( + memberId = memberId, + workbookId = subTargetWorkbookId, + ), + )?.let { + WorkbookSubscriptionHistory( + false, + WorkbookSubscriptionStatus( + workbookId = it.workbookId, + isActiveSub = it.isActiveSub, + day = it.day, + ), + ) + } ?: WorkbookSubscriptionHistory(true) when { /** 구독한 히스토리가 없는 경우 */ @@ -77,11 +80,12 @@ class SubscribeWorkbookUseCase( /** 이미 구독한 히스토리가 있고 구독이 취소된 경우 */ workbookSubscriptionHistory.isCancelSub -> { val cancelledWorkbookSubscriptionHistory = CancelledWorkbookSubscriptionHistory(workbookSubscriptionHistory) - val lastDay = subscriptionDao.countWorkbookMappedArticles( - CountWorkbookMappedArticlesQuery( - subTargetWorkbookId - ) - ) ?: throw NotFoundException("workbook.notfound.id") + val lastDay = + subscriptionDao.countWorkbookMappedArticles( + CountWorkbookMappedArticlesQuery( + subTargetWorkbookId, + ), + ) ?: throw NotFoundException("workbook.notfound.id") if (cancelledWorkbookSubscriptionHistory.isSubEnd(lastDay)) { /** 이미 구독이 종료된 경우 */ @@ -102,8 +106,8 @@ class SubscribeWorkbookUseCase( WorkbookSubscriptionEvent( workbookId = subTargetWorkbookId, memberId = memberId, - articleDayCol = workbookSubscriptionHistory.subDay - ) + articleDayCol = workbookSubscriptionHistory.subDay, + ), ) } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/UnsubscribeAllUseCase.kt b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/UnsubscribeAllUseCase.kt index aae9108c8..37a2bd523 100644 --- a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/UnsubscribeAllUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/UnsubscribeAllUseCase.kt @@ -1,8 +1,8 @@ package com.few.api.domain.subscription.usecase -import com.few.api.domain.subscription.usecase.dto.UnsubscribeAllUseCaseIn import com.few.api.domain.subscription.repo.SubscriptionDao import com.few.api.domain.subscription.repo.command.UpdateDeletedAtInAllSubscriptionCommand +import com.few.api.domain.subscription.usecase.dto.UnsubscribeAllUseCaseIn import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional @@ -10,7 +10,6 @@ import org.springframework.transaction.annotation.Transactional class UnsubscribeAllUseCase( private val subscriptionDao: SubscriptionDao, ) { - @Transactional fun execute(useCaseIn: UnsubscribeAllUseCaseIn) { // TODO: request sending email @@ -22,8 +21,8 @@ class UnsubscribeAllUseCase( subscriptionDao.updateDeletedAtInAllSubscription( UpdateDeletedAtInAllSubscriptionCommand( memberId = useCaseIn.memberId, - opinion = opinion - ) + opinion = opinion, + ), ) } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/UnsubscribeWorkbookUseCase.kt b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/UnsubscribeWorkbookUseCase.kt index 6f2f8e624..16c95ea6f 100644 --- a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/UnsubscribeWorkbookUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/UnsubscribeWorkbookUseCase.kt @@ -10,7 +10,6 @@ import org.springframework.transaction.annotation.Transactional class UnsubscribeWorkbookUseCase( private val subscriptionDao: SubscriptionDao, ) { - // todo add test @Transactional fun execute(useCaseIn: UnsubscribeWorkbookUseCaseIn) { @@ -24,8 +23,8 @@ class UnsubscribeWorkbookUseCase( UpdateDeletedAtInWorkbookSubscriptionCommand( memberId = useCaseIn.memberId, workbookId = useCaseIn.workbookId, - opinion = opinion - ) + opinion = opinion, + ), ) } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/UpdateSubscriptionDayUseCase.kt b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/UpdateSubscriptionDayUseCase.kt index a170363d1..f6e1d1449 100644 --- a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/UpdateSubscriptionDayUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/UpdateSubscriptionDayUseCase.kt @@ -1,8 +1,8 @@ package com.few.api.domain.subscription.usecase -import com.few.api.domain.subscription.usecase.dto.UpdateSubscriptionDayUseCaseIn import com.few.api.domain.subscription.repo.SubscriptionDao import com.few.api.domain.subscription.repo.command.BulkUpdateSubscriptionSendDayCommand +import com.few.api.domain.subscription.usecase.dto.UpdateSubscriptionDayUseCaseIn import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional @@ -15,16 +15,17 @@ class UpdateSubscriptionDayUseCase( /** * workbookId기 없으면, memberId로 구독중인 모든 workbookId를 가져와서 해당하는 모든 workbookId의 구독요일을 변경한다. */ - useCaseIn.workbookId ?: subscriptionDao.selectAllActiveSubscriptionWorkbookIds( - SubscriptionDao.SelectAllActiveSubscriptionWorkbookIdsQuery(useCaseIn.memberId) - ).let { - subscriptionDao.bulkUpdateSubscriptionSendDay( - BulkUpdateSubscriptionSendDayCommand( - useCaseIn.memberId, - useCaseIn.dayCode.code, - it + useCaseIn.workbookId ?: subscriptionDao + .selectAllActiveSubscriptionWorkbookIds( + SubscriptionDao.SelectAllActiveSubscriptionWorkbookIdsQuery(useCaseIn.memberId), + ).let { + subscriptionDao.bulkUpdateSubscriptionSendDay( + BulkUpdateSubscriptionSendDayCommand( + useCaseIn.memberId, + useCaseIn.dayCode.code, + it, + ), ) - ) - } + } } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/UpdateSubscriptionTimeUseCase.kt b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/UpdateSubscriptionTimeUseCase.kt index ec2882fc5..931531b1f 100644 --- a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/UpdateSubscriptionTimeUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/UpdateSubscriptionTimeUseCase.kt @@ -1,31 +1,31 @@ package com.few.api.domain.subscription.usecase -import com.few.api.domain.subscription.usecase.dto.UpdateSubscriptionTimeUseCaseIn import com.few.api.domain.subscription.repo.SubscriptionDao import com.few.api.domain.subscription.repo.command.BulkUpdateSubscriptionSendTimeCommand +import com.few.api.domain.subscription.usecase.dto.UpdateSubscriptionTimeUseCaseIn import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional @Component class UpdateSubscriptionTimeUseCase( private val subscriptionDao: SubscriptionDao, - ) { @Transactional fun execute(useCaseIn: UpdateSubscriptionTimeUseCaseIn) { /** * workbookId기 없으면, memberId로 구독중인 모든 workbookId를 가져와서 해당하는 모든 workbookId의 구독요일을 변경한다. */ - useCaseIn.workbookId ?: subscriptionDao.selectAllActiveSubscriptionWorkbookIds( - SubscriptionDao.SelectAllActiveSubscriptionWorkbookIdsQuery(useCaseIn.memberId) - ).let { - subscriptionDao.bulkUpdateSubscriptionSendTime( - BulkUpdateSubscriptionSendTimeCommand( - useCaseIn.memberId, - useCaseIn.time, - it + useCaseIn.workbookId ?: subscriptionDao + .selectAllActiveSubscriptionWorkbookIds( + SubscriptionDao.SelectAllActiveSubscriptionWorkbookIdsQuery(useCaseIn.memberId), + ).let { + subscriptionDao.bulkUpdateSubscriptionSendTime( + BulkUpdateSubscriptionSendTimeCommand( + useCaseIn.memberId, + useCaseIn.time, + it, + ), ) - ) - } + } } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/dto/BrowseSubscribeWorkbooksUseCaseOut.kt b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/dto/BrowseSubscribeWorkbooksUseCaseOut.kt index 7b07c00b4..ed2c0ffa0 100644 --- a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/dto/BrowseSubscribeWorkbooksUseCaseOut.kt +++ b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/dto/BrowseSubscribeWorkbooksUseCaseOut.kt @@ -36,14 +36,14 @@ class MainCardSubscribeWorkbookDetail( subscription: Subscription, val articleInfo: String = "{}", ) : SubscribeWorkbookDetail( - workbookId = workbookId, - isActiveSub = isActiveSub, - currentDay = currentDay, - totalDay = totalDay, - rank = rank, - totalSubscriber = totalSubscriber, - subscription = subscription -) + workbookId = workbookId, + isActiveSub = isActiveSub, + currentDay = currentDay, + totalDay = totalDay, + rank = rank, + totalSubscriber = totalSubscriber, + subscription = subscription, + ) class MyPageSubscribeWorkbookDetail( workbookId: Long, @@ -55,11 +55,11 @@ class MyPageSubscribeWorkbookDetail( subscription: Subscription, val workbookInfo: String = "{}", ) : SubscribeWorkbookDetail( - workbookId = workbookId, - isActiveSub = isActiveSub, - currentDay = currentDay, - totalDay = totalDay, - rank = rank, - totalSubscriber = totalSubscriber, - subscription = subscription -) \ No newline at end of file + workbookId = workbookId, + isActiveSub = isActiveSub, + currentDay = currentDay, + totalDay = totalDay, + rank = rank, + totalSubscriber = totalSubscriber, + subscription = subscription, + ) \ No newline at end of file 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 index 024c46790..97b764cd5 100644 --- 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 @@ -3,9 +3,8 @@ package com.few.api.domain.subscription.usecase.model class CancelledWorkbookSubscriptionHistory( workbookSubscriptionHistory: WorkbookSubscriptionHistory, ) : WorkbookSubscriptionHistory( - workbookSubscriptionHistory -) { - + workbookSubscriptionHistory, + ) { init { require(isCancelSub) { "CanceledWorkbookSubscriptionHistory is not for active subscription." @@ -15,7 +14,5 @@ class CancelledWorkbookSubscriptionHistory( /** * 구독이 종료되었는지 여부 확인 */ - fun isSubEnd(lastDay: Int): Boolean { - return (lastDay <= workbookSubscriptionStatus!!.day) - } + fun isSubEnd(lastDay: Int): Boolean = (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 index f32f975aa..b6537d0c9 100644 --- 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 @@ -4,7 +4,6 @@ open class WorkbookSubscriptionHistory( val isNew: Boolean, protected val workbookSubscriptionStatus: WorkbookSubscriptionStatus? = null, ) { - init { if (isNew) { require(workbookSubscriptionStatus == null) { @@ -19,7 +18,7 @@ open class WorkbookSubscriptionHistory( constructor(workbookSubscriptionHistory: WorkbookSubscriptionHistory) : this( workbookSubscriptionHistory.isNew, - workbookSubscriptionHistory.workbookSubscriptionStatus + workbookSubscriptionHistory.workbookSubscriptionStatus, ) /** diff --git a/api/src/main/kotlin/com/few/api/domain/workbook/article/controller/WorkBookArticleController.kt b/api/src/main/kotlin/com/few/api/domain/workbook/article/controller/WorkBookArticleController.kt index 92a11504f..81877219e 100644 --- a/api/src/main/kotlin/com/few/api/domain/workbook/article/controller/WorkBookArticleController.kt +++ b/api/src/main/kotlin/com/few/api/domain/workbook/article/controller/WorkBookArticleController.kt @@ -1,12 +1,8 @@ package com.few.api.domain.workbook.article.controller +import com.few.api.domain.workbook.article.controller.response.ReadWorkBookArticleResponse import com.few.api.domain.workbook.article.dto.ReadWorkBookArticleUseCaseIn import com.few.api.domain.workbook.article.usecase.ReadWorkBookArticleUseCase -import com.few.api.domain.workbook.article.controller.response.ReadWorkBookArticleResponse -import web.ApiResponse -import web.ApiResponseGenerator -import web.security.UserArgument -import web.security.UserArgumentDetails import jakarta.validation.constraints.Min import org.springframework.http.HttpStatus import org.springframework.http.MediaType @@ -15,6 +11,10 @@ import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController +import web.ApiResponse +import web.ApiResponseGenerator +import web.security.UserArgument +import web.security.UserArgumentDetails @Validated @RestController @@ -22,7 +22,6 @@ import org.springframework.web.bind.annotation.RestController class WorkBookArticleController( private val readWorkBookArticleUseCase: ReadWorkBookArticleUseCase, ) { - @GetMapping("/{articleId}") fun readWorkBookArticle( @UserArgument userArgumentDetails: UserArgumentDetails, @@ -35,13 +34,14 @@ class WorkBookArticleController( ): ApiResponse> { val memberId = userArgumentDetails.id.toLong() - val useCaseOut = ReadWorkBookArticleUseCaseIn( - workbookId = workbookId, - articleId = articleId, - memberId - ).let { useCaseIn: ReadWorkBookArticleUseCaseIn -> - readWorkBookArticleUseCase.execute(useCaseIn) - } + val useCaseOut = + ReadWorkBookArticleUseCaseIn( + workbookId = workbookId, + articleId = articleId, + memberId, + ).let { useCaseIn: ReadWorkBookArticleUseCaseIn -> + readWorkBookArticleUseCase.execute(useCaseIn) + } return ReadWorkBookArticleResponse(useCaseOut).let { ApiResponseGenerator.success(it, HttpStatus.OK) diff --git a/api/src/main/kotlin/com/few/api/domain/workbook/article/controller/response/ReadWorkBookArticleResponse.kt b/api/src/main/kotlin/com/few/api/domain/workbook/article/controller/response/ReadWorkBookArticleResponse.kt index a6040bcb9..5b7bb1aff 100644 --- a/api/src/main/kotlin/com/few/api/domain/workbook/article/controller/response/ReadWorkBookArticleResponse.kt +++ b/api/src/main/kotlin/com/few/api/domain/workbook/article/controller/response/ReadWorkBookArticleResponse.kt @@ -16,16 +16,17 @@ data class ReadWorkBookArticleResponse( ) { constructor(useCaseOut: ReadWorkBookArticleOut) : this( id = useCaseOut.id, - writer = WriterInfo( - id = useCaseOut.writer.id, - name = useCaseOut.writer.name, - url = useCaseOut.writer.url - ), + writer = + WriterInfo( + id = useCaseOut.writer.id, + name = useCaseOut.writer.name, + url = useCaseOut.writer.url, + ), title = useCaseOut.title, content = useCaseOut.content, problemIds = useCaseOut.problemIds, category = useCaseOut.category, createdAt = useCaseOut.createdAt, - day = useCaseOut.day + day = useCaseOut.day, ) } \ No newline at end of file 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 f88ded31c..2ea01a954 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,18 +1,18 @@ package com.few.api.domain.workbook.article.usecase import com.few.api.domain.article.event.dto.ReadArticleEvent -import com.few.api.domain.article.usecase.transaction.ArticleViewCountTxCase +import com.few.api.domain.article.repo.ArticleDao +import com.few.api.domain.article.repo.query.SelectWorkBookArticleRecordQuery 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 import com.few.api.domain.article.service.dto.ReadWriterRecordInDto +import com.few.api.domain.article.usecase.transaction.ArticleViewCountTxCase +import com.few.api.domain.common.exception.NotFoundException import com.few.api.domain.common.vo.CategoryType -import com.few.api.domain.workbook.article.dto.ReadWorkBookArticleUseCaseIn import com.few.api.domain.workbook.article.dto.ReadWorkBookArticleOut +import com.few.api.domain.workbook.article.dto.ReadWorkBookArticleUseCaseIn import com.few.api.domain.workbook.article.dto.WriterDetail -import com.few.api.domain.common.exception.NotFoundException -import com.few.api.domain.article.repo.ArticleDao -import com.few.api.domain.article.repo.query.SelectWorkBookArticleRecordQuery import org.springframework.context.ApplicationEventPublisher import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -27,12 +27,13 @@ class ReadWorkBookArticleUseCase( ) { @Transactional(readOnly = true) fun execute(useCaseIn: ReadWorkBookArticleUseCaseIn): ReadWorkBookArticleOut { - val articleRecord = articleDao.selectWorkBookArticleRecord( - SelectWorkBookArticleRecordQuery( - useCaseIn.workbookId, - useCaseIn.articleId - ) - ) ?: throw NotFoundException("article.notfound.articleidworkbookid") + val articleRecord = + articleDao.selectWorkBookArticleRecord( + SelectWorkBookArticleRecordQuery( + useCaseIn.workbookId, + useCaseIn.articleId, + ), + ) ?: throw NotFoundException("article.notfound.articleidworkbookid") val writerRecord = readArticleWriterRecordService.execute(ReadWriterRecordInDto(articleRecord.writerId)) @@ -46,25 +47,27 @@ class ReadWorkBookArticleUseCase( ReadArticleEvent( articleId = useCaseIn.articleId, memberId = useCaseIn.memberId, - category = CategoryType.fromCode(articleRecord.category) ?: throw NotFoundException( - "article.invalid.category" - ) - ) + category = + CategoryType.fromCode(articleRecord.category) ?: throw NotFoundException( + "article.invalid.category", + ), + ), ) return ReadWorkBookArticleOut( id = articleRecord.articleId, - writer = WriterDetail( - id = writerRecord.writerId, - name = writerRecord.name, - url = writerRecord.url - ), + writer = + WriterDetail( + id = writerRecord.writerId, + name = writerRecord.name, + url = writerRecord.url, + ), title = articleRecord.title, content = articleRecord.content, problemIds = problemIds.problemIds, category = CategoryType.convertToDisplayName(articleRecord.category), createdAt = articleRecord.createdAt, - day = articleRecord.day + day = articleRecord.day, ) } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/workbook/controller/WorkBookController.kt b/api/src/main/kotlin/com/few/api/domain/workbook/controller/WorkBookController.kt index 18562ad0b..3fd3b0ce9 100644 --- a/api/src/main/kotlin/com/few/api/domain/workbook/controller/WorkBookController.kt +++ b/api/src/main/kotlin/com/few/api/domain/workbook/controller/WorkBookController.kt @@ -1,19 +1,15 @@ package com.few.api.domain.workbook.controller -import com.few.api.domain.workbook.usecase.BrowseWorkbooksUseCase -import com.few.api.domain.workbook.usecase.dto.ReadWorkbookUseCaseIn -import com.few.api.domain.workbook.usecase.ReadWorkbookUseCase -import com.few.api.domain.workbook.usecase.dto.BrowseWorkbooksUseCaseIn import com.few.api.domain.common.vo.ViewCategory import com.few.api.domain.common.vo.WorkBookCategory import com.few.api.domain.workbook.controller.response.BrowseWorkBookInfo import com.few.api.domain.workbook.controller.response.BrowseWorkBooksResponse import com.few.api.domain.workbook.controller.response.ReadWorkBookResponse import com.few.api.domain.workbook.controller.response.WriterInfo -import web.ApiResponse -import web.ApiResponseGenerator -import web.security.UserArgument -import web.security.UserArgumentDetails +import com.few.api.domain.workbook.usecase.BrowseWorkbooksUseCase +import com.few.api.domain.workbook.usecase.ReadWorkbookUseCase +import com.few.api.domain.workbook.usecase.dto.BrowseWorkbooksUseCaseIn +import com.few.api.domain.workbook.usecase.dto.ReadWorkbookUseCaseIn import jakarta.validation.constraints.Min import org.springframework.http.HttpStatus import org.springframework.http.MediaType @@ -23,6 +19,10 @@ import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController +import web.ApiResponse +import web.ApiResponseGenerator +import web.security.UserArgument +import web.security.UserArgumentDetails @Validated @RestController @@ -31,21 +31,20 @@ class WorkBookController( private val readWorkbookUseCase: ReadWorkbookUseCase, private val browseWorkBooksUseCase: BrowseWorkbooksUseCase, ) { - @GetMapping("/categories") - fun browseWorkBookCategories(): ApiResponse>> { - return ApiResponseGenerator.success( + fun browseWorkBookCategories(): ApiResponse>> = + ApiResponseGenerator.success( mapOf( - "categories" to WorkBookCategory.entries.map { - mapOf( - "code" to it.code, - "name" to it.displayName - ) - } + "categories" to + WorkBookCategory.entries.map { + mapOf( + "code" to it.code, + "name" to it.displayName, + ) + }, ), - HttpStatus.OK + HttpStatus.OK, ) - } @GetMapping fun browseWorkBooks( @@ -75,12 +74,12 @@ class WorkBookController( WriterInfo( writerDetail.id, writerDetail.name, - writerDetail.url + writerDetail.url, ) }, - workBookDetail.subscriptionCount + workBookDetail.subscriptionCount, ) - } + }, ).let { response -> return ApiResponseGenerator.success(response, HttpStatus.OK) } @@ -92,9 +91,10 @@ class WorkBookController( @Min(value = 1, message = "{min.id}") workbookId: Long, ): ApiResponse> { - val useCaseOut = ReadWorkbookUseCaseIn(workbookId).let { useCaseIn -> - readWorkbookUseCase.execute(useCaseIn) - } + val useCaseOut = + ReadWorkbookUseCaseIn(workbookId).let { useCaseIn -> + readWorkbookUseCase.execute(useCaseIn) + } ReadWorkBookResponse(useCaseOut).let { response -> return ApiResponseGenerator.success(response, HttpStatus.OK) diff --git a/api/src/main/kotlin/com/few/api/domain/workbook/controller/response/ReadWorkBookResponse.kt b/api/src/main/kotlin/com/few/api/domain/workbook/controller/response/ReadWorkBookResponse.kt index 338904d57..221b159da 100644 --- a/api/src/main/kotlin/com/few/api/domain/workbook/controller/response/ReadWorkBookResponse.kt +++ b/api/src/main/kotlin/com/few/api/domain/workbook/controller/response/ReadWorkBookResponse.kt @@ -23,7 +23,7 @@ data class ReadWorkBookResponse( category = useCaseOut.category, createdAt = useCaseOut.createdAt, writers = useCaseOut.writers.map { WriterInfo(it.id, it.name, it.url) }, - articles = useCaseOut.articles.map { ArticleInfo(it.id, it.title) } + articles = useCaseOut.articles.map { ArticleInfo(it.id, it.title) }, ) } diff --git a/api/src/main/kotlin/com/few/api/domain/workbook/repo/WorkBookCacheManager.kt b/api/src/main/kotlin/com/few/api/domain/workbook/repo/WorkBookCacheManager.kt index 52f1a361a..15b7ea769 100644 --- a/api/src/main/kotlin/com/few/api/domain/workbook/repo/WorkBookCacheManager.kt +++ b/api/src/main/kotlin/com/few/api/domain/workbook/repo/WorkBookCacheManager.kt @@ -11,7 +11,6 @@ import javax.cache.Cache class WorkBookCacheManager( private val cacheManager: CacheManager, ) { - private var selectWorkBookCache: Cache = cacheManager.getCache(SELECT_WORKBOOK_RECORD_CACHE)?.nativeCache as Cache fun getAllSelectWorkBookValues(): List { diff --git a/api/src/main/kotlin/com/few/api/domain/workbook/repo/WorkbookDao.kt b/api/src/main/kotlin/com/few/api/domain/workbook/repo/WorkbookDao.kt index 2fe0b8c6e..cc8ed49fc 100644 --- a/api/src/main/kotlin/com/few/api/domain/workbook/repo/WorkbookDao.kt +++ b/api/src/main/kotlin/com/few/api/domain/workbook/repo/WorkbookDao.kt @@ -1,8 +1,8 @@ package com.few.api.domain.workbook.repo -import com.few.api.domain.common.vo.CategoryType import com.few.api.config.ApiLocalCacheConfig import com.few.api.config.ApiLocalCacheConfig.Companion.LOCAL_CM +import com.few.api.domain.common.vo.CategoryType import com.few.api.domain.workbook.repo.command.InsertWorkBookCommand import com.few.api.domain.workbook.repo.command.MapWorkBookToArticleCommand import com.few.api.domain.workbook.repo.query.BrowseWorkBookQueryWithSubscriptionCountQuery @@ -17,8 +17,8 @@ import jooq.jooq_dsl.tables.Subscription import jooq.jooq_dsl.tables.Workbook import org.jooq.Condition import org.jooq.DSLContext -import org.springframework.cache.annotation.Cacheable import org.jooq.impl.DSL +import org.springframework.cache.annotation.Cacheable import org.springframework.stereotype.Repository @Repository @@ -26,33 +26,32 @@ class WorkbookDao( private val dslContext: DSLContext, ) { @Cacheable(key = "#query.id", cacheManager = LOCAL_CM, cacheNames = [ApiLocalCacheConfig.SELECT_WORKBOOK_RECORD_CACHE]) - fun selectWorkBook(query: SelectWorkBookRecordQuery): SelectWorkBookRecord? { - return selectWorkBookQuery(query) + fun selectWorkBook(query: SelectWorkBookRecordQuery): SelectWorkBookRecord? = + selectWorkBookQuery(query) .fetchOneInto(SelectWorkBookRecord::class.java) - } fun selectWorkBookQuery(query: SelectWorkBookRecordQuery) = - dslContext.select( - Workbook.WORKBOOK.ID.`as`(SelectWorkBookRecord::id.name), - Workbook.WORKBOOK.TITLE.`as`(SelectWorkBookRecord::title.name), - Workbook.WORKBOOK.MAIN_IMAGE_URL.`as`(SelectWorkBookRecord::mainImageUrl.name), - Workbook.WORKBOOK.CATEGORY_CD.`as`(SelectWorkBookRecord::category.name), - Workbook.WORKBOOK.DESCRIPTION.`as`(SelectWorkBookRecord::description.name), - Workbook.WORKBOOK.CREATED_AT.`as`(SelectWorkBookRecord::createdAt.name) - ) - .from(Workbook.WORKBOOK) + dslContext + .select( + Workbook.WORKBOOK.ID.`as`(SelectWorkBookRecord::id.name), + Workbook.WORKBOOK.TITLE.`as`(SelectWorkBookRecord::title.name), + Workbook.WORKBOOK.MAIN_IMAGE_URL.`as`(SelectWorkBookRecord::mainImageUrl.name), + Workbook.WORKBOOK.CATEGORY_CD.`as`(SelectWorkBookRecord::category.name), + Workbook.WORKBOOK.DESCRIPTION.`as`(SelectWorkBookRecord::description.name), + Workbook.WORKBOOK.CREATED_AT.`as`(SelectWorkBookRecord::createdAt.name), + ).from(Workbook.WORKBOOK) .where(Workbook.WORKBOOK.ID.eq(query.id)) .and(Workbook.WORKBOOK.DELETED_AT.isNull) - fun insertWorkBook(command: InsertWorkBookCommand): Long? { - return insertWorkBookCommand(command) + fun insertWorkBook(command: InsertWorkBookCommand): Long? = + insertWorkBookCommand(command) .returning(Workbook.WORKBOOK.ID) .fetchOne() ?.id - } fun insertWorkBookCommand(command: InsertWorkBookCommand) = - dslContext.insertInto(Workbook.WORKBOOK) + dslContext + .insertInto(Workbook.WORKBOOK) .set(Workbook.WORKBOOK.TITLE, command.title) .set(Workbook.WORKBOOK.MAIN_IMAGE_URL, command.mainImageUrl.toString()) .set(Workbook.WORKBOOK.CATEGORY_CD, CategoryType.convertToCode(command.category)) @@ -64,7 +63,8 @@ class WorkbookDao( } fun mapWorkBookToArticleCommand(command: MapWorkBookToArticleCommand) = - dslContext.insertInto(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE) + dslContext + .insertInto(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE) .set(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID, command.workbookId) .set(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.ARTICLE_ID, command.articleId) .set(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DAY_COL, command.dayCol) @@ -73,90 +73,87 @@ class WorkbookDao( * category에 따라서 조회된 구독자 수가 포함된 Workbook 목록을 반환한다. * 정렬 순서는 구독자 수가 많은 순서로, 구독자 수가 같다면 생성일자가 최신인 순서로 반환한다. */ - fun browseWorkBookWithSubscriptionCount(query: BrowseWorkBookQueryWithSubscriptionCountQuery): List { - return browseWorkBookQuery(query) + fun browseWorkBookWithSubscriptionCount( + query: BrowseWorkBookQueryWithSubscriptionCountQuery, + ): List = + browseWorkBookQuery(query) .fetchInto(SelectWorkBookRecordWithSubscriptionCount::class.java) - } fun browseWorkBookQuery(query: BrowseWorkBookQueryWithSubscriptionCountQuery) = - dslContext.select( - Workbook.WORKBOOK.ID.`as`(SelectWorkBookRecordWithSubscriptionCount::id.name), - Workbook.WORKBOOK.TITLE.`as`(SelectWorkBookRecordWithSubscriptionCount::title.name), - Workbook.WORKBOOK.MAIN_IMAGE_URL.`as`(SelectWorkBookRecordWithSubscriptionCount::mainImageUrl.name), - Workbook.WORKBOOK.CATEGORY_CD.`as`(SelectWorkBookRecordWithSubscriptionCount::category.name), - Workbook.WORKBOOK.DESCRIPTION.`as`(SelectWorkBookRecordWithSubscriptionCount::description.name), - Workbook.WORKBOOK.CREATED_AT.`as`(SelectWorkBookRecordWithSubscriptionCount::createdAt.name), - /** 구독자 수가 없다면 0으로 반환한다. */ - DSL.coalesce( - DSL.field("subscription_count_table.subscription_count", Long::class.java), - 0 - ).`as`(SelectWorkBookRecordWithSubscriptionCount::subscriptionCount.name) - ) - .from(Workbook.WORKBOOK) + dslContext + .select( + Workbook.WORKBOOK.ID.`as`(SelectWorkBookRecordWithSubscriptionCount::id.name), + Workbook.WORKBOOK.TITLE.`as`(SelectWorkBookRecordWithSubscriptionCount::title.name), + Workbook.WORKBOOK.MAIN_IMAGE_URL.`as`(SelectWorkBookRecordWithSubscriptionCount::mainImageUrl.name), + Workbook.WORKBOOK.CATEGORY_CD.`as`(SelectWorkBookRecordWithSubscriptionCount::category.name), + Workbook.WORKBOOK.DESCRIPTION.`as`(SelectWorkBookRecordWithSubscriptionCount::description.name), + Workbook.WORKBOOK.CREATED_AT.`as`(SelectWorkBookRecordWithSubscriptionCount::createdAt.name), + /** 구독자 수가 없다면 0으로 반환한다. */ + DSL + .coalesce( + DSL.field("subscription_count_table.subscription_count", Long::class.java), + 0, + ).`as`(SelectWorkBookRecordWithSubscriptionCount::subscriptionCount.name), + ).from(Workbook.WORKBOOK) /** 구독자가 없는 Workbook도 조회하기 위해 LEFT JOIN을 사용한다. */ .leftJoin( /** Subscription 테이블을 이용하여 구독자 수를 조회한다. */ - dslContext.select( - Subscription.SUBSCRIPTION.TARGET_WORKBOOK_ID.`as`(Subscription.SUBSCRIPTION.TARGET_WORKBOOK_ID.name), - DSL.count(Subscription.SUBSCRIPTION.TARGET_WORKBOOK_ID) - .`as`("subscription_count") - ) - .from(Subscription.SUBSCRIPTION) + dslContext + .select( + Subscription.SUBSCRIPTION.TARGET_WORKBOOK_ID.`as`(Subscription.SUBSCRIPTION.TARGET_WORKBOOK_ID.name), + DSL + .count(Subscription.SUBSCRIPTION.TARGET_WORKBOOK_ID) + .`as`("subscription_count"), + ).from(Subscription.SUBSCRIPTION) .where(Subscription.SUBSCRIPTION.DELETED_AT.isNull) .groupBy(Subscription.SUBSCRIPTION.TARGET_WORKBOOK_ID) - .asTable("subscription_count_table") - ) - .on( + .asTable("subscription_count_table"), + ).on( Workbook.WORKBOOK.ID.eq( DSL.field( "subscription_count_table.${Subscription.SUBSCRIPTION.TARGET_WORKBOOK_ID.name}", - Long::class.java - ) - ) - ) - .where(browseWorkBookCategoryCondition(query)) + Long::class.java, + ), + ), + ).where(browseWorkBookCategoryCondition(query)) .and(Workbook.WORKBOOK.DELETED_AT.isNull) /** 구독자 수가 많은 순서로, 구독자 수가 같다면 생성일자가 최신인 순서로 정렬한다. */ .orderBy( DSL.field("subscription_count_table.subscription_count", Long::class.java).desc(), - Workbook.WORKBOOK.CREATED_AT.desc() - ) - .query + Workbook.WORKBOOK.CREATED_AT.desc(), + ).query /** * category에 따라서 조건을 생성한다. */ - private fun browseWorkBookCategoryCondition(query: BrowseWorkBookQueryWithSubscriptionCountQuery): Condition { - return when (query.category) { + private fun browseWorkBookCategoryCondition(query: BrowseWorkBookQueryWithSubscriptionCountQuery): Condition = + when (query.category) { (-1).toByte() -> DSL.noCondition() else -> Workbook.WORKBOOK.CATEGORY_CD.eq(query.category) } - } - fun selectWorkBookLastArticleId(query: SelectWorkBookLastArticleIdQuery): Long? { - return selectWorkBookLastArticleIdQuery(query) + fun selectWorkBookLastArticleId(query: SelectWorkBookLastArticleIdQuery): Long? = + 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) + 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) - fun selectAllWorkbookTitle(query: SelectAllWorkbookTitleQuery): List { - return selectAllWorkbookTitleQuery(query) + fun selectAllWorkbookTitle(query: SelectAllWorkbookTitleQuery): List = + selectAllWorkbookTitleQuery(query) .fetchInto(WorkbookTitleRecord::class.java) - } fun selectAllWorkbookTitleQuery(query: SelectAllWorkbookTitleQuery) = - dslContext.select( - Workbook.WORKBOOK.ID.`as`(WorkbookTitleRecord::workbookId.name), - Workbook.WORKBOOK.TITLE.`as`(WorkbookTitleRecord::title.name) - ) - .from(Workbook.WORKBOOK) + dslContext + .select( + Workbook.WORKBOOK.ID.`as`(WorkbookTitleRecord::workbookId.name), + Workbook.WORKBOOK.TITLE.`as`(WorkbookTitleRecord::title.name), + ).from(Workbook.WORKBOOK) .where(Workbook.WORKBOOK.ID.`in`(query.workbookIds)) } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/workbook/service/WorkbookArticleService.kt b/api/src/main/kotlin/com/few/api/domain/workbook/service/WorkbookArticleService.kt index 8035c8c43..b91b1c6d2 100644 --- a/api/src/main/kotlin/com/few/api/domain/workbook/service/WorkbookArticleService.kt +++ b/api/src/main/kotlin/com/few/api/domain/workbook/service/WorkbookArticleService.kt @@ -1,39 +1,35 @@ package com.few.api.domain.workbook.service -import com.few.api.domain.workbook.service.dto.BrowseWorkbookArticlesInDto -import com.few.api.domain.workbook.service.dto.WorkBookArticleOutDto import com.few.api.domain.article.repo.ArticleDao import com.few.api.domain.article.repo.query.SelectWorkbookMappedArticleRecordsQuery import com.few.api.domain.workbook.service.dto.ArticleDetailOutDto +import com.few.api.domain.workbook.service.dto.BrowseWorkbookArticlesInDto +import com.few.api.domain.workbook.service.dto.WorkBookArticleOutDto import org.springframework.stereotype.Service -fun List.writerIds(): List { - return this.map { it.writerId } -} +fun List.writerIds(): List = this.map { it.writerId } -fun List.toArticleDetails(): List { - return this.map { ArticleDetailOutDto(it.articleId, it.title) } -} +fun List.toArticleDetails(): List = this.map { ArticleDetailOutDto(it.articleId, it.title) } @Service class WorkbookArticleService( private val articleDao: ArticleDao, ) { - fun browseWorkbookArticles(query: BrowseWorkbookArticlesInDto): List { - return articleDao.selectWorkbookMappedArticleRecords( - SelectWorkbookMappedArticleRecordsQuery( - query.workbookId - ) - ).map { record -> - WorkBookArticleOutDto( - articleId = record.articleId, - writerId = record.writerId, - mainImageURL = record.mainImageURL, - title = record.title, - category = record.category, - content = record.content, - createdAt = record.createdAt - ) - } - } + fun browseWorkbookArticles(query: BrowseWorkbookArticlesInDto): List = + articleDao + .selectWorkbookMappedArticleRecords( + SelectWorkbookMappedArticleRecordsQuery( + query.workbookId, + ), + ).map { record -> + WorkBookArticleOutDto( + articleId = record.articleId, + writerId = record.writerId, + mainImageURL = record.mainImageURL, + title = record.title, + category = record.category, + content = record.content, + createdAt = record.createdAt, + ) + } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/workbook/service/WorkbookMemberService.kt b/api/src/main/kotlin/com/few/api/domain/workbook/service/WorkbookMemberService.kt index db3a7ff6c..ff0783c72 100644 --- a/api/src/main/kotlin/com/few/api/domain/workbook/service/WorkbookMemberService.kt +++ b/api/src/main/kotlin/com/few/api/domain/workbook/service/WorkbookMemberService.kt @@ -6,37 +6,34 @@ import com.few.api.domain.member.repo.query.SelectWritersQuery import com.few.api.domain.workbook.service.dto.* import org.springframework.stereotype.Service -fun List.toWriterDetails(): List { - return this.map { WriterDetailDto(it.writerId, it.name, it.url) } -} +fun List.toWriterDetails(): List = this.map { WriterDetailDto(it.writerId, it.name, it.url) } @Service class WorkbookMemberService( private val memberDao: MemberDao, ) { - fun browseWriterRecords(query: BrowseWriterRecordsInDto): List { - return memberDao.selectWriters(SelectWritersQuery(query.writerIds)).map { record -> + fun browseWriterRecords(query: BrowseWriterRecordsInDto): List = + memberDao.selectWriters(SelectWritersQuery(query.writerIds)).map { record -> WriterOutDto( writerId = record.writerId, name = record.name, - url = record.url + url = record.url, ) } - } /** * key: workbookId * value: writer list */ - fun browseWorkbookWriterRecords(query: BrowseWorkbookWriterRecordsInDto): Map> { - return memberDao.selectWriters(BrowseWorkbookWritersQuery(query.workbookIds)) + fun browseWorkbookWriterRecords(query: BrowseWorkbookWriterRecordsInDto): Map> = + memberDao + .selectWriters(BrowseWorkbookWritersQuery(query.workbookIds)) .map { record -> WriterMappedWorkbookOutDto( workbookId = record.workbookId, writerId = record.writerId, name = record.name, - url = record.url + url = record.url, ) }.groupBy { it.workbookId } - } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/workbook/service/WorkbookSubscribeService.kt b/api/src/main/kotlin/com/few/api/domain/workbook/service/WorkbookSubscribeService.kt index 789bcaa81..52c50cf8d 100644 --- a/api/src/main/kotlin/com/few/api/domain/workbook/service/WorkbookSubscribeService.kt +++ b/api/src/main/kotlin/com/few/api/domain/workbook/service/WorkbookSubscribeService.kt @@ -1,26 +1,26 @@ package com.few.api.domain.workbook.service -import com.few.api.domain.workbook.service.dto.BrowseMemberSubscribeWorkbooksInDto -import com.few.api.domain.workbook.service.dto.BrowseMemberSubscribeWorkbooksOutDto import com.few.api.domain.subscription.repo.SubscriptionDao import com.few.api.domain.subscription.repo.query.SelectAllMemberWorkbookActiveSubscriptionQuery import com.few.api.domain.subscription.repo.query.SelectAllMemberWorkbookInActiveSubscriptionQuery +import com.few.api.domain.workbook.service.dto.BrowseMemberSubscribeWorkbooksInDto +import com.few.api.domain.workbook.service.dto.BrowseMemberSubscribeWorkbooksOutDto import org.springframework.stereotype.Service @Service class WorkbookSubscribeService( private val subscriptionDao: SubscriptionDao, ) { - fun browseMemberSubscribeWorkbooks(dto: BrowseMemberSubscribeWorkbooksInDto): List { val inActiveSubscriptionRecords = subscriptionDao.selectAllInActiveWorkbookSubscriptionStatus( - SelectAllMemberWorkbookInActiveSubscriptionQuery(dto.memberId) + SelectAllMemberWorkbookInActiveSubscriptionQuery(dto.memberId), ) - val activeSubscriptionRecords = subscriptionDao.selectAllActiveWorkbookSubscriptionStatus( - SelectAllMemberWorkbookActiveSubscriptionQuery(dto.memberId) - ) + val activeSubscriptionRecords = + subscriptionDao.selectAllActiveWorkbookSubscriptionStatus( + SelectAllMemberWorkbookActiveSubscriptionQuery(dto.memberId), + ) val subscriptionRecords = inActiveSubscriptionRecords + activeSubscriptionRecords @@ -28,7 +28,7 @@ class WorkbookSubscribeService( BrowseMemberSubscribeWorkbooksOutDto( workbookId = it.workbookId, isActiveSub = it.isActiveSub, - currentDay = it.currentDay + currentDay = it.currentDay, ) } } diff --git a/api/src/main/kotlin/com/few/api/domain/workbook/usecase/BrowseWorkbooksUseCase.kt b/api/src/main/kotlin/com/few/api/domain/workbook/usecase/BrowseWorkbooksUseCase.kt index 320988a1f..c1d0c1539 100644 --- a/api/src/main/kotlin/com/few/api/domain/workbook/usecase/BrowseWorkbooksUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/workbook/usecase/BrowseWorkbooksUseCase.kt @@ -1,6 +1,10 @@ package com.few.api.domain.workbook.usecase import com.few.api.domain.common.vo.CategoryType +import com.few.api.domain.common.vo.ViewCategory +import com.few.api.domain.workbook.repo.WorkbookDao +import com.few.api.domain.workbook.repo.query.BrowseWorkBookQueryWithSubscriptionCountQuery +import com.few.api.domain.workbook.repo.record.SelectWorkBookRecordWithSubscriptionCount import com.few.api.domain.workbook.service.WorkbookMemberService import com.few.api.domain.workbook.service.WorkbookSubscribeService import com.few.api.domain.workbook.service.dto.BrowseMemberSubscribeWorkbooksInDto @@ -12,10 +16,6 @@ import com.few.api.domain.workbook.usecase.dto.BrowseWorkbooksUseCaseOut import com.few.api.domain.workbook.usecase.dto.WriterDetail import com.few.api.domain.workbook.usecase.model.* import com.few.api.domain.workbook.usecase.model.order.* -import com.few.api.domain.workbook.repo.WorkbookDao -import com.few.api.domain.workbook.repo.query.BrowseWorkBookQueryWithSubscriptionCountQuery -import com.few.api.domain.workbook.repo.record.SelectWorkBookRecordWithSubscriptionCount -import com.few.api.domain.common.vo.ViewCategory import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional @@ -43,63 +43,69 @@ class BrowseWorkbooksUseCase( private val workbookMemberService: WorkbookMemberService, private val workbookSubscribeService: WorkbookSubscribeService, ) { - @Transactional fun execute(useCaseIn: BrowseWorkbooksUseCaseIn): BrowseWorkbooksUseCaseOut { - val workbookRecords = workbookDao.browseWorkBookWithSubscriptionCount( - BrowseWorkBookQueryWithSubscriptionCountQuery(useCaseIn.category.code) - ) + val workbookRecords = + workbookDao.browseWorkBookWithSubscriptionCount( + BrowseWorkBookQueryWithSubscriptionCountQuery(useCaseIn.category.code), + ) val workbookIds = workbookRecords.map { it.id } - val writerRecords = workbookMemberService.browseWorkbookWriterRecords( - BrowseWorkbookWriterRecordsInDto(workbookIds) - ) + val writerRecords = + workbookMemberService.browseWorkbookWriterRecords( + BrowseWorkbookWriterRecordsInDto(workbookIds), + ) val orderStrategy = getOrderStrategy(useCaseIn) - val orderDelegator = when (orderStrategy) { - WorkBookOrderStrategy.MAIN_VIEW_AUTH -> { - genAuthMainViewWorkbookOrderDelegator(useCaseIn) + val orderDelegator = + when (orderStrategy) { + WorkBookOrderStrategy.MAIN_VIEW_AUTH -> { + genAuthMainViewWorkbookOrderDelegator(useCaseIn) + } + /** BASIC, MAIN_VIEW_UNAUTH -> 해당 경우는 DB 조회 결과를 그대로 반환 */ + else -> null } - /** BASIC, MAIN_VIEW_UNAUTH -> 해당 경우는 DB 조회 결과를 그대로 반환 */ - else -> null - } val workbooks = toWorkbooks(workbookRecords, writerRecords) - val orderedWorkbook = OrderTargetWorkBooks(workbooks).let { target -> - orderDelegator - ?.let { delegator -> - UnOrderedWorkBooks( - target, - delegator - ).order() - } - ?: run { OrderedWorkBooks(target) } - }.orderedWorkbooks + val orderedWorkbook = + OrderTargetWorkBooks(workbooks) + .let { target -> + orderDelegator + ?.let { delegator -> + UnOrderedWorkBooks( + target, + delegator, + ).order() + } + ?: run { OrderedWorkBooks(target) } + }.orderedWorkbooks val orderedWorkbookData = orderedWorkbook.workbookData - orderedWorkbookData.map { workBook -> - BrowseWorkBookDetail( - id = workBook.id, - mainImageUrl = workBook.mainImageUrl, - title = workBook.title, - description = workBook.description, - category = workBook.category, - createdAt = workBook.createdAt, - writerDetails = workBook.writerDetails.map { - WriterDetail( - id = it.id, - name = it.name, - url = it.url - ) - }, - subscriptionCount = workBook.subscriptionCount - ) - }.let { - return BrowseWorkbooksUseCaseOut( - workbooks = it - ) - } + orderedWorkbookData + .map { workBook -> + BrowseWorkBookDetail( + id = workBook.id, + mainImageUrl = workBook.mainImageUrl, + title = workBook.title, + description = workBook.description, + category = workBook.category, + createdAt = workBook.createdAt, + writerDetails = + workBook.writerDetails.map { + WriterDetail( + id = it.id, + name = it.name, + url = it.url, + ) + }, + subscriptionCount = workBook.subscriptionCount, + ) + }.let { + return BrowseWorkbooksUseCaseOut( + workbooks = it, + ) + } } private fun getOrderStrategy(useCaseIn: BrowseWorkbooksUseCaseIn) = @@ -109,43 +115,44 @@ class BrowseWorkbooksUseCase( else -> WorkBookOrderStrategy.BASIC } - private fun genAuthMainViewWorkbookOrderDelegator(useCaseIn: BrowseWorkbooksUseCaseIn): WorkbookOrderDelegator { - return BrowseMemberSubscribeWorkbooksInDto(useCaseIn.memberId!!).let { dto -> - workbookSubscribeService.browseMemberSubscribeWorkbooks(dto) - }.map { - MemberSubscribedWorkbook( - workbookId = it.workbookId, - isActiveSub = it.isActiveSub, - currentDay = it.currentDay - ) - }.let { subscribedWorkbooks -> - AuthMainViewWorkbookOrderDelegator(subscribedWorkbooks) - } - } + private fun genAuthMainViewWorkbookOrderDelegator(useCaseIn: BrowseWorkbooksUseCaseIn): WorkbookOrderDelegator = + BrowseMemberSubscribeWorkbooksInDto(useCaseIn.memberId!!) + .let { dto -> + workbookSubscribeService.browseMemberSubscribeWorkbooks(dto) + }.map { + MemberSubscribedWorkbook( + workbookId = it.workbookId, + isActiveSub = it.isActiveSub, + currentDay = it.currentDay, + ) + }.let { subscribedWorkbooks -> + AuthMainViewWorkbookOrderDelegator(subscribedWorkbooks) + } private fun toWorkbooks( workbookRecords: List, writerRecords: Map>, - ): WorkBooks { - return workbookRecords.map { record -> - WorkBook( - id = record.id, - mainImageUrl = record.mainImageUrl, - title = record.title, - description = record.description, - category = CategoryType.convertToDisplayName(record.category), - createdAt = record.createdAt, - writerDetails = writerRecords[record.id]?.map { - WorkBookWriter( - id = it.writerId, - name = it.name, - url = it.url - ) - } ?: emptyList(), - subscriptionCount = record.subscriptionCount - ) - }.let { - WorkBooks(it) - } - } + ): WorkBooks = + workbookRecords + .map { record -> + WorkBook( + id = record.id, + mainImageUrl = record.mainImageUrl, + title = record.title, + description = record.description, + category = CategoryType.convertToDisplayName(record.category), + createdAt = record.createdAt, + writerDetails = + writerRecords[record.id]?.map { + WorkBookWriter( + id = it.writerId, + name = it.name, + url = it.url, + ) + } ?: emptyList(), + subscriptionCount = record.subscriptionCount, + ) + }.let { + WorkBooks(it) + } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/workbook/usecase/ReadWorkbookUseCase.kt b/api/src/main/kotlin/com/few/api/domain/workbook/usecase/ReadWorkbookUseCase.kt index 1d238ff5d..118bd4b6e 100644 --- a/api/src/main/kotlin/com/few/api/domain/workbook/usecase/ReadWorkbookUseCase.kt +++ b/api/src/main/kotlin/com/few/api/domain/workbook/usecase/ReadWorkbookUseCase.kt @@ -1,15 +1,15 @@ package com.few.api.domain.workbook.usecase +import com.few.api.domain.common.exception.NotFoundException import com.few.api.domain.common.vo.CategoryType -import com.few.api.domain.workbook.usecase.dto.ReadWorkbookUseCaseIn -import com.few.api.domain.workbook.usecase.dto.ReadWorkbookUseCaseOut +import com.few.api.domain.workbook.repo.WorkbookDao +import com.few.api.domain.workbook.repo.query.SelectWorkBookRecordQuery import com.few.api.domain.workbook.service.* import com.few.api.domain.workbook.service.dto.BrowseWorkbookArticlesInDto import com.few.api.domain.workbook.service.dto.BrowseWriterRecordsInDto -import com.few.api.domain.common.exception.NotFoundException -import com.few.api.domain.workbook.repo.WorkbookDao -import com.few.api.domain.workbook.repo.query.SelectWorkBookRecordQuery import com.few.api.domain.workbook.usecase.dto.ArticleDetail +import com.few.api.domain.workbook.usecase.dto.ReadWorkbookUseCaseIn +import com.few.api.domain.workbook.usecase.dto.ReadWorkbookUseCaseOut import com.few.api.domain.workbook.usecase.dto.WriterDetail import org.springframework.stereotype.Component @@ -19,21 +19,22 @@ class ReadWorkbookUseCase( private val workbookArticleService: WorkbookArticleService, private val workbookMemberService: WorkbookMemberService, ) { - fun execute(useCaseIn: ReadWorkbookUseCaseIn): ReadWorkbookUseCaseOut { val workbookId = useCaseIn.workbookId - val workbookRecord = workbookDao.selectWorkBook(SelectWorkBookRecordQuery(workbookId)) - ?: throw NotFoundException("workbook.notfound.id") + val workbookRecord = + workbookDao.selectWorkBook(SelectWorkBookRecordQuery(workbookId)) + ?: throw NotFoundException("workbook.notfound.id") val workbookMappedArticles = workbookArticleService.browseWorkbookArticles(BrowseWorkbookArticlesInDto(workbookId)) - val writerRecords = workbookMemberService.browseWriterRecords( - BrowseWriterRecordsInDto( - workbookMappedArticles.writerIds() + val writerRecords = + workbookMemberService.browseWriterRecords( + BrowseWriterRecordsInDto( + workbookMappedArticles.writerIds(), + ), ) - ) return ReadWorkbookUseCaseOut( id = workbookRecord.id, @@ -42,19 +43,21 @@ class ReadWorkbookUseCase( description = workbookRecord.description, category = CategoryType.convertToDisplayName(workbookRecord.category), createdAt = workbookRecord.createdAt, - writers = writerRecords.toWriterDetails().map { - WriterDetail( - id = it.writerId, - name = it.name, - url = it.url - ) - }, - articles = workbookMappedArticles.toArticleDetails().map { - ArticleDetail( - id = it.articleId, - title = it.title - ) - } + writers = + writerRecords.toWriterDetails().map { + WriterDetail( + id = it.writerId, + name = it.name, + url = it.url, + ) + }, + articles = + workbookMappedArticles.toArticleDetails().map { + ArticleDetail( + id = it.articleId, + title = it.title, + ) + }, ) } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/few/api/domain/workbook/usecase/model/order/AuthMainViewWorkbookOrderDelegator.kt b/api/src/main/kotlin/com/few/api/domain/workbook/usecase/model/order/AuthMainViewWorkbookOrderDelegator.kt index 9f3bb9513..ea4a027da 100644 --- a/api/src/main/kotlin/com/few/api/domain/workbook/usecase/model/order/AuthMainViewWorkbookOrderDelegator.kt +++ b/api/src/main/kotlin/com/few/api/domain/workbook/usecase/model/order/AuthMainViewWorkbookOrderDelegator.kt @@ -7,7 +7,6 @@ import com.few.api.domain.workbook.usecase.model.WorkBooks class AuthMainViewWorkbookOrderDelegator( private val memberSubscribedWorkbooks: List, ) : WorkbookOrderDelegator { - /** * 메인 화면에 보여질 워크북을 정렬합니다. * 1. 활성화된 구독 워크북을 먼저 보여줍니다. @@ -19,9 +18,11 @@ class AuthMainViewWorkbookOrderDelegator( val workbooks = targetWorkBooks.workbooks.workbookData val allWorkbookIds = workbooks.associate { it.id to false }.toMutableMap() val activeSubWorkbookIds = - memberSubscribedWorkbooks.filter { it.isActiveSub }.sortedByDescending { - it.currentDay - }.map { it.workbookId } + memberSubscribedWorkbooks + .filter { it.isActiveSub } + .sortedByDescending { + it.currentDay + }.map { it.workbookId } val inActiveSubWorkbookIds = memberSubscribedWorkbooks.filter { !it.isActiveSub }.map { it.workbookId } diff --git a/api/src/main/kotlin/com/few/api/domain/workbook/usecase/model/order/WorkbookOrderDelegator.kt b/api/src/main/kotlin/com/few/api/domain/workbook/usecase/model/order/WorkbookOrderDelegator.kt index df281f9cc..eb7c7015c 100644 --- a/api/src/main/kotlin/com/few/api/domain/workbook/usecase/model/order/WorkbookOrderDelegator.kt +++ b/api/src/main/kotlin/com/few/api/domain/workbook/usecase/model/order/WorkbookOrderDelegator.kt @@ -1,7 +1,6 @@ package com.few.api.domain.workbook.usecase.model.order interface WorkbookOrderDelegator { - /** * 워크북을 정렬합니다. * @param targetWorkBooks 정렬할 워크북 목록 diff --git a/api/src/test/kotlin/com/few/api/config/web/controller/ApiControllerTestSpec.kt b/api/src/test/kotlin/com/few/api/config/web/controller/ApiControllerTestSpec.kt index 60d05077b..50635623f 100644 --- a/api/src/test/kotlin/com/few/api/config/web/controller/ApiControllerTestSpec.kt +++ b/api/src/test/kotlin/com/few/api/config/web/controller/ApiControllerTestSpec.kt @@ -4,9 +4,9 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.few.api.domain.admin.controller.AdminController import com.few.api.domain.admin.usecase.* import com.few.api.domain.article.controller.ArticleController -import com.few.api.domain.article.usecase.ReadArticleUseCase import com.few.api.domain.article.usecase.BrowseArticlesUseCase import com.few.api.domain.article.usecase.ReadArticleByEmailUseCase +import com.few.api.domain.article.usecase.ReadArticleUseCase import com.few.api.domain.log.controller.ApiLogController import com.few.api.domain.log.usecase.AddApiLogUseCase import com.few.api.domain.log.usecase.AddEmailLogUseCase @@ -47,7 +47,7 @@ import web.config.WebConfig @Import( WebConfig::class, SecurityConfig::class, - ApiControllerTestComponentConfig::class + ApiControllerTestComponentConfig::class, ) @WebMvcTest( controllers = [ @@ -58,12 +58,11 @@ import web.config.WebConfig ProblemController::class, SubscriptionController::class, WorkBookArticleController::class, - WorkBookController::class - ] + WorkBookController::class, + ], ) @ExtendWith(RestDocumentationExtension::class) abstract class ApiControllerTestSpec { - /** WebConfig */ @MockBean lateinit var restTemplate: RestTemplate diff --git a/api/src/test/kotlin/com/few/api/config/web/controller/ApiTestTokenUserDetailsService.kt b/api/src/test/kotlin/com/few/api/config/web/controller/ApiTestTokenUserDetailsService.kt index 58ed11219..8678e8366 100644 --- a/api/src/test/kotlin/com/few/api/config/web/controller/ApiTestTokenUserDetailsService.kt +++ b/api/src/test/kotlin/com/few/api/config/web/controller/ApiTestTokenUserDetailsService.kt @@ -8,13 +8,13 @@ import security.TokenUserDetails @TestComponent class ApiTestTokenUserDetailsService : UserDetailsService { - override fun loadUserByUsername(username: String): UserDetails { - return TokenUserDetails( - authorities = listOf( - Roles.ROLE_USER.authority - ), + override fun loadUserByUsername(username: String): UserDetails = + TokenUserDetails( + authorities = + listOf( + Roles.ROLE_USER.authority, + ), id = "1", - email = "test@gmail.com" + email = "test@gmail.com", ) - } } \ No newline at end of file diff --git a/api/src/test/kotlin/com/few/api/domain/admin/controller/AdminApiControllerTest.kt b/api/src/test/kotlin/com/few/api/domain/admin/controller/AdminApiControllerTest.kt index 04045528b..70aacb194 100644 --- a/api/src/test/kotlin/com/few/api/domain/admin/controller/AdminApiControllerTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/admin/controller/AdminApiControllerTest.kt @@ -3,8 +3,8 @@ package com.few.api.domain.admin.controller import com.epages.restdocs.apispec.ResourceDocumentation.resource import com.epages.restdocs.apispec.ResourceSnippetParameters import com.epages.restdocs.apispec.Schema -import com.few.api.domain.admin.controller.request.* import com.few.api.config.web.controller.ApiControllerTestSpec +import com.few.api.domain.admin.controller.request.* import com.few.api.domain.admin.usecase.dto.* import com.few.api.domain.common.vo.CategoryType import org.junit.jupiter.api.DisplayName @@ -24,7 +24,6 @@ import java.net.URL import java.util.stream.IntStream class AdminApiControllerTest : ApiControllerTestSpec() { - companion object { private const val BASE_URL = "/api/v1/admin" private const val TAG = "AdminController" @@ -35,7 +34,12 @@ class AdminApiControllerTest : ApiControllerTestSpec() { fun addWorkbook() { // given val api = "AddWorkbook" - val uri = UriComponentsBuilder.newInstance().path("$BASE_URL/workbooks").build().toUriString() + val uri = + UriComponentsBuilder + .newInstance() + .path("$BASE_URL/workbooks") + .build() + .toUriString() val title = "title" val mainImageUrl = URL("http://localhost:8080") val category = CategoryType.fromCode(0)!!.name @@ -49,27 +53,33 @@ class AdminApiControllerTest : ApiControllerTestSpec() { `when`(addWorkbookUseCase.execute(useCaseIn)).thenReturn(useCaseOut) // when - mockMvc.perform( - post(uri) - .content(body) - .contentType(MediaType.APPLICATION_JSON) - ) - .andExpect(status().isOk) + mockMvc + .perform( + post(uri) + .content(body) + .contentType(MediaType.APPLICATION_JSON), + ).andExpect(status().isOk) .andDo( document( api.toIdentifier(), resource( - ResourceSnippetParameters.builder().description("학습지 추가") - .summary(api.toIdentifier()).privateResource(false).deprecated(false) - .tag(TAG).requestSchema(Schema.schema(api.toRequestSchema())) - .responseSchema(Schema.schema(api.toResponseSchema())).responseFields( + ResourceSnippetParameters + .builder() + .description("학습지 추가") + .summary(api.toIdentifier()) + .privateResource(false) + .deprecated(false) + .tag(TAG) + .requestSchema(Schema.schema(api.toRequestSchema())) + .responseSchema(Schema.schema(api.toResponseSchema())) + .responseFields( *Description.fields( FieldDescription("data", "data").asObject(), - FieldDescription("data.workbookId", "워크북 Id").asNumber() - ) - ).build() - ) - ) + FieldDescription("data.workbookId", "워크북 Id").asNumber(), + ), + ).build(), + ), + ), ) } @@ -78,78 +88,95 @@ class AdminApiControllerTest : ApiControllerTestSpec() { fun addArticle() { // given val api = "AddArticle" - val uri = UriComponentsBuilder.newInstance().path("$BASE_URL/articles").build().toUriString() + val uri = + UriComponentsBuilder + .newInstance() + .path("$BASE_URL/articles") + .build() + .toUriString() val writerEmail = "writer@gmail.com" val articleImageURL = URL("http://localhost:8080") val title = "title" val category = CategoryType.fromCode(0)!!.name val contentType = "md" val contentSource = "content source" - val problemDTOs = IntStream.range(0, 2).mapToObj { - ProblemDto( - "title$it", - IntStream.range(0, 4).mapToObj { ProblemContentDto(it.toLong(), "content$it") }.toList(), - "$it", - "explanation$it" - ) - }.toList() - val problemDetails = problemDTOs.map { - ProblemDetail( - it.title, - it.contents.map { content -> ProblemContentDetail(content.number, content.content) }, - it.answer, - it.explanation + val problemDTOs = + IntStream + .range(0, 2) + .mapToObj { + ProblemDto( + "title$it", + IntStream.range(0, 4).mapToObj { ProblemContentDto(it.toLong(), "content$it") }.toList(), + "$it", + "explanation$it", + ) + }.toList() + val problemDetails = + problemDTOs.map { + ProblemDetail( + it.title, + it.contents.map { content -> ProblemContentDetail(content.number, content.content) }, + it.answer, + it.explanation, + ) + } + val request = + AddArticleRequest( + writerEmail, + articleImageURL, + title, + category, + contentType, + contentSource, + problemDTOs, ) - } - val request = AddArticleRequest( - writerEmail, - articleImageURL, - title, - category, - contentType, - contentSource, - problemDTOs - ) val body = objectMapper.writeValueAsString(request) - val useCaseIn = AddArticleUseCaseIn( - writerEmail, - articleImageURL, - title, - category, - contentType, - contentSource, - problemDetails - ) + val useCaseIn = + AddArticleUseCaseIn( + writerEmail, + articleImageURL, + title, + category, + contentType, + contentSource, + problemDetails, + ) val useCaseOut = AddArticleUseCaseOut(1L) `when`( addArticleUseCase.execute( - useCaseIn - ) + useCaseIn, + ), ).thenReturn(useCaseOut) // when - mockMvc.perform( - post(uri) - .content(body) - .contentType(MediaType.APPLICATION_JSON) - ) - .andExpect(status().isOk) + mockMvc + .perform( + post(uri) + .content(body) + .contentType(MediaType.APPLICATION_JSON), + ).andExpect(status().isOk) .andDo( document( api.toIdentifier(), resource( - ResourceSnippetParameters.builder().description("아티클 추가") - .summary(api.toIdentifier()).privateResource(false).deprecated(false) - .tag(TAG).requestSchema(Schema.schema(api.toRequestSchema())) - .responseSchema(Schema.schema(api.toResponseSchema())).responseFields( + ResourceSnippetParameters + .builder() + .description("아티클 추가") + .summary(api.toIdentifier()) + .privateResource(false) + .deprecated(false) + .tag(TAG) + .requestSchema(Schema.schema(api.toRequestSchema())) + .responseSchema(Schema.schema(api.toResponseSchema())) + .responseFields( *Description.fields( FieldDescription("data", "data").asObject(), - FieldDescription("data.articleId", "아티클 Id").asNumber() - ) - ).build() - ) - ) + FieldDescription("data.articleId", "아티클 Id").asNumber(), + ), + ).build(), + ), + ), ) } @@ -158,7 +185,12 @@ class AdminApiControllerTest : ApiControllerTestSpec() { fun mapArticle() { // given val api = "MapArticle" - val uri = UriComponentsBuilder.newInstance().path("$BASE_URL/relations/articles").build().toUriString() + val uri = + UriComponentsBuilder + .newInstance() + .path("$BASE_URL/relations/articles") + .build() + .toUriString() val workbookId = 1L val articleId = 1L val dayCol = 1 @@ -169,24 +201,30 @@ class AdminApiControllerTest : ApiControllerTestSpec() { doNothing().`when`(mapArticleUseCase).execute(useCaseIn) // when - mockMvc.perform( - post(uri) - .content(body) - .contentType(MediaType.APPLICATION_JSON) - ) - .andExpect(status().isOk) + mockMvc + .perform( + post(uri) + .content(body) + .contentType(MediaType.APPLICATION_JSON), + ).andExpect(status().isOk) .andDo( document( api.toIdentifier(), resource( - ResourceSnippetParameters.builder().description("아티클 매핑") - .summary(api.toIdentifier()).privateResource(false).deprecated(false) - .tag(TAG).requestSchema(Schema.schema(api.toRequestSchema())) - .responseSchema(Schema.schema(api.toResponseSchema())).responseFields( - *Description.describe() - ).build() - ) - ) + ResourceSnippetParameters + .builder() + .description("아티클 매핑") + .summary(api.toIdentifier()) + .privateResource(false) + .deprecated(false) + .tag(TAG) + .requestSchema(Schema.schema(api.toRequestSchema())) + .responseSchema(Schema.schema(api.toResponseSchema())) + .responseFields( + *Description.describe(), + ).build(), + ), + ), ) } @@ -195,7 +233,12 @@ class AdminApiControllerTest : ApiControllerTestSpec() { fun convertContent() { // given val api = "ConvertContent" - val uri = UriComponentsBuilder.newInstance().path("$BASE_URL/utilities/conversion/content").build().toUriString() + val uri = + UriComponentsBuilder + .newInstance() + .path("$BASE_URL/utilities/conversion/content") + .build() + .toUriString() val name = "content" val originalFilename = "test.md" val contentType = "text/markdown" @@ -207,30 +250,32 @@ class AdminApiControllerTest : ApiControllerTestSpec() { `when`(convertContentUseCase.execute(useCaseIn)).thenReturn(useCaseOut) // when - mockMvc.perform( - multipart(uri) - .file(request.content as MockMultipartFile) - .contentType(MediaType.MULTIPART_FORM_DATA_VALUE) - ) - .andExpect(status().isOk) + mockMvc + .perform( + multipart(uri) + .file(request.content as MockMultipartFile) + .contentType(MediaType.MULTIPART_FORM_DATA_VALUE), + ).andExpect(status().isOk) .andDo( document( api.toIdentifier(), resource( - ResourceSnippetParameters.builder() + ResourceSnippetParameters + .builder() .summary(api.toIdentifier()) .description("MD to HTML 변환") .tag(TAG) .requestSchema(Schema.schema(api.toRequestSchema())) - .responseSchema(Schema.schema(api.toResponseSchema())).responseFields( + .responseSchema(Schema.schema(api.toResponseSchema())) + .responseFields( *Description.fields( FieldDescription("data", "data").asObject(), FieldDescription("data.content", "변환된 컨텐츠").asString(), - FieldDescription("data.originDownLoadUrl", "원본 컨텐츠 다운로드 URL").asString() - ) - ).build() - ) - ) + FieldDescription("data.originDownLoadUrl", "원본 컨텐츠 다운로드 URL").asString(), + ), + ).build(), + ), + ), ) } @@ -239,7 +284,12 @@ class AdminApiControllerTest : ApiControllerTestSpec() { fun putImage() { // given val api = "PutImage" - val uri = UriComponentsBuilder.newInstance().path("$BASE_URL/utilities/conversion/image").build().toUriString() + val uri = + UriComponentsBuilder + .newInstance() + .path("$BASE_URL/utilities/conversion/image") + .build() + .toUriString() val name = "source" val originalFilename = "test.jpg" val contentType = "image/jpeg" @@ -251,31 +301,32 @@ class AdminApiControllerTest : ApiControllerTestSpec() { `when`(putImageUseCase.execute(useCaseIn)).thenReturn(useCaseOut) // when - mockMvc.perform( - multipart(uri) - .file(request.source as MockMultipartFile) - .contentType(MediaType.MULTIPART_FORM_DATA_VALUE) - ) - .andExpect(status().isOk) + mockMvc + .perform( + multipart(uri) + .file(request.source as MockMultipartFile) + .contentType(MediaType.MULTIPART_FORM_DATA_VALUE), + ).andExpect(status().isOk) .andDo( document( api.toIdentifier(), resource( - ResourceSnippetParameters.builder() + ResourceSnippetParameters + .builder() .summary(api.toIdentifier()) .description("이미지 업로드") .tag(TAG) .requestSchema(Schema.schema(api.toRequestSchema())) - .responseSchema(Schema.schema(api.toResponseSchema())).responseFields( + .responseSchema(Schema.schema(api.toResponseSchema())) + .responseFields( *Description.fields( FieldDescription("data", "data").asObject(), FieldDescription("data.url", "이미지 URL").asString(), - FieldDescription("data.supportSuffix", "지원하는 확장자").asArray() - ) - ).build() - ) - ) - + FieldDescription("data.supportSuffix", "지원하는 확장자").asArray(), + ), + ).build(), + ), + ), ) } } \ No newline at end of file diff --git a/api/src/test/kotlin/com/few/api/domain/article/controller/ArticleApiControllerTest.kt b/api/src/test/kotlin/com/few/api/domain/article/controller/ArticleApiControllerTest.kt index e07d6a05d..e4d0d79e2 100644 --- a/api/src/test/kotlin/com/few/api/domain/article/controller/ArticleApiControllerTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/article/controller/ArticleApiControllerTest.kt @@ -4,9 +4,8 @@ import com.epages.restdocs.apispec.ResourceDocumentation.parameterWithName import com.epages.restdocs.apispec.ResourceDocumentation.resource import com.epages.restdocs.apispec.ResourceSnippetParameters import com.epages.restdocs.apispec.Schema -import com.few.api.domain.article.usecase.dto.* import com.few.api.config.web.controller.ApiControllerTestSpec -import web.description.Description +import com.few.api.domain.article.usecase.dto.* import com.few.api.domain.common.vo.CategoryType import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test @@ -16,13 +15,13 @@ import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status import org.springframework.web.util.UriComponentsBuilder +import web.description.Description import web.helper.* import java.net.URL import java.time.LocalDateTime import java.util.stream.IntStream class ArticleApiControllerTest : ApiControllerTestSpec() { - companion object { private const val BASE_URL = "/api/v1/articles" private const val TAG = "ArticleController" @@ -37,51 +36,67 @@ class ArticleApiControllerTest : ApiControllerTestSpec() { // given val api = "ReadArticle" val token = "thisisaccesstoken" - val uri = UriComponentsBuilder.newInstance().path("$BASE_URL/{articleId}").build().toUriString() + val uri = + UriComponentsBuilder + .newInstance() + .path("$BASE_URL/{articleId}") + .build() + .toUriString() val articleId = 1L val memberId = 1L `when`(tokenResolver.resolveId(token)).thenReturn(memberId) val useCaseIn = ReadArticleUseCaseIn(articleId, memberId) - val useCaseOut = ReadArticleUseCaseOut( - id = 1L, - writer = WriterDetail( + val useCaseOut = + ReadArticleUseCaseOut( id = 1L, - name = "안나포", - url = URL("http://localhost:8080/api/v1/writers/1"), - imageUrl = URL("https://github.com/user-attachments/assets/28df9078-488c-49d6-9375-54ce5a250742") - ), - mainImageUrl = URL("https://github.com/YAPP-Github/24th-Web-Team-1-BE/assets/102807742/0643d805-5f3a-4563-8c48-2a7d51795326"), - title = "ETF(상장 지수 펀드)란? 모르면 손해라고?", - content = CategoryType.fromCode(0)!!.name, - problemIds = listOf(1L, 2L, 3L), - category = "경제", - createdAt = LocalDateTime.now(), - views = 1L - ) + writer = + WriterDetail( + id = 1L, + name = "안나포", + url = URL("http://localhost:8080/api/v1/writers/1"), + imageUrl = URL("https://github.com/user-attachments/assets/28df9078-488c-49d6-9375-54ce5a250742"), + ), + mainImageUrl = + URL( + "https://github.com/YAPP-Github/24th-Web-Team-1-BE/assets/102807742/0643d805-5f3a-4563-8c48-2a7d51795326", + ), + title = "ETF(상장 지수 펀드)란? 모르면 손해라고?", + content = CategoryType.fromCode(0)!!.name, + problemIds = listOf(1L, 2L, 3L), + category = "경제", + createdAt = LocalDateTime.now(), + views = 1L, + ) `when`(readArticleUseCase.execute(useCaseIn)).thenReturn( - useCaseOut + useCaseOut, ) // when - mockMvc.perform( - get(uri, articleId) - .header("Authorization", "Bearer $token") - .contentType(MediaType.APPLICATION_JSON) - ).andExpect(status().is2xxSuccessful) + mockMvc + .perform( + get(uri, articleId) + .header("Authorization", "Bearer $token") + .contentType(MediaType.APPLICATION_JSON), + ).andExpect(status().is2xxSuccessful) .andDo( document( api.toIdentifier(), resource( - ResourceSnippetParameters.builder().description("아티클 Id로 아티클 조회") - .summary(api.toIdentifier()).privateResource(false).deprecated(false) - .tag(TAG).requestSchema(Schema.schema(api.toRequestSchema())) + ResourceSnippetParameters + .builder() + .description("아티클 Id로 아티클 조회") + .summary(api.toIdentifier()) + .privateResource(false) + .deprecated(false) + .tag(TAG) + .requestSchema(Schema.schema(api.toRequestSchema())) .requestHeaders( - Description.authHeader(true) - ) - .pathParameters(parameterWithName("articleId").description("아티클 Id")) - .responseSchema(Schema.schema(api.toResponseSchema())).responseFields( + Description.authHeader(true), + ).pathParameters(parameterWithName("articleId").description("아티클 Id")) + .responseSchema(Schema.schema(api.toResponseSchema())) + .responseFields( *Description.fields( FieldDescription("data", "data").asObject(), FieldDescription("data.id", "아티클 Id").asNumber(), @@ -97,11 +112,11 @@ class ArticleApiControllerTest : ApiControllerTestSpec() { FieldDescription("data.category", "아티클 카테고리").asString(), FieldDescription("data.createdAt", "아티클 생성일").asString(), FieldDescription("data.views", "아티클 조회수").asNumber(), - FieldDescription("data.workbooks", "아티클이 포함된 학습지 정보(해당 API에선 사용되지 않음)").asArray() - ) - ).build() - ) - ) + FieldDescription("data.workbooks", "아티클이 포함된 학습지 정보(해당 API에선 사용되지 않음)").asArray(), + ), + ).build(), + ), + ), ) } @@ -112,59 +127,74 @@ class ArticleApiControllerTest : ApiControllerTestSpec() { val api = "ReadArticles" val prevArticleId = 1L val categoryCd: Byte = CategoryType.IT.code - val uri = UriComponentsBuilder.newInstance() - .path(BASE_URL) - .queryParam("prevArticleId", prevArticleId) - .queryParam("categoryCd", categoryCd) - .build() - .toUriString() + val uri = + UriComponentsBuilder + .newInstance() + .path(BASE_URL) + .queryParam("prevArticleId", prevArticleId) + .queryParam("categoryCd", categoryCd) + .build() + .toUriString() val useCaseIn = ReadArticlesUseCaseIn(prevArticleId, categoryCd) - val useCaseOut = ReadArticlesUseCaseOut( - IntStream.range(0, 10).mapToObj { - ReadArticleUseCaseOut( - id = it.toLong(), - writer = WriterDetail( - id = 1L, - name = "writer$it", - url = URL("http://localhost:8080/api/v1/writers/$it"), - imageUrl = URL("http://localhost:8080/api/v1/writers/images/$it") - ), - mainImageUrl = URL("http://localhost:8080/api/v1/articles/main/images/$it"), - title = "title$it", - content = "content$it", - problemIds = emptyList(), - category = CategoryType.ECONOMY.displayName, - createdAt = LocalDateTime.now(), - views = it.toLong(), - workbooks = IntStream.range(0, 2).mapToObj { j -> - WorkbookDetail( - id = "$it$j".toLong(), - title = "workbook$it$j" + val useCaseOut = + ReadArticlesUseCaseOut( + IntStream + .range(0, 10) + .mapToObj { + ReadArticleUseCaseOut( + id = it.toLong(), + writer = + WriterDetail( + id = 1L, + name = "writer$it", + url = URL("http://localhost:8080/api/v1/writers/$it"), + imageUrl = URL("http://localhost:8080/api/v1/writers/images/$it"), + ), + mainImageUrl = URL("http://localhost:8080/api/v1/articles/main/images/$it"), + title = "title$it", + content = "content$it", + problemIds = emptyList(), + category = CategoryType.ECONOMY.displayName, + createdAt = LocalDateTime.now(), + views = it.toLong(), + workbooks = + IntStream + .range(0, 2) + .mapToObj { j -> + WorkbookDetail( + id = "$it$j".toLong(), + title = "workbook$it$j", + ) + }.toList(), ) - }.toList() - ) - }.toList(), - true - ) + }.toList(), + true, + ) `when`(browseArticlesUseCase.execute(useCaseIn)).thenReturn(useCaseOut) // when - mockMvc.perform( - get(uri, prevArticleId, categoryCd).contentType(MediaType.APPLICATION_JSON) - ).andExpect(status().is2xxSuccessful) + mockMvc + .perform( + get(uri, prevArticleId, categoryCd).contentType(MediaType.APPLICATION_JSON), + ).andExpect(status().is2xxSuccessful) .andDo( document( api.toIdentifier(), resource( - ResourceSnippetParameters.builder().description("아티 목록 10개씩 조회(조회수 기반 정렬)") - .summary(api.toIdentifier()).privateResource(false).deprecated(false) - .tag(TAG).requestSchema(Schema.schema(api.toRequestSchema())) + ResourceSnippetParameters + .builder() + .description("아티 목록 10개씩 조회(조회수 기반 정렬)") + .summary(api.toIdentifier()) + .privateResource(false) + .deprecated(false) + .tag(TAG) + .requestSchema(Schema.schema(api.toRequestSchema())) .queryParameters( parameterWithName("prevArticleId").description("이전까지 조회한 아티클 Id"), - parameterWithName("categoryCd").description("아티클 카테고리 코드") - ) - .responseSchema(Schema.schema(api.toResponseSchema())).responseFields( + parameterWithName("categoryCd").description("아티클 카테고리 코드"), + ).responseSchema(Schema.schema(api.toResponseSchema())) + .responseFields( *Description.fields( FieldDescription("data", "data").asObject(), FieldDescription("data.isLast", "마지막 스크롤 유무").asBoolean(), @@ -184,11 +214,11 @@ class ArticleApiControllerTest : ApiControllerTestSpec() { FieldDescription("data.articles[].views", "아티클 조회수").asNumber(), FieldDescription("data.articles[].workbooks", "아티클이 포함된 학습지 정보").asArray(), FieldDescription("data.articles[].workbooks[].id", "아티클이 포함된 학습지 정보(학습지ID)").asNumber(), - FieldDescription("data.articles[].workbooks[].title", "아티클이 포함된 학습지 정보(학습지 제목)").asString() - ) - ).build() - ) - ) + FieldDescription("data.articles[].workbooks[].title", "아티클이 포함된 학습지 정보(학습지 제목)").asString(), + ), + ).build(), + ), + ), ) } @@ -197,32 +227,41 @@ class ArticleApiControllerTest : ApiControllerTestSpec() { fun browseArticleCategories() { // given val api = "browseArticleCategories" - val uri = UriComponentsBuilder.newInstance() - .path("$BASE_URL/categories") - .build() - .toUriString() + val uri = + UriComponentsBuilder + .newInstance() + .path("$BASE_URL/categories") + .build() + .toUriString() // when, then - mockMvc.perform( - get(uri).contentType(MediaType.APPLICATION_JSON) - ).andExpect(status().is2xxSuccessful) + mockMvc + .perform( + get(uri).contentType(MediaType.APPLICATION_JSON), + ).andExpect(status().is2xxSuccessful) .andDo( document( api.toIdentifier(), resource( - ResourceSnippetParameters.builder().description("아티클 카테고리 code, name 조회") - .summary(api.toIdentifier()).privateResource(false).deprecated(false) - .tag(TAG).requestSchema(Schema.schema(api.toRequestSchema())) - .responseSchema(Schema.schema(api.toResponseSchema())).responseFields( + ResourceSnippetParameters + .builder() + .description("아티클 카테고리 code, name 조회") + .summary(api.toIdentifier()) + .privateResource(false) + .deprecated(false) + .tag(TAG) + .requestSchema(Schema.schema(api.toRequestSchema())) + .responseSchema(Schema.schema(api.toResponseSchema())) + .responseFields( *Description.fields( FieldDescription("data", "data").asObject(), FieldDescription("data.categories", "카테고리 목록").asArray(), FieldDescription("data.categories[].code", "카테고리 코드").asNumber(), - FieldDescription("data.categories[].name", "카테고리 이름").asString() - ) - ).build() - ) - ) + FieldDescription("data.categories[].name", "카테고리 이름").asString(), + ), + ).build(), + ), + ), ) } } \ No newline at end of file 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 9786c09aa..b5ab6c1b6 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,13 +1,13 @@ package com.few.api.domain.article.usecase import com.few.api.domain.article.event.dto.ReadArticleEvent +import com.few.api.domain.article.repo.ArticleDao +import com.few.api.domain.article.repo.record.SelectArticleRecord 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.domain.article.repo.ArticleDao -import com.few.api.domain.article.repo.record.SelectArticleRecord import com.few.api.domain.article.usecase.transaction.ArticleViewCountTxCase import com.few.api.domain.common.vo.CategoryType import io.github.oshai.kotlinlogging.KotlinLogging @@ -16,138 +16,142 @@ 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 -class ReadArticleUseCaseTest : BehaviorSpec({ - val log = KotlinLogging.logger {} - - lateinit var articleDao: ArticleDao - lateinit var readArticleWriterRecordService: ReadArticleWriterRecordService - lateinit var browseArticleProblemsService: BrowseArticleProblemsService - lateinit var useCase: ReadArticleUseCase - lateinit var articleViewCountTxCase: ArticleViewCountTxCase - lateinit var applicationEventPublisher: ApplicationEventPublisher - - beforeContainer { - articleDao = mockk() - readArticleWriterRecordService = mockk() - browseArticleProblemsService = mockk() - articleViewCountTxCase = mockk() - applicationEventPublisher = mockk() - - useCase = ReadArticleUseCase( - articleDao, - readArticleWriterRecordService, - browseArticleProblemsService, - articleViewCountTxCase, - applicationEventPublisher - ) - } - - given("로그인 여부와 상관없이 아티클 조회 요청이 온 상황에서") { - val articleId = 1L - val memberId = 1L - val useCaseIn = ReadArticleUseCaseIn(articleId, memberId) - - `when`("요청한 아티클과 작가가 존재할 경우") { - val writerId = 1L - val mainImageURL = URL("http://localhost:8080/image/main/1") - val title = "title" - val category = CategoryType.ECONOMY.code - val content = "content" - every { articleDao.selectArticleRecord(any()) } returns SelectArticleRecord( - articleId = articleId, - writerId = writerId, - mainImageURL = mainImageURL, - title = title, - category = category, - content = content, - createdAt = LocalDateTime.now() - ) - - val writerName = "writer" - val writerProfileImageURL = URL("http://localhost:8080/image/writer/1") - every { readArticleWriterRecordService.execute(any()) } returns ReadWriterOutDto( - writerId = writerId, - name = writerName, - url = mainImageURL, - imageUrl = writerProfileImageURL - ) - - val problemIds = listOf(1L, 2L, 3L) - every { browseArticleProblemsService.execute(any()) } returns BrowseArticleProblemsOutDto(problemIds = problemIds) - - val views = 1L - every { articleViewCountTxCase.browseArticleViewCount(any()) } returns views - - every { applicationEventPublisher.publishEvent(any(ReadArticleEvent::class)) } just Runs - - then("아티클과 연관된 정보를 조회한다") { - val useCaseOut = useCase.execute(useCaseIn) - useCaseOut.id shouldBe articleId - useCaseOut.writer.id shouldBe writerId - useCaseOut.writer.name shouldBe writerName - useCaseOut.writer.url shouldBe mainImageURL - useCaseOut.writer.imageUrl shouldBe writerProfileImageURL - useCaseOut.mainImageUrl shouldBe mainImageURL - useCaseOut.title shouldBe title - useCaseOut.content shouldBe content - useCaseOut.problemIds shouldBe problemIds - useCaseOut.category shouldBe CategoryType.ECONOMY.displayName - useCaseOut.views shouldBe views - useCaseOut.workbooks shouldBe emptyList() - - verify(exactly = 1) { articleDao.selectArticleRecord(any()) } - verify(exactly = 1) { readArticleWriterRecordService.execute(any()) } - verify(exactly = 1) { browseArticleProblemsService.execute(any()) } - verify(exactly = 1) { articleViewCountTxCase.browseArticleViewCount(any()) } - verify(exactly = 1) { applicationEventPublisher.publishEvent(any(ReadArticleEvent::class)) } - } +class ReadArticleUseCaseTest : + BehaviorSpec({ + val log = KotlinLogging.logger {} + + lateinit var articleDao: ArticleDao + lateinit var readArticleWriterRecordService: ReadArticleWriterRecordService + lateinit var browseArticleProblemsService: BrowseArticleProblemsService + lateinit var useCase: ReadArticleUseCase + lateinit var articleViewCountTxCase: ArticleViewCountTxCase + lateinit var applicationEventPublisher: ApplicationEventPublisher + + beforeContainer { + articleDao = mockk() + readArticleWriterRecordService = mockk() + browseArticleProblemsService = mockk() + articleViewCountTxCase = mockk() + applicationEventPublisher = mockk() + + useCase = + ReadArticleUseCase( + articleDao, + readArticleWriterRecordService, + browseArticleProblemsService, + articleViewCountTxCase, + applicationEventPublisher, + ) } - `when`("요청한 아티클이 존재하지 않을 경우") { - every { articleDao.selectArticleRecord(any()) } returns null + given("로그인 여부와 상관없이 아티클 조회 요청이 온 상황에서") { + val articleId = 1L + val memberId = 1L + val useCaseIn = ReadArticleUseCaseIn(articleId, memberId) + + `when`("요청한 아티클과 작가가 존재할 경우") { + val writerId = 1L + val mainImageURL = URL("http://localhost:8080/image/main/1") + val title = "title" + val category = CategoryType.ECONOMY.code + val content = "content" + every { articleDao.selectArticleRecord(any()) } returns + SelectArticleRecord( + articleId = articleId, + writerId = writerId, + mainImageURL = mainImageURL, + title = title, + category = category, + content = content, + createdAt = LocalDateTime.now(), + ) + + val writerName = "writer" + val writerProfileImageURL = URL("http://localhost:8080/image/writer/1") + every { readArticleWriterRecordService.execute(any()) } returns + ReadWriterOutDto( + writerId = writerId, + name = writerName, + url = mainImageURL, + imageUrl = writerProfileImageURL, + ) + + val problemIds = listOf(1L, 2L, 3L) + every { browseArticleProblemsService.execute(any()) } returns BrowseArticleProblemsOutDto(problemIds = problemIds) + + val views = 1L + every { articleViewCountTxCase.browseArticleViewCount(any()) } returns views + + every { applicationEventPublisher.publishEvent(any(ReadArticleEvent::class)) } just Runs + + then("아티클과 연관된 정보를 조회한다") { + val useCaseOut = useCase.execute(useCaseIn) + useCaseOut.id shouldBe articleId + useCaseOut.writer.id shouldBe writerId + useCaseOut.writer.name shouldBe writerName + useCaseOut.writer.url shouldBe mainImageURL + useCaseOut.writer.imageUrl shouldBe writerProfileImageURL + useCaseOut.mainImageUrl shouldBe mainImageURL + useCaseOut.title shouldBe title + useCaseOut.content shouldBe content + useCaseOut.problemIds shouldBe problemIds + useCaseOut.category shouldBe CategoryType.ECONOMY.displayName + useCaseOut.views shouldBe views + useCaseOut.workbooks shouldBe emptyList() + + verify(exactly = 1) { articleDao.selectArticleRecord(any()) } + verify(exactly = 1) { readArticleWriterRecordService.execute(any()) } + verify(exactly = 1) { browseArticleProblemsService.execute(any()) } + verify(exactly = 1) { articleViewCountTxCase.browseArticleViewCount(any()) } + verify(exactly = 1) { applicationEventPublisher.publishEvent(any(ReadArticleEvent::class)) } + } + } + + `when`("요청한 아티클이 존재하지 않을 경우") { + every { articleDao.selectArticleRecord(any()) } returns null - then("예외가 발생한다") { - shouldThrow { useCase.execute(useCaseIn) } + then("예외가 발생한다") { + shouldThrow { useCase.execute(useCaseIn) } - verify(exactly = 1) { articleDao.selectArticleRecord(any()) } - verify(exactly = 0) { readArticleWriterRecordService.execute(any()) } - verify(exactly = 0) { browseArticleProblemsService.execute(any()) } - verify(exactly = 0) { articleViewCountTxCase.browseArticleViewCount(any()) } - verify(exactly = 0) { applicationEventPublisher.publishEvent(any(ReadArticleEvent::class)) } + verify(exactly = 1) { articleDao.selectArticleRecord(any()) } + verify(exactly = 0) { readArticleWriterRecordService.execute(any()) } + verify(exactly = 0) { browseArticleProblemsService.execute(any()) } + verify(exactly = 0) { articleViewCountTxCase.browseArticleViewCount(any()) } + verify(exactly = 0) { applicationEventPublisher.publishEvent(any(ReadArticleEvent::class)) } + } } - } - `when`("요청한 아티클의 작가가 존재하지 않을 경우") { - val writerId = 1L - val mainImageURL = URL("http://localhost:8080/image/main/1") - val title = "title" - val category = CategoryType.ECONOMY.code - val content = "content" - every { articleDao.selectArticleRecord(any()) } returns SelectArticleRecord( - articleId = articleId, - writerId = writerId, - mainImageURL = mainImageURL, - title = title, - category = category, - content = content, - createdAt = LocalDateTime.now() - ) - - every { readArticleWriterRecordService.execute(any()) } returns null - - then("예외가 발생한다") { - shouldThrow { useCase.execute(useCaseIn) } - - verify(exactly = 1) { articleDao.selectArticleRecord(any()) } - verify(exactly = 1) { readArticleWriterRecordService.execute(any()) } - verify(exactly = 0) { browseArticleProblemsService.execute(any()) } - verify(exactly = 0) { articleViewCountTxCase.browseArticleViewCount(any()) } - verify(exactly = 0) { applicationEventPublisher.publishEvent(any(ReadArticleEvent::class)) } + `when`("요청한 아티클의 작가가 존재하지 않을 경우") { + val writerId = 1L + val mainImageURL = URL("http://localhost:8080/image/main/1") + val title = "title" + val category = CategoryType.ECONOMY.code + val content = "content" + every { articleDao.selectArticleRecord(any()) } returns + SelectArticleRecord( + articleId = articleId, + writerId = writerId, + mainImageURL = mainImageURL, + title = title, + category = category, + content = content, + createdAt = LocalDateTime.now(), + ) + + every { readArticleWriterRecordService.execute(any()) } returns null + + then("예외가 발생한다") { + shouldThrow { useCase.execute(useCaseIn) } + + verify(exactly = 1) { articleDao.selectArticleRecord(any()) } + verify(exactly = 1) { readArticleWriterRecordService.execute(any()) } + verify(exactly = 0) { browseArticleProblemsService.execute(any()) } + verify(exactly = 0) { articleViewCountTxCase.browseArticleViewCount(any()) } + verify(exactly = 0) { applicationEventPublisher.publishEvent(any(ReadArticleEvent::class)) } + } } } - } -}) \ No newline at end of file + }) \ No newline at end of file diff --git a/api/src/test/kotlin/com/few/api/domain/log/controller/ApiLogApiControllerTest.kt b/api/src/test/kotlin/com/few/api/domain/log/controller/ApiLogApiControllerTest.kt index 5e144f630..473a22890 100644 --- a/api/src/test/kotlin/com/few/api/domain/log/controller/ApiLogApiControllerTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/log/controller/ApiLogApiControllerTest.kt @@ -3,24 +3,22 @@ package com.few.api.domain.log.controller import com.epages.restdocs.apispec.ResourceDocumentation.resource import com.epages.restdocs.apispec.ResourceSnippetParameters import com.epages.restdocs.apispec.Schema -import web.helper.toIdentifier -import web.helper.toRequestSchema -import web.helper.toResponseSchema -import com.few.api.domain.log.dto.AddApiLogUseCaseIn import com.few.api.config.web.controller.ApiControllerTestSpec import com.few.api.domain.log.controller.request.ApiLogRequest -import web.description.Description -import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post - +import com.few.api.domain.log.dto.AddApiLogUseCaseIn import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.mockito.Mockito import org.springframework.http.MediaType import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import web.description.Description +import web.helper.toIdentifier +import web.helper.toRequestSchema +import web.helper.toResponseSchema class ApiLogApiControllerTest : ApiControllerTestSpec() { - companion object { private const val BASE_URL = "/api/v1/logs" private const val TAG = "ApiLogControllerTest" @@ -40,26 +38,29 @@ class ApiLogApiControllerTest : ApiControllerTestSpec() { Mockito.doNothing().`when`(addApiLogUseCase).execute(useCaseIn) // When - mockMvc.perform( - post(BASE_URL) - .content(body) - .contentType(MediaType.APPLICATION_JSON) - ).andExpect( - status().is2xxSuccessful - ).andDo( - document( - api.toIdentifier(), - resource( - ResourceSnippetParameters.builder() - .summary(api.toIdentifier()) - .description("API 로그를 기록") - .tag(TAG) - .requestSchema(Schema.schema(api.toRequestSchema())) - .responseSchema(Schema.schema(api.toResponseSchema())).responseFields( - *Description.describe() - ).build() - ) + mockMvc + .perform( + post(BASE_URL) + .content(body) + .contentType(MediaType.APPLICATION_JSON), + ).andExpect( + status().is2xxSuccessful, + ).andDo( + document( + api.toIdentifier(), + resource( + ResourceSnippetParameters + .builder() + .summary(api.toIdentifier()) + .description("API 로그를 기록") + .tag(TAG) + .requestSchema(Schema.schema(api.toRequestSchema())) + .responseSchema(Schema.schema(api.toResponseSchema())) + .responseFields( + *Description.describe(), + ).build(), + ), + ), ) - ) } } \ No newline at end of file diff --git a/api/src/test/kotlin/com/few/api/domain/member/controller/MemberApiControllerTest.kt b/api/src/test/kotlin/com/few/api/domain/member/controller/MemberApiControllerTest.kt index 94866984a..aae310585 100644 --- a/api/src/test/kotlin/com/few/api/domain/member/controller/MemberApiControllerTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/member/controller/MemberApiControllerTest.kt @@ -4,14 +4,13 @@ import com.epages.restdocs.apispec.ResourceDocumentation import com.epages.restdocs.apispec.ResourceDocumentation.resource import com.epages.restdocs.apispec.ResourceSnippetParameters import com.epages.restdocs.apispec.Schema +import com.few.api.config.web.controller.ApiControllerTestSpec +import com.few.api.domain.member.controller.request.SaveMemberRequest +import com.few.api.domain.member.controller.request.TokenRequest import com.few.api.domain.member.usecase.dto.SaveMemberUseCaseIn import com.few.api.domain.member.usecase.dto.SaveMemberUseCaseOut import com.few.api.domain.member.usecase.dto.TokenUseCaseIn import com.few.api.domain.member.usecase.dto.TokenUseCaseOut -import com.few.api.config.web.controller.ApiControllerTestSpec -import web.description.Description -import com.few.api.domain.member.controller.request.SaveMemberRequest -import com.few.api.domain.member.controller.request.TokenRequest import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.mockito.Mockito.`when` @@ -20,10 +19,10 @@ import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status import org.springframework.web.util.UriComponentsBuilder +import web.description.Description import web.helper.* class MemberApiControllerTest : ApiControllerTestSpec() { - companion object { private const val BASE_URL = "/api/v1/members" private const val TAG = "MemberController" @@ -34,10 +33,12 @@ class MemberApiControllerTest : ApiControllerTestSpec() { fun saveMember() { // given val api = "SaveMember" - val uri = UriComponentsBuilder.newInstance() - .path(BASE_URL) - .build() - .toUriString() + val uri = + UriComponentsBuilder + .newInstance() + .path(BASE_URL) + .build() + .toUriString() val email = "test@gmail.com" val body = objectMapper.writeValueAsString(SaveMemberRequest(email = email)) @@ -46,17 +47,18 @@ class MemberApiControllerTest : ApiControllerTestSpec() { `when`(saveMemberUseCase.execute(useCaseIn)).thenReturn(useCaseOut) // when - mockMvc.perform( - post(uri) - .content(body) - .contentType(MediaType.APPLICATION_JSON) - ) - .andExpect(status().isOk) + mockMvc + .perform( + post(uri) + .content(body) + .contentType(MediaType.APPLICATION_JSON), + ).andExpect(status().isOk) .andDo( document( api.toIdentifier(), resource( - ResourceSnippetParameters.builder() + ResourceSnippetParameters + .builder() .description("회원가입") .summary(api.toIdentifier()) .privateResource(false) @@ -67,12 +69,11 @@ class MemberApiControllerTest : ApiControllerTestSpec() { .responseFields( *Description.fields( FieldDescription("data", "data").asObject(), - FieldDescription("data.isSendAuth", "이메일 인증 전송 여부").asBoolean() - ) - ) - .build() - ) - ) + FieldDescription("data.isSendAuth", "이메일 인증 전송 여부").asBoolean(), + ), + ).build(), + ), + ), ) } @@ -84,67 +85,79 @@ class MemberApiControllerTest : ApiControllerTestSpec() { val auth_token = "edb966d6ba882cc0df51579c9a94aca0" val at = 1000L val rt = 1000L - val tokenRequest = TokenRequest(refreshToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJtZW1iZXJJZCI6NTcsIm1lbWJlclJvbGUiOiJbUk9MRV9VU0VSXSIsImlhdCI6MTcyMjI1NTE4MywiZXhwIjoxNzUzODEyNzgzfQ.1KXRim0MVvz1vxOQB_700XPCD9zPQtHNItF_A9upvA8") + val tokenRequest = + TokenRequest( + refreshToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJtZW1iZXJJZCI6NTcsIm1lbWJlclJvbGUiOiJbUk9MRV9VU0VSXSIsImlhdCI6MTcyMjI1NTE4MywiZXhwIjoxNzUzODEyNzgzfQ.1KXRim0MVvz1vxOQB_700XPCD9zPQtHNItF_A9upvA8", + ) val body = objectMapper.writeValueAsString(tokenRequest) - val uri = UriComponentsBuilder.newInstance() - .path("$BASE_URL/token") - .build() - .toUriString() + val uri = + UriComponentsBuilder + .newInstance() + .path("$BASE_URL/token") + .build() + .toUriString() // set mock - val useCaseIn = TokenUseCaseIn( - token = "edb966d6ba882cc0df51579c9a94aca0", - at = at, - rt = rt, - refreshToken = tokenRequest.refreshToken - ) - val useCaseOut = TokenUseCaseOut( - accessToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJtZW1iZXJJZCI6NTcsIm1lbWJlclJvbGUiOiJbUk9MRV9VU0VSXSIsImlhdCI6MTcyMjI1NTE4MywiZXhwIjoxNzUzODEyNzgzfQ.1KXRim0MVvz1vxOQB_700XPCD9zPQtHNItF_A9upvA8", - refreshToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJtZW1iZXJJZCI6NTcsIm1lbWJlclJvbGUiOiJbUk9MRV9VU0VSXSIsImlhdCI6MTcyMjI1NTE4MywiZXhwIjoxNzUzODEyNzgzfQ.1KXRim0MVvz1vxOQB_700XPCD9zPQtHNItF_A9upvA8", - isLogin = false - ) + val useCaseIn = + TokenUseCaseIn( + token = "edb966d6ba882cc0df51579c9a94aca0", + at = at, + rt = rt, + refreshToken = tokenRequest.refreshToken, + ) + val useCaseOut = + TokenUseCaseOut( + accessToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJtZW1iZXJJZCI6NTcsIm1lbWJlclJvbGUiOiJbUk9MRV9VU0VSXSIsImlhdCI6MTcyMjI1NTE4MywiZXhwIjoxNzUzODEyNzgzfQ.1KXRim0MVvz1vxOQB_700XPCD9zPQtHNItF_A9upvA8", + refreshToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJtZW1iZXJJZCI6NTcsIm1lbWJlclJvbGUiOiJbUk9MRV9VU0VSXSIsImlhdCI6MTcyMjI1NTE4MywiZXhwIjoxNzUzODEyNzgzfQ.1KXRim0MVvz1vxOQB_700XPCD9zPQtHNItF_A9upvA8", + isLogin = false, + ) `when`(tokenUseCase.execute(useCaseIn)).thenReturn(useCaseOut) // when - mockMvc.perform( - post(uri) - .queryParam("auth_token", auth_token) - .queryParam("at", at.toString()) - .queryParam("rt", rt.toString()) - .content(body) - .contentType(MediaType.APPLICATION_JSON) - ) - .andExpect(status().isOk) + mockMvc + .perform( + post(uri) + .queryParam("auth_token", auth_token) + .queryParam("at", at.toString()) + .queryParam("rt", rt.toString()) + .content(body) + .contentType(MediaType.APPLICATION_JSON), + ).andExpect(status().isOk) .andDo( document( api.toIdentifier(), resource( - ResourceSnippetParameters.builder() + ResourceSnippetParameters + .builder() .summary(api.toIdentifier()) .description("토큰 발급") .tag(TAG) .requestSchema(Schema.schema(api.toRequestSchema())) .queryParameters( - ResourceDocumentation.parameterWithName("auth_token") - .description("아이디").optional(), - ResourceDocumentation.parameterWithName("at") - .description("액세스 토큰 만료 시간").optional(), - ResourceDocumentation.parameterWithName("rt") - .description("리프레시 토큰 만료 시간").optional() - ) - .responseSchema(Schema.schema(api.toResponseSchema())) + ResourceDocumentation + .parameterWithName("auth_token") + .description("아이디") + .optional(), + ResourceDocumentation + .parameterWithName("at") + .description("액세스 토큰 만료 시간") + .optional(), + ResourceDocumentation + .parameterWithName("rt") + .description("리프레시 토큰 만료 시간") + .optional(), + ).responseSchema(Schema.schema(api.toResponseSchema())) .responseFields( *Description.fields( FieldDescription("data", "data").asObject(), FieldDescription("data.accessToken", "accessToken").asString(), FieldDescription("data.refreshToken", "refreshToken").asString(), - FieldDescription("data.isLogin", "로그인/회원가입 여부").asBoolean() - ) - ) - .build() - ) - ) + FieldDescription("data.isLogin", "로그인/회원가입 여부").asBoolean(), + ), + ).build(), + ), + ), ) } } \ No newline at end of file diff --git a/api/src/test/kotlin/com/few/api/domain/member/usecase/SaveMemberUseCaseTest.kt b/api/src/test/kotlin/com/few/api/domain/member/usecase/SaveMemberUseCaseTest.kt index 16c5709b9..ff771991f 100644 --- a/api/src/test/kotlin/com/few/api/domain/member/usecase/SaveMemberUseCaseTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/member/usecase/SaveMemberUseCaseTest.kt @@ -1,13 +1,12 @@ package com.few.api.domain.member.usecase -import security.encryptor.IdEncryptor -import com.few.api.domain.member.usecase.dto.SaveMemberTxCaseOut -import com.few.api.domain.member.usecase.dto.SaveMemberUseCaseIn -import com.few.api.domain.member.usecase.transaction.SaveMemberTxCase +import com.few.api.domain.member.email.SendAuthEmailService import com.few.api.domain.member.repo.MemberDao import com.few.api.domain.member.repo.query.SelectMemberByEmailNotConsiderDeletedAtQuery import com.few.api.domain.member.repo.record.MemberIdAndIsDeletedRecord -import com.few.api.domain.member.email.SendAuthEmailService +import com.few.api.domain.member.usecase.dto.SaveMemberTxCaseOut +import com.few.api.domain.member.usecase.dto.SaveMemberUseCaseIn +import com.few.api.domain.member.usecase.transaction.SaveMemberTxCase import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe @@ -15,131 +14,139 @@ import io.mockk.every import io.mockk.mockk import io.mockk.verify import org.mockito.ArgumentMatchers.any +import security.encryptor.IdEncryptor -class SaveMemberUseCaseTest : BehaviorSpec({ - lateinit var memberDao: MemberDao - lateinit var sendAuthEmailService: SendAuthEmailService - lateinit var idEncryption: IdEncryptor - lateinit var saveMemberTxCase: SaveMemberTxCase - lateinit var useCase: SaveMemberUseCase - - beforeContainer { - memberDao = mockk() - sendAuthEmailService = mockk() - idEncryption = mockk() - saveMemberTxCase = mockk() - useCase = SaveMemberUseCase(memberDao, sendAuthEmailService, idEncryption, saveMemberTxCase) - } - - given("회원가입/로그인 요청이 온 상황에서") { - val email = "test@gmail.com" - val useCaseIn = SaveMemberUseCaseIn(email = email) - - `when`("요청의 이메일이 가입 이력이 없는 경우") { - every { memberDao.selectMemberByEmail(any(SelectMemberByEmailNotConsiderDeletedAtQuery::class)) } returns null - - every { saveMemberTxCase.execute(any()) } returns SaveMemberTxCaseOut( - headComment = "headComment", - subComment = "subComment", - memberId = 1L - ) - - val token = "encryptedToken" - every { idEncryption.encrypt(any()) } returns token - - every { sendAuthEmailService.send(any()) } returns "messageId" - - then("인증 이메일 발송 성공 응답을 반환한다") { - val useCaseOut = useCase.execute(useCaseIn) - useCaseOut.isSendAuthEmail shouldBe true - - verify(exactly = 1) { memberDao.selectMemberByEmail(any(SelectMemberByEmailNotConsiderDeletedAtQuery::class)) } - verify(exactly = 1) { saveMemberTxCase.execute(any()) } - verify(exactly = 1) { idEncryption.encrypt(any()) } - verify(exactly = 1) { sendAuthEmailService.send(any()) } - } +class SaveMemberUseCaseTest : + BehaviorSpec({ + lateinit var memberDao: MemberDao + lateinit var sendAuthEmailService: SendAuthEmailService + lateinit var idEncryption: IdEncryptor + lateinit var saveMemberTxCase: SaveMemberTxCase + lateinit var useCase: SaveMemberUseCase + + beforeContainer { + memberDao = mockk() + sendAuthEmailService = mockk() + idEncryption = mockk() + saveMemberTxCase = mockk() + useCase = SaveMemberUseCase(memberDao, sendAuthEmailService, idEncryption, saveMemberTxCase) } - `when`("요청의 이메일이 가입되어 있는 경우") { - val memberId = 1L - every { memberDao.selectMemberByEmail(any(SelectMemberByEmailNotConsiderDeletedAtQuery::class)) } returns MemberIdAndIsDeletedRecord( - memberId = memberId, - isDeleted = false - ) + given("회원가입/로그인 요청이 온 상황에서") { + val email = "test@gmail.com" + val useCaseIn = SaveMemberUseCaseIn(email = email) - every { saveMemberTxCase.execute(any()) } returns SaveMemberTxCaseOut( - headComment = "headComment", - subComment = "subComment", - memberId = 1L - ) + `when`("요청의 이메일이 가입 이력이 없는 경우") { + every { memberDao.selectMemberByEmail(any(SelectMemberByEmailNotConsiderDeletedAtQuery::class)) } returns null - val token = "encryptedToken" - every { idEncryption.encrypt(any()) } returns token + every { saveMemberTxCase.execute(any()) } returns + SaveMemberTxCaseOut( + headComment = "headComment", + subComment = "subComment", + memberId = 1L, + ) - every { sendAuthEmailService.send(any()) } returns "messageId" + val token = "encryptedToken" + every { idEncryption.encrypt(any()) } returns token - then("인증 이메일 발송 성공 응답을 반환한다") { - val useCaseOut = useCase.execute(useCaseIn) - useCaseOut.isSendAuthEmail shouldBe true + every { sendAuthEmailService.send(any()) } returns "messageId" - verify(exactly = 1) { memberDao.selectMemberByEmail(any(SelectMemberByEmailNotConsiderDeletedAtQuery::class)) } - verify(exactly = 1) { saveMemberTxCase.execute(any()) } - verify(exactly = 1) { idEncryption.encrypt(any()) } - verify(exactly = 1) { sendAuthEmailService.send(any()) } - } - } - - `when`("요청의 이메일이 삭제된 회원인 경우") { - val memberId = 1L - every { memberDao.selectMemberByEmail(any(SelectMemberByEmailNotConsiderDeletedAtQuery::class)) } returns MemberIdAndIsDeletedRecord( - memberId = memberId, - isDeleted = true - ) + then("인증 이메일 발송 성공 응답을 반환한다") { + val useCaseOut = useCase.execute(useCaseIn) + useCaseOut.isSendAuthEmail shouldBe true - every { saveMemberTxCase.execute(any()) } returns SaveMemberTxCaseOut( - headComment = "headComment", - subComment = "subComment", - memberId = 1L - ) - - val token = "encryptedToken" - every { idEncryption.encrypt(any()) } returns token - - every { sendAuthEmailService.send(any()) } returns "messageId" + verify(exactly = 1) { memberDao.selectMemberByEmail(any(SelectMemberByEmailNotConsiderDeletedAtQuery::class)) } + verify(exactly = 1) { saveMemberTxCase.execute(any()) } + verify(exactly = 1) { idEncryption.encrypt(any()) } + verify(exactly = 1) { sendAuthEmailService.send(any()) } + } + } - then("인증 이메일 발송 성공 응답을 반환한다") { - val useCaseOut = useCase.execute(useCaseIn) - useCaseOut.isSendAuthEmail shouldBe true + `when`("요청의 이메일이 가입되어 있는 경우") { + val memberId = 1L + every { memberDao.selectMemberByEmail(any(SelectMemberByEmailNotConsiderDeletedAtQuery::class)) } returns + MemberIdAndIsDeletedRecord( + memberId = memberId, + isDeleted = false, + ) + + every { saveMemberTxCase.execute(any()) } returns + SaveMemberTxCaseOut( + headComment = "headComment", + subComment = "subComment", + memberId = 1L, + ) + + val token = "encryptedToken" + every { idEncryption.encrypt(any()) } returns token + + every { sendAuthEmailService.send(any()) } returns "messageId" + + then("인증 이메일 발송 성공 응답을 반환한다") { + val useCaseOut = useCase.execute(useCaseIn) + useCaseOut.isSendAuthEmail shouldBe true + + verify(exactly = 1) { memberDao.selectMemberByEmail(any(SelectMemberByEmailNotConsiderDeletedAtQuery::class)) } + verify(exactly = 1) { saveMemberTxCase.execute(any()) } + verify(exactly = 1) { idEncryption.encrypt(any()) } + verify(exactly = 1) { sendAuthEmailService.send(any()) } + } + } - verify(exactly = 1) { memberDao.selectMemberByEmail(any(SelectMemberByEmailNotConsiderDeletedAtQuery::class)) } - verify(exactly = 1) { saveMemberTxCase.execute(any()) } - verify(exactly = 1) { idEncryption.encrypt(any()) } - verify(exactly = 1) { sendAuthEmailService.send(any()) } + `when`("요청의 이메일이 삭제된 회원인 경우") { + val memberId = 1L + every { memberDao.selectMemberByEmail(any(SelectMemberByEmailNotConsiderDeletedAtQuery::class)) } returns + MemberIdAndIsDeletedRecord( + memberId = memberId, + isDeleted = true, + ) + + every { saveMemberTxCase.execute(any()) } returns + SaveMemberTxCaseOut( + headComment = "headComment", + subComment = "subComment", + memberId = 1L, + ) + + val token = "encryptedToken" + every { idEncryption.encrypt(any()) } returns token + + every { sendAuthEmailService.send(any()) } returns "messageId" + + then("인증 이메일 발송 성공 응답을 반환한다") { + val useCaseOut = useCase.execute(useCaseIn) + useCaseOut.isSendAuthEmail shouldBe true + + verify(exactly = 1) { memberDao.selectMemberByEmail(any(SelectMemberByEmailNotConsiderDeletedAtQuery::class)) } + verify(exactly = 1) { saveMemberTxCase.execute(any()) } + verify(exactly = 1) { idEncryption.encrypt(any()) } + verify(exactly = 1) { sendAuthEmailService.send(any()) } + } } - } - `when`("인증 이메일 발송에 실패한 경우") { - every { memberDao.selectMemberByEmail(any(SelectMemberByEmailNotConsiderDeletedAtQuery::class)) } returns null + `when`("인증 이메일 발송에 실패한 경우") { + every { memberDao.selectMemberByEmail(any(SelectMemberByEmailNotConsiderDeletedAtQuery::class)) } returns null - every { saveMemberTxCase.execute(any()) } returns SaveMemberTxCaseOut( - headComment = "headComment", - subComment = "subComment", - memberId = 1L - ) + every { saveMemberTxCase.execute(any()) } returns + SaveMemberTxCaseOut( + headComment = "headComment", + subComment = "subComment", + memberId = 1L, + ) - val token = "encryptedToken" - every { idEncryption.encrypt(any()) } returns token + val token = "encryptedToken" + every { idEncryption.encrypt(any()) } returns token - every { sendAuthEmailService.send(any()) } throws Exception() + every { sendAuthEmailService.send(any()) } throws Exception() - then("인증 이메일 발송 실패 응답을 반환한다") { - val useCaseOut = useCase.execute(useCaseIn) - useCaseOut.isSendAuthEmail shouldBe false + then("인증 이메일 발송 실패 응답을 반환한다") { + val useCaseOut = useCase.execute(useCaseIn) + useCaseOut.isSendAuthEmail shouldBe false - shouldThrow { - sendAuthEmailService.send(any()) + shouldThrow { + sendAuthEmailService.send(any()) + } } } } - } -}) \ No newline at end of file + }) \ No newline at end of file diff --git a/api/src/test/kotlin/com/few/api/domain/member/usecase/TokenUseCaseTest.kt b/api/src/test/kotlin/com/few/api/domain/member/usecase/TokenUseCaseTest.kt index 204056c29..8323b1c07 100644 --- a/api/src/test/kotlin/com/few/api/domain/member/usecase/TokenUseCaseTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/member/usecase/TokenUseCaseTest.kt @@ -1,11 +1,10 @@ package com.few.api.domain.member.usecase import com.few.api.domain.common.vo.MemberType -import security.encryptor.IdEncryptor -import com.few.api.domain.member.usecase.dto.TokenUseCaseIn import com.few.api.domain.member.repo.MemberDao import com.few.api.domain.member.repo.command.UpdateMemberTypeCommand import com.few.api.domain.member.repo.record.MemberEmailAndTypeRecord +import com.few.api.domain.member.usecase.dto.TokenUseCaseIn import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe @@ -15,152 +14,161 @@ import io.mockk.verify import security.AuthToken import security.TokenGenerator import security.TokenResolver +import security.encryptor.IdEncryptor -class TokenUseCaseTest : BehaviorSpec({ - lateinit var tokenGenerator: TokenGenerator - lateinit var tokenResolver: TokenResolver - lateinit var memberDao: MemberDao - lateinit var idEncryption: IdEncryptor - lateinit var useCase: TokenUseCase - - beforeContainer { - tokenGenerator = mockk() - tokenResolver = mockk() - memberDao = mockk() - idEncryption = mockk() - useCase = TokenUseCase(tokenGenerator, tokenResolver, memberDao, idEncryption) - } - - given("리프레시 토큰이 포함된 토큰 갱신 요청이 온 상황에서") { - val oldRefreshToken = "refreshToken" - val useCaseIn = TokenUseCaseIn( - token = null, - refreshToken = oldRefreshToken, - at = null, - rt = null - ) - - `when`("유효한 리프레시 토큰인 경우") { - val memberId = 1L - every { tokenResolver.resolveId(any()) } returns memberId - - val email = "test@gmail.com" - every { tokenResolver.resolveEmail(any()) } returns email - - val accessToken = "newAccessToken" - val refreshToken = "newRefreshToken" - every { tokenGenerator.generateAuthToken(any(), any(), any()) } returns AuthToken( - accessToken = accessToken, - refreshToken = refreshToken - ) - - then("새로운 토큰을 반환한다") { - val useCaseOut = useCase.execute(useCaseIn) - useCaseOut.accessToken shouldBe accessToken - useCaseOut.refreshToken shouldBe refreshToken - useCaseOut.isLogin shouldBe true - - verify(exactly = 1) { tokenResolver.resolveId(any()) } - verify(exactly = 1) { tokenResolver.resolveEmail(any()) } - verify(exactly = 1) { tokenGenerator.generateAuthToken(any(), any(), any()) } - } +class TokenUseCaseTest : + BehaviorSpec({ + lateinit var tokenGenerator: TokenGenerator + lateinit var tokenResolver: TokenResolver + lateinit var memberDao: MemberDao + lateinit var idEncryption: IdEncryptor + lateinit var useCase: TokenUseCase + + beforeContainer { + tokenGenerator = mockk() + tokenResolver = mockk() + memberDao = mockk() + idEncryption = mockk() + useCase = TokenUseCase(tokenGenerator, tokenResolver, memberDao, idEncryption) } - `when`("유효하지 않은 리프레시 토큰인 경우") { - every { tokenResolver.resolveId(any()) } throws IllegalStateException() + given("리프레시 토큰이 포함된 토큰 갱신 요청이 온 상황에서") { + val oldRefreshToken = "refreshToken" + val useCaseIn = + TokenUseCaseIn( + token = null, + refreshToken = oldRefreshToken, + at = null, + rt = null, + ) + + `when`("유효한 리프레시 토큰인 경우") { + val memberId = 1L + every { tokenResolver.resolveId(any()) } returns memberId + + val email = "test@gmail.com" + every { tokenResolver.resolveEmail(any()) } returns email + + val accessToken = "newAccessToken" + val refreshToken = "newRefreshToken" + every { tokenGenerator.generateAuthToken(any(), any(), any()) } returns + AuthToken( + accessToken = accessToken, + refreshToken = refreshToken, + ) + + then("새로운 토큰을 반환한다") { + val useCaseOut = useCase.execute(useCaseIn) + useCaseOut.accessToken shouldBe accessToken + useCaseOut.refreshToken shouldBe refreshToken + useCaseOut.isLogin shouldBe true + + verify(exactly = 1) { tokenResolver.resolveId(any()) } + verify(exactly = 1) { tokenResolver.resolveEmail(any()) } + verify(exactly = 1) { tokenGenerator.generateAuthToken(any(), any(), any()) } + } + } + + `when`("유효하지 않은 리프레시 토큰인 경우") { + every { tokenResolver.resolveId(any()) } throws IllegalStateException() - then("예외를 반환한다") { - shouldThrow { useCase.execute(useCaseIn) } + then("예외를 반환한다") { + shouldThrow { useCase.execute(useCaseIn) } - verify(exactly = 1) { tokenResolver.resolveId(any()) } - verify(exactly = 0) { tokenResolver.resolveEmail(any()) } - verify(exactly = 0) { tokenGenerator.generateAuthToken(any(), any(), any()) } + verify(exactly = 1) { tokenResolver.resolveId(any()) } + verify(exactly = 0) { tokenResolver.resolveEmail(any()) } + verify(exactly = 0) { tokenGenerator.generateAuthToken(any(), any(), any()) } + } } } - } - - given("멤버 아이디 정보를 암호화한 토큰이 포함된 토큰 갱신 요청이 온 상황에서") { - val encryptedIdToken = "token" - val useCaseIn = TokenUseCaseIn( - token = encryptedIdToken, - refreshToken = null, - at = null, - rt = null - ) - - `when`("멤버 인증 토큰이 유효하고 인증을 위한 요청인 경우") { - val decryptedId = "1" - every { idEncryption.decrypt(any()) } returns decryptedId - - val accessToken = "newAccessToken" - val refreshToken = "newRefreshToken" - every { tokenGenerator.generateAuthToken(any(), any(), any(), any(), any()) } returns AuthToken( - accessToken = accessToken, - refreshToken = refreshToken - ) - - val email = "test@gmail.com" - every { memberDao.selectMemberEmailAndType(any()) } returns MemberEmailAndTypeRecord( - email = email, - memberType = MemberType.NORMAL - ) - - then("새로운 토큰을 반환한다") { - val useCaseOut = useCase.execute(useCaseIn) - useCaseOut.accessToken shouldBe accessToken - useCaseOut.refreshToken shouldBe refreshToken - useCaseOut.isLogin shouldBe true - - verify(exactly = 1) { idEncryption.decrypt(any()) } - verify(exactly = 1) { memberDao.selectMemberEmailAndType(any()) } - verify(exactly = 0) { memberDao.updateMemberType(any(UpdateMemberTypeCommand::class)) } - verify(exactly = 1) { tokenGenerator.generateAuthToken(any(), any(), any(), any(), any()) } + + given("멤버 아이디 정보를 암호화한 토큰이 포함된 토큰 갱신 요청이 온 상황에서") { + val encryptedIdToken = "token" + val useCaseIn = + TokenUseCaseIn( + token = encryptedIdToken, + refreshToken = null, + at = null, + rt = null, + ) + + `when`("멤버 인증 토큰이 유효하고 인증을 위한 요청인 경우") { + val decryptedId = "1" + every { idEncryption.decrypt(any()) } returns decryptedId + + val accessToken = "newAccessToken" + val refreshToken = "newRefreshToken" + every { tokenGenerator.generateAuthToken(any(), any(), any(), any(), any()) } returns + AuthToken( + accessToken = accessToken, + refreshToken = refreshToken, + ) + + val email = "test@gmail.com" + every { memberDao.selectMemberEmailAndType(any()) } returns + MemberEmailAndTypeRecord( + email = email, + memberType = MemberType.NORMAL, + ) + + then("새로운 토큰을 반환한다") { + val useCaseOut = useCase.execute(useCaseIn) + useCaseOut.accessToken shouldBe accessToken + useCaseOut.refreshToken shouldBe refreshToken + useCaseOut.isLogin shouldBe true + + verify(exactly = 1) { idEncryption.decrypt(any()) } + verify(exactly = 1) { memberDao.selectMemberEmailAndType(any()) } + verify(exactly = 0) { memberDao.updateMemberType(any(UpdateMemberTypeCommand::class)) } + verify(exactly = 1) { tokenGenerator.generateAuthToken(any(), any(), any(), any(), any()) } + } } - } - `when`("멤버 인증 토큰이 유효하고 가입을 위한 요청인 경우") { - val decryptedId = "1" - every { idEncryption.decrypt(any()) } returns decryptedId - - val accessToken = "newAccessToken" - val refreshToken = "newRefreshToken" - every { tokenGenerator.generateAuthToken(any(), any(), any(), any(), any()) } returns AuthToken( - accessToken = accessToken, - refreshToken = refreshToken - ) - - val email = "test@gmail.com" - every { memberDao.selectMemberEmailAndType(any()) } returns MemberEmailAndTypeRecord( - email = email, - memberType = MemberType.PREAUTH - ) - - every { memberDao.updateMemberType(any(UpdateMemberTypeCommand::class)) } returns Unit - - then("새로운 토큰을 반환한다") { - val useCaseOut = useCase.execute(useCaseIn) - useCaseOut.accessToken shouldBe accessToken - useCaseOut.refreshToken shouldBe refreshToken - useCaseOut.isLogin shouldBe false - - verify(exactly = 1) { idEncryption.decrypt(any()) } - verify(exactly = 1) { memberDao.selectMemberEmailAndType(any()) } - verify(exactly = 1) { memberDao.updateMemberType(any(UpdateMemberTypeCommand::class)) } - verify(exactly = 1) { tokenGenerator.generateAuthToken(any(), any(), any(), any(), any()) } + `when`("멤버 인증 토큰이 유효하고 가입을 위한 요청인 경우") { + val decryptedId = "1" + every { idEncryption.decrypt(any()) } returns decryptedId + + val accessToken = "newAccessToken" + val refreshToken = "newRefreshToken" + every { tokenGenerator.generateAuthToken(any(), any(), any(), any(), any()) } returns + AuthToken( + accessToken = accessToken, + refreshToken = refreshToken, + ) + + val email = "test@gmail.com" + every { memberDao.selectMemberEmailAndType(any()) } returns + MemberEmailAndTypeRecord( + email = email, + memberType = MemberType.PREAUTH, + ) + + every { memberDao.updateMemberType(any(UpdateMemberTypeCommand::class)) } returns Unit + + then("새로운 토큰을 반환한다") { + val useCaseOut = useCase.execute(useCaseIn) + useCaseOut.accessToken shouldBe accessToken + useCaseOut.refreshToken shouldBe refreshToken + useCaseOut.isLogin shouldBe false + + verify(exactly = 1) { idEncryption.decrypt(any()) } + verify(exactly = 1) { memberDao.selectMemberEmailAndType(any()) } + verify(exactly = 1) { memberDao.updateMemberType(any(UpdateMemberTypeCommand::class)) } + verify(exactly = 1) { tokenGenerator.generateAuthToken(any(), any(), any(), any(), any()) } + } } - } - `when`("유효하지 않은 멤버 인증 토큰인 경우") { - every { idEncryption.decrypt(any()) } throws IllegalStateException() + `when`("유효하지 않은 멤버 인증 토큰인 경우") { + every { idEncryption.decrypt(any()) } throws IllegalStateException() - then("예외를 반환한다") { - shouldThrow { useCase.execute(useCaseIn) } + then("예외를 반환한다") { + shouldThrow { useCase.execute(useCaseIn) } - verify(exactly = 1) { idEncryption.decrypt(any()) } - verify(exactly = 0) { memberDao.selectMemberEmailAndType(any()) } - verify(exactly = 0) { memberDao.updateMemberType(any(UpdateMemberTypeCommand::class)) } - verify(exactly = 0) { tokenGenerator.generateAuthToken(any(), any(), any(), any(), any()) } + verify(exactly = 1) { idEncryption.decrypt(any()) } + verify(exactly = 0) { memberDao.selectMemberEmailAndType(any()) } + verify(exactly = 0) { memberDao.updateMemberType(any(UpdateMemberTypeCommand::class)) } + verify(exactly = 0) { tokenGenerator.generateAuthToken(any(), any(), any(), any(), any()) } + } } } - } -}) \ No newline at end of file + }) \ No newline at end of file diff --git a/api/src/test/kotlin/com/few/api/domain/problem/controller/ProblemApiControllerTest.kt b/api/src/test/kotlin/com/few/api/domain/problem/controller/ProblemApiControllerTest.kt index 4cd874c85..e7e6ecce9 100644 --- a/api/src/test/kotlin/com/few/api/domain/problem/controller/ProblemApiControllerTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/problem/controller/ProblemApiControllerTest.kt @@ -4,10 +4,9 @@ import com.epages.restdocs.apispec.ResourceDocumentation.parameterWithName import com.epages.restdocs.apispec.ResourceDocumentation.resource import com.epages.restdocs.apispec.ResourceSnippetParameters import com.epages.restdocs.apispec.Schema -import com.few.api.domain.problem.usecase.dto.* import com.few.api.config.web.controller.ApiControllerTestSpec -import web.description.Description import com.few.api.domain.problem.controller.request.CheckProblemRequest +import com.few.api.domain.problem.usecase.dto.* import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.mockito.Mockito.`when` @@ -17,10 +16,10 @@ import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status import org.springframework.web.util.UriComponentsBuilder +import web.description.Description import web.helper.* class ProblemApiControllerTest : ApiControllerTestSpec() { - companion object { private const val BASE_URL = "/api/v1/problems" private const val TAG = "ProblemController" @@ -32,26 +31,30 @@ class ProblemApiControllerTest : ApiControllerTestSpec() { // given val api = "BrowseProblems" val articleId = 1L - val uri = UriComponentsBuilder.newInstance() - .path(BASE_URL) - .queryParam("articleId", articleId) - .build() - .toUriString() + val uri = + UriComponentsBuilder + .newInstance() + .path(BASE_URL) + .queryParam("articleId", articleId) + .build() + .toUriString() val useCaseIn = BrowseProblemsUseCaseIn(articleId) val useCaseOut = BrowseProblemsUseCaseOut(listOf(1L, 2L, 3L)) `when`(browseProblemsUseCase.execute(useCaseIn)).thenReturn(useCaseOut) // when - mockMvc.perform( - get(uri) - .contentType(MediaType.APPLICATION_JSON) - ).andExpect(status().is2xxSuccessful) + mockMvc + .perform( + get(uri) + .contentType(MediaType.APPLICATION_JSON), + ).andExpect(status().is2xxSuccessful) .andDo( document( api.toIdentifier(), resource( - ResourceSnippetParameters.builder() + ResourceSnippetParameters + .builder() .description("아티클 Id로 문제 목록 조회") .summary(api.toIdentifier()) .privateResource(false) @@ -64,12 +67,11 @@ class ProblemApiControllerTest : ApiControllerTestSpec() { *Description.fields( FieldDescription("data", "data").asObject(), FieldDescription("data.problemIds[]", "문제 Id 목록").asArray(), - FieldDescription("data.size", "문제 갯수").asNumber() - ) - ) - .build() - ) - ) + FieldDescription("data.size", "문제 갯수").asNumber(), + ), + ).build(), + ), + ), ) } @@ -78,37 +80,43 @@ class ProblemApiControllerTest : ApiControllerTestSpec() { fun readProblem() { // given val api = "ReadProblem" - val uri = UriComponentsBuilder.newInstance() - .path("$BASE_URL/{problemId}") - .build() - .toUriString() + val uri = + UriComponentsBuilder + .newInstance() + .path("$BASE_URL/{problemId}") + .build() + .toUriString() val problemId = 1L val articleId = 3L val useCaseIn = ReadProblemUseCaseIn(problemId) - val useCaseOut = ReadProblemUseCaseOut( - id = problemId, - title = "ETF(상장지수펀드)의 특징이 아닌것은?", - contents = listOf( - ReadProblemContentsUseCaseOutDetail(1L, "분산투자"), - ReadProblemContentsUseCaseOutDetail(2L, "높은 운용 비용"), - ReadProblemContentsUseCaseOutDetail(3L, "유동성"), - ReadProblemContentsUseCaseOutDetail(4L, "투명성") - ), - articleId = articleId - ) + val useCaseOut = + ReadProblemUseCaseOut( + id = problemId, + title = "ETF(상장지수펀드)의 특징이 아닌것은?", + contents = + listOf( + ReadProblemContentsUseCaseOutDetail(1L, "분산투자"), + ReadProblemContentsUseCaseOutDetail(2L, "높은 운용 비용"), + ReadProblemContentsUseCaseOutDetail(3L, "유동성"), + ReadProblemContentsUseCaseOutDetail(4L, "투명성"), + ), + articleId = articleId, + ) `when`(readProblemUseCase.execute(useCaseIn)).thenReturn(useCaseOut) // when - mockMvc.perform( - get(uri, problemId) - .contentType(MediaType.APPLICATION_JSON) - ).andExpect(status().is2xxSuccessful) + mockMvc + .perform( + get(uri, problemId) + .contentType(MediaType.APPLICATION_JSON), + ).andExpect(status().is2xxSuccessful) .andDo( document( api.toIdentifier(), resource( - ResourceSnippetParameters.builder() + ResourceSnippetParameters + .builder() .description("문제 Id로 문제 조회") .summary(api.toIdentifier()) .privateResource(false) @@ -125,12 +133,11 @@ class ProblemApiControllerTest : ApiControllerTestSpec() { FieldDescription("data.contents[]", "문제 선지 목록").asArray(), FieldDescription("data.contents[].number", "문제 선지 번호").asNumber(), FieldDescription("data.contents[].content", "문제 선지 내용").asString(), - FieldDescription("data.articleId", "문제가 속한 아티클 ID").asNumber() - ) - ) - .build() - ) - ) + FieldDescription("data.articleId", "문제가 속한 아티클 ID").asNumber(), + ), + ).build(), + ), + ), ) } @@ -139,33 +146,39 @@ class ProblemApiControllerTest : ApiControllerTestSpec() { fun checkProblem() { // given val api = "CheckProblem" - val uri = UriComponentsBuilder.newInstance() - .path("$BASE_URL/{problemId}").build().toUriString() + val uri = + UriComponentsBuilder + .newInstance() + .path("$BASE_URL/{problemId}") + .build() + .toUriString() val memberId = 0L val problemId = 1L val sub = "제출답" val body = objectMapper.writeValueAsString(CheckProblemRequest(sub = sub)) val useCaseIn = CheckProblemUseCaseIn(memberId, problemId, sub) - val useCaseOut = CheckProblemUseCaseOut( - explanation = "ETF는 일반적으로 낮은 운용 비용을 특징으로 합니다.이는 ETF가 보통 지수 추종(passive management) 방식으로 운용되기 때문입니다. 지수를 추종하는 전략은 액티브 매니지먼트(active management)에 비해 관리가 덜 복잡하고, 따라서 비용이 낮습니다.", - answer = "2", - isSolved = true - ) + val useCaseOut = + CheckProblemUseCaseOut( + explanation = "ETF는 일반적으로 낮은 운용 비용을 특징으로 합니다.이는 ETF가 보통 지수 추종(passive management) 방식으로 운용되기 때문입니다. 지수를 추종하는 전략은 액티브 매니지먼트(active management)에 비해 관리가 덜 복잡하고, 따라서 비용이 낮습니다.", + answer = "2", + isSolved = true, + ) `when`(checkProblemUseCase.execute(useCaseIn)).thenReturn(useCaseOut) // when - mockMvc.perform( - post(uri, problemId) - .content(body) - .contentType(MediaType.APPLICATION_JSON) - ) - .andExpect(status().isOk) + mockMvc + .perform( + post(uri, problemId) + .content(body) + .contentType(MediaType.APPLICATION_JSON), + ).andExpect(status().isOk) .andDo( document( api.toIdentifier(), resource( - ResourceSnippetParameters.builder() + ResourceSnippetParameters + .builder() .description("문제 Id로 문제 정답 확인") .summary(api.toIdentifier()) .privateResource(false) @@ -179,12 +192,11 @@ class ProblemApiControllerTest : ApiControllerTestSpec() { FieldDescription("data", "data").asObject(), FieldDescription("data.explanation", "문제 해설").asString(), FieldDescription("data.answer", "문제 정답").asString(), - FieldDescription("data.isSolved", "문제 정답 여부").asBoolean() - ) - ) - .build() - ) - ) + FieldDescription("data.isSolved", "문제 정답 여부").asBoolean(), + ), + ).build(), + ), + ), ) } @@ -194,23 +206,29 @@ class ProblemApiControllerTest : ApiControllerTestSpec() { // given val api = "BrowseUndoneProblems" val memberId = 0L - val uri = UriComponentsBuilder.newInstance() - .path("$BASE_URL/unsubmitted").build().toUriString() + val uri = + UriComponentsBuilder + .newInstance() + .path("$BASE_URL/unsubmitted") + .build() + .toUriString() val useCaseIn = BrowseUndoneProblemsUseCaseIn(memberId) val useCaseOut = BrowseProblemsUseCaseOut(listOf(1L, 2L, 3L)) `when`(browseUndoneProblemsUseCase.execute(useCaseIn)).thenReturn(useCaseOut) // when - mockMvc.perform( - get(uri) - .contentType(MediaType.APPLICATION_JSON) - ).andExpect(status().is2xxSuccessful) + mockMvc + .perform( + get(uri) + .contentType(MediaType.APPLICATION_JSON), + ).andExpect(status().is2xxSuccessful) .andDo( document( api.toIdentifier(), resource( - ResourceSnippetParameters.builder() + ResourceSnippetParameters + .builder() .description("밀린 문제 ID 목록 조회") .summary(api.toIdentifier()) .privateResource(false) @@ -222,12 +240,11 @@ class ProblemApiControllerTest : ApiControllerTestSpec() { *Description.fields( FieldDescription("data", "data").asObject(), FieldDescription("data.problemIds[]", "밀린 문제 Id 목록").asArray(), - FieldDescription("data.size", "밀린 문제 갯수").asNumber() - ) - ) - .build() - ) - ) + FieldDescription("data.size", "밀린 문제 갯수").asNumber(), + ), + ).build(), + ), + ), ) } } \ No newline at end of file diff --git a/api/src/test/kotlin/com/few/api/domain/problem/usecase/BrowseProblemsUseCaseTest.kt b/api/src/test/kotlin/com/few/api/domain/problem/usecase/BrowseProblemsUseCaseTest.kt index 00115ab0c..8e3af2159 100644 --- a/api/src/test/kotlin/com/few/api/domain/problem/usecase/BrowseProblemsUseCaseTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/problem/usecase/BrowseProblemsUseCaseTest.kt @@ -1,8 +1,8 @@ package com.few.api.domain.problem.usecase -import com.few.api.domain.problem.usecase.dto.BrowseProblemsUseCaseIn import com.few.api.domain.problem.repo.ProblemDao import com.few.api.domain.problem.repo.record.ProblemIdsRecord +import com.few.api.domain.problem.usecase.dto.BrowseProblemsUseCaseIn import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe @@ -10,40 +10,41 @@ import io.mockk.every import io.mockk.mockk import io.mockk.verify -class BrowseProblemsUseCaseTest : BehaviorSpec({ +class BrowseProblemsUseCaseTest : + BehaviorSpec({ - lateinit var problemDao: ProblemDao - lateinit var useCase: BrowseProblemsUseCase + lateinit var problemDao: ProblemDao + lateinit var useCase: BrowseProblemsUseCase - beforeContainer { - problemDao = mockk() - useCase = BrowseProblemsUseCase(problemDao) - } + beforeContainer { + problemDao = mockk() + useCase = BrowseProblemsUseCase(problemDao) + } - given("특정 아티클에 대한 문제 조회 요청이 온 상황에서") { - val articleId = 1L - val useCaseIn = BrowseProblemsUseCaseIn(articleId = articleId) + given("특정 아티클에 대한 문제 조회 요청이 온 상황에서") { + val articleId = 1L + val useCaseIn = BrowseProblemsUseCaseIn(articleId = articleId) - `when`("아티클의 문제가 존재할 경우") { - val problemIds = listOf(1L, 2L, 3L) - every { problemDao.selectProblemsByArticleId(any()) } returns ProblemIdsRecord(problemIds) + `when`("아티클의 문제가 존재할 경우") { + val problemIds = listOf(1L, 2L, 3L) + every { problemDao.selectProblemsByArticleId(any()) } returns ProblemIdsRecord(problemIds) - then("문제 목록을 반환한다") { - val useCaseOut = useCase.execute(useCaseIn) - useCaseOut.problemIds shouldBe problemIds + then("문제 목록을 반환한다") { + val useCaseOut = useCase.execute(useCaseIn) + useCaseOut.problemIds shouldBe problemIds - verify(exactly = 1) { problemDao.selectProblemsByArticleId(any()) } + verify(exactly = 1) { problemDao.selectProblemsByArticleId(any()) } + } } - } - `when`("아티클의 문제가 존재하지 않을 경우") { - every { problemDao.selectProblemsByArticleId(any()) } returns null + `when`("아티클의 문제가 존재하지 않을 경우") { + every { problemDao.selectProblemsByArticleId(any()) } returns null - then("예외가 발생한다") { - shouldThrow { useCase.execute(useCaseIn) } + then("예외가 발생한다") { + shouldThrow { useCase.execute(useCaseIn) } - verify(exactly = 1) { problemDao.selectProblemsByArticleId(any()) } + verify(exactly = 1) { problemDao.selectProblemsByArticleId(any()) } + } } } - } -}) \ No newline at end of file + }) \ No newline at end of file diff --git a/api/src/test/kotlin/com/few/api/domain/problem/usecase/BrowseUndoneProblemsUseCaseTest.kt b/api/src/test/kotlin/com/few/api/domain/problem/usecase/BrowseUndoneProblemsUseCaseTest.kt index 1dfbe36cc..dc133ccdd 100644 --- a/api/src/test/kotlin/com/few/api/domain/problem/usecase/BrowseUndoneProblemsUseCaseTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/problem/usecase/BrowseUndoneProblemsUseCaseTest.kt @@ -1,95 +1,101 @@ package com.few.api.domain.problem.usecase -import com.few.api.domain.problem.service.ProblemArticleService -import com.few.api.domain.problem.service.ProblemSubscriptionService -import com.few.api.domain.problem.service.dto.SubscriptionProgressOutDto -import com.few.api.domain.problem.usecase.dto.BrowseUndoneProblemsUseCaseIn import com.few.api.domain.problem.repo.ProblemDao import com.few.api.domain.problem.repo.SubmitHistoryDao import com.few.api.domain.problem.repo.record.ProblemIdAndArticleIdRecord import com.few.api.domain.problem.repo.record.SubmittedProblemIdsRecord +import com.few.api.domain.problem.service.ProblemArticleService +import com.few.api.domain.problem.service.ProblemSubscriptionService +import com.few.api.domain.problem.service.dto.SubscriptionProgressOutDto +import com.few.api.domain.problem.usecase.dto.BrowseUndoneProblemsUseCaseIn 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 -class BrowseUndoneProblemsUseCaseTest : BehaviorSpec({ - lateinit var problemDao: ProblemDao - lateinit var problemSubscriptionService: ProblemSubscriptionService - lateinit var problemArticleService: ProblemArticleService - lateinit var submitHistoryDao: SubmitHistoryDao - lateinit var useCase: BrowseUndoneProblemsUseCase - - beforeContainer { - problemDao = mockk() - problemSubscriptionService = mockk() - problemArticleService = mockk() - submitHistoryDao = mockk() - useCase = BrowseUndoneProblemsUseCase(problemDao, problemSubscriptionService, problemArticleService, submitHistoryDao) - } - - given("밀린 문제 ID 조회 요청이 온 상황에서") { - val memberId = 0L - val useCaseIn = BrowseUndoneProblemsUseCaseIn(memberId = memberId) - - `when`("밀린 문제가 존재할 경우") { - every { problemSubscriptionService.browseWorkbookIdAndProgress(any()) } returns listOf( - SubscriptionProgressOutDto(1L, 3), - SubscriptionProgressOutDto(2L, 3), - SubscriptionProgressOutDto(3L, 5), - SubscriptionProgressOutDto(4L, 7) - ) - - every { problemArticleService.browseArticleIdByWorkbookIdLimitDay(any()) } returns listOf(1L, 2L) - - every { problemDao.selectProblemIdByArticleIds(any()) } returns listOf( - ProblemIdAndArticleIdRecord(1L, 2L), - ProblemIdAndArticleIdRecord(2L, 2L) - ) - - every { submitHistoryDao.selectProblemIdByProblemIds(any()) } returns SubmittedProblemIdsRecord( - listOf( - 1L, - 2L - ) - ) - - then("밀린 문제 ID 목록을 반환한다") { - useCase.execute(useCaseIn) - - verify(exactly = 1) { problemSubscriptionService.browseWorkbookIdAndProgress(any()) } - verify(exactly = 4) { problemArticleService.browseArticleIdByWorkbookIdLimitDay(any()) } - verify(exactly = 1) { problemDao.selectProblemIdByArticleIds(any()) } - verify(exactly = 1) { submitHistoryDao.selectProblemIdByProblemIds(any()) } - } +class BrowseUndoneProblemsUseCaseTest : + BehaviorSpec({ + lateinit var problemDao: ProblemDao + lateinit var problemSubscriptionService: ProblemSubscriptionService + lateinit var problemArticleService: ProblemArticleService + lateinit var submitHistoryDao: SubmitHistoryDao + lateinit var useCase: BrowseUndoneProblemsUseCase + + beforeContainer { + problemDao = mockk() + problemSubscriptionService = mockk() + problemArticleService = mockk() + submitHistoryDao = mockk() + useCase = BrowseUndoneProblemsUseCase(problemDao, problemSubscriptionService, problemArticleService, submitHistoryDao) } - `when`("구독중이 워크북이 없을 경우") { - every { problemSubscriptionService.browseWorkbookIdAndProgress(any()) } returns emptyList() - - every { problemArticleService.browseArticleIdByWorkbookIdLimitDay(any()) } returns listOf(1L, 2L) - - every { problemDao.selectProblemIdByArticleIds(any()) } returns listOf( - ProblemIdAndArticleIdRecord(1L, 2L), - ProblemIdAndArticleIdRecord(2L, 2L) - ) - - every { submitHistoryDao.selectProblemIdByProblemIds(any()) } returns SubmittedProblemIdsRecord( - listOf( - 1L, - 2L - ) - ) - - then("에러를 반환한다") { - shouldThrow { useCase.execute(useCaseIn) } + given("밀린 문제 ID 조회 요청이 온 상황에서") { + val memberId = 0L + val useCaseIn = BrowseUndoneProblemsUseCaseIn(memberId = memberId) + + `when`("밀린 문제가 존재할 경우") { + every { problemSubscriptionService.browseWorkbookIdAndProgress(any()) } returns + listOf( + SubscriptionProgressOutDto(1L, 3), + SubscriptionProgressOutDto(2L, 3), + SubscriptionProgressOutDto(3L, 5), + SubscriptionProgressOutDto(4L, 7), + ) + + every { problemArticleService.browseArticleIdByWorkbookIdLimitDay(any()) } returns listOf(1L, 2L) + + every { problemDao.selectProblemIdByArticleIds(any()) } returns + listOf( + ProblemIdAndArticleIdRecord(1L, 2L), + ProblemIdAndArticleIdRecord(2L, 2L), + ) + + every { submitHistoryDao.selectProblemIdByProblemIds(any()) } returns + SubmittedProblemIdsRecord( + listOf( + 1L, + 2L, + ), + ) + + then("밀린 문제 ID 목록을 반환한다") { + useCase.execute(useCaseIn) + + verify(exactly = 1) { problemSubscriptionService.browseWorkbookIdAndProgress(any()) } + verify(exactly = 4) { problemArticleService.browseArticleIdByWorkbookIdLimitDay(any()) } + verify(exactly = 1) { problemDao.selectProblemIdByArticleIds(any()) } + verify(exactly = 1) { submitHistoryDao.selectProblemIdByProblemIds(any()) } + } + } - verify(exactly = 1) { problemSubscriptionService.browseWorkbookIdAndProgress(any()) } - verify(exactly = 0) { problemArticleService.browseArticleIdByWorkbookIdLimitDay(any()) } - verify(exactly = 0) { problemDao.selectProblemIdByArticleIds(any()) } - verify(exactly = 0) { submitHistoryDao.selectProblemIdByProblemIds(any()) } + `when`("구독중이 워크북이 없을 경우") { + every { problemSubscriptionService.browseWorkbookIdAndProgress(any()) } returns emptyList() + + every { problemArticleService.browseArticleIdByWorkbookIdLimitDay(any()) } returns listOf(1L, 2L) + + every { problemDao.selectProblemIdByArticleIds(any()) } returns + listOf( + ProblemIdAndArticleIdRecord(1L, 2L), + ProblemIdAndArticleIdRecord(2L, 2L), + ) + + every { submitHistoryDao.selectProblemIdByProblemIds(any()) } returns + SubmittedProblemIdsRecord( + listOf( + 1L, + 2L, + ), + ) + + then("에러를 반환한다") { + shouldThrow { useCase.execute(useCaseIn) } + + verify(exactly = 1) { problemSubscriptionService.browseWorkbookIdAndProgress(any()) } + verify(exactly = 0) { problemArticleService.browseArticleIdByWorkbookIdLimitDay(any()) } + verify(exactly = 0) { problemDao.selectProblemIdByArticleIds(any()) } + verify(exactly = 0) { submitHistoryDao.selectProblemIdByProblemIds(any()) } + } } } - } -}) \ No newline at end of file + }) \ No newline at end of file diff --git a/api/src/test/kotlin/com/few/api/domain/problem/usecase/CheckProblemUseCaseTest.kt b/api/src/test/kotlin/com/few/api/domain/problem/usecase/CheckProblemUseCaseTest.kt index 8b1c59911..7d3899618 100644 --- a/api/src/test/kotlin/com/few/api/domain/problem/usecase/CheckProblemUseCaseTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/problem/usecase/CheckProblemUseCaseTest.kt @@ -1,9 +1,9 @@ package com.few.api.domain.problem.usecase -import com.few.api.domain.problem.usecase.dto.CheckProblemUseCaseIn import com.few.api.domain.problem.repo.ProblemDao import com.few.api.domain.problem.repo.SubmitHistoryDao import com.few.api.domain.problem.repo.record.SelectProblemAnswerRecord +import com.few.api.domain.problem.usecase.dto.CheckProblemUseCaseIn import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe @@ -11,75 +11,78 @@ import io.mockk.every import io.mockk.mockk import io.mockk.verify -class CheckProblemUseCaseTest : BehaviorSpec({ - - lateinit var problemDao: ProblemDao - lateinit var submitHistoryDao: SubmitHistoryDao - lateinit var useCase: CheckProblemUseCase - - beforeContainer { - problemDao = mockk() - submitHistoryDao = mockk() - useCase = CheckProblemUseCase(problemDao, submitHistoryDao) - } - - given("특정 문제에 대한 정답 확인 요청이 온 상황에서") { - val problemId = 1L - val submissionVal = "1" - val useCaseIn = - CheckProblemUseCaseIn(memberId = 0, problemId = problemId, sub = submissionVal) +class CheckProblemUseCaseTest : + BehaviorSpec({ - `when`("제출 값과 문제 정답이 같을 경우") { - val answer = submissionVal - val explanation = "해설입니다." - every { problemDao.selectProblemAnswer(any()) } returns SelectProblemAnswerRecord(id = problemId, answer = answer, explanation = explanation) + lateinit var problemDao: ProblemDao + lateinit var submitHistoryDao: SubmitHistoryDao + lateinit var useCase: CheckProblemUseCase - val problemSubmitHistoryId = 1L - every { submitHistoryDao.insertSubmitHistory(any()) } returns problemSubmitHistoryId - - then("문제가 정답처리 된다") { - val useCaseOut = useCase.execute(useCaseIn) - useCaseOut.isSolved shouldBe true - useCaseOut.answer shouldBe answer - useCaseOut.explanation shouldBe explanation + beforeContainer { + problemDao = mockk() + submitHistoryDao = mockk() + useCase = CheckProblemUseCase(problemDao, submitHistoryDao) + } - verify(exactly = 1) { problemDao.selectProblemAnswer(any()) } - verify(exactly = 1) { submitHistoryDao.insertSubmitHistory(any()) } + given("특정 문제에 대한 정답 확인 요청이 온 상황에서") { + val problemId = 1L + val submissionVal = "1" + val useCaseIn = + CheckProblemUseCaseIn(memberId = 0, problemId = problemId, sub = submissionVal) + + `when`("제출 값과 문제 정답이 같을 경우") { + val answer = submissionVal + val explanation = "해설입니다." + every { problemDao.selectProblemAnswer(any()) } returns + SelectProblemAnswerRecord(id = problemId, answer = answer, explanation = explanation) + + val problemSubmitHistoryId = 1L + every { submitHistoryDao.insertSubmitHistory(any()) } returns problemSubmitHistoryId + + then("문제가 정답처리 된다") { + val useCaseOut = useCase.execute(useCaseIn) + useCaseOut.isSolved shouldBe true + useCaseOut.answer shouldBe answer + useCaseOut.explanation shouldBe explanation + + verify(exactly = 1) { problemDao.selectProblemAnswer(any()) } + verify(exactly = 1) { submitHistoryDao.insertSubmitHistory(any()) } + } } - } - `when`("제출 값과 문제 정답이 다를 경우") { - val answer = "2" - val explanation = "해설입니다." - every { problemDao.selectProblemAnswer(any()) } returns SelectProblemAnswerRecord( - id = problemId, - answer = answer, - explanation = explanation - ) - - val problemSubmitHistoryId = 1L - every { submitHistoryDao.insertSubmitHistory(any()) } returns problemSubmitHistoryId - - then("문제가 오답처리 된다") { - val useCaseOut = useCase.execute(useCaseIn) - useCaseOut.isSolved shouldBe false - useCaseOut.answer shouldBe answer - useCaseOut.explanation shouldBe explanation - - verify(exactly = 1) { problemDao.selectProblemAnswer(any()) } - verify(exactly = 1) { submitHistoryDao.insertSubmitHistory(any()) } + `when`("제출 값과 문제 정답이 다를 경우") { + val answer = "2" + val explanation = "해설입니다." + every { problemDao.selectProblemAnswer(any()) } returns + SelectProblemAnswerRecord( + id = problemId, + answer = answer, + explanation = explanation, + ) + + val problemSubmitHistoryId = 1L + every { submitHistoryDao.insertSubmitHistory(any()) } returns problemSubmitHistoryId + + then("문제가 오답처리 된다") { + val useCaseOut = useCase.execute(useCaseIn) + useCaseOut.isSolved shouldBe false + useCaseOut.answer shouldBe answer + useCaseOut.explanation shouldBe explanation + + verify(exactly = 1) { problemDao.selectProblemAnswer(any()) } + verify(exactly = 1) { submitHistoryDao.insertSubmitHistory(any()) } + } } - } - `when`("존재하지 않는 문제일 경우") { - every { problemDao.selectProblemAnswer(any()) } returns null + `when`("존재하지 않는 문제일 경우") { + every { problemDao.selectProblemAnswer(any()) } returns null - then("예외가 발생한다") { - shouldThrow { useCase.execute(useCaseIn) } + then("예외가 발생한다") { + shouldThrow { useCase.execute(useCaseIn) } - verify(exactly = 1) { problemDao.selectProblemAnswer(any()) } - verify(exactly = 0) { submitHistoryDao.insertSubmitHistory(any()) } + verify(exactly = 1) { problemDao.selectProblemAnswer(any()) } + verify(exactly = 0) { submitHistoryDao.insertSubmitHistory(any()) } + } } } - } -}) \ No newline at end of file + }) \ No newline at end of file diff --git a/api/src/test/kotlin/com/few/api/domain/problem/usecase/ReadProblemUseCaseTest.kt b/api/src/test/kotlin/com/few/api/domain/problem/usecase/ReadProblemUseCaseTest.kt index a119d6785..3fd9704bf 100644 --- a/api/src/test/kotlin/com/few/api/domain/problem/usecase/ReadProblemUseCaseTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/problem/usecase/ReadProblemUseCaseTest.kt @@ -1,11 +1,11 @@ package com.few.api.domain.problem.usecase -import com.few.api.domain.problem.usecase.dto.ReadProblemUseCaseIn import com.few.api.domain.problem.repo.ProblemDao import com.few.api.domain.problem.repo.record.SelectProblemRecord import com.few.api.domain.problem.repo.support.Content import com.few.api.domain.problem.repo.support.Contents import com.few.api.domain.problem.repo.support.ContentsJsonMapper +import com.few.api.domain.problem.usecase.dto.ReadProblemUseCaseIn import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe @@ -14,58 +14,63 @@ import io.mockk.mockk import io.mockk.verify import java.util.stream.IntStream -class ReadProblemUseCaseTest : BehaviorSpec({ +class ReadProblemUseCaseTest : + BehaviorSpec({ - lateinit var problemDao: ProblemDao - lateinit var contentsJsonMapper: ContentsJsonMapper - lateinit var useCase: ReadProblemUseCase + lateinit var problemDao: ProblemDao + lateinit var contentsJsonMapper: ContentsJsonMapper + lateinit var useCase: ReadProblemUseCase - beforeContainer { - problemDao = mockk() - contentsJsonMapper = mockk() - useCase = ReadProblemUseCase(problemDao, contentsJsonMapper) - } + beforeContainer { + problemDao = mockk() + contentsJsonMapper = mockk() + useCase = ReadProblemUseCase(problemDao, contentsJsonMapper) + } - given("특정 문제를 조회하는 요청이 온 상황에서") { - val problemId = 1L - val useCaseIn = ReadProblemUseCaseIn(problemId = problemId) + given("특정 문제를 조회하는 요청이 온 상황에서") { + val problemId = 1L + val useCaseIn = ReadProblemUseCaseIn(problemId = problemId) - `when`("문제가 존재할 경우") { - val title = "title" - val problemContents = "{}" - val articleId = 3L - every { problemDao.selectProblemContents(any()) } returns SelectProblemRecord(id = problemId, title = title, contents = problemContents, articleId = articleId) + `when`("문제가 존재할 경우") { + val title = "title" + val problemContents = "{}" + val articleId = 3L + every { problemDao.selectProblemContents(any()) } returns + SelectProblemRecord(id = problemId, title = title, contents = problemContents, articleId = articleId) - val contentCount = 2 - every { contentsJsonMapper.toObject(any()) } returns Contents( - IntStream.range(1, 1 + contentCount) - .mapToObj { Content(number = it.toLong(), content = "{}") }.toList() - ) + val contentCount = 2 + every { contentsJsonMapper.toObject(any()) } returns + Contents( + IntStream + .range(1, 1 + contentCount) + .mapToObj { Content(number = it.toLong(), content = "{}") } + .toList(), + ) - then("문제 정보를 반환한다") { - val useCaseOut = useCase.execute(useCaseIn) - useCaseOut.id shouldBe problemId - useCaseOut.title shouldBe title - useCaseOut.contents.size shouldBe contentCount - useCaseOut.contents.forEachIndexed { index, content -> - content.number shouldBe (index + 1).toLong() - content.content shouldBe "{}" - } + then("문제 정보를 반환한다") { + val useCaseOut = useCase.execute(useCaseIn) + useCaseOut.id shouldBe problemId + useCaseOut.title shouldBe title + useCaseOut.contents.size shouldBe contentCount + useCaseOut.contents.forEachIndexed { index, content -> + content.number shouldBe (index + 1).toLong() + content.content shouldBe "{}" + } - verify(exactly = 1) { problemDao.selectProblemContents(any()) } - verify(exactly = 1) { contentsJsonMapper.toObject(any()) } + verify(exactly = 1) { problemDao.selectProblemContents(any()) } + verify(exactly = 1) { contentsJsonMapper.toObject(any()) } + } } - } - `when`("문제가 존재하지 않을 경우") { - every { problemDao.selectProblemContents(any()) } returns null + `when`("문제가 존재하지 않을 경우") { + every { problemDao.selectProblemContents(any()) } returns null - then("예외가 발생한다") { - shouldThrow { useCase.execute(useCaseIn) } + then("예외가 발생한다") { + shouldThrow { useCase.execute(useCaseIn) } - verify(exactly = 1) { problemDao.selectProblemContents(any()) } - verify(exactly = 0) { contentsJsonMapper.toObject(any()) } + verify(exactly = 1) { problemDao.selectProblemContents(any()) } + verify(exactly = 0) { contentsJsonMapper.toObject(any()) } + } } } - } -}) \ No newline at end of file + }) \ No newline at end of file diff --git a/api/src/test/kotlin/com/few/api/domain/subscription/controller/SubscriptionApiControllerTest.kt b/api/src/test/kotlin/com/few/api/domain/subscription/controller/SubscriptionApiControllerTest.kt index f2062c2b4..a46807a33 100644 --- a/api/src/test/kotlin/com/few/api/domain/subscription/controller/SubscriptionApiControllerTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/subscription/controller/SubscriptionApiControllerTest.kt @@ -5,14 +5,13 @@ import com.epages.restdocs.apispec.ResourceDocumentation.parameterWithName import com.epages.restdocs.apispec.ResourceSnippetParameters import com.epages.restdocs.apispec.Schema import com.few.api.config.web.controller.ApiControllerTestSpec -import web.description.Description -import com.few.api.domain.subscription.controller.request.UnsubscribeWorkbookRequest -import com.few.api.domain.subscription.usecase.dto.* -import com.few.api.domain.subscription.controller.request.UpdateSubscriptionDayRequest -import com.few.api.domain.subscription.controller.request.UpdateSubscriptionTimeRequest import com.few.api.domain.common.vo.DayCode import com.few.api.domain.common.vo.ViewCategory import com.few.api.domain.common.vo.WorkBookStatus +import com.few.api.domain.subscription.controller.request.UnsubscribeWorkbookRequest +import com.few.api.domain.subscription.controller.request.UpdateSubscriptionDayRequest +import com.few.api.domain.subscription.controller.request.UpdateSubscriptionTimeRequest +import com.few.api.domain.subscription.usecase.dto.* import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.mockito.Mockito.doNothing @@ -23,11 +22,11 @@ import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.* import org.springframework.security.test.context.support.WithUserDetails import org.springframework.test.web.servlet.result.MockMvcResultMatchers import org.springframework.web.util.UriComponentsBuilder +import web.description.Description import web.helper.* import java.time.LocalTime class SubscriptionApiControllerTest : ApiControllerTestSpec() { - companion object { private const val BASE_URL = "/api/v1/" private const val TAG = "WorkbookSubscriptionController" @@ -41,62 +40,68 @@ class SubscriptionApiControllerTest : ApiControllerTestSpec() { val api = "BrowseSubscribeWorkBooksViewMainCard" val token = "thisisaccesstoken" val view = ViewCategory.MAIN_CARD - val uri = UriComponentsBuilder.newInstance() - .path("$BASE_URL/subscriptions/workbooks") - .queryParam("view", view.viewName) - .build() - .toUriString() + val uri = + UriComponentsBuilder + .newInstance() + .path("$BASE_URL/subscriptions/workbooks") + .queryParam("view", view.viewName) + .build() + .toUriString() val memberId = 1L val useCaseIn = BrowseSubscribeWorkbooksUseCaseIn(memberId, view) - val useCaseOut = BrowseSubscribeWorkbooksUseCaseOut( - clazz = MainCardSubscribeWorkbookDetail::class.java, - workbooks = listOf( - MainCardSubscribeWorkbookDetail( - workbookId = 1L, - isActiveSub = WorkBookStatus.ACTIVE, - currentDay = 1, - totalDay = 3, - rank = 0, - totalSubscriber = 100, - subscription = Subscription(), - articleInfo = "{\"articleId\":1}" - ), - MainCardSubscribeWorkbookDetail( - workbookId = 2L, - isActiveSub = WorkBookStatus.ACTIVE, - currentDay = 2, - totalDay = 3, - rank = 0, - totalSubscriber = 1, - subscription = Subscription(), - articleInfo = "{\"articleId\":5}" - ), - MainCardSubscribeWorkbookDetail( - workbookId = 3L, - isActiveSub = WorkBookStatus.DONE, - currentDay = 3, - totalDay = 3, - rank = 0, - totalSubscriber = 2, - subscription = Subscription(), - articleInfo = "{\"articleId\":6}" - ) + val useCaseOut = + BrowseSubscribeWorkbooksUseCaseOut( + clazz = MainCardSubscribeWorkbookDetail::class.java, + workbooks = + listOf( + MainCardSubscribeWorkbookDetail( + workbookId = 1L, + isActiveSub = WorkBookStatus.ACTIVE, + currentDay = 1, + totalDay = 3, + rank = 0, + totalSubscriber = 100, + subscription = Subscription(), + articleInfo = "{\"articleId\":1}", + ), + MainCardSubscribeWorkbookDetail( + workbookId = 2L, + isActiveSub = WorkBookStatus.ACTIVE, + currentDay = 2, + totalDay = 3, + rank = 0, + totalSubscriber = 1, + subscription = Subscription(), + articleInfo = "{\"articleId\":5}", + ), + MainCardSubscribeWorkbookDetail( + workbookId = 3L, + isActiveSub = WorkBookStatus.DONE, + currentDay = 3, + totalDay = 3, + rank = 0, + totalSubscriber = 2, + subscription = Subscription(), + articleInfo = "{\"articleId\":6}", + ), + ), ) - ) doReturn(useCaseOut).`when`(browseSubscribeWorkbooksUseCase).execute(useCaseIn) // when - mockMvc.perform( - get(uri) - .header("Authorization", "Bearer $token") - .contentType(MediaType.APPLICATION_JSON) - ).andExpect(MockMvcResultMatchers.status().is2xxSuccessful) + mockMvc + .perform( + get(uri) + .header("Authorization", "Bearer $token") + .contentType(MediaType.APPLICATION_JSON), + ).andExpect(MockMvcResultMatchers.status().is2xxSuccessful) .andDo( document( api.toIdentifier(), ResourceDocumentation.resource( - ResourceSnippetParameters.builder() + ResourceSnippetParameters + .builder() .description("메인 카드에 표시할 구독한 학습지 정보 목록을 조회합니다.") .summary(api.toIdentifier()) .privateResource(false) @@ -118,12 +123,11 @@ class SubscriptionApiControllerTest : ApiControllerTestSpec() { FieldDescription("data.workbooks[].articleInfo", "아티클 정보(Json 타입)").asString(), FieldDescription("data.workbooks[].subscription", "구독 정보").asObject(), FieldDescription("data.workbooks[].subscription.time", "구독 시간").asString(), - FieldDescription("data.workbooks[].subscription.dateTimeCode", "구독 시간 코드").asString() - ) - ) - .build() - ) - ) + FieldDescription("data.workbooks[].subscription.dateTimeCode", "구독 시간 코드").asString(), + ), + ).build(), + ), + ), ) } @@ -135,52 +139,58 @@ class SubscriptionApiControllerTest : ApiControllerTestSpec() { val api = "BrowseSubscribeWorkBooksViewMyPage" val token = "thisisaccesstoken" val view = ViewCategory.MY_PAGE - val uri = UriComponentsBuilder.newInstance() - .path("$BASE_URL/subscriptions/workbooks") - .queryParam("view", view.viewName) - .build() - .toUriString() + val uri = + UriComponentsBuilder + .newInstance() + .path("$BASE_URL/subscriptions/workbooks") + .queryParam("view", view.viewName) + .build() + .toUriString() val memberId = 1L val useCaseIn = BrowseSubscribeWorkbooksUseCaseIn(memberId, view) - val useCaseOut = BrowseSubscribeWorkbooksUseCaseOut( - clazz = MyPageSubscribeWorkbookDetail::class.java, - workbooks = listOf( - MyPageSubscribeWorkbookDetail( - workbookId = 1L, - isActiveSub = WorkBookStatus.ACTIVE, - currentDay = 1, - totalDay = 3, - rank = 0, - totalSubscriber = 100, - subscription = Subscription(), - workbookInfo = "{\"id\":1, \"title\":\"title1\"}" - ), - MyPageSubscribeWorkbookDetail( - workbookId = 2L, - isActiveSub = WorkBookStatus.ACTIVE, - currentDay = 2, - totalDay = 3, - rank = 0, - totalSubscriber = 1, - subscription = Subscription(), - workbookInfo = "{\"id\":2, \"title\":\"title2\"}" - ) + val useCaseOut = + BrowseSubscribeWorkbooksUseCaseOut( + clazz = MyPageSubscribeWorkbookDetail::class.java, + workbooks = + listOf( + MyPageSubscribeWorkbookDetail( + workbookId = 1L, + isActiveSub = WorkBookStatus.ACTIVE, + currentDay = 1, + totalDay = 3, + rank = 0, + totalSubscriber = 100, + subscription = Subscription(), + workbookInfo = "{\"id\":1, \"title\":\"title1\"}", + ), + MyPageSubscribeWorkbookDetail( + workbookId = 2L, + isActiveSub = WorkBookStatus.ACTIVE, + currentDay = 2, + totalDay = 3, + rank = 0, + totalSubscriber = 1, + subscription = Subscription(), + workbookInfo = "{\"id\":2, \"title\":\"title2\"}", + ), + ), ) - ) doReturn(useCaseOut).`when`(browseSubscribeWorkbooksUseCase).execute(useCaseIn) // when - mockMvc.perform( - get(uri) - .header("Authorization", "Bearer $token") - .contentType(MediaType.APPLICATION_JSON) - ).andExpect(MockMvcResultMatchers.status().is2xxSuccessful) + mockMvc + .perform( + get(uri) + .header("Authorization", "Bearer $token") + .contentType(MediaType.APPLICATION_JSON), + ).andExpect(MockMvcResultMatchers.status().is2xxSuccessful) .andDo( document( api.toIdentifier(), ResourceDocumentation.resource( - ResourceSnippetParameters.builder() + ResourceSnippetParameters + .builder() .description("마이 페이지에 표시할 구독한 학습지 정보 목록을 조회합니다.") .summary(api.toIdentifier()) .privateResource(false) @@ -202,12 +212,11 @@ class SubscriptionApiControllerTest : ApiControllerTestSpec() { FieldDescription("data.workbooks[].workbookInfo", "학습지 정보(Json 타입)").asString(), FieldDescription("data.workbooks[].subscription", "구독 정보").asObject(), FieldDescription("data.workbooks[].subscription.time", "구독 시간").asString(), - FieldDescription("data.workbooks[].subscription.dateTimeCode", "구독 시간 코드").asString() - ) - ) - .build() - ) - ) + FieldDescription("data.workbooks[].subscription.dateTimeCode", "구독 시간 코드").asString(), + ), + ).build(), + ), + ), ) } @@ -218,9 +227,12 @@ class SubscriptionApiControllerTest : ApiControllerTestSpec() { // given val api = "SubscribeWorkBook" val token = "thisisaccesstoken" - val uri = UriComponentsBuilder.newInstance() - .path("$BASE_URL/workbooks/{workbookId}/subs") - .build().toUriString() + val uri = + UriComponentsBuilder + .newInstance() + .path("$BASE_URL/workbooks/{workbookId}/subs") + .build() + .toUriString() val memberId = 1L val workbookId = 1L @@ -228,16 +240,18 @@ class SubscriptionApiControllerTest : ApiControllerTestSpec() { doNothing().`when`(subscribeWorkbookUseCase).execute(useCaseIn) // when - mockMvc.perform( - post(uri, workbookId) - .header("Authorization", "Bearer $token") - .contentType(MediaType.APPLICATION_JSON) - ).andExpect(MockMvcResultMatchers.status().is2xxSuccessful) + mockMvc + .perform( + post(uri, workbookId) + .header("Authorization", "Bearer $token") + .contentType(MediaType.APPLICATION_JSON), + ).andExpect(MockMvcResultMatchers.status().is2xxSuccessful) .andDo( document( api.toIdentifier(), ResourceDocumentation.resource( - ResourceSnippetParameters.builder() + ResourceSnippetParameters + .builder() .description("이메일을 입력하여 학습지를 구독합니다.") .summary(api.toIdentifier()) .privateResource(false) @@ -248,11 +262,10 @@ class SubscriptionApiControllerTest : ApiControllerTestSpec() { .pathParameters(parameterWithName("workbookId").description("학습지 Id")) .responseSchema(Schema.schema(api.toResponseSchema())) .responseFields( - *Description.describe() - ) - .build() - ) - ) + *Description.describe(), + ).build(), + ), + ), ) } @@ -263,36 +276,42 @@ class SubscriptionApiControllerTest : ApiControllerTestSpec() { // given val api = "UnsubscribeWorkBook" val token = "thisisaccesstoken" - val uri = UriComponentsBuilder.newInstance() - .path("$BASE_URL/workbooks/{workbookId}/unsubs") - .build() - .toUriString() + val uri = + UriComponentsBuilder + .newInstance() + .path("$BASE_URL/workbooks/{workbookId}/unsubs") + .build() + .toUriString() val workbookId = 1L val memberId = 1L val opinion = "cancel." - val body = objectMapper.writeValueAsString( - UnsubscribeWorkbookRequest(opinion = opinion) - ) - val useCaseIn = UnsubscribeWorkbookUseCaseIn( - workbookId = workbookId, - memberId = memberId, - opinion = opinion - ) + val body = + objectMapper.writeValueAsString( + UnsubscribeWorkbookRequest(opinion = opinion), + ) + val useCaseIn = + UnsubscribeWorkbookUseCaseIn( + workbookId = workbookId, + memberId = memberId, + opinion = opinion, + ) doNothing().`when`(unsubscribeWorkbookUseCase).execute(useCaseIn) // when - mockMvc.perform( - post(uri, workbookId) - .header("Authorization", "Bearer $token") - .contentType(MediaType.APPLICATION_JSON) - .content(body) - ).andExpect(MockMvcResultMatchers.status().is2xxSuccessful) + mockMvc + .perform( + post(uri, workbookId) + .header("Authorization", "Bearer $token") + .contentType(MediaType.APPLICATION_JSON) + .content(body), + ).andExpect(MockMvcResultMatchers.status().is2xxSuccessful) .andDo( document( api.toIdentifier(), ResourceDocumentation.resource( - ResourceSnippetParameters.builder() + ResourceSnippetParameters + .builder() .description("학습지 구독을 취소합니다.") .summary(api.toIdentifier()) .privateResource(false) @@ -303,11 +322,10 @@ class SubscriptionApiControllerTest : ApiControllerTestSpec() { .requestHeaders(Description.authHeader()) .responseSchema(Schema.schema(api.toResponseSchema())) .responseFields( - *Description.describe() - ) - .build() - ) - ) + *Description.describe(), + ).build(), + ), + ), ) } @@ -318,35 +336,41 @@ class SubscriptionApiControllerTest : ApiControllerTestSpec() { // given val api = "UnsubscribeAll" val token = "thisisaccesstoken" - val uri = UriComponentsBuilder.newInstance() - .path("$BASE_URL/subscriptions/unsubs") - .build() - .toUriString() + val uri = + UriComponentsBuilder + .newInstance() + .path("$BASE_URL/subscriptions/unsubs") + .build() + .toUriString() val opinion = "전체 수신거부합니다." - val body = objectMapper.writeValueAsString( - UnsubscribeWorkbookRequest(opinion = opinion) - ) + val body = + objectMapper.writeValueAsString( + UnsubscribeWorkbookRequest(opinion = opinion), + ) val memberId = 1L - val useCaseIn = UnsubscribeAllUseCaseIn( - memberId = memberId, - opinion = opinion - ) + val useCaseIn = + UnsubscribeAllUseCaseIn( + memberId = memberId, + opinion = opinion, + ) doNothing().`when`(unsubscribeAllUseCase).execute(useCaseIn) // when - mockMvc.perform( - post(uri) - .header("Authorization", "Bearer $token") - .content(body) - .contentType(MediaType.APPLICATION_JSON) - ).andExpect(MockMvcResultMatchers.status().is2xxSuccessful) + mockMvc + .perform( + post(uri) + .header("Authorization", "Bearer $token") + .content(body) + .contentType(MediaType.APPLICATION_JSON), + ).andExpect(MockMvcResultMatchers.status().is2xxSuccessful) .andDo( document( api.toIdentifier(), ResourceDocumentation.resource( - ResourceSnippetParameters.builder() + ResourceSnippetParameters + .builder() .description("학습지 구독을 취소합니다.") .summary(api.toIdentifier()) .privateResource(false) @@ -356,11 +380,10 @@ class SubscriptionApiControllerTest : ApiControllerTestSpec() { .requestHeaders(Description.authHeader()) .responseSchema(Schema.schema(api.toResponseSchema())) .responseFields( - *Description.describe() - ) - .build() - ) - ) + *Description.describe(), + ).build(), + ), + ), ) } @@ -371,32 +394,37 @@ class SubscriptionApiControllerTest : ApiControllerTestSpec() { // given val api = "UpdateSubscriptionTime" val token = "thisisaccesstoken" - val uri = UriComponentsBuilder.newInstance() - .path("$BASE_URL/subscriptions/time") - .build() - .toUriString() + val uri = + UriComponentsBuilder + .newInstance() + .path("$BASE_URL/subscriptions/time") + .build() + .toUriString() val time = LocalTime.of(8, 0) val workbookId = 1L - val body = objectMapper.writeValueAsString( - UpdateSubscriptionTimeRequest( - time = time, - workbookId = workbookId + val body = + objectMapper.writeValueAsString( + UpdateSubscriptionTimeRequest( + time = time, + workbookId = workbookId, + ), ) - ) // when - mockMvc.perform( - patch(uri) - .header("Authorization", "Bearer $token") - .content(body) - .contentType(MediaType.APPLICATION_JSON) - ).andExpect(MockMvcResultMatchers.status().is2xxSuccessful) + mockMvc + .perform( + patch(uri) + .header("Authorization", "Bearer $token") + .content(body) + .contentType(MediaType.APPLICATION_JSON), + ).andExpect(MockMvcResultMatchers.status().is2xxSuccessful) .andDo( document( api.toIdentifier(), ResourceDocumentation.resource( - ResourceSnippetParameters.builder() + ResourceSnippetParameters + .builder() .description("구독 시간을 변경합니다.") .summary(api.toIdentifier()) .privateResource(false) @@ -406,11 +434,10 @@ class SubscriptionApiControllerTest : ApiControllerTestSpec() { .requestHeaders(Description.authHeader()) .responseSchema(Schema.schema(api.toResponseSchema())) .responseFields( - *Description.describe() - ) - .build() - ) - ) + *Description.describe(), + ).build(), + ), + ), ) } @@ -421,32 +448,37 @@ class SubscriptionApiControllerTest : ApiControllerTestSpec() { // given val api = "UpdateSubscriptionDay" val token = "thisisaccesstoken" - val uri = UriComponentsBuilder.newInstance() - .path("$BASE_URL/subscriptions/day") - .build() - .toUriString() + val uri = + UriComponentsBuilder + .newInstance() + .path("$BASE_URL/subscriptions/day") + .build() + .toUriString() val dateTimeCode = DayCode.MON_TUE_WED_THU_FRI_SAT_SUN val workbookId = 1L - val body = objectMapper.writeValueAsString( - UpdateSubscriptionDayRequest( - workbookId = workbookId, - dayCode = dateTimeCode.code + val body = + objectMapper.writeValueAsString( + UpdateSubscriptionDayRequest( + workbookId = workbookId, + dayCode = dateTimeCode.code, + ), ) - ) // when - mockMvc.perform( - patch(uri) - .header("Authorization", "Bearer $token") - .content(body) - .contentType(MediaType.APPLICATION_JSON) - ).andExpect(MockMvcResultMatchers.status().is2xxSuccessful) + mockMvc + .perform( + patch(uri) + .header("Authorization", "Bearer $token") + .content(body) + .contentType(MediaType.APPLICATION_JSON), + ).andExpect(MockMvcResultMatchers.status().is2xxSuccessful) .andDo( document( api.toIdentifier(), ResourceDocumentation.resource( - ResourceSnippetParameters.builder() + ResourceSnippetParameters + .builder() .description("구독 요일을 변경합니다.") .summary(api.toIdentifier()) .privateResource(false) @@ -456,11 +488,10 @@ class SubscriptionApiControllerTest : ApiControllerTestSpec() { .requestHeaders(Description.authHeader()) .responseSchema(Schema.schema(api.toResponseSchema())) .responseFields( - *Description.describe() - ) - .build() - ) - ) + *Description.describe(), + ).build(), + ), + ), ) } } \ No newline at end of file diff --git a/api/src/test/kotlin/com/few/api/domain/subscription/usecase/BrowseSubscribeWorkbooksUseCaseTest.kt b/api/src/test/kotlin/com/few/api/domain/subscription/usecase/BrowseSubscribeWorkbooksUseCaseTest.kt index 0f59a6169..617f8cda1 100644 --- a/api/src/test/kotlin/com/few/api/domain/subscription/usecase/BrowseSubscribeWorkbooksUseCaseTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/subscription/usecase/BrowseSubscribeWorkbooksUseCaseTest.kt @@ -1,17 +1,17 @@ package com.few.api.domain.subscription.usecase import com.fasterxml.jackson.databind.ObjectMapper +import com.few.api.domain.common.vo.DayCode +import com.few.api.domain.common.vo.ViewCategory +import com.few.api.domain.common.vo.WorkBookStatus +import com.few.api.domain.subscription.repo.SubscriptionDao +import com.few.api.domain.subscription.repo.record.MemberWorkbookSubscriptionStatusRecord +import com.few.api.domain.subscription.repo.record.SubscriptionSendStatusRecord import com.few.api.domain.subscription.service.SubscriptionArticleService import com.few.api.domain.subscription.service.SubscriptionWorkbookService import com.few.api.domain.subscription.usecase.dto.BrowseSubscribeWorkbooksUseCaseIn import com.few.api.domain.subscription.usecase.dto.MainCardSubscribeWorkbookDetail import com.few.api.domain.subscription.usecase.dto.MyPageSubscribeWorkbookDetail -import com.few.api.domain.subscription.repo.SubscriptionDao -import com.few.api.domain.subscription.repo.record.MemberWorkbookSubscriptionStatusRecord -import com.few.api.domain.subscription.repo.record.SubscriptionSendStatusRecord -import com.few.api.domain.common.vo.DayCode -import com.few.api.domain.common.vo.ViewCategory -import com.few.api.domain.common.vo.WorkBookStatus import io.github.oshai.kotlinlogging.KotlinLogging import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe @@ -20,287 +20,304 @@ import io.mockk.mockk import io.mockk.verify import java.time.LocalTime -class BrowseSubscribeWorkbooksUseCaseTest : BehaviorSpec({ - val log = KotlinLogging.logger {} - - lateinit var subscriptionDao: SubscriptionDao - lateinit var subscriptionArticleService: SubscriptionArticleService - lateinit var subscriptionWorkbookService: SubscriptionWorkbookService - lateinit var objectMapper: ObjectMapper - lateinit var useCase: BrowseSubscribeWorkbooksUseCase - - beforeContainer { - subscriptionDao = mockk() - subscriptionArticleService = mockk() - subscriptionWorkbookService = mockk() - objectMapper = mockk() - useCase = BrowseSubscribeWorkbooksUseCase( - subscriptionDao, - subscriptionArticleService, - subscriptionWorkbookService, - objectMapper - ) - } - - given("메인 카드 뷰에서 멤버의 구독 워크북 정보를 조회하는 경우") { - val memberId = 1L - val useCaseIn = - BrowseSubscribeWorkbooksUseCaseIn(memberId = memberId, view = ViewCategory.MAIN_CARD) - - `when`("멤버의 구독 워크북 정보가 존재할 경우") { - val activeWorkbookId = 1L - val activeWorkbookCurrentDay = 1 - val activeWorkbookTotalDay = 3 - every { subscriptionDao.selectAllActiveWorkbookSubscriptionStatus(any()) } returns listOf( - MemberWorkbookSubscriptionStatusRecord( - workbookId = activeWorkbookId, - isActiveSub = true, - currentDay = activeWorkbookCurrentDay, - totalDay = activeWorkbookTotalDay - ) - ) - - val inactiveWorkbookId = 2L - val inactiveWorkbookCurrentDay = 2 - val inactiveWorkbookTotalDay = 3 - every { subscriptionDao.selectAllInActiveWorkbookSubscriptionStatus(any()) } returns listOf( - MemberWorkbookSubscriptionStatusRecord( - workbookId = inactiveWorkbookId, - isActiveSub = false, - currentDay = inactiveWorkbookCurrentDay, - totalDay = inactiveWorkbookTotalDay - ) - ) - - every { - subscriptionArticleService.readArticleIdByWorkbookIdAndDay(any()) - } returns inactiveWorkbookId andThen activeWorkbookId - - val activeWorkbookSubscriptionCount = 1 - val inactiveWorkbookSubscriptionCount = 2 - every { subscriptionDao.countAllWorkbookSubscription(any()) } returns mapOf( - inactiveWorkbookId to inactiveWorkbookSubscriptionCount, - activeWorkbookId to activeWorkbookSubscriptionCount - ) - - every { subscriptionDao.selectAllSubscriptionSendStatus(any()) } returns listOf( - SubscriptionSendStatusRecord( - workbookId = inactiveWorkbookId, - sendTime = LocalTime.of(8, 0), - sendDay = DayCode.MON_TUE_WED_THU_FRI_SAT_SUN.code - ), - SubscriptionSendStatusRecord( - workbookId = activeWorkbookId, - sendTime = LocalTime.of(8, 0), - sendDay = DayCode.MON_TUE_WED_THU_FRI_SAT_SUN.code +class BrowseSubscribeWorkbooksUseCaseTest : + BehaviorSpec({ + val log = KotlinLogging.logger {} + + lateinit var subscriptionDao: SubscriptionDao + lateinit var subscriptionArticleService: SubscriptionArticleService + lateinit var subscriptionWorkbookService: SubscriptionWorkbookService + lateinit var objectMapper: ObjectMapper + lateinit var useCase: BrowseSubscribeWorkbooksUseCase + + beforeContainer { + subscriptionDao = mockk() + subscriptionArticleService = mockk() + subscriptionWorkbookService = mockk() + objectMapper = mockk() + useCase = + BrowseSubscribeWorkbooksUseCase( + subscriptionDao, + subscriptionArticleService, + subscriptionWorkbookService, + objectMapper, ) - ) - - every { objectMapper.writeValueAsString(any()) } returns "{\"articleId\":$activeWorkbookId}" andThen "{\"articleId\":$inactiveWorkbookId}" - - then("멤버의 구독 워크북 정보를 반환한다") { - val useCaseOut = useCase.execute(useCaseIn) - useCaseOut.workbooks.size shouldBe 2 - - val activeSubscriptionWorkbook = useCaseOut.workbooks[0] as MainCardSubscribeWorkbookDetail - activeSubscriptionWorkbook.workbookId shouldBe activeWorkbookId - activeSubscriptionWorkbook.isActiveSub shouldBe WorkBookStatus.ACTIVE - activeSubscriptionWorkbook.currentDay shouldBe activeWorkbookCurrentDay - activeSubscriptionWorkbook.totalDay shouldBe activeWorkbookTotalDay - activeSubscriptionWorkbook.totalSubscriber shouldBe activeWorkbookSubscriptionCount - activeSubscriptionWorkbook.articleInfo shouldBe "{\"articleId\":$activeWorkbookId}" - - val inActiveSubscriptionWorkbook = useCaseOut.workbooks[1] as MainCardSubscribeWorkbookDetail - inActiveSubscriptionWorkbook.workbookId shouldBe inactiveWorkbookId - inActiveSubscriptionWorkbook.isActiveSub shouldBe WorkBookStatus.DONE - inActiveSubscriptionWorkbook.currentDay shouldBe inactiveWorkbookCurrentDay - inActiveSubscriptionWorkbook.totalDay shouldBe inactiveWorkbookTotalDay - inActiveSubscriptionWorkbook.totalSubscriber shouldBe inactiveWorkbookSubscriptionCount - inActiveSubscriptionWorkbook.articleInfo shouldBe "{\"articleId\":$inactiveWorkbookId}" - - verify(exactly = 1) { subscriptionDao.selectAllInActiveWorkbookSubscriptionStatus(any()) } - verify(exactly = 1) { subscriptionDao.selectAllActiveWorkbookSubscriptionStatus(any()) } - verify(exactly = 1) { subscriptionDao.countAllWorkbookSubscription(any()) } - verify(exactly = 1) { subscriptionDao.selectAllSubscriptionSendStatus(any()) } - verify(exactly = 2) { subscriptionArticleService.readArticleIdByWorkbookIdAndDay(any()) } - verify(exactly = 2) { objectMapper.writeValueAsString(any()) } - } } - `when`("멤버의 구독 비활성 워크북 정보만 존재할 경우") { - val inactiveWorkbookId = 1L - val inactiveWorkbookCurrentDay = 2 - val inactiveWorkbookTotalDay = 3 - every { subscriptionDao.selectAllInActiveWorkbookSubscriptionStatus(any()) } returns listOf( - MemberWorkbookSubscriptionStatusRecord( - workbookId = inactiveWorkbookId, - isActiveSub = false, - currentDay = inactiveWorkbookCurrentDay, - totalDay = inactiveWorkbookTotalDay - ) - ) - - every { subscriptionDao.selectAllActiveWorkbookSubscriptionStatus(any()) } returns emptyList() - - every { - subscriptionArticleService.readArticleIdByWorkbookIdAndDay(any()) - } returns inactiveWorkbookId - - val inactiveWorkbookSubscriptionCount = 2 - every { subscriptionDao.countAllWorkbookSubscription(any()) } returns mapOf( - inactiveWorkbookId to inactiveWorkbookSubscriptionCount - ) + given("메인 카드 뷰에서 멤버의 구독 워크북 정보를 조회하는 경우") { + val memberId = 1L + val useCaseIn = + BrowseSubscribeWorkbooksUseCaseIn(memberId = memberId, view = ViewCategory.MAIN_CARD) + + `when`("멤버의 구독 워크북 정보가 존재할 경우") { + val activeWorkbookId = 1L + val activeWorkbookCurrentDay = 1 + val activeWorkbookTotalDay = 3 + every { subscriptionDao.selectAllActiveWorkbookSubscriptionStatus(any()) } returns + listOf( + MemberWorkbookSubscriptionStatusRecord( + workbookId = activeWorkbookId, + isActiveSub = true, + currentDay = activeWorkbookCurrentDay, + totalDay = activeWorkbookTotalDay, + ), + ) + + val inactiveWorkbookId = 2L + val inactiveWorkbookCurrentDay = 2 + val inactiveWorkbookTotalDay = 3 + every { subscriptionDao.selectAllInActiveWorkbookSubscriptionStatus(any()) } returns + listOf( + MemberWorkbookSubscriptionStatusRecord( + workbookId = inactiveWorkbookId, + isActiveSub = false, + currentDay = inactiveWorkbookCurrentDay, + totalDay = inactiveWorkbookTotalDay, + ), + ) + + every { + subscriptionArticleService.readArticleIdByWorkbookIdAndDay(any()) + } returns inactiveWorkbookId andThen activeWorkbookId + + val activeWorkbookSubscriptionCount = 1 + val inactiveWorkbookSubscriptionCount = 2 + every { subscriptionDao.countAllWorkbookSubscription(any()) } returns + mapOf( + inactiveWorkbookId to inactiveWorkbookSubscriptionCount, + activeWorkbookId to activeWorkbookSubscriptionCount, + ) + + every { subscriptionDao.selectAllSubscriptionSendStatus(any()) } returns + listOf( + SubscriptionSendStatusRecord( + workbookId = inactiveWorkbookId, + sendTime = LocalTime.of(8, 0), + sendDay = DayCode.MON_TUE_WED_THU_FRI_SAT_SUN.code, + ), + SubscriptionSendStatusRecord( + workbookId = activeWorkbookId, + sendTime = LocalTime.of(8, 0), + sendDay = DayCode.MON_TUE_WED_THU_FRI_SAT_SUN.code, + ), + ) + + every { objectMapper.writeValueAsString(any()) } returns "{\"articleId\":$activeWorkbookId}" andThen + "{\"articleId\":$inactiveWorkbookId}" + + then("멤버의 구독 워크북 정보를 반환한다") { + val useCaseOut = useCase.execute(useCaseIn) + useCaseOut.workbooks.size shouldBe 2 + + val activeSubscriptionWorkbook = useCaseOut.workbooks[0] as MainCardSubscribeWorkbookDetail + activeSubscriptionWorkbook.workbookId shouldBe activeWorkbookId + activeSubscriptionWorkbook.isActiveSub shouldBe WorkBookStatus.ACTIVE + activeSubscriptionWorkbook.currentDay shouldBe activeWorkbookCurrentDay + activeSubscriptionWorkbook.totalDay shouldBe activeWorkbookTotalDay + activeSubscriptionWorkbook.totalSubscriber shouldBe activeWorkbookSubscriptionCount + activeSubscriptionWorkbook.articleInfo shouldBe "{\"articleId\":$activeWorkbookId}" + + val inActiveSubscriptionWorkbook = useCaseOut.workbooks[1] as MainCardSubscribeWorkbookDetail + inActiveSubscriptionWorkbook.workbookId shouldBe inactiveWorkbookId + inActiveSubscriptionWorkbook.isActiveSub shouldBe WorkBookStatus.DONE + inActiveSubscriptionWorkbook.currentDay shouldBe inactiveWorkbookCurrentDay + inActiveSubscriptionWorkbook.totalDay shouldBe inactiveWorkbookTotalDay + inActiveSubscriptionWorkbook.totalSubscriber shouldBe inactiveWorkbookSubscriptionCount + inActiveSubscriptionWorkbook.articleInfo shouldBe "{\"articleId\":$inactiveWorkbookId}" + + verify(exactly = 1) { subscriptionDao.selectAllInActiveWorkbookSubscriptionStatus(any()) } + verify(exactly = 1) { subscriptionDao.selectAllActiveWorkbookSubscriptionStatus(any()) } + verify(exactly = 1) { subscriptionDao.countAllWorkbookSubscription(any()) } + verify(exactly = 1) { subscriptionDao.selectAllSubscriptionSendStatus(any()) } + verify(exactly = 2) { subscriptionArticleService.readArticleIdByWorkbookIdAndDay(any()) } + verify(exactly = 2) { objectMapper.writeValueAsString(any()) } + } + } - every { subscriptionDao.selectAllSubscriptionSendStatus(any()) } returns listOf( - SubscriptionSendStatusRecord( - workbookId = inactiveWorkbookId, - sendTime = LocalTime.of(8, 0), - sendDay = DayCode.MON_TUE_WED_THU_FRI_SAT_SUN.code - ) - ) - - every { objectMapper.writeValueAsString(any()) } returns "{\"articleId\":$inactiveWorkbookId}" - - then("멤버의 구독 비활성 워크북 정보를 반환한다") { - val useCaseOut = useCase.execute(useCaseIn) - useCaseOut.workbooks.size shouldBe 1 - - val inActiveSubscriptionWorkbook = useCaseOut.workbooks[0] as MainCardSubscribeWorkbookDetail - inActiveSubscriptionWorkbook.workbookId shouldBe inactiveWorkbookId - inActiveSubscriptionWorkbook.isActiveSub shouldBe WorkBookStatus.DONE - inActiveSubscriptionWorkbook.currentDay shouldBe inactiveWorkbookCurrentDay - inActiveSubscriptionWorkbook.totalDay shouldBe inactiveWorkbookTotalDay - inActiveSubscriptionWorkbook.totalSubscriber shouldBe inactiveWorkbookSubscriptionCount - inActiveSubscriptionWorkbook.articleInfo shouldBe "{\"articleId\":$inactiveWorkbookId}" - - verify(exactly = 1) { subscriptionDao.selectAllInActiveWorkbookSubscriptionStatus(any()) } - verify(exactly = 1) { subscriptionDao.selectAllActiveWorkbookSubscriptionStatus(any()) } - verify(exactly = 1) { subscriptionArticleService.readArticleIdByWorkbookIdAndDay(any()) } - verify(exactly = 1) { subscriptionDao.countAllWorkbookSubscription(any()) } - verify(exactly = 1) { subscriptionDao.selectAllSubscriptionSendStatus(any()) } - verify(exactly = 1) { objectMapper.writeValueAsString(any()) } + `when`("멤버의 구독 비활성 워크북 정보만 존재할 경우") { + val inactiveWorkbookId = 1L + val inactiveWorkbookCurrentDay = 2 + val inactiveWorkbookTotalDay = 3 + every { subscriptionDao.selectAllInActiveWorkbookSubscriptionStatus(any()) } returns + listOf( + MemberWorkbookSubscriptionStatusRecord( + workbookId = inactiveWorkbookId, + isActiveSub = false, + currentDay = inactiveWorkbookCurrentDay, + totalDay = inactiveWorkbookTotalDay, + ), + ) + + every { subscriptionDao.selectAllActiveWorkbookSubscriptionStatus(any()) } returns emptyList() + + every { + subscriptionArticleService.readArticleIdByWorkbookIdAndDay(any()) + } returns inactiveWorkbookId + + val inactiveWorkbookSubscriptionCount = 2 + every { subscriptionDao.countAllWorkbookSubscription(any()) } returns + mapOf( + inactiveWorkbookId to inactiveWorkbookSubscriptionCount, + ) + + every { subscriptionDao.selectAllSubscriptionSendStatus(any()) } returns + listOf( + SubscriptionSendStatusRecord( + workbookId = inactiveWorkbookId, + sendTime = LocalTime.of(8, 0), + sendDay = DayCode.MON_TUE_WED_THU_FRI_SAT_SUN.code, + ), + ) + + every { objectMapper.writeValueAsString(any()) } returns "{\"articleId\":$inactiveWorkbookId}" + + then("멤버의 구독 비활성 워크북 정보를 반환한다") { + val useCaseOut = useCase.execute(useCaseIn) + useCaseOut.workbooks.size shouldBe 1 + + val inActiveSubscriptionWorkbook = useCaseOut.workbooks[0] as MainCardSubscribeWorkbookDetail + inActiveSubscriptionWorkbook.workbookId shouldBe inactiveWorkbookId + inActiveSubscriptionWorkbook.isActiveSub shouldBe WorkBookStatus.DONE + inActiveSubscriptionWorkbook.currentDay shouldBe inactiveWorkbookCurrentDay + inActiveSubscriptionWorkbook.totalDay shouldBe inactiveWorkbookTotalDay + inActiveSubscriptionWorkbook.totalSubscriber shouldBe inactiveWorkbookSubscriptionCount + inActiveSubscriptionWorkbook.articleInfo shouldBe "{\"articleId\":$inactiveWorkbookId}" + + verify(exactly = 1) { subscriptionDao.selectAllInActiveWorkbookSubscriptionStatus(any()) } + verify(exactly = 1) { subscriptionDao.selectAllActiveWorkbookSubscriptionStatus(any()) } + verify(exactly = 1) { subscriptionArticleService.readArticleIdByWorkbookIdAndDay(any()) } + verify(exactly = 1) { subscriptionDao.countAllWorkbookSubscription(any()) } + verify(exactly = 1) { subscriptionDao.selectAllSubscriptionSendStatus(any()) } + verify(exactly = 1) { objectMapper.writeValueAsString(any()) } + } } - } - `when`("멤버의 구독 활성 워크북 정보만 존재할 경우") { - every { subscriptionDao.selectAllInActiveWorkbookSubscriptionStatus(any()) } returns emptyList() - - val activeWorkbookId = 1L - val activeWorkbookCurrentDay = 2 - val activeWorkbookTotalDay = 3 - every { subscriptionDao.selectAllActiveWorkbookSubscriptionStatus(any()) } returns listOf( - MemberWorkbookSubscriptionStatusRecord( - workbookId = activeWorkbookId, - isActiveSub = false, - currentDay = activeWorkbookCurrentDay, - totalDay = activeWorkbookTotalDay - ) - ) - - every { - subscriptionArticleService.readArticleIdByWorkbookIdAndDay(any()) - } returns activeWorkbookId - - val activeWorkbookSubscriptionCount = 1 - every { subscriptionDao.countAllWorkbookSubscription(any()) } returns mapOf( - activeWorkbookId to activeWorkbookSubscriptionCount - ) - - every { subscriptionDao.selectAllSubscriptionSendStatus(any()) } returns listOf( - SubscriptionSendStatusRecord( - workbookId = activeWorkbookId, - sendTime = LocalTime.of(8, 0), - sendDay = DayCode.MON_TUE_WED_THU_FRI_SAT_SUN.code - ) - ) - - every { objectMapper.writeValueAsString(any()) } returns "{\"articleId\":$activeWorkbookId}" - - then("멤버의 구독 활성 워크북 정보를 반환한다") { - val useCaseOut = useCase.execute(useCaseIn) - useCaseOut.workbooks.size shouldBe 1 - - val inActiveSubscriptionWorkbook = useCaseOut.workbooks[0] as MainCardSubscribeWorkbookDetail - inActiveSubscriptionWorkbook.workbookId shouldBe activeWorkbookId - inActiveSubscriptionWorkbook.isActiveSub shouldBe WorkBookStatus.DONE - inActiveSubscriptionWorkbook.currentDay shouldBe activeWorkbookCurrentDay - inActiveSubscriptionWorkbook.totalDay shouldBe activeWorkbookTotalDay - inActiveSubscriptionWorkbook.totalSubscriber shouldBe activeWorkbookSubscriptionCount - inActiveSubscriptionWorkbook.articleInfo shouldBe "{\"articleId\":$activeWorkbookId}" - - verify(exactly = 1) { subscriptionDao.selectAllInActiveWorkbookSubscriptionStatus(any()) } - verify(exactly = 1) { subscriptionDao.selectAllActiveWorkbookSubscriptionStatus(any()) } - verify(exactly = 1) { subscriptionDao.countAllWorkbookSubscription(any()) } - verify(exactly = 1) { subscriptionDao.selectAllSubscriptionSendStatus(any()) } - verify(exactly = 1) { subscriptionArticleService.readArticleIdByWorkbookIdAndDay(any()) } - verify(exactly = 1) { objectMapper.writeValueAsString(any()) } + `when`("멤버의 구독 활성 워크북 정보만 존재할 경우") { + every { subscriptionDao.selectAllInActiveWorkbookSubscriptionStatus(any()) } returns emptyList() + + val activeWorkbookId = 1L + val activeWorkbookCurrentDay = 2 + val activeWorkbookTotalDay = 3 + every { subscriptionDao.selectAllActiveWorkbookSubscriptionStatus(any()) } returns + listOf( + MemberWorkbookSubscriptionStatusRecord( + workbookId = activeWorkbookId, + isActiveSub = false, + currentDay = activeWorkbookCurrentDay, + totalDay = activeWorkbookTotalDay, + ), + ) + + every { + subscriptionArticleService.readArticleIdByWorkbookIdAndDay(any()) + } returns activeWorkbookId + + val activeWorkbookSubscriptionCount = 1 + every { subscriptionDao.countAllWorkbookSubscription(any()) } returns + mapOf( + activeWorkbookId to activeWorkbookSubscriptionCount, + ) + + every { subscriptionDao.selectAllSubscriptionSendStatus(any()) } returns + listOf( + SubscriptionSendStatusRecord( + workbookId = activeWorkbookId, + sendTime = LocalTime.of(8, 0), + sendDay = DayCode.MON_TUE_WED_THU_FRI_SAT_SUN.code, + ), + ) + + every { objectMapper.writeValueAsString(any()) } returns "{\"articleId\":$activeWorkbookId}" + + then("멤버의 구독 활성 워크북 정보를 반환한다") { + val useCaseOut = useCase.execute(useCaseIn) + useCaseOut.workbooks.size shouldBe 1 + + val inActiveSubscriptionWorkbook = useCaseOut.workbooks[0] as MainCardSubscribeWorkbookDetail + inActiveSubscriptionWorkbook.workbookId shouldBe activeWorkbookId + inActiveSubscriptionWorkbook.isActiveSub shouldBe WorkBookStatus.DONE + inActiveSubscriptionWorkbook.currentDay shouldBe activeWorkbookCurrentDay + inActiveSubscriptionWorkbook.totalDay shouldBe activeWorkbookTotalDay + inActiveSubscriptionWorkbook.totalSubscriber shouldBe activeWorkbookSubscriptionCount + inActiveSubscriptionWorkbook.articleInfo shouldBe "{\"articleId\":$activeWorkbookId}" + + verify(exactly = 1) { subscriptionDao.selectAllInActiveWorkbookSubscriptionStatus(any()) } + verify(exactly = 1) { subscriptionDao.selectAllActiveWorkbookSubscriptionStatus(any()) } + verify(exactly = 1) { subscriptionDao.countAllWorkbookSubscription(any()) } + verify(exactly = 1) { subscriptionDao.selectAllSubscriptionSendStatus(any()) } + verify(exactly = 1) { subscriptionArticleService.readArticleIdByWorkbookIdAndDay(any()) } + verify(exactly = 1) { objectMapper.writeValueAsString(any()) } + } } } - } - - given("마이 페이지에서 멤버의 구독 워크북 정보를 조회하는 경우") { - val memberId = 1L - val useCaseIn = - BrowseSubscribeWorkbooksUseCaseIn(memberId = memberId, view = ViewCategory.MY_PAGE) - - `when`("멤버의 구독 활성 워크북 정보이 존재할 경우") { - every { subscriptionDao.selectAllInActiveWorkbookSubscriptionStatus(any()) } returns emptyList() - - val activeWorkbookId = 1L - val activeWorkbookCurrentDay = 2 - val activeWorkbookTotalDay = 3 - every { subscriptionDao.selectAllActiveWorkbookSubscriptionStatus(any()) } returns listOf( - MemberWorkbookSubscriptionStatusRecord( - workbookId = activeWorkbookId, - isActiveSub = false, - currentDay = activeWorkbookCurrentDay, - totalDay = activeWorkbookTotalDay - ) - ) - - every { - subscriptionArticleService.readArticleIdByWorkbookIdAndDay(any()) - } returns activeWorkbookId - - val activeWorkbookSubscriptionCount = 1 - every { subscriptionDao.countAllWorkbookSubscription(any()) } returns mapOf( - activeWorkbookId to activeWorkbookSubscriptionCount - ) - - every { subscriptionDao.selectAllSubscriptionSendStatus(any()) } returns listOf( - SubscriptionSendStatusRecord( - workbookId = activeWorkbookId, - sendTime = LocalTime.of(8, 0), - sendDay = DayCode.MON_TUE_WED_THU_FRI_SAT_SUN.code - ) - ) - - every { subscriptionWorkbookService.readAllWorkbookTitle(any()) } returns mapOf( - activeWorkbookId to "title" - ) - - every { objectMapper.writeValueAsString(any()) } returns "{\"id\":$activeWorkbookId, \"title\":\"title\"}" - - then("멤버의 구독 워크북 정보를 반환한다") { - val useCaseOut = useCase.execute(useCaseIn) - useCaseOut.workbooks.size shouldBe 1 - - val inActiveSubscriptionWorkbook = useCaseOut.workbooks[0] as MyPageSubscribeWorkbookDetail - inActiveSubscriptionWorkbook.workbookId shouldBe activeWorkbookId - inActiveSubscriptionWorkbook.isActiveSub shouldBe WorkBookStatus.DONE - inActiveSubscriptionWorkbook.currentDay shouldBe activeWorkbookCurrentDay - inActiveSubscriptionWorkbook.totalDay shouldBe activeWorkbookTotalDay - inActiveSubscriptionWorkbook.totalSubscriber shouldBe activeWorkbookSubscriptionCount - inActiveSubscriptionWorkbook.workbookInfo shouldBe "{\"id\":$activeWorkbookId, \"title\":\"title\"}" - - verify(exactly = 0) { subscriptionDao.selectAllInActiveWorkbookSubscriptionStatus(any()) } - verify(exactly = 1) { subscriptionDao.selectAllActiveWorkbookSubscriptionStatus(any()) } - verify(exactly = 1) { subscriptionDao.countAllWorkbookSubscription(any()) } - verify(exactly = 1) { subscriptionDao.selectAllSubscriptionSendStatus(any()) } - verify(exactly = 0) { subscriptionArticleService.readArticleIdByWorkbookIdAndDay(any()) } - verify(exactly = 1) { subscriptionWorkbookService.readAllWorkbookTitle(any()) } - verify(exactly = 1) { objectMapper.writeValueAsString(any()) } + + given("마이 페이지에서 멤버의 구독 워크북 정보를 조회하는 경우") { + val memberId = 1L + val useCaseIn = + BrowseSubscribeWorkbooksUseCaseIn(memberId = memberId, view = ViewCategory.MY_PAGE) + + `when`("멤버의 구독 활성 워크북 정보이 존재할 경우") { + every { subscriptionDao.selectAllInActiveWorkbookSubscriptionStatus(any()) } returns emptyList() + + val activeWorkbookId = 1L + val activeWorkbookCurrentDay = 2 + val activeWorkbookTotalDay = 3 + every { subscriptionDao.selectAllActiveWorkbookSubscriptionStatus(any()) } returns + listOf( + MemberWorkbookSubscriptionStatusRecord( + workbookId = activeWorkbookId, + isActiveSub = false, + currentDay = activeWorkbookCurrentDay, + totalDay = activeWorkbookTotalDay, + ), + ) + + every { + subscriptionArticleService.readArticleIdByWorkbookIdAndDay(any()) + } returns activeWorkbookId + + val activeWorkbookSubscriptionCount = 1 + every { subscriptionDao.countAllWorkbookSubscription(any()) } returns + mapOf( + activeWorkbookId to activeWorkbookSubscriptionCount, + ) + + every { subscriptionDao.selectAllSubscriptionSendStatus(any()) } returns + listOf( + SubscriptionSendStatusRecord( + workbookId = activeWorkbookId, + sendTime = LocalTime.of(8, 0), + sendDay = DayCode.MON_TUE_WED_THU_FRI_SAT_SUN.code, + ), + ) + + every { subscriptionWorkbookService.readAllWorkbookTitle(any()) } returns + mapOf( + activeWorkbookId to "title", + ) + + every { objectMapper.writeValueAsString(any()) } returns "{\"id\":$activeWorkbookId, \"title\":\"title\"}" + + then("멤버의 구독 워크북 정보를 반환한다") { + val useCaseOut = useCase.execute(useCaseIn) + useCaseOut.workbooks.size shouldBe 1 + + val inActiveSubscriptionWorkbook = useCaseOut.workbooks[0] as MyPageSubscribeWorkbookDetail + inActiveSubscriptionWorkbook.workbookId shouldBe activeWorkbookId + inActiveSubscriptionWorkbook.isActiveSub shouldBe WorkBookStatus.DONE + inActiveSubscriptionWorkbook.currentDay shouldBe activeWorkbookCurrentDay + inActiveSubscriptionWorkbook.totalDay shouldBe activeWorkbookTotalDay + inActiveSubscriptionWorkbook.totalSubscriber shouldBe activeWorkbookSubscriptionCount + inActiveSubscriptionWorkbook.workbookInfo shouldBe "{\"id\":$activeWorkbookId, \"title\":\"title\"}" + + verify(exactly = 0) { subscriptionDao.selectAllInActiveWorkbookSubscriptionStatus(any()) } + verify(exactly = 1) { subscriptionDao.selectAllActiveWorkbookSubscriptionStatus(any()) } + verify(exactly = 1) { subscriptionDao.countAllWorkbookSubscription(any()) } + verify(exactly = 1) { subscriptionDao.selectAllSubscriptionSendStatus(any()) } + verify(exactly = 0) { subscriptionArticleService.readArticleIdByWorkbookIdAndDay(any()) } + verify(exactly = 1) { subscriptionWorkbookService.readAllWorkbookTitle(any()) } + verify(exactly = 1) { objectMapper.writeValueAsString(any()) } + } } } - } -}) \ No newline at end of file + }) \ 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 4f2ed9de7..6e1a59f49 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 @@ -2,175 +2,183 @@ package com.few.api.domain.subscription.usecase import com.few.api.domain.common.vo.DayCode import com.few.api.domain.subscription.event.dto.WorkbookSubscriptionEvent -import com.few.api.domain.subscription.usecase.dto.SubscribeWorkbookUseCaseIn import com.few.api.domain.subscription.repo.SubscriptionDao import com.few.api.domain.subscription.repo.record.SubscriptionSendStatus import com.few.api.domain.subscription.repo.record.WorkbookSubscriptionStatus +import com.few.api.domain.subscription.usecase.dto.SubscribeWorkbookUseCaseIn import io.github.oshai.kotlinlogging.KotlinLogging import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.BehaviorSpec +import io.mockk.Runs import io.mockk.every +import io.mockk.just import io.mockk.mockk import io.mockk.verify -import io.mockk.just -import io.mockk.Runs import org.springframework.context.ApplicationEventPublisher import java.time.LocalTime -class SubscribeWorkbookUseCaseTest : BehaviorSpec({ - val log = KotlinLogging.logger {} - - lateinit var subscriptionDao: SubscriptionDao - lateinit var applicationEventPublisher: ApplicationEventPublisher - lateinit var useCase: SubscribeWorkbookUseCase - - beforeContainer { - subscriptionDao = mockk() - applicationEventPublisher = mockk() - useCase = SubscribeWorkbookUseCase(subscriptionDao, applicationEventPublisher) - } - - given("멤버의 워크북 구독 요청이 온 상황에서") { - val workbookId = 1L - val memberId = 1L - val useCaseIn = SubscribeWorkbookUseCaseIn(workbookId = workbookId, memberId = memberId) - - `when`("멤버의 구독 히스토리가 없는 경우") { - every { subscriptionDao.selectSubscriptionSendStatus(any()) } returns listOf( - SubscriptionSendStatus( - workbookId = workbookId, - memberId = memberId, - sendDay = DayCode.MON_TUE_WED_THU_FRI.code, - sendTime = LocalTime.of(8, 0) - ) - ) - - every { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } returns null - - every { subscriptionDao.insertWorkbookSubscription(any()) } just Runs - - val event = WorkbookSubscriptionEvent(workbookId, memberId, 1) - every { applicationEventPublisher.publishEvent(event) } answers { - log.debug { "Mocking applicationEventPublisher.publishEvent(any()) was called" } - } +class SubscribeWorkbookUseCaseTest : + BehaviorSpec({ + val log = KotlinLogging.logger {} - then("구독한다") { - useCase.execute(useCaseIn) + lateinit var subscriptionDao: SubscriptionDao + lateinit var applicationEventPublisher: ApplicationEventPublisher + lateinit var useCase: SubscribeWorkbookUseCase - verify(exactly = 1) { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } - verify(exactly = 1) { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } - verify(exactly = 1) { subscriptionDao.insertWorkbookSubscription(any()) } - verify(exactly = 0) { subscriptionDao.countWorkbookMappedArticles(any()) } - verify(exactly = 0) { subscriptionDao.reSubscribeWorkbookSubscription(any()) } - verify(exactly = 1) { applicationEventPublisher.publishEvent(event) } - } + beforeContainer { + subscriptionDao = mockk() + applicationEventPublisher = mockk() + useCase = SubscribeWorkbookUseCase(subscriptionDao, applicationEventPublisher) } - `when`("이미 구독한 히스토리가 있고 구독이 취소된 경우") { - every { subscriptionDao.selectSubscriptionSendStatus(any()) } returns listOf( - SubscriptionSendStatus( - workbookId = workbookId, - memberId = memberId, - sendDay = DayCode.MON_TUE_WED_THU_FRI.code, - sendTime = LocalTime.of(8, 0) - ) - ) - - val day = 2 - every { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } returns WorkbookSubscriptionStatus( - workbookId = workbookId, - isActiveSub = false, - day - ) - - val lastDay = 3 - 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" } - } + given("멤버의 워크북 구독 요청이 온 상황에서") { + val workbookId = 1L + val memberId = 1L + val useCaseIn = SubscribeWorkbookUseCaseIn(workbookId = workbookId, memberId = memberId) + + `when`("멤버의 구독 히스토리가 없는 경우") { + every { subscriptionDao.selectSubscriptionSendStatus(any()) } returns + listOf( + SubscriptionSendStatus( + workbookId = workbookId, + memberId = memberId, + sendDay = DayCode.MON_TUE_WED_THU_FRI.code, + sendTime = LocalTime.of(8, 0), + ), + ) - then("재구독한다") { - useCase.execute(useCaseIn) + every { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } returns null - verify(exactly = 1) { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } - verify(exactly = 1) { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } - verify(exactly = 0) { subscriptionDao.insertWorkbookSubscription(any()) } - verify(exactly = 1) { subscriptionDao.countWorkbookMappedArticles(any()) } - verify(exactly = 1) { subscriptionDao.reSubscribeWorkbookSubscription(any()) } - verify(exactly = 1) { applicationEventPublisher.publishEvent(event) } + every { subscriptionDao.insertWorkbookSubscription(any()) } just Runs + + val event = WorkbookSubscriptionEvent(workbookId, memberId, 1) + every { applicationEventPublisher.publishEvent(event) } answers { + log.debug { "Mocking applicationEventPublisher.publishEvent(any()) was called" } + } + + then("구독한다") { + useCase.execute(useCaseIn) + + verify(exactly = 1) { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } + verify(exactly = 1) { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } + verify(exactly = 1) { subscriptionDao.insertWorkbookSubscription(any()) } + verify(exactly = 0) { subscriptionDao.countWorkbookMappedArticles(any()) } + verify(exactly = 0) { subscriptionDao.reSubscribeWorkbookSubscription(any()) } + verify(exactly = 1) { applicationEventPublisher.publishEvent(event) } + } } - } - `when`("이미 구독한 히스토리가 있고 구독을 완료한 경우") { - every { subscriptionDao.selectSubscriptionSendStatus(any()) } returns listOf( - SubscriptionSendStatus( - workbookId = workbookId, - memberId = memberId, - sendDay = DayCode.MON_TUE_WED_THU_FRI.code, - sendTime = LocalTime.of(8, 0) - ) - ) - - 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" } + `when`("이미 구독한 히스토리가 있고 구독이 취소된 경우") { + every { subscriptionDao.selectSubscriptionSendStatus(any()) } returns + listOf( + SubscriptionSendStatus( + workbookId = workbookId, + memberId = memberId, + sendDay = DayCode.MON_TUE_WED_THU_FRI.code, + sendTime = LocalTime.of(8, 0), + ), + ) + + val day = 2 + every { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } returns + WorkbookSubscriptionStatus( + workbookId = workbookId, + isActiveSub = false, + day, + ) + + val lastDay = 3 + 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("재구독한다") { + useCase.execute(useCaseIn) + + verify(exactly = 1) { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } + verify(exactly = 1) { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } + verify(exactly = 0) { subscriptionDao.insertWorkbookSubscription(any()) } + verify(exactly = 1) { subscriptionDao.countWorkbookMappedArticles(any()) } + verify(exactly = 1) { subscriptionDao.reSubscribeWorkbookSubscription(any()) } + verify(exactly = 1) { applicationEventPublisher.publishEvent(event) } + } } - then("예외가 발생한다") { - shouldThrow { useCase.execute(useCaseIn) } + `when`("이미 구독한 히스토리가 있고 구독을 완료한 경우") { + every { subscriptionDao.selectSubscriptionSendStatus(any()) } returns + listOf( + SubscriptionSendStatus( + workbookId = workbookId, + memberId = memberId, + sendDay = DayCode.MON_TUE_WED_THU_FRI.code, + sendTime = LocalTime.of(8, 0), + ), + ) + + 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" } + } - verify(exactly = 1) { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } - 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) } + then("예외가 발생한다") { + shouldThrow { useCase.execute(useCaseIn) } + + verify(exactly = 1) { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } + 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`("구독 중인 경우") { - every { subscriptionDao.selectSubscriptionSendStatus(any()) } returns listOf( - SubscriptionSendStatus( - workbookId = workbookId, - memberId = memberId, - sendDay = DayCode.MON_TUE_WED_THU_FRI.code, - sendTime = LocalTime.of(8, 0) - ) - ) - - val day = 2 - every { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } returns WorkbookSubscriptionStatus(workbookId = workbookId, isActiveSub = true, day) - - then("예외가 발생한다") { - shouldThrow { useCase.execute(useCaseIn) } - - verify(exactly = 1) { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } - verify(exactly = 1) { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } - 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, memberId, day) + `when`("구독 중인 경우") { + every { subscriptionDao.selectSubscriptionSendStatus(any()) } returns + listOf( + SubscriptionSendStatus( + workbookId = workbookId, + memberId = memberId, + sendDay = DayCode.MON_TUE_WED_THU_FRI.code, + sendTime = LocalTime.of(8, 0), + ), ) + + val day = 2 + every { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } returns + WorkbookSubscriptionStatus(workbookId = workbookId, isActiveSub = true, day) + + then("예외가 발생한다") { + shouldThrow { useCase.execute(useCaseIn) } + + verify(exactly = 1) { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } + verify(exactly = 1) { subscriptionDao.selectTopWorkbookSubscriptionStatus(any()) } + 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, memberId, day), + ) + } } } } - } -}) \ No newline at end of file + }) \ No newline at end of file diff --git a/api/src/test/kotlin/com/few/api/domain/subscription/usecase/UnsubscribeAllUseCaseTest.kt b/api/src/test/kotlin/com/few/api/domain/subscription/usecase/UnsubscribeAllUseCaseTest.kt index 6a31b7e2b..65f85992b 100644 --- a/api/src/test/kotlin/com/few/api/domain/subscription/usecase/UnsubscribeAllUseCaseTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/subscription/usecase/UnsubscribeAllUseCaseTest.kt @@ -1,54 +1,56 @@ package com.few.api.domain.subscription.usecase -import com.few.api.domain.subscription.usecase.dto.UnsubscribeAllUseCaseIn import com.few.api.domain.subscription.repo.SubscriptionDao +import com.few.api.domain.subscription.usecase.dto.UnsubscribeAllUseCaseIn import io.github.oshai.kotlinlogging.KotlinLogging import io.kotest.core.spec.style.BehaviorSpec +import io.mockk.Runs import io.mockk.every +import io.mockk.just import io.mockk.mockk import io.mockk.verify -import io.mockk.just -import io.mockk.Runs -class UnsubscribeAllUseCaseTest : BehaviorSpec({ - val log = KotlinLogging.logger {} - lateinit var subscriptionDao: SubscriptionDao - lateinit var useCase: UnsubscribeAllUseCase +class UnsubscribeAllUseCaseTest : + BehaviorSpec({ + val log = KotlinLogging.logger {} - beforeContainer { - subscriptionDao = mockk() - useCase = UnsubscribeAllUseCase(subscriptionDao) - } + lateinit var subscriptionDao: SubscriptionDao + lateinit var useCase: UnsubscribeAllUseCase + + beforeContainer { + subscriptionDao = mockk() + useCase = UnsubscribeAllUseCase(subscriptionDao) + } - given("구독 취소 의견이 포함된 전체 구독 취소 요청이 온 상황에서") { - val memberId = 1L - val opinion = "취소합니다." - val useCaseIn = UnsubscribeAllUseCaseIn(memberId = memberId, opinion = opinion) + given("구독 취소 의견이 포함된 전체 구독 취소 요청이 온 상황에서") { + val memberId = 1L + val opinion = "취소합니다." + val useCaseIn = UnsubscribeAllUseCaseIn(memberId = memberId, opinion = opinion) - `when`("멤버의 구독 히스토리가 있는 경우") { - every { subscriptionDao.updateDeletedAtInAllSubscription(any()) } just Runs + `when`("멤버의 구독 히스토리가 있는 경우") { + every { subscriptionDao.updateDeletedAtInAllSubscription(any()) } just Runs - then("의견을 저장하고 구독 전체를 취소한다") { - useCase.execute(useCaseIn) + then("의견을 저장하고 구독 전체를 취소한다") { + useCase.execute(useCaseIn) - verify(exactly = 1) { subscriptionDao.updateDeletedAtInAllSubscription(any()) } + verify(exactly = 1) { subscriptionDao.updateDeletedAtInAllSubscription(any()) } + } } } - } - given("구독 취소 의견이 포함되지 않은 전체 구독 취소 요청이 온 상황에서") { - val memberId = 1L - val opinion = "" - val useCaseIn = UnsubscribeAllUseCaseIn(memberId = memberId, opinion = opinion) + given("구독 취소 의견이 포함되지 않은 전체 구독 취소 요청이 온 상황에서") { + val memberId = 1L + val opinion = "" + val useCaseIn = UnsubscribeAllUseCaseIn(memberId = memberId, opinion = opinion) - `when`("멤버의 구독 히스토리가 있는 경우") { - every { subscriptionDao.updateDeletedAtInAllSubscription(any()) } just Runs + `when`("멤버의 구독 히스토리가 있는 경우") { + every { subscriptionDao.updateDeletedAtInAllSubscription(any()) } just Runs - then("의견을 cancel로 저장하고 구독 전체를 취소한다") { - useCase.execute(useCaseIn) + then("의견을 cancel로 저장하고 구독 전체를 취소한다") { + useCase.execute(useCaseIn) - verify(exactly = 1) { subscriptionDao.updateDeletedAtInAllSubscription(any()) } + verify(exactly = 1) { subscriptionDao.updateDeletedAtInAllSubscription(any()) } + } } } - } -}) \ No newline at end of file + }) \ No newline at end of file diff --git a/api/src/test/kotlin/com/few/api/domain/subscription/usecase/UnsubscribeWorkbookUseCaseTest.kt b/api/src/test/kotlin/com/few/api/domain/subscription/usecase/UnsubscribeWorkbookUseCaseTest.kt index 4b479eb7b..cea6dc508 100644 --- a/api/src/test/kotlin/com/few/api/domain/subscription/usecase/UnsubscribeWorkbookUseCaseTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/subscription/usecase/UnsubscribeWorkbookUseCaseTest.kt @@ -1,53 +1,54 @@ package com.few.api.domain.subscription.usecase -import com.few.api.domain.subscription.usecase.dto.UnsubscribeWorkbookUseCaseIn import com.few.api.domain.subscription.repo.SubscriptionDao +import com.few.api.domain.subscription.usecase.dto.UnsubscribeWorkbookUseCaseIn import io.github.oshai.kotlinlogging.KotlinLogging import io.kotest.core.spec.style.BehaviorSpec import io.mockk.* -class UnsubscribeWorkbookUseCaseTest : BehaviorSpec({ - val log = KotlinLogging.logger {} +class UnsubscribeWorkbookUseCaseTest : + BehaviorSpec({ + val log = KotlinLogging.logger {} - lateinit var subscriptionDao: SubscriptionDao - lateinit var useCase: UnsubscribeWorkbookUseCase + lateinit var subscriptionDao: SubscriptionDao + lateinit var useCase: UnsubscribeWorkbookUseCase - beforeContainer { - subscriptionDao = mockk() - useCase = UnsubscribeWorkbookUseCase(subscriptionDao) - } + beforeContainer { + subscriptionDao = mockk() + useCase = UnsubscribeWorkbookUseCase(subscriptionDao) + } - given("구독 취소 의견이 포함된 특정 워크북 구독 취소 요청이 온 상황에서") { - val memberId = 1L - val workbookId = 1L - val opinion = "취소합니다." - val useCaseIn = UnsubscribeWorkbookUseCaseIn(memberId = memberId, workbookId = workbookId, opinion = opinion) + given("구독 취소 의견이 포함된 특정 워크북 구독 취소 요청이 온 상황에서") { + val memberId = 1L + val workbookId = 1L + val opinion = "취소합니다." + val useCaseIn = UnsubscribeWorkbookUseCaseIn(memberId = memberId, workbookId = workbookId, opinion = opinion) - `when`("멤버의 특정 워크북 구독 히스토리가 있는 경우") { - every { subscriptionDao.updateDeletedAtInWorkbookSubscription(any()) } just Runs + `when`("멤버의 특정 워크북 구독 히스토리가 있는 경우") { + every { subscriptionDao.updateDeletedAtInWorkbookSubscription(any()) } just Runs - then("의견을 저장하고 구독 전체를 취소한다") { - useCase.execute(useCaseIn) + then("의견을 저장하고 구독 전체를 취소한다") { + useCase.execute(useCaseIn) - verify(exactly = 1) { subscriptionDao.updateDeletedAtInWorkbookSubscription(any()) } + verify(exactly = 1) { subscriptionDao.updateDeletedAtInWorkbookSubscription(any()) } + } } } - } - given("구독 취소 의견이 포함되지 않은 특정 워크북 구독 취소 요청이 온 상황에서") { - val memberId = 1L - val workbookId = 1L - val opinion = "" - val useCaseIn = UnsubscribeWorkbookUseCaseIn(memberId = memberId, workbookId = workbookId, opinion = opinion) + given("구독 취소 의견이 포함되지 않은 특정 워크북 구독 취소 요청이 온 상황에서") { + val memberId = 1L + val workbookId = 1L + val opinion = "" + val useCaseIn = UnsubscribeWorkbookUseCaseIn(memberId = memberId, workbookId = workbookId, opinion = opinion) - `when`("멤버의 특정 워크북 구독 히스토리가 있는 경우") { - every { subscriptionDao.updateDeletedAtInWorkbookSubscription(any()) } just Runs + `when`("멤버의 특정 워크북 구독 히스토리가 있는 경우") { + every { subscriptionDao.updateDeletedAtInWorkbookSubscription(any()) } just Runs - then("의견을 cancel로 저장하고 특정 위크북에 대한 구독을 취소한다") { - useCase.execute(useCaseIn) + then("의견을 cancel로 저장하고 특정 위크북에 대한 구독을 취소한다") { + useCase.execute(useCaseIn) - verify(exactly = 1) { subscriptionDao.updateDeletedAtInWorkbookSubscription(any()) } + verify(exactly = 1) { subscriptionDao.updateDeletedAtInWorkbookSubscription(any()) } + } } } - } -}) \ No newline at end of file + }) \ 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 index 322b1d3f5..fa0fd18dc 100644 --- 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 @@ -5,18 +5,18 @@ 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 - ) + workbookSubscriptionStatus = + WorkbookSubscriptionStatus( + workbookId = 1, + isActiveSub = true, + day = 1, + ), ) } } @@ -26,7 +26,7 @@ class WorkbookSubscriptionHistoryTest { // given & when & then assertThrows(IllegalArgumentException::class.java) { WorkbookSubscriptionHistory( - isNew = false + isNew = false, ) } } @@ -34,14 +34,16 @@ class WorkbookSubscriptionHistoryTest { @Test fun `새로 생성된 구독이 아니고 구독 상태가 취소된 경우`() { // given - val workbookSubscriptionHistory = WorkbookSubscriptionHistory( - isNew = false, - workbookSubscriptionStatus = WorkbookSubscriptionStatus( - workbookId = 1, - isActiveSub = false, - day = 1 + val workbookSubscriptionHistory = + WorkbookSubscriptionHistory( + isNew = false, + workbookSubscriptionStatus = + WorkbookSubscriptionStatus( + workbookId = 1, + isActiveSub = false, + day = 1, + ), ) - ) // when val isCancel = workbookSubscriptionHistory.isCancelSub @@ -53,14 +55,16 @@ class WorkbookSubscriptionHistoryTest { @Test fun `새로 생성된 구독이 아니고 구독 상태가 취소되지 않은 경우`() { // given - val workbookSubscriptionHistory = WorkbookSubscriptionHistory( - isNew = false, - workbookSubscriptionStatus = WorkbookSubscriptionStatus( - workbookId = 1, - isActiveSub = true, - day = 1 + val workbookSubscriptionHistory = + WorkbookSubscriptionHistory( + isNew = false, + workbookSubscriptionStatus = + WorkbookSubscriptionStatus( + workbookId = 1, + isActiveSub = true, + day = 1, + ), ) - ) // when val isCancel = workbookSubscriptionHistory.isCancelSub @@ -72,9 +76,10 @@ class WorkbookSubscriptionHistoryTest { @Test fun `새로 생성된 구독인 경우 구독 날짜`() { // given - val workbookSubscriptionHistory = WorkbookSubscriptionHistory( - isNew = true - ) + val workbookSubscriptionHistory = + WorkbookSubscriptionHistory( + isNew = true, + ) // when val subDay = workbookSubscriptionHistory.subDay @@ -86,14 +91,16 @@ class WorkbookSubscriptionHistoryTest { @Test fun `새로 생성된 구독이 아닌 경우 구독 날짜`() { // given - val workbookSubscriptionHistory = WorkbookSubscriptionHistory( - isNew = false, - workbookSubscriptionStatus = WorkbookSubscriptionStatus( - workbookId = 1, - isActiveSub = true, - day = 2 + val workbookSubscriptionHistory = + WorkbookSubscriptionHistory( + isNew = false, + workbookSubscriptionStatus = + WorkbookSubscriptionStatus( + workbookId = 1, + isActiveSub = true, + day = 2, + ), ) - ) // when val subDay = workbookSubscriptionHistory.subDay @@ -104,18 +111,19 @@ class WorkbookSubscriptionHistoryTest { @Nested inner class CancelledWorkbookSubscriptionHistoryTest { - @Test fun `구독 취소된 히스토리가 취소되지 않은 구독 히스토리로 생성되는 경우`() { // given - val workbookSubscriptionHistory = WorkbookSubscriptionHistory( - isNew = false, - workbookSubscriptionStatus = WorkbookSubscriptionStatus( - workbookId = 1, - isActiveSub = true, - day = 1 + val workbookSubscriptionHistory = + WorkbookSubscriptionHistory( + isNew = false, + workbookSubscriptionStatus = + WorkbookSubscriptionStatus( + workbookId = 1, + isActiveSub = true, + day = 1, + ), ) - ) // when & then assertThrows(IllegalArgumentException::class.java) { @@ -126,9 +134,10 @@ class WorkbookSubscriptionHistoryTest { @Test fun `구독 취소된 히스토리가 새로운 구독 히스토리로 생성되는 경우`() { // given - val workbookSubscriptionHistory = WorkbookSubscriptionHistory( - isNew = true - ) + val workbookSubscriptionHistory = + WorkbookSubscriptionHistory( + isNew = true, + ) // when & then assertThrows(IllegalArgumentException::class.java) { @@ -139,14 +148,16 @@ class WorkbookSubscriptionHistoryTest { @Test fun `구독 취소된 히스토리가 종료된 경우`() { // given - val workbookSubscriptionHistory = WorkbookSubscriptionHistory( - isNew = false, - workbookSubscriptionStatus = WorkbookSubscriptionStatus( - workbookId = 1, - isActiveSub = false, - day = 1 + val workbookSubscriptionHistory = + WorkbookSubscriptionHistory( + isNew = false, + workbookSubscriptionStatus = + WorkbookSubscriptionStatus( + workbookId = 1, + isActiveSub = false, + day = 1, + ), ) - ) // when val cancelledWorkbookSubscriptionHistory = CancelledWorkbookSubscriptionHistory(workbookSubscriptionHistory) @@ -159,14 +170,16 @@ class WorkbookSubscriptionHistoryTest { @Test fun `구독 취소된 히스토리가 종료되지 않은 경우`() { // given - val workbookSubscriptionHistory = WorkbookSubscriptionHistory( - isNew = false, - workbookSubscriptionStatus = WorkbookSubscriptionStatus( - workbookId = 1, - isActiveSub = false, - day = 1 + val workbookSubscriptionHistory = + WorkbookSubscriptionHistory( + isNew = false, + workbookSubscriptionStatus = + WorkbookSubscriptionStatus( + workbookId = 1, + isActiveSub = false, + day = 1, + ), ) - ) // when val cancelledWorkbookSubscriptionHistory = CancelledWorkbookSubscriptionHistory(workbookSubscriptionHistory) diff --git a/api/src/test/kotlin/com/few/api/domain/workbook/article/controller/WorkBookArticleApiControllerTest.kt b/api/src/test/kotlin/com/few/api/domain/workbook/article/controller/WorkBookArticleApiControllerTest.kt index 39728a49f..316db6e55 100644 --- a/api/src/test/kotlin/com/few/api/domain/workbook/article/controller/WorkBookArticleApiControllerTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/workbook/article/controller/WorkBookArticleApiControllerTest.kt @@ -4,12 +4,11 @@ import com.epages.restdocs.apispec.ResourceDocumentation import com.epages.restdocs.apispec.ResourceDocumentation.parameterWithName import com.epages.restdocs.apispec.ResourceSnippetParameters import com.epages.restdocs.apispec.Schema -import com.few.api.domain.workbook.article.dto.ReadWorkBookArticleUseCaseIn -import com.few.api.domain.workbook.article.dto.ReadWorkBookArticleOut -import com.few.api.domain.workbook.article.dto.WriterDetail import com.few.api.config.web.controller.ApiControllerTestSpec -import web.description.Description import com.few.api.domain.common.vo.CategoryType +import com.few.api.domain.workbook.article.dto.ReadWorkBookArticleOut +import com.few.api.domain.workbook.article.dto.ReadWorkBookArticleUseCaseIn +import com.few.api.domain.workbook.article.dto.WriterDetail import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.mockito.Mockito.`when` @@ -18,12 +17,12 @@ import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get import org.springframework.security.test.context.support.WithUserDetails import org.springframework.test.web.servlet.result.MockMvcResultMatchers import org.springframework.web.util.UriComponentsBuilder +import web.description.Description import web.helper.* import java.net.URL import java.time.LocalDateTime class WorkBookArticleApiControllerTest : ApiControllerTestSpec() { - companion object { private const val BASE_URL = "/api/v1/workbooks/{workbookId}/articles" private const val TAG = "WorkBookArticleController" @@ -36,10 +35,12 @@ class WorkBookArticleApiControllerTest : ApiControllerTestSpec() { // given val api = "ReadWorkBookArticle" val token = "thisisaccesstoken" - val uri = UriComponentsBuilder.newInstance() - .path("$BASE_URL/{articleId}") - .build() - .toUriString() + val uri = + UriComponentsBuilder + .newInstance() + .path("$BASE_URL/{articleId}") + .build() + .toUriString() val memberId = 1L `when`(tokenResolver.resolveId(token)).thenReturn(memberId) @@ -48,32 +49,36 @@ class WorkBookArticleApiControllerTest : ApiControllerTestSpec() { val articleId = 1L val useCaseIn = ReadWorkBookArticleUseCaseIn(workbookId, articleId, memberId = memberId) - val useCaseOut = ReadWorkBookArticleOut( - id = 1L, - writer = WriterDetail( + val useCaseOut = + ReadWorkBookArticleOut( id = 1L, - name = "안나포", - url = URL("http://localhost:8080/api/v1/writers/1") - ), - title = "ETF(상장 지수 펀드)란? 모르면 손해라고?", - content = "content", - problemIds = listOf(1L, 2L, 3L), - category = CategoryType.fromCode(0)!!.name, - createdAt = LocalDateTime.now(), - day = 1L - ) + writer = + WriterDetail( + id = 1L, + name = "안나포", + url = URL("http://localhost:8080/api/v1/writers/1"), + ), + title = "ETF(상장 지수 펀드)란? 모르면 손해라고?", + content = "content", + problemIds = listOf(1L, 2L, 3L), + category = CategoryType.fromCode(0)!!.name, + createdAt = LocalDateTime.now(), + day = 1L, + ) `when`(readWorkBookArticleUseCase.execute(useCaseIn)).thenReturn(useCaseOut) // when - mockMvc.perform( - get(uri, workbookId, articleId) - .header("Authorization", "Bearer $token") - ).andExpect(MockMvcResultMatchers.status().is2xxSuccessful) + mockMvc + .perform( + get(uri, workbookId, articleId) + .header("Authorization", "Bearer $token"), + ).andExpect(MockMvcResultMatchers.status().is2xxSuccessful) .andDo( MockMvcRestDocumentation.document( api.toIdentifier(), ResourceDocumentation.resource( - ResourceSnippetParameters.builder() + ResourceSnippetParameters + .builder() .description("학습지 Id와 아티클 Id를 입력하여 아티클 정보를 조회합니다.") .summary(api.toIdentifier()) .privateResource(false) @@ -82,9 +87,8 @@ class WorkBookArticleApiControllerTest : ApiControllerTestSpec() { .requestSchema(Schema.schema(api.toRequestSchema())) .pathParameters( parameterWithName("workbookId").description("학습지 Id"), - parameterWithName("articleId").description("아티클 Id") - ) - .responseSchema(Schema.schema(api.toResponseSchema())) + parameterWithName("articleId").description("아티클 Id"), + ).responseSchema(Schema.schema(api.toResponseSchema())) .responseFields( *Description.fields( FieldDescription("data", "data").asObject(), @@ -98,12 +102,11 @@ class WorkBookArticleApiControllerTest : ApiControllerTestSpec() { FieldDescription("data.problemIds", "아티클 문제 목록").asArray(), FieldDescription("data.category", "아티클 카테고리").asString(), FieldDescription("data.createdAt", "아티클 생성일").asString(), - FieldDescription("data.day", "아티클 Day 정보").asNumber() - ) - ) - .build() - ) - ) + FieldDescription("data.day", "아티클 Day 정보").asNumber(), + ), + ).build(), + ), + ), ) } } \ No newline at end of file diff --git a/api/src/test/kotlin/com/few/api/domain/workbook/controller/WorkBookApiControllerTest.kt b/api/src/test/kotlin/com/few/api/domain/workbook/controller/WorkBookApiControllerTest.kt index 5b5989de2..1adabd53c 100644 --- a/api/src/test/kotlin/com/few/api/domain/workbook/controller/WorkBookApiControllerTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/workbook/controller/WorkBookApiControllerTest.kt @@ -4,26 +4,25 @@ import com.epages.restdocs.apispec.ResourceDocumentation.parameterWithName import com.epages.restdocs.apispec.ResourceDocumentation.resource import com.epages.restdocs.apispec.ResourceSnippetParameters import com.epages.restdocs.apispec.Schema -import com.few.api.domain.workbook.usecase.dto.* import com.few.api.config.web.controller.ApiControllerTestSpec -import web.description.Description import com.few.api.domain.common.vo.CategoryType import com.few.api.domain.common.vo.ViewCategory import com.few.api.domain.common.vo.WorkBookCategory -import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get +import com.few.api.domain.workbook.usecase.dto.* import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.mockito.Mockito.`when` import org.springframework.http.MediaType import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status import org.springframework.web.util.UriComponentsBuilder +import web.description.Description import web.helper.* import java.net.URL import java.time.LocalDateTime class WorkBookApiControllerTest : ApiControllerTestSpec() { - companion object { private const val BASE_URL = "/api/v1/workbooks" private const val TAG = "WorkBookController" @@ -34,37 +33,41 @@ class WorkBookApiControllerTest : ApiControllerTestSpec() { fun browseWorkBookCategories() { // given val api = "BrowseWorkBookCategories" - val uri = UriComponentsBuilder.newInstance() - .path("$BASE_URL/categories") - .build() - .toUriString() + val uri = + UriComponentsBuilder + .newInstance() + .path("$BASE_URL/categories") + .build() + .toUriString() // when - mockMvc.perform( - get(uri) - .contentType(MediaType.APPLICATION_JSON) - ).andExpect( - status().is2xxSuccessful - ).andDo( - document( - api.toIdentifier(), - resource( - ResourceSnippetParameters.builder() - .summary(api.toIdentifier()) - .description("학습지 카테고리 목록을 조회합니다.") - .tag(TAG) - .responseSchema(Schema.schema(api.toResponseSchema())) - .responseFields( - *Description.fields( - FieldDescription("data", "data").asObject(), - FieldDescription("data.categories[]", "카테고리 목록").asArray(), - FieldDescription("data.categories[].code", "카테고리 코드").asNumber(), - FieldDescription("data.categories[].name", "카테고리 이름").asString() - ) - ).build() - ) + mockMvc + .perform( + get(uri) + .contentType(MediaType.APPLICATION_JSON), + ).andExpect( + status().is2xxSuccessful, + ).andDo( + document( + api.toIdentifier(), + resource( + ResourceSnippetParameters + .builder() + .summary(api.toIdentifier()) + .description("학습지 카테고리 목록을 조회합니다.") + .tag(TAG) + .responseSchema(Schema.schema(api.toResponseSchema())) + .responseFields( + *Description.fields( + FieldDescription("data", "data").asObject(), + FieldDescription("data.categories[]", "카테고리 목록").asArray(), + FieldDescription("data.categories[].code", "카테고리 코드").asNumber(), + FieldDescription("data.categories[].name", "카테고리 이름").asString(), + ), + ).build(), + ), + ), ) - ) } /** @@ -77,79 +80,86 @@ class WorkBookApiControllerTest : ApiControllerTestSpec() { val api = "BrowseWorkBooks" val token = "thisisaccesstoken" val viewCategory = ViewCategory.MAIN_CARD - val uri = UriComponentsBuilder.newInstance() - .path(BASE_URL) - .queryParam("category", WorkBookCategory.ECONOMY.code) - .queryParam("view", viewCategory.viewName) - .build() - .toUriString() + val uri = + UriComponentsBuilder + .newInstance() + .path(BASE_URL) + .queryParam("category", WorkBookCategory.ECONOMY.code) + .queryParam("view", viewCategory.viewName) + .build() + .toUriString() val memberId = 1L `when`(tokenResolver.resolveId(token)).thenReturn(memberId) val useCaseIn = BrowseWorkbooksUseCaseIn(WorkBookCategory.ECONOMY, viewCategory, memberId) - val useCaseOut = BrowseWorkbooksUseCaseOut( - workbooks = listOf( - BrowseWorkBookDetail( - id = 1L, - mainImageUrl = URL("http://localhost:8080/api/v1/workbooks/1/image"), - title = "재태크, 투자 필수 용어 모음집", - description = "사회 초년생부터, 직장인, 은퇴자까지 모두가 알아야 할 기본적인 재태크, 투자 필수 용어 모음집 입니다.", - category = CategoryType.fromCode(0)!!.name, - createdAt = LocalDateTime.now(), - writerDetails = listOf( - WriterDetail(1L, "안나포", URL("http://localhost:8080/api/v1/users/1")), - WriterDetail(2L, "퓨퓨", URL("http://localhost:8080/api/v1/users/2")), - WriterDetail(3L, "프레소", URL("http://localhost:8080/api/v1/users/3")) + val useCaseOut = + BrowseWorkbooksUseCaseOut( + workbooks = + listOf( + BrowseWorkBookDetail( + id = 1L, + mainImageUrl = URL("http://localhost:8080/api/v1/workbooks/1/image"), + title = "재태크, 투자 필수 용어 모음집", + description = "사회 초년생부터, 직장인, 은퇴자까지 모두가 알아야 할 기본적인 재태크, 투자 필수 용어 모음집 입니다.", + category = CategoryType.fromCode(0)!!.name, + createdAt = LocalDateTime.now(), + writerDetails = + listOf( + WriterDetail(1L, "안나포", URL("http://localhost:8080/api/v1/users/1")), + WriterDetail(2L, "퓨퓨", URL("http://localhost:8080/api/v1/users/2")), + WriterDetail(3L, "프레소", URL("http://localhost:8080/api/v1/users/3")), + ), + subscriptionCount = 100, + ), ), - subscriptionCount = 100 - ) ) - ) `when`(browseWorkBooksUseCase.execute(useCaseIn)).thenReturn(useCaseOut) // when - mockMvc.perform( - get(uri) - .header("Authorization", "Bearer $token") - .contentType(MediaType.APPLICATION_JSON) - ).andExpect( - status().is2xxSuccessful - ).andDo( - document( - api.toIdentifier(), - resource( - ResourceSnippetParameters.builder() - .summary(api.toIdentifier()) - .description("학습지 목록을 조회합니다.") - .tag(TAG) - .requestSchema(Schema.schema(api.toRequestSchema())) - .queryParameters( - parameterWithName("category").description("학습지 카테고리").optional(), - parameterWithName("view").description("뷰 카테고리").optional() - ) - .requestHeaders(Description.authHeader(true)) - .responseSchema(Schema.schema(api.toResponseSchema())).responseFields( - *Description.fields( - FieldDescription("data", "data").asObject(), - FieldDescription("data.workbooks[]", "워크북 목록").asArray(), - FieldDescription("data.workbooks[].id", "워크북 Id").asNumber(), - FieldDescription("data.workbooks[].mainImageUrl", "워크북 대표 이미지 Url").asString(), - FieldDescription("data.workbooks[].title", "워크북 제목").asString(), - FieldDescription("data.workbooks[].description", "워크북 개요").asString(), - FieldDescription("data.workbooks[].category", "워크북 카테고리").asString(), - FieldDescription("data.workbooks[].createdAt", "워크북 생성일시").asString(), - FieldDescription("data.workbooks[].writers[]", "워크북 작가 목록").asArray(), - FieldDescription("data.workbooks[].writers[].id", "워크북 작가 Id").asNumber(), - FieldDescription("data.workbooks[].writers[].name", "워크북 작가 이름").asString(), - FieldDescription("data.workbooks[].writers[].url", "워크북 작가 외부 링크").asString(), - FieldDescription("data.workbooks[].subscriberCount", "워크북 현재 구독자 수").asNumber() - ) - ).build() - ) + mockMvc + .perform( + get(uri) + .header("Authorization", "Bearer $token") + .contentType(MediaType.APPLICATION_JSON), + ).andExpect( + status().is2xxSuccessful, + ).andDo( + document( + api.toIdentifier(), + resource( + ResourceSnippetParameters + .builder() + .summary(api.toIdentifier()) + .description("학습지 목록을 조회합니다.") + .tag(TAG) + .requestSchema(Schema.schema(api.toRequestSchema())) + .queryParameters( + parameterWithName("category").description("학습지 카테고리").optional(), + parameterWithName("view").description("뷰 카테고리").optional(), + ).requestHeaders(Description.authHeader(true)) + .responseSchema(Schema.schema(api.toResponseSchema())) + .responseFields( + *Description.fields( + FieldDescription("data", "data").asObject(), + FieldDescription("data.workbooks[]", "워크북 목록").asArray(), + FieldDescription("data.workbooks[].id", "워크북 Id").asNumber(), + FieldDescription("data.workbooks[].mainImageUrl", "워크북 대표 이미지 Url").asString(), + FieldDescription("data.workbooks[].title", "워크북 제목").asString(), + FieldDescription("data.workbooks[].description", "워크북 개요").asString(), + FieldDescription("data.workbooks[].category", "워크북 카테고리").asString(), + FieldDescription("data.workbooks[].createdAt", "워크북 생성일시").asString(), + FieldDescription("data.workbooks[].writers[]", "워크북 작가 목록").asArray(), + FieldDescription("data.workbooks[].writers[].id", "워크북 작가 Id").asNumber(), + FieldDescription("data.workbooks[].writers[].name", "워크북 작가 이름").asString(), + FieldDescription("data.workbooks[].writers[].url", "워크북 작가 외부 링크").asString(), + FieldDescription("data.workbooks[].subscriberCount", "워크북 현재 구독자 수").asNumber(), + ), + ).build(), + ), + ), ) - ) } @Test @@ -157,45 +167,52 @@ class WorkBookApiControllerTest : ApiControllerTestSpec() { fun readWorkBook() { // given val api = "ReadWorkBook" - val uri = UriComponentsBuilder.newInstance() - .path("$BASE_URL/{workbookId}") - .build() - .toUriString() + val uri = + UriComponentsBuilder + .newInstance() + .path("$BASE_URL/{workbookId}") + .build() + .toUriString() val workbookId = 1L val useCaseIn = ReadWorkbookUseCaseIn(workbookId) - val useCaseOut = ReadWorkbookUseCaseOut( - id = 1L, - mainImageUrl = URL("http://localhost:8080/api/v1/workbooks/1/image"), - title = "재태크, 투자 필수 용어 모음집", - description = "사회 초년생부터, 직장인, 은퇴자까지 모두가 알아야 할 기본적인 재태크, 투자 필수 용어 모음집 입니다.", - category = CategoryType.fromCode(0)!!.name, - createdAt = LocalDateTime.now(), - writers = listOf( - WriterDetail(1L, "안나포", URL("http://localhost:8080/api/v1/users/1")), - WriterDetail(2L, "퓨퓨", URL("http://localhost:8080/api/v1/users/2")), - WriterDetail(3L, "프레소", URL("http://localhost:8080/api/v1/users/3")) - ), - articles = listOf( - ArticleDetail(1L, "ISA(개인종합자산관리계좌)란?"), - ArticleDetail( - 2L, - "ISA(개인종합자산관리계좌)란? ISA(개인종합자산관리계좌)란? ISA(개인종합자산관리계좌)란? ISA(개인종합자산관리계좌)란? ISA(개인종합자산관리계좌)란? ISA(개인종합자산관리계좌)란?" - ) + val useCaseOut = + ReadWorkbookUseCaseOut( + id = 1L, + mainImageUrl = URL("http://localhost:8080/api/v1/workbooks/1/image"), + title = "재태크, 투자 필수 용어 모음집", + description = "사회 초년생부터, 직장인, 은퇴자까지 모두가 알아야 할 기본적인 재태크, 투자 필수 용어 모음집 입니다.", + category = CategoryType.fromCode(0)!!.name, + createdAt = LocalDateTime.now(), + writers = + listOf( + WriterDetail(1L, "안나포", URL("http://localhost:8080/api/v1/users/1")), + WriterDetail(2L, "퓨퓨", URL("http://localhost:8080/api/v1/users/2")), + WriterDetail(3L, "프레소", URL("http://localhost:8080/api/v1/users/3")), + ), + articles = + listOf( + ArticleDetail(1L, "ISA(개인종합자산관리계좌)란?"), + ArticleDetail( + 2L, + "ISA(개인종합자산관리계좌)란? ISA(개인종합자산관리계좌)란? ISA(개인종합자산관리계좌)란? ISA(개인종합자산관리계좌)란? ISA(개인종합자산관리계좌)란? ISA(개인종합자산관리계좌)란?", + ), + ), ) - ) `when`(readWorkbookUseCase.execute(useCaseIn)).thenReturn(useCaseOut) // when - mockMvc.perform( - get(uri, workbookId) - .contentType(MediaType.APPLICATION_JSON) - ).andExpect(status().is2xxSuccessful) + mockMvc + .perform( + get(uri, workbookId) + .contentType(MediaType.APPLICATION_JSON), + ).andExpect(status().is2xxSuccessful) .andDo( document( api.toIdentifier(), resource( - ResourceSnippetParameters.builder() + ResourceSnippetParameters + .builder() .description("학습지 Id를 입력하여 학습지 정보를 조회합니다.") .summary(api.toIdentifier()) .privateResource(false) @@ -219,12 +236,11 @@ class WorkBookApiControllerTest : ApiControllerTestSpec() { FieldDescription("data.writers[].url", "학습지 작가 링크").asString(), FieldDescription("data.articles[]", "학습지에 포함된 아티클 목록").asArray(), FieldDescription("data.articles[].id", "학습지에 포함된 아티클 Id").asNumber(), - FieldDescription("data.articles[].title", "학습지에 포함된 아티클 제목").asString() - ) - ) - .build() - ) - ) + FieldDescription("data.articles[].title", "학습지에 포함된 아티클 제목").asString(), + ), + ).build(), + ), + ), ) } } \ No newline at end of file diff --git a/api/src/test/kotlin/com/few/api/domain/workbook/usecase/BrowseWorkbooksUseCaseTest.kt b/api/src/test/kotlin/com/few/api/domain/workbook/usecase/BrowseWorkbooksUseCaseTest.kt index 45a8f499f..e0ccbde33 100644 --- a/api/src/test/kotlin/com/few/api/domain/workbook/usecase/BrowseWorkbooksUseCaseTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/workbook/usecase/BrowseWorkbooksUseCaseTest.kt @@ -1,229 +1,246 @@ package com.few.api.domain.workbook.usecase import com.few.api.domain.common.vo.CategoryType +import com.few.api.domain.common.vo.ViewCategory +import com.few.api.domain.common.vo.WorkBookCategory +import com.few.api.domain.workbook.repo.WorkbookDao +import com.few.api.domain.workbook.repo.record.SelectWorkBookRecordWithSubscriptionCount import com.few.api.domain.workbook.service.WorkbookMemberService import com.few.api.domain.workbook.service.WorkbookSubscribeService import com.few.api.domain.workbook.service.dto.BrowseMemberSubscribeWorkbooksOutDto import com.few.api.domain.workbook.service.dto.WriterMappedWorkbookOutDto import com.few.api.domain.workbook.usecase.dto.BrowseWorkbooksUseCaseIn -import com.few.api.domain.workbook.repo.WorkbookDao -import com.few.api.domain.workbook.repo.record.SelectWorkBookRecordWithSubscriptionCount -import com.few.api.domain.common.vo.ViewCategory -import com.few.api.domain.common.vo.WorkBookCategory import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe import io.mockk.every import io.mockk.mockk import io.mockk.verify - import java.net.URL import java.time.LocalDateTime import java.util.stream.IntStream -class BrowseWorkbooksUseCaseTest : BehaviorSpec({ - lateinit var workbookDao: WorkbookDao - lateinit var workbookMemberService: WorkbookMemberService - lateinit var workbookSubscribeService: WorkbookSubscribeService - lateinit var useCase: BrowseWorkbooksUseCase - - beforeContainer { - workbookDao = mockk() - workbookMemberService = mockk() - workbookSubscribeService = mockk() - useCase = - BrowseWorkbooksUseCase(workbookDao, workbookMemberService, workbookSubscribeService) - } - - given("메인 뷰에서 로그인 된 상태로 카테고리를 지정하여 다수 워크북 조회 요청이 온 상황에서") { - val memberId = 1L - val useCaseIn = BrowseWorkbooksUseCaseIn(category = WorkBookCategory.ECONOMY, viewCategory = ViewCategory.MAIN_CARD, memberId = memberId) - - `when`("특정 카테고리로 지정되어 있을 경우") { - val workbookCount = 2 - every { workbookDao.browseWorkBookWithSubscriptionCount(any()) } returns IntStream.range(1, 1 + workbookCount).mapToObj { - SelectWorkBookRecordWithSubscriptionCount( - id = it.toLong(), - title = "workbook title$it", - mainImageUrl = URL("http://localhost:8080/image/main/$it"), - category = CategoryType.ECONOMY.code, - description = "workbook$it description", - createdAt = LocalDateTime.now(), - subscriptionCount = it.toLong() - ) - }.toList() - - every { workbookMemberService.browseWorkbookWriterRecords(any()) } returns mapOf( - 1L to listOf( - WriterMappedWorkbookOutDto( - writerId = 1L, - name = "writer1", - url = URL("http://localhost:8080/image/writer/1"), - workbookId = 1L +class BrowseWorkbooksUseCaseTest : + BehaviorSpec({ + lateinit var workbookDao: WorkbookDao + lateinit var workbookMemberService: WorkbookMemberService + lateinit var workbookSubscribeService: WorkbookSubscribeService + lateinit var useCase: BrowseWorkbooksUseCase + + beforeContainer { + workbookDao = mockk() + workbookMemberService = mockk() + workbookSubscribeService = mockk() + useCase = + BrowseWorkbooksUseCase(workbookDao, workbookMemberService, workbookSubscribeService) + } + + given("메인 뷰에서 로그인 된 상태로 카테고리를 지정하여 다수 워크북 조회 요청이 온 상황에서") { + val memberId = 1L + val useCaseIn = + BrowseWorkbooksUseCaseIn(category = WorkBookCategory.ECONOMY, viewCategory = ViewCategory.MAIN_CARD, memberId = memberId) + + `when`("특정 카테고리로 지정되어 있을 경우") { + val workbookCount = 2 + every { workbookDao.browseWorkBookWithSubscriptionCount(any()) } returns + IntStream + .range(1, 1 + workbookCount) + .mapToObj { + SelectWorkBookRecordWithSubscriptionCount( + id = it.toLong(), + title = "workbook title$it", + mainImageUrl = URL("http://localhost:8080/image/main/$it"), + category = CategoryType.ECONOMY.code, + description = "workbook$it description", + createdAt = LocalDateTime.now(), + subscriptionCount = it.toLong(), + ) + }.toList() + + every { workbookMemberService.browseWorkbookWriterRecords(any()) } returns + mapOf( + 1L to + listOf( + WriterMappedWorkbookOutDto( + writerId = 1L, + name = "writer1", + url = URL("http://localhost:8080/image/writer/1"), + workbookId = 1L, + ), + ), + 2L to + listOf( + WriterMappedWorkbookOutDto( + writerId = 2L, + name = "writer2", + url = URL("http://localhost:8080/image/writer/2"), + workbookId = 2L, + ), + ), ) - ), - 2L to listOf( - WriterMappedWorkbookOutDto( - writerId = 2L, - name = "writer2", - url = URL("http://localhost:8080/image/writer/2"), - workbookId = 2L + + every { + workbookSubscribeService.browseMemberSubscribeWorkbooks(any()) + } returns + listOf( + BrowseMemberSubscribeWorkbooksOutDto( + workbookId = 1L, + isActiveSub = true, + currentDay = 1, + ), + BrowseMemberSubscribeWorkbooksOutDto( + workbookId = 2L, + isActiveSub = false, + currentDay = 1, + ), ) - ) - ) - - every { - workbookSubscribeService.browseMemberSubscribeWorkbooks(any()) - } returns listOf( - BrowseMemberSubscribeWorkbooksOutDto( - workbookId = 1L, - isActiveSub = true, - currentDay = 1 - ), - BrowseMemberSubscribeWorkbooksOutDto( - workbookId = 2L, - isActiveSub = false, - currentDay = 1 - ) - ) - - then("경제 카테고리의 워크북이 조회된다") { - val useCaseOut = useCase.execute(useCaseIn) - useCaseOut.workbooks.size shouldBe workbookCount - - useCaseOut.workbooks.forEachIndexed { index, browseWorkBookDetail -> - browseWorkBookDetail.id shouldBe (index + 1).toLong() - browseWorkBookDetail.title shouldBe "workbook title${index + 1}" - browseWorkBookDetail.mainImageUrl shouldBe URL("http://localhost:8080/image/main/${index + 1}") - browseWorkBookDetail.category shouldBe WorkBookCategory.ECONOMY.displayName - browseWorkBookDetail.description shouldBe "workbook${index + 1} description" - browseWorkBookDetail.writerDetails.size shouldBe 1 - browseWorkBookDetail.writerDetails[0].id shouldBe (index + 1).toLong() - browseWorkBookDetail.writerDetails[0].name shouldBe "writer${index + 1}" - browseWorkBookDetail.writerDetails[0].url shouldBe URL("http://localhost:8080/image/writer/${index + 1}") - browseWorkBookDetail.subscriptionCount shouldBe (index + 1).toLong() + + then("경제 카테고리의 워크북이 조회된다") { + val useCaseOut = useCase.execute(useCaseIn) + useCaseOut.workbooks.size shouldBe workbookCount + + useCaseOut.workbooks.forEachIndexed { index, browseWorkBookDetail -> + browseWorkBookDetail.id shouldBe (index + 1).toLong() + browseWorkBookDetail.title shouldBe "workbook title${index + 1}" + browseWorkBookDetail.mainImageUrl shouldBe URL("http://localhost:8080/image/main/${index + 1}") + browseWorkBookDetail.category shouldBe WorkBookCategory.ECONOMY.displayName + browseWorkBookDetail.description shouldBe "workbook${index + 1} description" + browseWorkBookDetail.writerDetails.size shouldBe 1 + browseWorkBookDetail.writerDetails[0].id shouldBe (index + 1).toLong() + browseWorkBookDetail.writerDetails[0].name shouldBe "writer${index + 1}" + browseWorkBookDetail.writerDetails[0].url shouldBe URL("http://localhost:8080/image/writer/${index + 1}") + browseWorkBookDetail.subscriptionCount shouldBe (index + 1).toLong() + } + + verify(exactly = 1) { workbookDao.browseWorkBookWithSubscriptionCount(any()) } + verify(exactly = 1) { workbookMemberService.browseWorkbookWriterRecords(any()) } + verify(exactly = 1) { workbookSubscribeService.browseMemberSubscribeWorkbooks(any()) } } + } + } - verify(exactly = 1) { workbookDao.browseWorkBookWithSubscriptionCount(any()) } - verify(exactly = 1) { workbookMemberService.browseWorkbookWriterRecords(any()) } - verify(exactly = 1) { workbookSubscribeService.browseMemberSubscribeWorkbooks(any()) } + given("메인 뷰에서 로그인 안된 상태로 카테고리를 지정하여 다수 워크북 조회 요청이 온 상황에서") { + val useCaseIn = + BrowseWorkbooksUseCaseIn(category = WorkBookCategory.ECONOMY, viewCategory = ViewCategory.MAIN_CARD, memberId = null) + + `when`("특정 카테고리로 지정되어 있을 경우") { + val workbookCount = 2 + every { workbookDao.browseWorkBookWithSubscriptionCount(any()) } returns + IntStream + .range(1, 1 + workbookCount) + .mapToObj { + SelectWorkBookRecordWithSubscriptionCount( + id = it.toLong(), + title = "workbook title$it", + mainImageUrl = URL("http://localhost:8080/image/main/$it"), + category = CategoryType.ECONOMY.code, + description = "workbook$it description", + createdAt = LocalDateTime.now(), + subscriptionCount = it.toLong(), + ) + }.toList() + + val workbookWriterRecords = HashMap>() + for (i in 1..workbookCount) { + workbookWriterRecords[i.toLong()] = + listOf( + WriterMappedWorkbookOutDto( + writerId = i.toLong(), + name = "writer$i", + url = URL("http://localhost:8080/image/writer/$i"), + workbookId = i.toLong(), + ), + ) + } + every { workbookMemberService.browseWorkbookWriterRecords(any()) } returns workbookWriterRecords + + then("경제 카테고리의 워크북이 조회된다") { + val useCaseOut = useCase.execute(useCaseIn) + useCaseOut.workbooks.size shouldBe workbookCount + + useCaseOut.workbooks.forEachIndexed { index, browseWorkBookDetail -> + browseWorkBookDetail.id shouldBe (index + 1).toLong() + browseWorkBookDetail.title shouldBe "workbook title${index + 1}" + browseWorkBookDetail.mainImageUrl shouldBe URL("http://localhost:8080/image/main/${index + 1}") + browseWorkBookDetail.category shouldBe WorkBookCategory.ECONOMY.displayName + browseWorkBookDetail.description shouldBe "workbook${index + 1} description" + browseWorkBookDetail.writerDetails.size shouldBe 1 + browseWorkBookDetail.writerDetails[0].id shouldBe (index + 1).toLong() + browseWorkBookDetail.writerDetails[0].name shouldBe "writer${index + 1}" + browseWorkBookDetail.writerDetails[0].url shouldBe URL("http://localhost:8080/image/writer/${index + 1}") + browseWorkBookDetail.subscriptionCount shouldBe (index + 1).toLong() + } + + verify(exactly = 1) { workbookDao.browseWorkBookWithSubscriptionCount(any()) } + verify(exactly = 1) { workbookMemberService.browseWorkbookWriterRecords(any()) } + verify(exactly = 0) { workbookSubscribeService.browseMemberSubscribeWorkbooks(any()) } + } } } - } - given("메인 뷰에서 로그인 안된 상태로 카테고리를 지정하여 다수 워크북 조회 요청이 온 상황에서") { - val useCaseIn = BrowseWorkbooksUseCaseIn(category = WorkBookCategory.ECONOMY, viewCategory = ViewCategory.MAIN_CARD, memberId = null) + given("메인 뷰가 아닌 상태로 카테고리를 지정하여 다수 워크북 조회 요청이 온 상황에서") { + val useCaseIn = BrowseWorkbooksUseCaseIn(category = WorkBookCategory.ECONOMY, viewCategory = null, memberId = null) - `when`("특정 카테고리로 지정되어 있을 경우") { val workbookCount = 2 - every { workbookDao.browseWorkBookWithSubscriptionCount(any()) } returns - IntStream.range(1, 1 + workbookCount).mapToObj { - SelectWorkBookRecordWithSubscriptionCount( - id = it.toLong(), - title = "workbook title$it", - mainImageUrl = URL("http://localhost:8080/image/main/$it"), - category = CategoryType.ECONOMY.code, - description = "workbook$it description", - createdAt = LocalDateTime.now(), - subscriptionCount = it.toLong() - ) - }.toList() - - val workbookWriterRecords = HashMap>() - for (i in 1..workbookCount) { - workbookWriterRecords[i.toLong()] = listOf( - WriterMappedWorkbookOutDto( - writerId = i.toLong(), - name = "writer$i", - url = URL("http://localhost:8080/image/writer/$i"), - workbookId = i.toLong() + `when`("특정 카테고리로 지정되어 있을 경우") { + every { workbookDao.browseWorkBookWithSubscriptionCount(any()) } returns + IntStream + .range( + 1, + 1 + workbookCount, + ).mapToObj { + SelectWorkBookRecordWithSubscriptionCount( + id = it.toLong(), + title = "workbook title$it", + mainImageUrl = URL("http://localhost:8080/image/main/$it"), + category = CategoryType.ECONOMY.code, + description = "workbook$it description", + createdAt = LocalDateTime.now(), + subscriptionCount = it.toLong(), + ) + }.toList() + + every { workbookMemberService.browseWorkbookWriterRecords(any()) } returns + mapOf( + 1L to + listOf( + WriterMappedWorkbookOutDto( + writerId = 1L, + name = "writer1", + url = URL("http://localhost:8080/image/writer/1"), + workbookId = 1L, + ), + ), + 2L to + listOf( + WriterMappedWorkbookOutDto( + writerId = 2L, + name = "writer2", + url = URL("http://localhost:8080/image/writer/2"), + workbookId = 2L, + ), + ), ) - ) - } - every { workbookMemberService.browseWorkbookWriterRecords(any()) } returns workbookWriterRecords - - then("경제 카테고리의 워크북이 조회된다") { - val useCaseOut = useCase.execute(useCaseIn) - useCaseOut.workbooks.size shouldBe workbookCount - - useCaseOut.workbooks.forEachIndexed { index, browseWorkBookDetail -> - browseWorkBookDetail.id shouldBe (index + 1).toLong() - browseWorkBookDetail.title shouldBe "workbook title${index + 1}" - browseWorkBookDetail.mainImageUrl shouldBe URL("http://localhost:8080/image/main/${index + 1}") - browseWorkBookDetail.category shouldBe WorkBookCategory.ECONOMY.displayName - browseWorkBookDetail.description shouldBe "workbook${index + 1} description" - browseWorkBookDetail.writerDetails.size shouldBe 1 - browseWorkBookDetail.writerDetails[0].id shouldBe (index + 1).toLong() - browseWorkBookDetail.writerDetails[0].name shouldBe "writer${index + 1}" - browseWorkBookDetail.writerDetails[0].url shouldBe URL("http://localhost:8080/image/writer/${index + 1}") - browseWorkBookDetail.subscriptionCount shouldBe (index + 1).toLong() - } - verify(exactly = 1) { workbookDao.browseWorkBookWithSubscriptionCount(any()) } - verify(exactly = 1) { workbookMemberService.browseWorkbookWriterRecords(any()) } - verify(exactly = 0) { workbookSubscribeService.browseMemberSubscribeWorkbooks(any()) } - } - } - } - - given("메인 뷰가 아닌 상태로 카테고리를 지정하여 다수 워크북 조회 요청이 온 상황에서") { - val useCaseIn = BrowseWorkbooksUseCaseIn(category = WorkBookCategory.ECONOMY, viewCategory = null, memberId = null) - - val workbookCount = 2 - `when`("특정 카테고리로 지정되어 있을 경우") { - every { workbookDao.browseWorkBookWithSubscriptionCount(any()) } returns IntStream.range( - 1, - 1 + workbookCount - ).mapToObj { - SelectWorkBookRecordWithSubscriptionCount( - id = it.toLong(), - title = "workbook title$it", - mainImageUrl = URL("http://localhost:8080/image/main/$it"), - category = CategoryType.ECONOMY.code, - description = "workbook$it description", - createdAt = LocalDateTime.now(), - subscriptionCount = it.toLong() - ) - }.toList() - - every { workbookMemberService.browseWorkbookWriterRecords(any()) } returns mapOf( - 1L to listOf( - WriterMappedWorkbookOutDto( - writerId = 1L, - name = "writer1", - url = URL("http://localhost:8080/image/writer/1"), - workbookId = 1L - ) - ), - 2L to listOf( - WriterMappedWorkbookOutDto( - writerId = 2L, - name = "writer2", - url = URL("http://localhost:8080/image/writer/2"), - workbookId = 2L - ) - ) - ) - - then("경제 카테고리의 워크북이 조회된다") { - val useCaseOut = useCase.execute(useCaseIn) - useCaseOut.workbooks.size shouldBe workbookCount - useCaseOut.workbooks.forEachIndexed { index, browseWorkBookDetail -> - browseWorkBookDetail.id shouldBe (index + 1).toLong() - browseWorkBookDetail.title shouldBe "workbook title${index + 1}" - browseWorkBookDetail.mainImageUrl shouldBe URL("http://localhost:8080/image/main/${index + 1}") - browseWorkBookDetail.category shouldBe WorkBookCategory.ECONOMY.displayName - browseWorkBookDetail.description shouldBe "workbook${index + 1} description" - browseWorkBookDetail.writerDetails.size shouldBe 1 - browseWorkBookDetail.writerDetails[0].id shouldBe (index + 1).toLong() - browseWorkBookDetail.writerDetails[0].name shouldBe "writer${index + 1}" - browseWorkBookDetail.writerDetails[0].url shouldBe URL("http://localhost:8080/image/writer/${index + 1}") - browseWorkBookDetail.subscriptionCount shouldBe (index + 1).toLong() + then("경제 카테고리의 워크북이 조회된다") { + val useCaseOut = useCase.execute(useCaseIn) + useCaseOut.workbooks.size shouldBe workbookCount + useCaseOut.workbooks.forEachIndexed { index, browseWorkBookDetail -> + browseWorkBookDetail.id shouldBe (index + 1).toLong() + browseWorkBookDetail.title shouldBe "workbook title${index + 1}" + browseWorkBookDetail.mainImageUrl shouldBe URL("http://localhost:8080/image/main/${index + 1}") + browseWorkBookDetail.category shouldBe WorkBookCategory.ECONOMY.displayName + browseWorkBookDetail.description shouldBe "workbook${index + 1} description" + browseWorkBookDetail.writerDetails.size shouldBe 1 + browseWorkBookDetail.writerDetails[0].id shouldBe (index + 1).toLong() + browseWorkBookDetail.writerDetails[0].name shouldBe "writer${index + 1}" + browseWorkBookDetail.writerDetails[0].url shouldBe URL("http://localhost:8080/image/writer/${index + 1}") + browseWorkBookDetail.subscriptionCount shouldBe (index + 1).toLong() + } + + verify(exactly = 1) { workbookDao.browseWorkBookWithSubscriptionCount(any()) } + verify(exactly = 1) { workbookMemberService.browseWorkbookWriterRecords(any()) } + verify(exactly = 0) { workbookSubscribeService.browseMemberSubscribeWorkbooks(any()) } } - - verify(exactly = 1) { workbookDao.browseWorkBookWithSubscriptionCount(any()) } - verify(exactly = 1) { workbookMemberService.browseWorkbookWriterRecords(any()) } - verify(exactly = 0) { workbookSubscribeService.browseMemberSubscribeWorkbooks(any()) } } } - } -}) \ No newline at end of file + }) \ No newline at end of file diff --git a/api/src/test/kotlin/com/few/api/domain/workbook/usecase/ReadWorkbookUseCaseTest.kt b/api/src/test/kotlin/com/few/api/domain/workbook/usecase/ReadWorkbookUseCaseTest.kt index 0cc4ec2fd..04ff5da3d 100644 --- a/api/src/test/kotlin/com/few/api/domain/workbook/usecase/ReadWorkbookUseCaseTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/workbook/usecase/ReadWorkbookUseCaseTest.kt @@ -1,108 +1,111 @@ package com.few.api.domain.workbook.usecase import com.few.api.domain.common.vo.CategoryType +import com.few.api.domain.workbook.repo.WorkbookDao +import com.few.api.domain.workbook.repo.record.SelectWorkBookRecord import com.few.api.domain.workbook.service.WorkbookArticleService import com.few.api.domain.workbook.service.WorkbookMemberService import com.few.api.domain.workbook.service.dto.WorkBookArticleOutDto import com.few.api.domain.workbook.service.dto.WriterOutDto import com.few.api.domain.workbook.usecase.dto.ReadWorkbookUseCaseIn -import com.few.api.domain.workbook.repo.WorkbookDao -import com.few.api.domain.workbook.repo.record.SelectWorkBookRecord import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe import io.mockk.every import io.mockk.mockk import io.mockk.verify - import java.net.URL import java.time.LocalDateTime -class ReadWorkbookUseCaseTest : BehaviorSpec({ - lateinit var workbookDao: WorkbookDao - lateinit var workbookArticleService: WorkbookArticleService - lateinit var workbookMemberService: WorkbookMemberService - lateinit var useCase: ReadWorkbookUseCase +class ReadWorkbookUseCaseTest : + BehaviorSpec({ + lateinit var workbookDao: WorkbookDao + lateinit var workbookArticleService: WorkbookArticleService + lateinit var workbookMemberService: WorkbookMemberService + lateinit var useCase: ReadWorkbookUseCase - beforeContainer { - workbookDao = mockk() - workbookArticleService = mockk() - workbookMemberService = mockk() - useCase = ReadWorkbookUseCase(workbookDao, workbookArticleService, workbookMemberService) - } - - given("특정 워크북 조회 요청이 온 상황에서") { - val workbookId = 1L - val useCaseIn = ReadWorkbookUseCaseIn(workbookId = workbookId) + beforeContainer { + workbookDao = mockk() + workbookArticleService = mockk() + workbookMemberService = mockk() + useCase = ReadWorkbookUseCase(workbookDao, workbookArticleService, workbookMemberService) + } - `when`("워크북이 존재할 경우") { + given("특정 워크북 조회 요청이 온 상황에서") { val workbookId = 1L - val title = "workbook title" - val workBookMainImageUrl = URL("http://localhost:8080/image/main/1") - val category = CategoryType.ECONOMY.code - val workbookDescription = "workbook description" - every { workbookDao.selectWorkBook(any()) } returns SelectWorkBookRecord( - id = workbookId, - title = title, - mainImageUrl = workBookMainImageUrl, - category = category, - description = workbookDescription, - createdAt = LocalDateTime.now() - ) + val useCaseIn = ReadWorkbookUseCaseIn(workbookId = workbookId) - val articleId = 1L - val articleWriterId = 1L - val articleMainImageUrl = URL("http://localhost:8080/image/main/1") - val articleTitle = "article title" - val articleContent = "article content" - every { workbookArticleService.browseWorkbookArticles(any()) } returns listOf( - WorkBookArticleOutDto( - articleId = articleId, - writerId = articleWriterId, - mainImageURL = articleMainImageUrl, - title = articleTitle, - category = category, - content = articleContent, - createdAt = LocalDateTime.now() - ) - ) + `when`("워크북이 존재할 경우") { + val workbookId = 1L + val title = "workbook title" + val workBookMainImageUrl = URL("http://localhost:8080/image/main/1") + val category = CategoryType.ECONOMY.code + val workbookDescription = "workbook description" + every { workbookDao.selectWorkBook(any()) } returns + SelectWorkBookRecord( + id = workbookId, + title = title, + mainImageUrl = workBookMainImageUrl, + category = category, + description = workbookDescription, + createdAt = LocalDateTime.now(), + ) - val writerName = "writer" - val writerUrl = URL("http://localhost:8080/writer/1") - every { workbookMemberService.browseWriterRecords(any()) } returns listOf( - WriterOutDto( - writerId = articleWriterId, - name = writerName, - url = writerUrl - ) - ) + val articleId = 1L + val articleWriterId = 1L + val articleMainImageUrl = URL("http://localhost:8080/image/main/1") + val articleTitle = "article title" + val articleContent = "article content" + every { workbookArticleService.browseWorkbookArticles(any()) } returns + listOf( + WorkBookArticleOutDto( + articleId = articleId, + writerId = articleWriterId, + mainImageURL = articleMainImageUrl, + title = articleTitle, + category = category, + content = articleContent, + createdAt = LocalDateTime.now(), + ), + ) - then("워크북과 관련된 정보를 반환한다") { - val useCaseOut = useCase.execute(useCaseIn) - useCaseOut.id shouldBe workbookId - useCaseOut.title shouldBe title - useCaseOut.mainImageUrl shouldBe workBookMainImageUrl - useCaseOut.category shouldBe CategoryType.convertToDisplayName(category) - useCaseOut.description shouldBe workbookDescription - useCaseOut.writers.size shouldBe 1 - useCaseOut.articles.size shouldBe 1 + val writerName = "writer" + val writerUrl = URL("http://localhost:8080/writer/1") + every { workbookMemberService.browseWriterRecords(any()) } returns + listOf( + WriterOutDto( + writerId = articleWriterId, + name = writerName, + url = writerUrl, + ), + ) - verify(exactly = 1) { workbookDao.selectWorkBook(any()) } - verify(exactly = 1) { workbookArticleService.browseWorkbookArticles(any()) } - verify(exactly = 1) { workbookMemberService.browseWriterRecords(any()) } + then("워크북과 관련된 정보를 반환한다") { + val useCaseOut = useCase.execute(useCaseIn) + useCaseOut.id shouldBe workbookId + useCaseOut.title shouldBe title + useCaseOut.mainImageUrl shouldBe workBookMainImageUrl + useCaseOut.category shouldBe CategoryType.convertToDisplayName(category) + useCaseOut.description shouldBe workbookDescription + useCaseOut.writers.size shouldBe 1 + useCaseOut.articles.size shouldBe 1 + + verify(exactly = 1) { workbookDao.selectWorkBook(any()) } + verify(exactly = 1) { workbookArticleService.browseWorkbookArticles(any()) } + verify(exactly = 1) { workbookMemberService.browseWriterRecords(any()) } + } } - } - `when`("워크북이 존재하지 않을 경우") { - every { workbookDao.selectWorkBook(any()) } returns null + `when`("워크북이 존재하지 않을 경우") { + every { workbookDao.selectWorkBook(any()) } returns null - then("예외가 발생한다") { - shouldThrow { useCase.execute(useCaseIn) } + then("예외가 발생한다") { + shouldThrow { useCase.execute(useCaseIn) } - verify(exactly = 1) { workbookDao.selectWorkBook(any()) } - verify(exactly = 0) { workbookArticleService.browseWorkbookArticles(any()) } - verify(exactly = 0) { workbookMemberService.browseWriterRecords(any()) } + verify(exactly = 1) { workbookDao.selectWorkBook(any()) } + verify(exactly = 0) { workbookArticleService.browseWorkbookArticles(any()) } + verify(exactly = 0) { workbookMemberService.browseWriterRecords(any()) } + } } } - } -}) \ No newline at end of file + }) \ No newline at end of file diff --git a/api/src/test/kotlin/com/few/api/domain/workbook/usecase/model/order/AuthMainViewWorkbookOrderDelegatorTest.kt b/api/src/test/kotlin/com/few/api/domain/workbook/usecase/model/order/AuthMainViewWorkbookOrderDelegatorTest.kt index 39218116f..1fdfdb7e6 100644 --- a/api/src/test/kotlin/com/few/api/domain/workbook/usecase/model/order/AuthMainViewWorkbookOrderDelegatorTest.kt +++ b/api/src/test/kotlin/com/few/api/domain/workbook/usecase/model/order/AuthMainViewWorkbookOrderDelegatorTest.kt @@ -11,25 +11,25 @@ import java.util.stream.IntStream import kotlin.streams.toList class AuthMainViewWorkbookOrderDelegatorTest { - @Test fun `워크북과 멤버 구독 워크북이 모두 주어지는 경우`() { // given val totalWorkbookCount = 10 - val workbooksOrderBySubscriptionCount = IntStream.range(1, 1 + totalWorkbookCount) - .mapToObj { - WorkBook( - it.toLong(), - URL("http://localhost:8080/$it"), - "title$it", - "description$it", - "category$it", - LocalDateTime.now(), - emptyList(), - (1 + totalWorkbookCount) - it.toLong() - ) - } - .toList() + val workbooksOrderBySubscriptionCount = + IntStream + .range(1, 1 + totalWorkbookCount) + .mapToObj { + WorkBook( + it.toLong(), + URL("http://localhost:8080/$it"), + "title$it", + "description$it", + "category$it", + LocalDateTime.now(), + emptyList(), + (1 + totalWorkbookCount) - it.toLong(), + ) + }.toList() /** * 1 : inactive, current day 5 @@ -41,23 +41,29 @@ class AuthMainViewWorkbookOrderDelegatorTest { val activeWorkbookIds = listOf(2, 4) val inActiveList = listOf(1, 3, 5) val totalMemberSubscribedWorkbookCount = 5 - val memberSubscribedWorkbooksReverseOrderByCurrentDay = IntStream.range(1, 1 + totalMemberSubscribedWorkbookCount) - .mapToObj { - MemberSubscribedWorkbook( - it.toLong(), - it % 2 == 0, - (1 + totalMemberSubscribedWorkbookCount) - it - ) - } - .toList() - - val notSubscribeWorkbookIds = workbooksOrderBySubscriptionCount.filter { !activeWorkbookIds.contains(it.id.toInt()) && !inActiveList.contains(it.id.toInt()) } - .map { it.id.toInt() } + val memberSubscribedWorkbooksReverseOrderByCurrentDay = + IntStream + .range(1, 1 + totalMemberSubscribedWorkbookCount) + .mapToObj { + MemberSubscribedWorkbook( + it.toLong(), + it % 2 == 0, + (1 + totalMemberSubscribedWorkbookCount) - it, + ) + }.toList() + + val notSubscribeWorkbookIds = + workbooksOrderBySubscriptionCount + .filter { + !activeWorkbookIds.contains(it.id.toInt()) && + !inActiveList.contains(it.id.toInt()) + }.map { it.id.toInt() } // when - val delegator = AuthMainViewWorkbookOrderDelegator( - memberSubscribedWorkbooksReverseOrderByCurrentDay - ) + val delegator = + AuthMainViewWorkbookOrderDelegator( + memberSubscribedWorkbooksReverseOrderByCurrentDay, + ) // then val orderedWorkbooks = delegator.order(OrderTargetWorkBooks(WorkBooks(workbooksOrderBySubscriptionCount))) @@ -73,20 +79,21 @@ class AuthMainViewWorkbookOrderDelegatorTest { fun `워크북과 멤버 구독 워크북만 주어지는 경우`() { // given val totalWorkbookCount = 10 - val workbooksOrderBySubscriptionCount = IntStream.range(1, 1 + totalWorkbookCount) - .mapToObj { - WorkBook( - it.toLong(), - URL("http://localhost:8080/$it"), - "title$it", - "description$it", - "category$it", - LocalDateTime.now(), - emptyList(), - (1 + totalWorkbookCount) - it.toLong() - ) - } - .toList() + val workbooksOrderBySubscriptionCount = + IntStream + .range(1, 1 + totalWorkbookCount) + .mapToObj { + WorkBook( + it.toLong(), + URL("http://localhost:8080/$it"), + "title$it", + "description$it", + "category$it", + LocalDateTime.now(), + emptyList(), + (1 + totalWorkbookCount) - it.toLong(), + ) + }.toList() /** * 1 : active, current day 5 @@ -97,26 +104,30 @@ class AuthMainViewWorkbookOrderDelegatorTest { */ val totalMemberSubscribedWorkbookCount = 5 val activeWorkbookIds = IntStream.range(1, 1 + totalMemberSubscribedWorkbookCount).toList() - val memberSubscribedWorkbooksReverseOrderByCurrentDay = IntStream.range(1, 1 + totalMemberSubscribedWorkbookCount) - .mapToObj { - MemberSubscribedWorkbook( - it.toLong(), - true, - (1 + totalMemberSubscribedWorkbookCount) - it - ) - } - .toList() - - val notSubscribeWorkbookIds = workbooksOrderBySubscriptionCount.filter { - !activeWorkbookIds.contains( - it.id.toInt() - ) - }.map { it.id.toInt() } + val memberSubscribedWorkbooksReverseOrderByCurrentDay = + IntStream + .range(1, 1 + totalMemberSubscribedWorkbookCount) + .mapToObj { + MemberSubscribedWorkbook( + it.toLong(), + true, + (1 + totalMemberSubscribedWorkbookCount) - it, + ) + }.toList() + + val notSubscribeWorkbookIds = + workbooksOrderBySubscriptionCount + .filter { + !activeWorkbookIds.contains( + it.id.toInt(), + ) + }.map { it.id.toInt() } // when - val delegator = AuthMainViewWorkbookOrderDelegator( - memberSubscribedWorkbooksReverseOrderByCurrentDay - ) + val delegator = + AuthMainViewWorkbookOrderDelegator( + memberSubscribedWorkbooksReverseOrderByCurrentDay, + ) // then val orderedWorkbooks = delegator.order(OrderTargetWorkBooks(WorkBooks(workbooksOrderBySubscriptionCount))) @@ -132,20 +143,21 @@ class AuthMainViewWorkbookOrderDelegatorTest { fun `워크북과 멤버 구독 완료 워크북만 주어지는 경우`() { // given val totalWorkbookCount = 10 - val workbooksOrderBySubscriptionCount = IntStream.range(1, 1 + totalWorkbookCount) - .mapToObj { - WorkBook( - it.toLong(), - URL("http://localhost:8080/$it"), - "title$it", - "description$it", - "category$it", - LocalDateTime.now(), - emptyList(), - (1 + totalWorkbookCount) - it.toLong() - ) - } - .toList() + val workbooksOrderBySubscriptionCount = + IntStream + .range(1, 1 + totalWorkbookCount) + .mapToObj { + WorkBook( + it.toLong(), + URL("http://localhost:8080/$it"), + "title$it", + "description$it", + "category$it", + LocalDateTime.now(), + emptyList(), + (1 + totalWorkbookCount) - it.toLong(), + ) + }.toList() /** * 1 : inactive, current day 5 @@ -156,26 +168,30 @@ class AuthMainViewWorkbookOrderDelegatorTest { */ val totalMemberSubscribedWorkbookCount = 5 val inActiveWorkbookIds = IntStream.range(1, 1 + totalMemberSubscribedWorkbookCount).toList() - val memberSubscribedWorkbooksReverseOrderByCurrentDay = IntStream.range(1, 1 + totalMemberSubscribedWorkbookCount) - .mapToObj { - MemberSubscribedWorkbook( - it.toLong(), - false, - (1 + totalMemberSubscribedWorkbookCount) - it - ) - } - .toList() - - val notSubscribeWorkbookIds = workbooksOrderBySubscriptionCount.filter { - !inActiveWorkbookIds.contains( - it.id.toInt() - ) - }.map { it.id.toInt() } + val memberSubscribedWorkbooksReverseOrderByCurrentDay = + IntStream + .range(1, 1 + totalMemberSubscribedWorkbookCount) + .mapToObj { + MemberSubscribedWorkbook( + it.toLong(), + false, + (1 + totalMemberSubscribedWorkbookCount) - it, + ) + }.toList() + + val notSubscribeWorkbookIds = + workbooksOrderBySubscriptionCount + .filter { + !inActiveWorkbookIds.contains( + it.id.toInt(), + ) + }.map { it.id.toInt() } // when - val delegator = AuthMainViewWorkbookOrderDelegator( - memberSubscribedWorkbooksReverseOrderByCurrentDay - ) + val delegator = + AuthMainViewWorkbookOrderDelegator( + memberSubscribedWorkbooksReverseOrderByCurrentDay, + ) // then val orderedWorkbooks = delegator.order(OrderTargetWorkBooks(WorkBooks(workbooksOrderBySubscriptionCount))) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 876c922b2..b22ed732f 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -4,4 +4,4 @@ plugins { repositories { mavenCentral() -} +} \ No newline at end of file diff --git a/email/src/main/kotlin/email/EmailContext.kt b/email/src/main/kotlin/email/EmailContext.kt index f1657586c..6f1f255ee 100644 --- a/email/src/main/kotlin/email/EmailContext.kt +++ b/email/src/main/kotlin/email/EmailContext.kt @@ -6,11 +6,12 @@ import org.thymeleaf.context.Context class EmailContext { private val context = Context() - fun setVariable(name: String, value: Any) { + fun setVariable( + name: String, + value: Any, + ) { context.setVariable(name, value) } - fun getContext(): AbstractContext { - return context - } + fun getContext(): AbstractContext = context } \ No newline at end of file diff --git a/email/src/main/kotlin/email/EmailSender.kt b/email/src/main/kotlin/email/EmailSender.kt index 4c51facf5..a0abd7ee5 100644 --- a/email/src/main/kotlin/email/EmailSender.kt +++ b/email/src/main/kotlin/email/EmailSender.kt @@ -7,8 +7,10 @@ abstract class EmailSender>( private val mailProperties: MailProperties, private val defaultEmailSendProvider: EmailSendProvider, ) { - - fun send(args: T, emailSendProvider: EmailSendProvider? = null): String { + fun send( + args: T, + emailSendProvider: EmailSendProvider? = null, + ): String { val from = mailProperties.username val to = args.to val subject = args.subject @@ -19,7 +21,7 @@ abstract class EmailSender>( "FEW Letter <$from>", to, subject, - message + message, ) } } diff --git a/email/src/main/kotlin/email/EmailTemplateProcessor.kt b/email/src/main/kotlin/email/EmailTemplateProcessor.kt index ed12dec31..4608433a4 100644 --- a/email/src/main/kotlin/email/EmailTemplateProcessor.kt +++ b/email/src/main/kotlin/email/EmailTemplateProcessor.kt @@ -7,8 +7,8 @@ import org.thymeleaf.TemplateEngine class EmailTemplateProcessor( private val templateEngine: TemplateEngine, ) { - - fun process(template: String, context: EmailContext): String { - return templateEngine.process(template, context.getContext()) - } + fun process( + template: String, + context: EmailContext, + ): String = templateEngine.process(template, context.getContext()) } \ No newline at end of file diff --git a/email/src/main/kotlin/email/config/MailSenderConfig.kt b/email/src/main/kotlin/email/config/MailSenderConfig.kt index f0ee389f5..9021c864d 100644 --- a/email/src/main/kotlin/email/config/MailSenderConfig.kt +++ b/email/src/main/kotlin/email/config/MailSenderConfig.kt @@ -73,9 +73,7 @@ class MailSenderConfig { @Bean(name = [AWS_EMAIL_PROVIDER_PROPERTIES]) @ConfigurationProperties(prefix = "spring.mail.provider.aws") - fun awsProviderProperties(): AwsEmailProviderProperties { - return AwsEmailProviderProperties() - } + fun awsProviderProperties(): AwsEmailProviderProperties = AwsEmailProviderProperties() @Bean(name = [MAIL_SENDER]) fun javaMailSender(): JavaMailSender { @@ -103,11 +101,13 @@ class MailSenderConfig { fun awsEmailSender(): AmazonSimpleEmailService { val properties = awsProviderProperties() val basicAWSCredentials = BasicAWSCredentials(properties.accessKey, properties.secretKey) - val awsStaticCredentialsProvider = AWSStaticCredentialsProvider( - basicAWSCredentials - ) + val awsStaticCredentialsProvider = + AWSStaticCredentialsProvider( + basicAWSCredentials, + ) - return AmazonSimpleEmailServiceClientBuilder.standard() + return AmazonSimpleEmailServiceClientBuilder + .standard() .withCredentials(awsStaticCredentialsProvider) .withRegion(properties.region) .build() diff --git a/email/src/main/kotlin/email/provider/ArticleAwsSESEmailSendProvider.kt b/email/src/main/kotlin/email/provider/ArticleAwsSESEmailSendProvider.kt index 794e0d60a..d3e2528ce 100644 --- a/email/src/main/kotlin/email/provider/ArticleAwsSESEmailSendProvider.kt +++ b/email/src/main/kotlin/email/provider/ArticleAwsSESEmailSendProvider.kt @@ -8,10 +8,8 @@ class ArticleAwsSESEmailSendProvider( amazonSimpleEmailService: AmazonSimpleEmailService, javaEmailSendProvider: JavaEmailSendProvider, ) : AwsSESEmailSendProvider( - amazonSimpleEmailService, - javaEmailSendProvider -) { - override fun getWithConfigurationSetName(): String { - return "few-article-configuration-set" - } + amazonSimpleEmailService, + javaEmailSendProvider, + ) { + override fun getWithConfigurationSetName(): String = "few-article-configuration-set" } \ No newline at end of file diff --git a/email/src/main/kotlin/email/provider/AwsSESEmailSendProvider.kt b/email/src/main/kotlin/email/provider/AwsSESEmailSendProvider.kt index f0423df57..c52f4add3 100644 --- a/email/src/main/kotlin/email/provider/AwsSESEmailSendProvider.kt +++ b/email/src/main/kotlin/email/provider/AwsSESEmailSendProvider.kt @@ -13,21 +13,29 @@ class AwsSESEmailSendProvider( private val javaEmailSendProvider: JavaEmailSendProvider, ) : EmailSendProvider { private val log = KotlinLogging.logger {} + companion object { private const val UTF_8 = "utf-8" } - override fun sendEmail(from: String, to: String, subject: String, message: String): String { + override fun sendEmail( + from: String, + to: String, + subject: String, + message: String, + ): 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 sendMessage = + Message() + .withSubject(Content().withCharset(UTF_8).withData(subject)) + .withBody(Body().withHtml(Content().withCharset(UTF_8).withData(message))) - val sendEmailRequest = SendEmailRequest() - .withSource(from) - .withDestination(destination) - .withMessage(sendMessage) - .withConfigurationSetName(getWithConfigurationSetName()) + val sendEmailRequest = + SendEmailRequest() + .withSource(from) + .withDestination(destination) + .withMessage(sendMessage) + .withConfigurationSetName(getWithConfigurationSetName()) runCatching { amazonSimpleEmailService.sendEmail(sendEmailRequest).messageId @@ -54,7 +62,5 @@ class AwsSESEmailSendProvider( /** * Default configuration set name is "few-configuration-set" */ - fun getWithConfigurationSetName(): String { - return "few-configuration-set" - } + fun getWithConfigurationSetName(): String = "few-configuration-set" } \ No newline at end of file diff --git a/email/src/main/kotlin/email/provider/EmailSendProvider.kt b/email/src/main/kotlin/email/provider/EmailSendProvider.kt index 1b62ad966..ce8a3b1c1 100644 --- a/email/src/main/kotlin/email/provider/EmailSendProvider.kt +++ b/email/src/main/kotlin/email/provider/EmailSendProvider.kt @@ -1,5 +1,10 @@ package email.provider interface EmailSendProvider { - fun sendEmail(from: String, to: String, subject: String, message: String): String + fun sendEmail( + from: String, + to: String, + subject: String, + message: String, + ): String } \ No newline at end of file diff --git a/email/src/main/kotlin/email/provider/JavaEmailSendProvider.kt b/email/src/main/kotlin/email/provider/JavaEmailSendProvider.kt index 69eec426c..79c8f82cb 100644 --- a/email/src/main/kotlin/email/provider/JavaEmailSendProvider.kt +++ b/email/src/main/kotlin/email/provider/JavaEmailSendProvider.kt @@ -13,7 +13,13 @@ class JavaEmailSendProvider( companion object { private const val UTF_8 = "utf-8" } - override fun sendEmail(from: String, to: String, subject: String, message: String): String { + + override fun sendEmail( + from: String, + to: String, + subject: String, + message: String, + ): String { val sendMessage: MimeMessage = emailSender.createMimeMessage() val helper = MimeMessageHelper(sendMessage, UTF_8) try { diff --git a/repo/src/main/kotlin/repo/config/DataSourceConfig.kt b/repo/src/main/kotlin/repo/config/DataSourceConfig.kt index c529bc90d..fba982723 100644 --- a/repo/src/main/kotlin/repo/config/DataSourceConfig.kt +++ b/repo/src/main/kotlin/repo/config/DataSourceConfig.kt @@ -11,7 +11,6 @@ import javax.sql.DataSource @Configuration class DataSourceConfig { - companion object { const val API_DATASOURCE = RepoConfig.BEAN_NAME_PREFIX + "DataSource" const val API_TX = RepoConfig.BEAN_NAME_PREFIX + "TransactionManager" @@ -19,12 +18,8 @@ class DataSourceConfig { @Bean(name = [API_DATASOURCE]) @ConfigurationProperties(prefix = "spring.datasource.hikari") - fun apiDataSource(): DataSource { - return DataSourceBuilder.create().type(HikariDataSource::class.java).build() - } + fun apiDataSource(): DataSource = DataSourceBuilder.create().type(HikariDataSource::class.java).build() @Bean(name = [API_TX]) - fun apiTransactionManager(): PlatformTransactionManager { - return DataSourceTransactionManager(apiDataSource()) - } + fun apiTransactionManager(): PlatformTransactionManager = DataSourceTransactionManager(apiDataSource()) } \ No newline at end of file diff --git a/repo/src/main/kotlin/repo/config/FlywayConfig.kt b/repo/src/main/kotlin/repo/config/FlywayConfig.kt index 6f771203b..aaa8a1d88 100644 --- a/repo/src/main/kotlin/repo/config/FlywayConfig.kt +++ b/repo/src/main/kotlin/repo/config/FlywayConfig.kt @@ -25,32 +25,20 @@ class FlywayConfig { } @Bean(name = [FLYWAY]) - fun flyway( - configuration: org.flywaydb.core.api.configuration.Configuration?, - ): Flyway { - return Flyway(configuration) - } + fun flyway(configuration: org.flywaydb.core.api.configuration.Configuration?): Flyway = Flyway(configuration) @Profile("!new") @Bean(name = [FLYWAY_VALIDATE_INITIALIZER]) - fun flywayValidateInitializer( - flyway: Flyway?, - ): FlywayMigrationInitializer { - return FlywayMigrationInitializer(flyway) { obj: Flyway -> obj.validate() } - } + fun flywayValidateInitializer(flyway: Flyway?): FlywayMigrationInitializer = + FlywayMigrationInitializer(flyway) { obj: Flyway -> obj.validate() } @Bean(name = [FLYWAY_MIGRATION_INITIALIZER]) - fun flywayMigrationInitializer( - flyway: Flyway?, - ): FlywayMigrationInitializer { - return FlywayMigrationInitializer(flyway) { obj: Flyway -> obj.migrate() } - } + fun flywayMigrationInitializer(flyway: Flyway?): FlywayMigrationInitializer = + FlywayMigrationInitializer(flyway) { obj: Flyway -> obj.migrate() } @Bean(name = [FLYWAY_PROPERTIES]) @ConfigurationProperties(prefix = "spring.flyway") - fun flywayProperties(): FlywayProperties { - return FlywayProperties() - } + fun flywayProperties(): FlywayProperties = FlywayProperties() @Bean(name = [FLYWAY_CONFIGURATION]) fun configuration( @@ -61,9 +49,9 @@ class FlywayConfig { flywayProperties().locations.forEach( Consumer { locations: String? -> configuration.locations( - locations + locations, ) - } + }, ) return configuration } diff --git a/repo/src/main/kotlin/repo/config/JooqConfig.kt b/repo/src/main/kotlin/repo/config/JooqConfig.kt index f5ce3b4f6..8ad4178d2 100644 --- a/repo/src/main/kotlin/repo/config/JooqConfig.kt +++ b/repo/src/main/kotlin/repo/config/JooqConfig.kt @@ -25,9 +25,7 @@ class JooqConfig( } @Bean(name = [DSL]) - fun dsl(): DefaultDSLContext { - return DefaultDSLContext(configuration()) - } + fun dsl(): DefaultDSLContext = DefaultDSLContext(configuration()) @Bean(name = [JOOQ_CONFIGURATION]) fun configuration(): DefaultConfiguration { @@ -41,7 +39,5 @@ class JooqConfig( } @Bean(name = [JOOQ_CONNECTION_PROVIDER]) - fun connectionProvider(): DataSourceConnectionProvider { - return DataSourceConnectionProvider(dataSource) - } + fun connectionProvider(): DataSourceConnectionProvider = DataSourceConnectionProvider(dataSource) } \ No newline at end of file diff --git a/repo/src/main/kotlin/repo/flyway/support/ExceptionTranslator.kt b/repo/src/main/kotlin/repo/flyway/support/ExceptionTranslator.kt index 2df5be36c..ba13c1999 100644 --- a/repo/src/main/kotlin/repo/flyway/support/ExceptionTranslator.kt +++ b/repo/src/main/kotlin/repo/flyway/support/ExceptionTranslator.kt @@ -7,11 +7,10 @@ import org.springframework.jdbc.support.SQLExceptionTranslator class ExceptionTranslator( private val translator: SQLExceptionTranslator, ) : ExecuteListener { - override fun exception(context: ExecuteContext) { context.exception( translator - .translate("Access database using Jooq", context.sql(), context.sqlException()!!) + .translate("Access database using Jooq", context.sql(), context.sqlException()!!), ) } } \ No newline at end of file diff --git a/repo/src/main/kotlin/repo/flyway/support/PerformanceListener.kt b/repo/src/main/kotlin/repo/flyway/support/PerformanceListener.kt index fb7ac5a73..6d2ac85cc 100644 --- a/repo/src/main/kotlin/repo/flyway/support/PerformanceListener.kt +++ b/repo/src/main/kotlin/repo/flyway/support/PerformanceListener.kt @@ -12,6 +12,7 @@ class PerformanceListener( private var watch: StopWatch = StopWatch(), ) : ExecuteListener { private val log = KotlinLogging.logger {} + companion object { const val SLOW_QUERY_STANDARD = 5000000000L // 5 seconds } diff --git a/security/src/main/kotlin/security/AuthorityUtils.kt b/security/src/main/kotlin/security/AuthorityUtils.kt index e19eaf9a5..7fbe050cf 100644 --- a/security/src/main/kotlin/security/AuthorityUtils.kt +++ b/security/src/main/kotlin/security/AuthorityUtils.kt @@ -4,7 +4,6 @@ import org.apache.commons.lang3.StringUtils import org.springframework.security.core.GrantedAuthority object AuthorityUtils { - @Throws(IllegalArgumentException::class) fun toAuthorities(roles: String): List { val tokens = StringUtils.splitPreserveAllTokens(roles, "[,]") diff --git a/security/src/main/kotlin/security/Encryptor.kt b/security/src/main/kotlin/security/Encryptor.kt index 813e8f860..373409e44 100644 --- a/security/src/main/kotlin/security/Encryptor.kt +++ b/security/src/main/kotlin/security/Encryptor.kt @@ -1,7 +1,6 @@ package security interface Encryptor { - fun encrypt(plainText: T): R fun decrypt(encryptedText: R): T diff --git a/security/src/main/kotlin/security/Roles.kt b/security/src/main/kotlin/security/Roles.kt index 7494bdac5..f8a5f9c5a 100644 --- a/security/src/main/kotlin/security/Roles.kt +++ b/security/src/main/kotlin/security/Roles.kt @@ -2,7 +2,9 @@ package security import org.springframework.security.core.GrantedAuthority -enum class Roles(val role: String) { +enum class Roles( + val role: String, +) { ROLE_USER("ROLE_USER") { override val authority: GrantedAuthority get() = UserAuthority() diff --git a/security/src/main/kotlin/security/TokenClaim.kt b/security/src/main/kotlin/security/TokenClaim.kt index c8c9c4dea..0a73e0f8c 100644 --- a/security/src/main/kotlin/security/TokenClaim.kt +++ b/security/src/main/kotlin/security/TokenClaim.kt @@ -1,6 +1,8 @@ package security -enum class TokenClaim(val key: String) { +enum class TokenClaim( + val key: String, +) { MEMBER_ID_CLAIM("memberId"), MEMBER_EMAIL_CLAIM("memberEmail"), MEMBER_ROLE_CLAIM("memberRole"), diff --git a/security/src/main/kotlin/security/TokenGenerator.kt b/security/src/main/kotlin/security/TokenGenerator.kt index f894dd989..6286b579f 100644 --- a/security/src/main/kotlin/security/TokenGenerator.kt +++ b/security/src/main/kotlin/security/TokenGenerator.kt @@ -1,7 +1,12 @@ package security interface TokenGenerator { - fun generateAuthToken(memberId: Long?, memberEmail: String?, memberRoles: List): AuthToken + fun generateAuthToken( + memberId: Long?, + memberEmail: String?, + memberRoles: List, + ): AuthToken + fun generateAuthToken( memberId: Long?, memberEmail: String?, diff --git a/security/src/main/kotlin/security/TokenResolver.kt b/security/src/main/kotlin/security/TokenResolver.kt index 3aab5578a..f71bf523a 100644 --- a/security/src/main/kotlin/security/TokenResolver.kt +++ b/security/src/main/kotlin/security/TokenResolver.kt @@ -4,7 +4,10 @@ import io.jsonwebtoken.Claims interface TokenResolver { fun resolve(token: String?): Claims? + fun resolveId(token: String?): Long? + fun resolveEmail(token: String?): String? + fun resolveRole(token: String?): String? } \ No newline at end of file diff --git a/security/src/main/kotlin/security/TokenUserDetails.kt b/security/src/main/kotlin/security/TokenUserDetails.kt index 6a7f4345e..b4ed0cc23 100644 --- a/security/src/main/kotlin/security/TokenUserDetails.kt +++ b/security/src/main/kotlin/security/TokenUserDetails.kt @@ -12,31 +12,17 @@ open class TokenUserDetails( private const val NOT_USE_PASSWORD_VALUE = "0" } - override fun getAuthorities(): MutableCollection { - return authorities.toMutableList() - } + override fun getAuthorities(): MutableCollection = authorities.toMutableList() - override fun getPassword(): String { - return NOT_USE_PASSWORD_VALUE - } + override fun getPassword(): String = NOT_USE_PASSWORD_VALUE - override fun getUsername(): String { - return id - } + override fun getUsername(): String = id - override fun isAccountNonExpired(): Boolean { - return true - } + override fun isAccountNonExpired(): Boolean = true - override fun isAccountNonLocked(): Boolean { - return true - } + override fun isAccountNonLocked(): Boolean = true - override fun isCredentialsNonExpired(): Boolean { - return true - } + override fun isCredentialsNonExpired(): Boolean = true - override fun isEnabled(): Boolean { - return true - } + override fun isEnabled(): Boolean = true } \ No newline at end of file diff --git a/security/src/main/kotlin/security/UserAuthority.kt b/security/src/main/kotlin/security/UserAuthority.kt index 109438ef7..e054cc349 100644 --- a/security/src/main/kotlin/security/UserAuthority.kt +++ b/security/src/main/kotlin/security/UserAuthority.kt @@ -3,7 +3,5 @@ package security import org.springframework.security.core.GrantedAuthority class UserAuthority : GrantedAuthority { - override fun getAuthority(): String { - return Roles.ROLE_USER.role - } + override fun getAuthority(): String = Roles.ROLE_USER.role } \ No newline at end of file diff --git a/security/src/main/kotlin/security/authentication/token/TokenAuthProvider.kt b/security/src/main/kotlin/security/authentication/token/TokenAuthProvider.kt index 5d0c6867e..4c11ada42 100644 --- a/security/src/main/kotlin/security/authentication/token/TokenAuthProvider.kt +++ b/security/src/main/kotlin/security/authentication/token/TokenAuthProvider.kt @@ -13,34 +13,33 @@ import java.util.* class TokenAuthProvider( private val tokenUserDetailsService: UserDetailsService, ) : AuthenticationProvider { - @Throws(AuthenticationException::class, AccessDeniedException::class) override fun authenticate(authentication: Authentication): Authentication? { - val token = Optional.ofNullable(authentication.principal) - .map { obj: Any? -> - String::class.java.cast( - obj - ) - } - .orElseThrow { - IllegalArgumentException( - "token is missing" - ) - } + val token = + Optional + .ofNullable(authentication.principal) + .map { obj: Any? -> + String::class.java.cast( + obj, + ) + }.orElseThrow { + IllegalArgumentException( + "token is missing", + ) + } val userDetails: UserDetails = tokenUserDetailsService.loadUserByUsername(token) SecurityContextHolder.getContext().authentication = authentication return if (authentication is PreAuthenticatedAuthenticationToken) { PreAuthenticatedAuthenticationToken( userDetails, userDetails.password, - userDetails.authorities + userDetails.authorities, ) } else { null } } - override fun supports(authentication: Class<*>?): Boolean { - return PreAuthenticatedAuthenticationToken::class.java.isAssignableFrom(authentication) - } + override fun supports(authentication: Class<*>?): Boolean = + PreAuthenticatedAuthenticationToken::class.java.isAssignableFrom(authentication) } \ No newline at end of file diff --git a/security/src/main/kotlin/security/authentication/token/TokenUserDetailsService.kt b/security/src/main/kotlin/security/authentication/token/TokenUserDetailsService.kt index 911602b4a..bd9dcfc9b 100644 --- a/security/src/main/kotlin/security/authentication/token/TokenUserDetailsService.kt +++ b/security/src/main/kotlin/security/authentication/token/TokenUserDetailsService.kt @@ -1,14 +1,14 @@ package security.authentication.token -import security.AuthorityUtils import io.github.oshai.kotlinlogging.KotlinLogging import io.jsonwebtoken.Claims import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.core.userdetails.UserDetailsService -import security.exception.SecurityAccessTokenInvalidException +import security.AuthorityUtils import security.TokenClaim import security.TokenResolver import security.TokenUserDetails +import security.exception.SecurityAccessTokenInvalidException class TokenUserDetailsService( private val tokenResolver: TokenResolver, @@ -16,24 +16,29 @@ class TokenUserDetailsService( private val log = KotlinLogging.logger {} override fun loadUserByUsername(token: String?): UserDetails { - val claims: Claims = tokenResolver - .resolve(token) - ?: throw SecurityAccessTokenInvalidException("Invalid access token. accessToken: $token") - - val id = claims.get( - TokenClaim.MEMBER_ID_CLAIM.key, - Integer::class.java - ).toLong() - - val roles = claims.get( - TokenClaim.MEMBER_ROLE_CLAIM.key, - String::class.java - ) - - val email = claims.get( - TokenClaim.MEMBER_EMAIL_CLAIM.key, - String::class.java - ) + val claims: Claims = + tokenResolver + .resolve(token) + ?: throw SecurityAccessTokenInvalidException("Invalid access token. accessToken: $token") + + val id = + claims + .get( + TokenClaim.MEMBER_ID_CLAIM.key, + Integer::class.java, + ).toLong() + + val roles = + claims.get( + TokenClaim.MEMBER_ROLE_CLAIM.key, + String::class.java, + ) + + val email = + claims.get( + TokenClaim.MEMBER_EMAIL_CLAIM.key, + String::class.java, + ) val authorities = AuthorityUtils.toAuthorities(roles) diff --git a/security/src/main/kotlin/security/config/SecurityConfig.kt b/security/src/main/kotlin/security/config/SecurityConfig.kt index c16c26eb1..0b485b203 100644 --- a/security/src/main/kotlin/security/config/SecurityConfig.kt +++ b/security/src/main/kotlin/security/config/SecurityConfig.kt @@ -22,7 +22,5 @@ class SecurityConfig { @Value("\${security.encryption.transformation}")transformation: String, @Value("\${security.encryption.keySize}")keySize: Int, @Value("\${security.encryption.iv}")iv: String, - ): IdEncryptor { - return IdEncryptor(algorithm, secretKey, transformation, keySize, iv) - } + ): IdEncryptor = IdEncryptor(algorithm, secretKey, transformation, keySize, iv) } \ No newline at end of file diff --git a/security/src/main/kotlin/security/encryptor/IdEncryptor.kt b/security/src/main/kotlin/security/encryptor/IdEncryptor.kt index e09d5da99..ecc7e5d68 100644 --- a/security/src/main/kotlin/security/encryptor/IdEncryptor.kt +++ b/security/src/main/kotlin/security/encryptor/IdEncryptor.kt @@ -13,18 +13,22 @@ class IdEncryptor( private val keySize: Int, private val iv: String, ) : Encryptor { - - private var key: SecretKeySpec = KeyGenerator.getInstance(algorithm).apply { - init(keySize) - }.run { - SecretKeySpec(secretKey.toByteArray(), this@IdEncryptor.algorithm) - } - private var encodeCipher: Cipher = Cipher.getInstance(transformation).apply { - init(Cipher.ENCRYPT_MODE, key, IvParameterSpec(this@IdEncryptor.iv.toByteArray())) - } - private var decodeCipher: Cipher = Cipher.getInstance(transformation).apply { - init(Cipher.DECRYPT_MODE, key, IvParameterSpec(this@IdEncryptor.iv.toByteArray())) - } + private var key: SecretKeySpec = + KeyGenerator + .getInstance(algorithm) + .apply { + init(keySize) + }.run { + SecretKeySpec(secretKey.toByteArray(), this@IdEncryptor.algorithm) + } + private var encodeCipher: Cipher = + Cipher.getInstance(transformation).apply { + init(Cipher.ENCRYPT_MODE, key, IvParameterSpec(this@IdEncryptor.iv.toByteArray())) + } + private var decodeCipher: Cipher = + Cipher.getInstance(transformation).apply { + init(Cipher.DECRYPT_MODE, key, IvParameterSpec(this@IdEncryptor.iv.toByteArray())) + } override fun encrypt(plainText: String): String { val encrypted: ByteArray = encodeCipher.doFinal(plainText.toByteArray()) diff --git a/security/src/main/kotlin/security/exception/SecurityAccessTokenInvalidException.kt b/security/src/main/kotlin/security/exception/SecurityAccessTokenInvalidException.kt index 83ea5ea33..3021b9ebf 100644 --- a/security/src/main/kotlin/security/exception/SecurityAccessTokenInvalidException.kt +++ b/security/src/main/kotlin/security/exception/SecurityAccessTokenInvalidException.kt @@ -2,4 +2,6 @@ package security.exception import org.springframework.security.core.AuthenticationException -class SecurityAccessTokenInvalidException(msg: String?) : AuthenticationException(msg) \ No newline at end of file +class SecurityAccessTokenInvalidException( + msg: String?, +) : AuthenticationException(msg) \ No newline at end of file diff --git a/security/src/main/kotlin/security/token/SecurityTokenGenerator.kt b/security/src/main/kotlin/security/token/SecurityTokenGenerator.kt index 343ad3bee..b425def44 100644 --- a/security/src/main/kotlin/security/token/SecurityTokenGenerator.kt +++ b/security/src/main/kotlin/security/token/SecurityTokenGenerator.kt @@ -14,18 +14,20 @@ class SecurityTokenGenerator( private val accessTokenValidTime: Long, private val refreshTokenValidTime: Long, ) : TokenGenerator { - - override fun generateAuthToken(memberId: Long?, memberEmail: String?, memberRoles: List): AuthToken { - return AuthToken( + override fun generateAuthToken( + memberId: Long?, + memberEmail: String?, + memberRoles: List, + ): AuthToken = + AuthToken( generateAccessToken( memberId, memberEmail, memberRoles, - accessTokenValidTime + accessTokenValidTime, ), - generateRefreshToken(memberId, memberEmail, memberRoles, refreshTokenValidTime) + generateRefreshToken(memberId, memberEmail, memberRoles, refreshTokenValidTime), ) - } override fun generateAuthToken( memberId: Long?, @@ -33,12 +35,11 @@ class SecurityTokenGenerator( memberRoles: List, accessTokenValidTime: Long?, refreshTokenValidTime: Long?, - ): AuthToken { - return AuthToken( + ): AuthToken = + AuthToken( generateAccessToken(memberId, memberEmail, memberRoles, accessTokenValidTime), - generateRefreshToken(memberId, memberEmail, memberRoles, refreshTokenValidTime) + generateRefreshToken(memberId, memberEmail, memberRoles, refreshTokenValidTime), ) - } override fun generateAccessToken( memberId: Long?, @@ -49,7 +50,8 @@ class SecurityTokenGenerator( val now = Date() val roles = convertToStringList(memberRoles) val acValidTime = accessTokenValidTime ?: this.accessTokenValidTime - return Jwts.builder() + return Jwts + .builder() .setHeaderParam(Header.TYPE, Header.JWT_TYPE) .claim(TokenClaim.MEMBER_ID_CLAIM.key, memberId) .claim(TokenClaim.MEMBER_EMAIL_CLAIM.key, memberEmail) @@ -69,7 +71,8 @@ class SecurityTokenGenerator( val now = Date() val roles = convertToStringList(memberRoles) val rfValidTime = refreshTokenValidTime ?: this.refreshTokenValidTime - return Jwts.builder() + return Jwts + .builder() .setHeaderParam(Header.TYPE, Header.JWT_TYPE) .claim(TokenClaim.MEMBER_ID_CLAIM.key, memberId) .claim(TokenClaim.MEMBER_EMAIL_CLAIM.key, memberEmail) diff --git a/security/src/main/kotlin/security/token/SecurityTokenResolver.kt b/security/src/main/kotlin/security/token/SecurityTokenResolver.kt index 944165a8b..131cdbad9 100644 --- a/security/src/main/kotlin/security/token/SecurityTokenResolver.kt +++ b/security/src/main/kotlin/security/token/SecurityTokenResolver.kt @@ -6,12 +6,15 @@ import io.jsonwebtoken.Jwts import security.TokenClaim import security.TokenResolver -class SecurityTokenResolver(private val secretKey: String) : TokenResolver { +class SecurityTokenResolver( + private val secretKey: String, +) : TokenResolver { private val log = KotlinLogging.logger {} - override fun resolve(token: String?): Claims? { - return try { - Jwts.parserBuilder() + override fun resolve(token: String?): Claims? = + try { + Jwts + .parserBuilder() .setSigningKey(secretKey.toByteArray()) .build() .parseClaimsJws(token) @@ -20,11 +23,11 @@ class SecurityTokenResolver(private val secretKey: String) : TokenResolver { log.warn { "${"Failed to get memberId. token: {}"} $token" } null } - } override fun resolveId(token: String?): Long? { return try { - Jwts.parserBuilder() + Jwts + .parserBuilder() .setSigningKey(secretKey.toByteArray()) .build() .parseClaimsJws(token) @@ -39,7 +42,8 @@ class SecurityTokenResolver(private val secretKey: String) : TokenResolver { override fun resolveEmail(token: String?): String? { return try { - Jwts.parserBuilder() + Jwts + .parserBuilder() .setSigningKey(secretKey.toByteArray()) .build() .parseClaimsJws(token) @@ -53,7 +57,8 @@ class SecurityTokenResolver(private val secretKey: String) : TokenResolver { override fun resolveRole(token: String?): String? { return try { - Jwts.parserBuilder() + Jwts + .parserBuilder() .setSigningKey(secretKey.toByteArray()) .build() .parseClaimsJws(token) diff --git a/storage/src/main/kotlin/storage/GetPreSignedObjectUrlProvider.kt b/storage/src/main/kotlin/storage/GetPreSignedObjectUrlProvider.kt index 365ec0676..57ce26336 100644 --- a/storage/src/main/kotlin/storage/GetPreSignedObjectUrlProvider.kt +++ b/storage/src/main/kotlin/storage/GetPreSignedObjectUrlProvider.kt @@ -1,6 +1,5 @@ package storage interface GetPreSignedObjectUrlProvider { - fun execute(image: String): String? } \ No newline at end of file diff --git a/storage/src/main/kotlin/storage/PutObjectProvider.kt b/storage/src/main/kotlin/storage/PutObjectProvider.kt index 054c11861..d0b53f217 100644 --- a/storage/src/main/kotlin/storage/PutObjectProvider.kt +++ b/storage/src/main/kotlin/storage/PutObjectProvider.kt @@ -3,6 +3,8 @@ package storage import java.io.File interface PutObjectProvider { - - fun execute(name: String, file: File): T? + fun execute( + name: String, + file: File, + ): T? } \ No newline at end of file diff --git a/storage/src/main/kotlin/storage/config/StorageClientConfig.kt b/storage/src/main/kotlin/storage/config/StorageClientConfig.kt index b4f5a44e4..19cad0898 100644 --- a/storage/src/main/kotlin/storage/config/StorageClientConfig.kt +++ b/storage/src/main/kotlin/storage/config/StorageClientConfig.kt @@ -17,25 +17,25 @@ class StorageClientConfig( @Value("\${storage.secret-key}") val secretKey: String, @Value("\${storage.region}") val region: String, ) { - @Profile("!prd") @Bean fun localS3StorageClient(): AmazonS3Client { - val builder = AmazonS3ClientBuilder.standard() - .withCredentials( - AWSStaticCredentialsProvider( - BasicAWSCredentials( - accessKey, - secretKey - ) - ) - ) - .withEndpointConfiguration( - AwsClientBuilder.EndpointConfiguration( - url, - region + val builder = + AmazonS3ClientBuilder + .standard() + .withCredentials( + AWSStaticCredentialsProvider( + BasicAWSCredentials( + accessKey, + secretKey, + ), + ), + ).withEndpointConfiguration( + AwsClientBuilder.EndpointConfiguration( + url, + region, + ), ) - ) builder.build().let { client -> return client as AmazonS3Client @@ -45,16 +45,18 @@ class StorageClientConfig( @Profile("prd") @Bean fun prdS3StorageClient(): AmazonS3Client { - AmazonS3Client.builder() + AmazonS3Client + .builder() .withRegion(region) .withCredentials( AWSStaticCredentialsProvider( BasicAWSCredentials( accessKey, - secretKey - ) - ) - ).build().let { client -> + secretKey, + ), + ), + ).build() + .let { client -> return client as AmazonS3Client } } diff --git a/storage/src/main/kotlin/storage/document/PutDocumentProvider.kt b/storage/src/main/kotlin/storage/document/PutDocumentProvider.kt index 4973ce29a..5205431e9 100644 --- a/storage/src/main/kotlin/storage/document/PutDocumentProvider.kt +++ b/storage/src/main/kotlin/storage/document/PutDocumentProvider.kt @@ -5,5 +5,8 @@ import storage.document.client.dto.DocumentWriteResponse import java.io.File interface PutDocumentProvider : PutObjectProvider { - override fun execute(name: String, file: File): DocumentWriteResponse? + override fun execute( + name: String, + file: File, + ): DocumentWriteResponse? } \ No newline at end of file diff --git a/storage/src/main/kotlin/storage/document/client/S3DocumentStoreClient.kt b/storage/src/main/kotlin/storage/document/client/S3DocumentStoreClient.kt index b155e63e3..d9f74e4fd 100644 --- a/storage/src/main/kotlin/storage/document/client/S3DocumentStoreClient.kt +++ b/storage/src/main/kotlin/storage/document/client/S3DocumentStoreClient.kt @@ -1,24 +1,25 @@ package storage.document.client import com.amazonaws.services.s3.AmazonS3Client +import io.github.oshai.kotlinlogging.KotlinLogging import storage.document.client.dto.DocumentGetPreSignedObjectUrlArgs import storage.document.client.dto.DocumentPutObjectArgs import storage.document.client.dto.DocumentWriteResponse import storage.document.client.dto.toS3Args -import io.github.oshai.kotlinlogging.KotlinLogging class S3DocumentStoreClient( private val s3client: AmazonS3Client, private val region: String, ) : DocumentStoreClient { - private val log = KotlinLogging.logger {} override fun getPreSignedObjectUrl(args: DocumentGetPreSignedObjectUrlArgs): String? { - args.toS3Args() + args + .toS3Args() .let { s3 -> try { - s3client.generatePresignedUrl(s3) + s3client + .generatePresignedUrl(s3) .let { url -> return url.toString() } @@ -32,7 +33,8 @@ class S3DocumentStoreClient( } override fun putObject(args: DocumentPutObjectArgs): DocumentWriteResponse? { - args.toS3Args() + args + .toS3Args() .let { s3 -> try { s3client.putObject(s3).let { owr -> @@ -41,7 +43,7 @@ class S3DocumentStoreClient( region, args.imagePath, owr.eTag ?: "", - owr.versionId ?: "" + owr.versionId ?: "", ) } } catch (e: Exception) { diff --git a/storage/src/main/kotlin/storage/document/client/dto/DocumentObjectArgs.kt b/storage/src/main/kotlin/storage/document/client/dto/DocumentObjectArgs.kt index 648193ff8..0fc54854c 100644 --- a/storage/src/main/kotlin/storage/document/client/dto/DocumentObjectArgs.kt +++ b/storage/src/main/kotlin/storage/document/client/dto/DocumentObjectArgs.kt @@ -8,17 +8,14 @@ import com.amazonaws.services.s3.model.PutObjectRequest import org.apache.http.entity.ContentType import java.io.InputStream -fun DocumentGetPreSignedObjectUrlArgs.toS3Args(): GeneratePresignedUrlRequest { - return GeneratePresignedUrlRequest( +fun DocumentGetPreSignedObjectUrlArgs.toS3Args(): GeneratePresignedUrlRequest = + GeneratePresignedUrlRequest( this.bucket, this.imagePath, - HttpMethod.valueOf(this.method) + HttpMethod.valueOf(this.method), ) -} -fun DocumentRemoveObjectArgs.toS3Args(): DeleteObjectRequest { - return DeleteObjectRequest(this.bucket, this.imagePath) -} +fun DocumentRemoveObjectArgs.toS3Args(): DeleteObjectRequest = DeleteObjectRequest(this.bucket, this.imagePath) fun DocumentPutObjectArgs.toS3Args(): PutObjectRequest { val objectSize = this.objectSize @@ -29,7 +26,7 @@ fun DocumentPutObjectArgs.toS3Args(): PutObjectRequest { ObjectMetadata().apply { contentType = this.contentType contentLength = objectSize - } + }, ) } diff --git a/storage/src/main/kotlin/storage/document/client/util/DocumentArgsGenerator.kt b/storage/src/main/kotlin/storage/document/client/util/DocumentArgsGenerator.kt index e889b98f9..807a2c6f8 100644 --- a/storage/src/main/kotlin/storage/document/client/util/DocumentArgsGenerator.kt +++ b/storage/src/main/kotlin/storage/document/client/util/DocumentArgsGenerator.kt @@ -1,25 +1,37 @@ package storage.document.client.util +import org.apache.http.entity.ContentType import storage.document.client.dto.DocumentGetPreSignedObjectUrlArgs import storage.document.client.dto.DocumentPutObjectArgs import storage.document.client.dto.DocumentRemoveObjectArgs -import org.apache.http.entity.ContentType import java.io.BufferedInputStream import java.io.File import java.io.FileInputStream class DocumentArgsGenerator { companion object { - fun preSignedUrl(bucket: String, document: String): DocumentGetPreSignedObjectUrlArgs { - return DocumentGetPreSignedObjectUrlArgs(bucket, document, "GET") - } + fun preSignedUrl( + bucket: String, + document: String, + ): DocumentGetPreSignedObjectUrlArgs = DocumentGetPreSignedObjectUrlArgs(bucket, document, "GET") - fun putDocument(bucket: String, name: String, document: File): DocumentPutObjectArgs { - return DocumentPutObjectArgs(bucket, name, BufferedInputStream(FileInputStream(document)), document.length(), -1, ContentType.APPLICATION_OCTET_STREAM) - } + fun putDocument( + bucket: String, + name: String, + document: File, + ): DocumentPutObjectArgs = + DocumentPutObjectArgs( + bucket, + name, + BufferedInputStream(FileInputStream(document)), + document.length(), + -1, + ContentType.APPLICATION_OCTET_STREAM, + ) - fun remove(bucket: String, document: String): DocumentRemoveObjectArgs { - return DocumentRemoveObjectArgs(bucket, document) - } + fun remove( + bucket: String, + document: String, + ): DocumentRemoveObjectArgs = DocumentRemoveObjectArgs(bucket, document) } } \ No newline at end of file diff --git a/storage/src/main/kotlin/storage/document/config/DocumentStorageConfig.kt b/storage/src/main/kotlin/storage/document/config/DocumentStorageConfig.kt index 65099dae1..aab4277c3 100644 --- a/storage/src/main/kotlin/storage/document/config/DocumentStorageConfig.kt +++ b/storage/src/main/kotlin/storage/document/config/DocumentStorageConfig.kt @@ -1,9 +1,9 @@ package storage.document.config -import storage.config.StorageClientConfig import org.springframework.context.annotation.ComponentScan import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Import +import storage.config.StorageClientConfig @Configuration @ComponentScan(basePackages = [DocumentStorageConfig.BASE_PACKAGE]) diff --git a/storage/src/main/kotlin/storage/document/config/S3DocumentStoreConfig.kt b/storage/src/main/kotlin/storage/document/config/S3DocumentStoreConfig.kt index aa84c7472..198801ab9 100644 --- a/storage/src/main/kotlin/storage/document/config/S3DocumentStoreConfig.kt +++ b/storage/src/main/kotlin/storage/document/config/S3DocumentStoreConfig.kt @@ -1,9 +1,6 @@ package storage.document.config import com.amazonaws.services.s3.AmazonS3Client -import storage.config.StorageClientConfig -import storage.document.client.DocumentStoreClient -import storage.document.client.S3DocumentStoreClient import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.beans.factory.annotation.Value import org.springframework.context.ApplicationListener @@ -11,8 +8,11 @@ import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Import import org.springframework.context.event.ContextRefreshedEvent +import storage.config.StorageClientConfig import storage.document.GetPreSignedDocumentUrlProvider import storage.document.PutDocumentProvider +import storage.document.client.DocumentStoreClient +import storage.document.client.S3DocumentStoreClient import storage.document.provider.s3.S3GetPreSignedDocumentUrlProvider import storage.document.provider.s3.S3PutDocumentProvider @@ -54,15 +54,11 @@ class S3DocumentStoreConfig( fun s3PutDocumentProvider( @Value("\${document.store.bucket-name}") bucket: String, documentStoreClient: DocumentStoreClient, - ): PutDocumentProvider { - return S3PutDocumentProvider(bucket, documentStoreClient) - } + ): PutDocumentProvider = S3PutDocumentProvider(bucket, documentStoreClient) @Bean(name = [S3_GET_PRE_SIGNED_DOCUMENT_URL_PROVIDER]) fun s3GetPreSignedDocumentUrlProvider( @Value("\${document.store.bucket-name}") bucket: String, documentStoreClient: DocumentStoreClient, - ): GetPreSignedDocumentUrlProvider { - return S3GetPreSignedDocumentUrlProvider(bucket, documentStoreClient) - } + ): GetPreSignedDocumentUrlProvider = S3GetPreSignedDocumentUrlProvider(bucket, documentStoreClient) } \ No newline at end of file diff --git a/storage/src/main/kotlin/storage/document/provider/s3/S3GetPreSignedDocumentUrlProvider.kt b/storage/src/main/kotlin/storage/document/provider/s3/S3GetPreSignedDocumentUrlProvider.kt index 757183793..c06951c5d 100644 --- a/storage/src/main/kotlin/storage/document/provider/s3/S3GetPreSignedDocumentUrlProvider.kt +++ b/storage/src/main/kotlin/storage/document/provider/s3/S3GetPreSignedDocumentUrlProvider.kt @@ -1,8 +1,8 @@ package storage.document.provider.s3 +import storage.document.GetPreSignedDocumentUrlProvider import storage.document.client.DocumentStoreClient import storage.document.client.util.DocumentArgsGenerator -import storage.document.GetPreSignedDocumentUrlProvider class S3GetPreSignedDocumentUrlProvider( val bucket: String, diff --git a/storage/src/main/kotlin/storage/document/provider/s3/S3PutDocumentProvider.kt b/storage/src/main/kotlin/storage/document/provider/s3/S3PutDocumentProvider.kt index fe752b5ad..508f32273 100644 --- a/storage/src/main/kotlin/storage/document/provider/s3/S3PutDocumentProvider.kt +++ b/storage/src/main/kotlin/storage/document/provider/s3/S3PutDocumentProvider.kt @@ -1,16 +1,19 @@ package storage.document.provider.s3 +import storage.document.PutDocumentProvider import storage.document.client.DocumentStoreClient import storage.document.client.dto.DocumentWriteResponse import storage.document.client.util.DocumentArgsGenerator -import storage.document.PutDocumentProvider import java.io.File class S3PutDocumentProvider( val bucket: String, private val documentStoreClient: DocumentStoreClient, ) : PutDocumentProvider { - override fun execute(name: String, file: File): DocumentWriteResponse? { + override fun execute( + name: String, + file: File, + ): DocumentWriteResponse? { DocumentArgsGenerator.putDocument(bucket, name, file).let { args -> return documentStoreClient.putObject(args) } diff --git a/storage/src/main/kotlin/storage/image/PutImageProvider.kt b/storage/src/main/kotlin/storage/image/PutImageProvider.kt index f7c839d2a..dd0fe4e34 100644 --- a/storage/src/main/kotlin/storage/image/PutImageProvider.kt +++ b/storage/src/main/kotlin/storage/image/PutImageProvider.kt @@ -5,5 +5,8 @@ import storage.image.client.dto.ImageWriteResponse import java.io.File fun interface PutImageProvider : PutObjectProvider { - override fun execute(name: String, file: File): ImageWriteResponse? + override fun execute( + name: String, + file: File, + ): ImageWriteResponse? } \ No newline at end of file diff --git a/storage/src/main/kotlin/storage/image/client/ImageStoreClient.kt b/storage/src/main/kotlin/storage/image/client/ImageStoreClient.kt index 43fc2dce6..10b170aa8 100644 --- a/storage/src/main/kotlin/storage/image/client/ImageStoreClient.kt +++ b/storage/src/main/kotlin/storage/image/client/ImageStoreClient.kt @@ -6,7 +6,6 @@ import storage.image.client.dto.ImageRemoveObjectArgs import storage.image.client.dto.ImageWriteResponse interface ImageStoreClient { - fun getPreSignedObjectUrl(args: ImageGetPreSignedObjectUrlArgs): String? fun removeObject(args: ImageRemoveObjectArgs): Boolean diff --git a/storage/src/main/kotlin/storage/image/client/S3ImageStoreClient.kt b/storage/src/main/kotlin/storage/image/client/S3ImageStoreClient.kt index d44b765b0..b618b1243 100644 --- a/storage/src/main/kotlin/storage/image/client/S3ImageStoreClient.kt +++ b/storage/src/main/kotlin/storage/image/client/S3ImageStoreClient.kt @@ -1,21 +1,22 @@ package storage.image.client import com.amazonaws.services.s3.AmazonS3Client -import storage.image.client.dto.* import io.github.oshai.kotlinlogging.KotlinLogging +import storage.image.client.dto.* class S3ImageStoreClient( private val s3client: AmazonS3Client, private val region: String, ) : ImageStoreClient { - private val log = KotlinLogging.logger {} override fun getPreSignedObjectUrl(args: ImageGetPreSignedObjectUrlArgs): String? { - args.toS3Args() + args + .toS3Args() .let { s3 -> try { - s3client.generatePresignedUrl(s3) + s3client + .generatePresignedUrl(s3) .let { url -> return url.toString() } @@ -29,7 +30,8 @@ class S3ImageStoreClient( } override fun removeObject(args: ImageRemoveObjectArgs): Boolean { - args.toS3Args() + args + .toS3Args() .let { s3 -> try { s3client.deleteObject(s3) @@ -44,7 +46,8 @@ class S3ImageStoreClient( } override fun putObject(args: ImagePutObjectArgs): ImageWriteResponse? { - args.toS3Args() + args + .toS3Args() .let { s3 -> try { s3client.putObject(s3).let { owr -> @@ -53,7 +56,7 @@ class S3ImageStoreClient( region, args.imagePath, owr.eTag ?: "", - owr.versionId ?: "" + owr.versionId ?: "", ) } } catch (e: Exception) { diff --git a/storage/src/main/kotlin/storage/image/client/dto/ImageObjectArgs.kt b/storage/src/main/kotlin/storage/image/client/dto/ImageObjectArgs.kt index 89037e2a5..261a39ca9 100644 --- a/storage/src/main/kotlin/storage/image/client/dto/ImageObjectArgs.kt +++ b/storage/src/main/kotlin/storage/image/client/dto/ImageObjectArgs.kt @@ -8,17 +8,14 @@ import com.amazonaws.services.s3.model.PutObjectRequest import org.apache.http.entity.ContentType import java.io.InputStream -fun ImageGetPreSignedObjectUrlArgs.toS3Args(): GeneratePresignedUrlRequest { - return GeneratePresignedUrlRequest( +fun ImageGetPreSignedObjectUrlArgs.toS3Args(): GeneratePresignedUrlRequest = + GeneratePresignedUrlRequest( this.bucket, this.imagePath, - HttpMethod.valueOf(this.method) + HttpMethod.valueOf(this.method), ) -} -fun ImageRemoveObjectArgs.toS3Args(): DeleteObjectRequest { - return DeleteObjectRequest(this.bucket, this.imagePath) -} +fun ImageRemoveObjectArgs.toS3Args(): DeleteObjectRequest = DeleteObjectRequest(this.bucket, this.imagePath) fun ImagePutObjectArgs.toS3Args(): PutObjectRequest { val objectSize = this.objectSize @@ -30,7 +27,7 @@ fun ImagePutObjectArgs.toS3Args(): PutObjectRequest { ObjectMetadata().apply { this.contentType = contentType this.contentLength = objectSize - } + }, ) } diff --git a/storage/src/main/kotlin/storage/image/client/util/ImageArgsGenerator.kt b/storage/src/main/kotlin/storage/image/client/util/ImageArgsGenerator.kt index f5345263d..cea621732 100644 --- a/storage/src/main/kotlin/storage/image/client/util/ImageArgsGenerator.kt +++ b/storage/src/main/kotlin/storage/image/client/util/ImageArgsGenerator.kt @@ -1,35 +1,42 @@ package storage.image.client.util +import org.apache.http.entity.ContentType import storage.image.client.dto.ImageGetPreSignedObjectUrlArgs import storage.image.client.dto.ImagePutObjectArgs import storage.image.client.dto.ImageRemoveObjectArgs -import org.apache.http.entity.ContentType import java.io.BufferedInputStream import java.io.File import java.io.FileInputStream class ImageArgsGenerator { companion object { - fun preSignedUrl(bucket: String, image: String): ImageGetPreSignedObjectUrlArgs { - return ImageGetPreSignedObjectUrlArgs(bucket, image, "GET") - } + fun preSignedUrl( + bucket: String, + image: String, + ): ImageGetPreSignedObjectUrlArgs = ImageGetPreSignedObjectUrlArgs(bucket, image, "GET") - fun putImage(bucket: String, name: String, image: File): ImagePutObjectArgs { - val contentType = image.extension.let { ext -> - when (ext) { - "jpg", "jpeg" -> ContentType.IMAGE_JPEG - "png" -> ContentType.IMAGE_PNG - "gif" -> ContentType.IMAGE_GIF - "svg" -> ContentType.IMAGE_SVG - "webp" -> ContentType.IMAGE_WEBP - else -> throw IllegalArgumentException("Unsupported image type: $ext") + fun putImage( + bucket: String, + name: String, + image: File, + ): ImagePutObjectArgs { + val contentType = + image.extension.let { ext -> + when (ext) { + "jpg", "jpeg" -> ContentType.IMAGE_JPEG + "png" -> ContentType.IMAGE_PNG + "gif" -> ContentType.IMAGE_GIF + "svg" -> ContentType.IMAGE_SVG + "webp" -> ContentType.IMAGE_WEBP + else -> throw IllegalArgumentException("Unsupported image type: $ext") + } } - } return ImagePutObjectArgs(bucket, name, BufferedInputStream(FileInputStream(image)), image.length(), -1, contentType) } - fun remove(bucket: String, image: String): ImageRemoveObjectArgs { - return ImageRemoveObjectArgs(bucket, image) - } + fun remove( + bucket: String, + image: String, + ): ImageRemoveObjectArgs = ImageRemoveObjectArgs(bucket, image) } } \ No newline at end of file diff --git a/storage/src/main/kotlin/storage/image/config/ImageStorageConfig.kt b/storage/src/main/kotlin/storage/image/config/ImageStorageConfig.kt index 9aaf24b7c..73344d87d 100644 --- a/storage/src/main/kotlin/storage/image/config/ImageStorageConfig.kt +++ b/storage/src/main/kotlin/storage/image/config/ImageStorageConfig.kt @@ -2,10 +2,10 @@ package storage.image.config import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Bean -import storage.config.StorageClientConfig import org.springframework.context.annotation.ComponentScan import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Import +import storage.config.StorageClientConfig import storage.image.config.properties.CdnProperty @Configuration @@ -21,7 +21,5 @@ class ImageStorageConfig { @Bean(name = [CDN_PROPERTY]) fun cdnProperty( @Value("\${cdn.url}") url: String, - ): CdnProperty { - return CdnProperty(url) - } + ): CdnProperty = CdnProperty(url) } \ No newline at end of file diff --git a/storage/src/main/kotlin/storage/image/config/S3ImageStoreConfig.kt b/storage/src/main/kotlin/storage/image/config/S3ImageStoreConfig.kt index 4783095cd..cc0517cd2 100644 --- a/storage/src/main/kotlin/storage/image/config/S3ImageStoreConfig.kt +++ b/storage/src/main/kotlin/storage/image/config/S3ImageStoreConfig.kt @@ -1,8 +1,6 @@ package storage.image.config import com.amazonaws.services.s3.AmazonS3Client -import storage.image.client.ImageStoreClient -import storage.image.client.S3ImageStoreClient import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.beans.factory.annotation.Value import org.springframework.context.ApplicationListener @@ -12,6 +10,8 @@ import org.springframework.context.event.ContextRefreshedEvent import storage.image.GetPreSignedImageUrlProvider import storage.image.PutImageProvider import storage.image.RemoveImageProvider +import storage.image.client.ImageStoreClient +import storage.image.client.S3ImageStoreClient import storage.image.provider.s3.S3GetPreSignedImageUrlProvider import storage.image.provider.s3.S3PutImageProvider import storage.image.provider.s3.S3RemoveImageProvider @@ -22,6 +22,7 @@ class S3ImageStoreConfig( @Value("\${storage.region}") val region: String, ) : ApplicationListener { private val log = KotlinLogging.logger {} + companion object { const val S3_IMAGE_STORE_CLIENT = ImageStorageConfig.BEAN_NAME_PREFIX + "S3ImageStoreClient" const val S3_PUT_IMAGE_PROVIDER = ImageStorageConfig.BEAN_NAME_PREFIX + "S3PutImageProvider" @@ -53,23 +54,17 @@ class S3ImageStoreConfig( fun s3PutImageProvider( @Value("\${image.store.bucket-name}") bucket: String, imageStoreClient: ImageStoreClient, - ): PutImageProvider { - return S3PutImageProvider(bucket, imageStoreClient) - } + ): PutImageProvider = S3PutImageProvider(bucket, imageStoreClient) @Bean(name = [S3_GET_PRE_SIGNED_IMAGE_URL_PROVIDER]) fun s3GetPreSignedImageUrlProvider( @Value("\${image.store.bucket-name}") bucket: String, imageStoreClient: ImageStoreClient, - ): GetPreSignedImageUrlProvider { - return S3GetPreSignedImageUrlProvider(bucket, imageStoreClient) - } + ): GetPreSignedImageUrlProvider = S3GetPreSignedImageUrlProvider(bucket, imageStoreClient) @Bean(name = [S3_REMOVE_IMAGE_PROVIDER]) fun s3DeleteImageProvider( @Value("\${image.store.bucket-name}") bucket: String, imageStoreClient: ImageStoreClient, - ): RemoveImageProvider { - return S3RemoveImageProvider(bucket, imageStoreClient) - } + ): RemoveImageProvider = S3RemoveImageProvider(bucket, imageStoreClient) } \ No newline at end of file diff --git a/storage/src/main/kotlin/storage/image/provider/s3/S3GetPreSignedImageUrlProvider.kt b/storage/src/main/kotlin/storage/image/provider/s3/S3GetPreSignedImageUrlProvider.kt index a877d7533..8d63cb627 100644 --- a/storage/src/main/kotlin/storage/image/provider/s3/S3GetPreSignedImageUrlProvider.kt +++ b/storage/src/main/kotlin/storage/image/provider/s3/S3GetPreSignedImageUrlProvider.kt @@ -1,8 +1,8 @@ package storage.image.provider.s3 +import storage.image.GetPreSignedImageUrlProvider import storage.image.client.ImageStoreClient import storage.image.client.util.ImageArgsGenerator -import storage.image.GetPreSignedImageUrlProvider class S3GetPreSignedImageUrlProvider( val bucket: String, diff --git a/storage/src/main/kotlin/storage/image/provider/s3/S3PutImageProvider.kt b/storage/src/main/kotlin/storage/image/provider/s3/S3PutImageProvider.kt index 042cdc4c5..469802ebd 100644 --- a/storage/src/main/kotlin/storage/image/provider/s3/S3PutImageProvider.kt +++ b/storage/src/main/kotlin/storage/image/provider/s3/S3PutImageProvider.kt @@ -1,16 +1,19 @@ package storage.image.provider.s3 +import storage.image.PutImageProvider import storage.image.client.ImageStoreClient import storage.image.client.dto.ImageWriteResponse import storage.image.client.util.ImageArgsGenerator -import storage.image.PutImageProvider import java.io.File class S3PutImageProvider( val bucket: String, private val imageStoreClient: ImageStoreClient, ) : PutImageProvider { - override fun execute(name: String, file: File): ImageWriteResponse? { + override fun execute( + name: String, + file: File, + ): ImageWriteResponse? { ImageArgsGenerator.putImage(bucket, name, file).let { args -> return imageStoreClient.putObject(args) } diff --git a/storage/src/main/kotlin/storage/image/provider/s3/S3RemoveImageProvider.kt b/storage/src/main/kotlin/storage/image/provider/s3/S3RemoveImageProvider.kt index 7aa3aef6f..f901138fc 100644 --- a/storage/src/main/kotlin/storage/image/provider/s3/S3RemoveImageProvider.kt +++ b/storage/src/main/kotlin/storage/image/provider/s3/S3RemoveImageProvider.kt @@ -1,8 +1,8 @@ package storage.image.provider.s3 +import storage.image.RemoveImageProvider import storage.image.client.ImageStoreClient import storage.image.client.util.ImageArgsGenerator -import storage.image.RemoveImageProvider class S3RemoveImageProvider( val bucket: String, diff --git a/web/src/main/kotlin/web/ApiResponse.kt b/web/src/main/kotlin/web/ApiResponse.kt index d6b0c9135..cb1682e84 100644 --- a/web/src/main/kotlin/web/ApiResponse.kt +++ b/web/src/main/kotlin/web/ApiResponse.kt @@ -8,7 +8,9 @@ class ApiResponse : ResponseEntity { constructor(status: HttpStatus?) : super(status!!) constructor(body: B, status: HttpStatus?) : super(body, status!!) - class FailureBody(val message: String) : Serializable + class FailureBody( + val message: String, + ) : Serializable class SuccessBody( val data: D, diff --git a/web/src/main/kotlin/web/ApiResponseGenerator.kt b/web/src/main/kotlin/web/ApiResponseGenerator.kt index a961d1895..9be66b772 100644 --- a/web/src/main/kotlin/web/ApiResponseGenerator.kt +++ b/web/src/main/kotlin/web/ApiResponseGenerator.kt @@ -3,60 +3,48 @@ package web import org.springframework.http.HttpStatus object ApiResponseGenerator { - - fun success(status: HttpStatus): ApiResponse { - return ApiResponse( + fun success(status: HttpStatus): ApiResponse = + ApiResponse( ApiResponse.Success(MessageCode.SUCCESS.value), - status + status, ) - } fun success( status: HttpStatus, code: MessageCode, - ): ApiResponse { - return ApiResponse(ApiResponse.Success(code.value), status) - } + ): ApiResponse = ApiResponse(ApiResponse.Success(code.value), status) fun success( data: D, status: HttpStatus, - ): ApiResponse> { - return ApiResponse( + ): ApiResponse> = + ApiResponse( ApiResponse.SuccessBody( data, - MessageCode.SUCCESS.value + MessageCode.SUCCESS.value, ), - status + status, ) - } fun success( data: D, status: HttpStatus, code: MessageCode, - ): ApiResponse> { - return ApiResponse( + ): ApiResponse> = + ApiResponse( ApiResponse.SuccessBody(data, code.value), - status + status, ) - } - fun fail(status: HttpStatus): ApiResponse { - return ApiResponse(status) - } + fun fail(status: HttpStatus): ApiResponse = ApiResponse(status) fun fail( body: ApiResponse.FailureBody, status: HttpStatus, - ): ApiResponse { - return ApiResponse(body, status) - } + ): ApiResponse = ApiResponse(body, status) fun fail( message: String, status: HttpStatus, - ): ApiResponse { - return ApiResponse(ApiResponse.FailureBody(message), status) - } + ): ApiResponse = ApiResponse(ApiResponse.FailureBody(message), status) } \ No newline at end of file diff --git a/web/src/main/kotlin/web/ExceptionMessage.kt b/web/src/main/kotlin/web/ExceptionMessage.kt index 5b41d07f5..570892ff6 100644 --- a/web/src/main/kotlin/web/ExceptionMessage.kt +++ b/web/src/main/kotlin/web/ExceptionMessage.kt @@ -1,6 +1,9 @@ package web -enum class ExceptionMessage(val code: String, val message: String) { +enum class ExceptionMessage( + val code: String, + val message: String, +) { FAIL("fail", "알 수 없는 오류가 발생했어요."), FAIL_NOT_FOUND("fail.notfound", "일치하는 결과를 찾을 수 없어요."), FAIL_AUTHENTICATION("fail.authentication", "인증이 필요해요."), diff --git a/web/src/main/kotlin/web/MessageCode.kt b/web/src/main/kotlin/web/MessageCode.kt index be8c84f80..02d273bdf 100644 --- a/web/src/main/kotlin/web/MessageCode.kt +++ b/web/src/main/kotlin/web/MessageCode.kt @@ -1,6 +1,9 @@ package web -enum class MessageCode(val code: String, val value: String) { +enum class MessageCode( + val code: String, + val value: String, +) { SUCCESS("success", "성공"), RESOURCE_DELETED("resource.deleted", "삭제되었습니다."), RESOURCE_UPDATED("resource.updated", "수정되었습니다."), diff --git a/web/src/main/kotlin/web/client/config/ClientConfig.kt b/web/src/main/kotlin/web/client/config/ClientConfig.kt index 958fc7df5..9ae103236 100644 --- a/web/src/main/kotlin/web/client/config/ClientConfig.kt +++ b/web/src/main/kotlin/web/client/config/ClientConfig.kt @@ -10,7 +10,6 @@ import java.time.Duration @Configuration class ClientConfig { - companion object { const val REST_TEMPLATE = WebConfig.BEAN_NAME_PREFIX + "RestTemplate" } @@ -20,10 +19,9 @@ class ClientConfig { restTemplateBuilder: RestTemplateBuilder, @Value("\${web.client.timeout.connect}") connectTimeout: Int, @Value("\${web.client.timeout.read}") readTimeout: Int, - ): RestTemplate { - return restTemplateBuilder + ): RestTemplate = + restTemplateBuilder .setConnectTimeout(Duration.ofSeconds(connectTimeout.toLong())) .setReadTimeout(Duration.ofSeconds(readTimeout.toLong())) .build() - } } \ No newline at end of file diff --git a/web/src/main/kotlin/web/config/WebConfig.kt b/web/src/main/kotlin/web/config/WebConfig.kt index a186a257f..96897b74a 100644 --- a/web/src/main/kotlin/web/config/WebConfig.kt +++ b/web/src/main/kotlin/web/config/WebConfig.kt @@ -14,7 +14,7 @@ import web.handler.LoggingHandler @Configuration @ComponentScan(basePackages = [WebConfig.BASE_PACKAGE]) @Import( - SecurityConfig::class + SecurityConfig::class, ) class WebConfig { companion object { @@ -26,17 +26,11 @@ class WebConfig { } @Bean(name = [WEB_CONFIGURER]) - fun webConfigurer(): WebMvcConfigurer { - return WebConfigurer() - } + fun webConfigurer(): WebMvcConfigurer = WebConfigurer() @Bean(name = [MDC_LOG_FILTER]) - fun mdcLogFilter(objectMapper: ObjectMapper): Filter { - return MDCLogFilter(objectMapper) - } + fun mdcLogFilter(objectMapper: ObjectMapper): Filter = MDCLogFilter(objectMapper) @Bean(name = [LOGGING_HANDLER]) - fun loggingHandler(): LoggingHandler { - return LoggingHandler() - } + fun loggingHandler(): LoggingHandler = LoggingHandler() } \ No newline at end of file diff --git a/web/src/main/kotlin/web/config/WebConfigurer.kt b/web/src/main/kotlin/web/config/WebConfigurer.kt index 2e2de77ed..e5f2cdae6 100644 --- a/web/src/main/kotlin/web/config/WebConfigurer.kt +++ b/web/src/main/kotlin/web/config/WebConfigurer.kt @@ -6,9 +6,9 @@ import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry import org.springframework.web.servlet.config.annotation.WebMvcConfigurer class WebConfigurer : WebMvcConfigurer { - override fun addCorsMappings(registry: CorsRegistry) { - registry.addMapping("/**") + registry + .addMapping("/**") .allowedOriginPatterns(CorsConfiguration.ALL) .allowedMethods(CorsConfiguration.ALL) .allowedHeaders(CorsConfiguration.ALL) @@ -17,7 +17,8 @@ class WebConfigurer : WebMvcConfigurer { } override fun addResourceHandlers(registry: ResourceHandlerRegistry) { - registry.addResourceHandler("/**") + registry + .addResourceHandler("/**") .addResourceLocations("classpath:/static/") } } \ No newline at end of file diff --git a/web/src/main/kotlin/web/filter/MDCLogFilter.kt b/web/src/main/kotlin/web/filter/MDCLogFilter.kt index cb09ceff6..cacb753bf 100644 --- a/web/src/main/kotlin/web/filter/MDCLogFilter.kt +++ b/web/src/main/kotlin/web/filter/MDCLogFilter.kt @@ -11,7 +11,9 @@ import org.apache.commons.lang3.RandomStringUtils import org.slf4j.MDC import org.springframework.http.HttpHeaders -class MDCLogFilter(private val mapper: ObjectMapper) : Filter { +class MDCLogFilter( + private val mapper: ObjectMapper, +) : Filter { private val log = KotlinLogging.logger {} override fun doFilter( diff --git a/web/src/main/kotlin/web/handler/ControllerExceptionHandler.kt b/web/src/main/kotlin/web/handler/ControllerExceptionHandler.kt index 08233cc54..daa898657 100644 --- a/web/src/main/kotlin/web/handler/ControllerExceptionHandler.kt +++ b/web/src/main/kotlin/web/handler/ControllerExceptionHandler.kt @@ -22,7 +22,6 @@ import java.nio.file.AccessDeniedException class ControllerExceptionHandler( private val loggingHandler: LoggingHandler, ) { - @ExceptionHandler(IllegalArgumentException::class) fun handleBadRequest( ex: IllegalArgumentException, @@ -41,7 +40,7 @@ class ControllerExceptionHandler( DecodingException::class, ConstraintViolationException::class, ServerWebInputException::class, - HttpMessageNotReadableException::class + HttpMessageNotReadableException::class, ) fun handleBadRequest( ex: Exception, @@ -60,7 +59,7 @@ class ControllerExceptionHandler( } return ApiResponseGenerator.fail( ExceptionMessage.FAIL_REQUEST.message, - HttpStatus.BAD_REQUEST + HttpStatus.BAD_REQUEST, ) } @@ -74,7 +73,7 @@ class ControllerExceptionHandler( val messageDetail = ExceptionMessage.REQUEST_INVALID.message + " : " + filedErrors return ApiResponseGenerator.fail( messageDetail, - HttpStatus.BAD_REQUEST + HttpStatus.BAD_REQUEST, ) } @@ -86,7 +85,7 @@ class ControllerExceptionHandler( loggingHandler.writeLog(ex, request) return ApiResponseGenerator.fail( ExceptionMessage.FAIL.message, - HttpStatus.INTERNAL_SERVER_ERROR + HttpStatus.INTERNAL_SERVER_ERROR, ) } @@ -98,7 +97,7 @@ class ControllerExceptionHandler( loggingHandler.writeLog(ex, request) return ApiResponseGenerator.fail( ExceptionMessage.ACCESS_DENIED.message, - HttpStatus.FORBIDDEN + HttpStatus.FORBIDDEN, ) } @@ -110,7 +109,7 @@ class ControllerExceptionHandler( loggingHandler.writeLog(ex, request) return ApiResponseGenerator.fail( ExceptionMessage.FAIL.message, - HttpStatus.INTERNAL_SERVER_ERROR + HttpStatus.INTERNAL_SERVER_ERROR, ) } } \ No newline at end of file diff --git a/web/src/main/kotlin/web/handler/LoggingHandler.kt b/web/src/main/kotlin/web/handler/LoggingHandler.kt index 2e13537a2..e56cac4b6 100644 --- a/web/src/main/kotlin/web/handler/LoggingHandler.kt +++ b/web/src/main/kotlin/web/handler/LoggingHandler.kt @@ -6,14 +6,17 @@ import jakarta.servlet.http.HttpServletRequest class LoggingHandler { private val log = KotlinLogging.logger {} - fun writeLog(ex: Exception, request: HttpServletRequest) { + fun writeLog( + ex: Exception, + request: HttpServletRequest, + ) { try { log.error( LOG_MESSAGE_FORMAT, request.method, request.requestURL, ex.message, - ex + ex, ) } catch (e: Exception) { log.error(LOG_MESSAGE_FORMAT, UNCAUGHT_LOG_MESSAGE, UNCAUGHT_LOG_MESSAGE, e.message, e) @@ -23,638 +26,639 @@ class LoggingHandler { companion object { private const val LOG_MESSAGE_FORMAT = "{} '{}' - {}" private const val UNCAUGHT_LOG_MESSAGE = "??" - private val IGNORE_REQUEST_URI_LIST: Set = HashSet( - mutableListOf( - "/", - "/.aws/credentials", - "/.env", - "/.env.", - "/.env.backup", - "/.env.bak", - "/.env.dev", - "/.env.dev.local", - "/.env.development.local", - "/.env.ec2-3-34-67-210", - "/.env.ec2-3-37-143-124", - "/.env.example", - "/.env.live", - "/.env.local", - "/.env.old", - "/.env.prod", - "/.env.prod.local", - "/.env.production", - "/.env.production.local", - "/.env.save", - "/.env.stage", - "/.env.www", - "/.env_1", - "/.env_sample", - "/.git/HEAD", - "/.git/config", - "/.git2/config", - "/.svn/wc.db", - "/.well-known/security.txt", - "/1-200611214053U8.jpg", - "/202110/images/public.css", - "/6/", - "/71624567", - "/99vt", - "/99vu", - "/9h/", - "/:80:undefined?id=", - "/?XDEBUG_SESSION_START=phpstorm", - "/AWSconf.git/config", - "/Application/Buy/Static/js/if.js", - "/Autodiscover/Autodiscover.xml", - "/Autodiscover/autodiscover.json?a=margart@rodriguez.io/mapi/nspi/", - "/Content/Wap/base.css", - "/Content/css/wzwstylel.css", - "/Content/favicon.ico", - "/Content/m_1/js/m_1_Jquery.js", - "/Core/Skin/Login.aspx", - "/Css/Hm.css", - "/HNAP1", - "/Home/Bind/binding", - "/Home/Get/getJnd28", - "/Home/GetAllGameCategory", - "/Home/GetInitSource", - "/Home/Index/ajaxTJ", - "/JS/loginstatus.js", - "/Pay_Index.html", - "/Pc/Lang/index.html", - "/Public/Home/ecshe_css/main.css?v=1543997196", - "/Public/Home/js/cls.js", - "/Public/Mobile/ecshe_css/wapmain.css?v=1545408652", - "/Public/Wchat/css/index.css", - "/Public/Wchat/js/cvphp.js", - "/Public/css/_pk10.css", - "/Public/css/errorCss.css", - "/Public/css/hall.css", - "/Public/home/common/js/index.js", - "/Public/home/js/check.js", - "/Public/home/js/fukuang.js", - "/Public/home/wap/css/qdgame.css", - "/Public/initJs.php", - "/Public/js/common.js", - "/Public/uploads/web/step1.png", - "/ReportServer", - "/Res/font/font.css", - "/Res/login.html", - "/Scripts/common.js", - "/SiteLoader", - "/T9sA/static/image/t-06.jpg", - "/Telerik.Web.UI.WebResource.axd?type=rau", - "/Template/Mobile/js/main.js", - "/Templates/user/finance/css/userPay.css", - "/Templates/user/js/global.js", - "/V5Wz", - "/WuEL", - "/__MACOSX/.git/config", - "/_ignition/execute-solution", - "/_profiler/empty/search/results", - "/_profiler/phpinfo", - "/_wpeprivate/config.json", - "/a", - "/a/.git/config", - "/a/other/codepay/js/codepay_util.js", - "/aaaaaaaaaaaaaaaaaaaaaaaaaqr", - "/aab8", - "/aab9", - "/ab2g", - "/ab2h", - "/actuator", - "/actuator/env", - "/actuator/gateway/routes", - "/admin", - "/admin/", - "/admin/.env", - "/admin/.git/config", - "/admin/assets/js/views/login.js", - "/admin/config.php", - "/admin/webadmin.php?mod=do&act=login", - "/ajax/allcoin_a/id/0?t=0.3782499195965951", - "/amphtml/.git/config", - "/anquan/qgga.asp", - "/aomanalyzer/.git/config", - "/api", - "/api/", - "/api/.env", - "/api/.git/config", - "/api/GetConfigByKeys?keys=of_we", - "/api/Index/getLottery", - "/api/Index/getconfig", - "/api/app-info", - "/api/appVersion?mobile_system=2", - "/api/apps", - "/api/apps/config", - "/api/auth", - "/api/config", - "/api/config-init", - "/api/config/getwebsitename", - "/api/config/info", - "/api/config/proxy_servers?key=1", - "/api/contactWay", - "/api/currency/quotation_new", - "/api/customerServiceLink", - "/api/exclude/siteConfig/webSiteConfig", - "/api/getUserCertificationStatus", - "/api/getconfig.aspx", - "/api/grame/getHomePtLottery", - "/api/help_documents/nitice_list", - "/api/hevent", - "/api/home_index", - "/api/ht/xy1", - "/api/im/conf", - "/api/im/v2/app/config", - "/api/index", - "/api/index/config", - "/api/index/grailindex", - "/api/index/index", - "/api/index/webconfig", - "/api/jsonws/", - "/api/lastestBetLog", - "/api/link/platform", - "/api/linkPF", - "/api/lottery/color", - "/api/message/webInfo", - "/api/notice", - "/api/other/appSetting", - "/api/pay/query_order", - "/api/public/?service=Home.getConfig", - "/api/shares/hqStrList", - "/api/site/getInfo.do", - "/api/st/index/?ac=2", - "/api/stock/getSingleStock.do?code=002405", - "/api/sys/var", - "/api/system/system/config/get", - "/api/system/systemConfigs/getCustomerServiceLink", - "/api/uploads/apimap", - "/api/user/dataDictionaryService/list", - "/api/user/index", - "/api/user/ismustmobile", - "/api/user/mobilelogin", - "/api/v/index/queryOfficePage?officeCode=customHomeLink", - "/api/v1", - "/api/v1/about", - "/api/v1/app-info", - "/api/v1/clients", - "/api/v1/config", - "/api/v1/notices?page=1&per_page=1&thread_name=general", - "/api/v2/static/not.found", - "/api/vue/transaction/config", - "/apiApp/app/site/info", - "/apiapp/Ygn_Girl.CityList", - "/apis/apps/v1/namespaces/kube-system/daemonsets", - "/app/", - "/app/.git/config", - "/app/common/getRegisterSet", - "/app/config/getConfig", - "/app/js/base.js", - "/application.ini", - "/application/.git/config", - "/application/application.ini", - "/application/configs/application.ini", - "/appspec.yaml", - "/appspec.yml", - "/appxz/index.html", - "/assets../.git/config", - "/assets/AssetManifest.json", - "/assets/app-manifest.json", - "/assets/etc/query.css", - "/assets/extension/market/css/mt4.css", - "/assets/images/dy.jpg", - "/assets/images/redian.jpg", - "/assets/js/dmshub.js", - "/assets/mstock/newimg/guid-info.png", - "/assets/res/mods/room.js", - "/assets/shebao/banner.png", - "/assets/source/list.css", - "/assets/yibao/Nweshebao/img/icon2.png", - "/auth.asp", - "/autodiscover/autodiscover.json", - "/autodiscover/autodiscover.json?@test.com/owa/?&Email=autodiscover/autodiscover.json%3F@test.com", - "/autodiscover/autodiscover.json?@zdi/Powershell", - "/autodiscover/autodiscover.json?a..foo.var/owa/?&Email=autodiscover/autodiscover.json?a..foo.var&Protocol=XYZ&FooProtocol=%50owershell", - "/aws.yml", - "/backup/.git/config", - "/banner.do?code=1", - "/bapi/st/news/", - "/baseConfig", - "/bet/lotteryinfo/allLotteryInfoList", - "/beta/.git/config", - "/biz/server/config", - "/blog/.git/config", - "/blog/wp-content/themes/.git/config", - "/build/.git/config", - "/c/", - "/c/version.js", - "/cgi-bin/printenv.pl", - "/chat.html", - "/client/api/findConfigByKey?configKey=level_config", - "/client/static/icon/hangqingicon.png", - "/cms/.git/config", - "/code/js/config.js", - "/common.js", - "/common/.git/config", - "/common/member/js/user.util.js", - "/common/template/lottery/lecai/css/style.css", - "/config", - "/config.js", - "/config.js.backup", - "/config.js.bak", - "/config.js.old", - "/config.js.save", - "/config.json", - "/config/application.ini", - "/config/aws.yml", - "/config/config.json", - "/config/default.json", - "/configs/application.ini", - "/configuration.php-dist", - "/console/", - "/content../.git/config", - "/core/.env", - "/credentials/config.json", - "/csjs/bankCheck.js", - "/css../.git/config", - "/css/all.css", - "/css/m.css", - "/css/main.css", - "/css/nsc/reset.css", - "/css/other.css", - "/css/scanner.css", - "/css/skin/ymPrompt.css", - "/css/style.css", - "/d1/OK.php", - "/data/.git/config", - "/data/json/config.json", - "/data/ticker_24hr", - "/database/.git/config", - "/demo/.git/config", - "/detail1/js/add.js", - "/detaila/images/header_v1b.css", - "/detaila/images/logo.png", - "/dev/.git/config", - "/developer/.git/config", - "/ding/", - "/dist/images/mask/bg1.jpg", - "/dist/images/mask/guide/cn/step1.jpg", - "/dist/index.html?v=32d9d4", - "/dns-query?name=dnsscan.shadowserver.org&type=A", - "/dot.git/config", - "/download/file.ext", - "/dqgqoeCXckuwPtxov", - "/ecp/Current/exporttool/microsoft.exchange.ediscovery.exporttool.application", - "/env.dev.js", - "/env.development.js", - "/env.js", - "/env.prod.js", - "/env.production.js", - "/env.test.js", - "/events../.git/config", - "/evox/about", - "/favicon-16x16.png", - "/favicon-32x32.png", - "/favicon.ico", - "/fePublicInfo/", - "/files/pub_rem.js", - "/files/pub_reset.css", - "/flock/.git/config", - "/flu/403.html", - "/friendGroup/list", - "/front/index/getSiteSetting", - "/gate.php", - "/getConfig/getArticle.do?code=1", - "/getConfig/getArticle.do?code=19", - "/getConfig/listPopFrame.do?code=1&position=index&_=1601489645097", - "/getConfig/listPopFrame.do?code=14&position=index&_=1601489645097", - "/getLocale", - "/git/.git/config", - "/guocanju/shijiuling/", - "/h5", - "/h5/", - "/h5/static/cert/icon_yanzhengma.png", - "/h5/static/tabbar/txl.png", - "/hetong/yq?id=500", - "/home/", - "/home/GetQrCodeInfo", - "/home/banner/getLogo", - "/home/help", - "/home/login.jpg", - "/home/login/login_index.html", - "/home/main/login", - "/homes/", - "/hooked-center/config/offline/list", - "/im/", - "/im/App/config", - "/im/h5/", - "/im/in/GetUuid", - "/images../.git/config", - "/images/favicon.ico", - "/images/no.jpg", - "/images/src_images_but_dianz_s.png", - "/img../.git/config", - "/img/close.png", - "/img/phonetrackeronline.png", - "/img/subsidy/kh.png", - "/img/xxing.png", - "/img/zllqdk.png", - "/index", - "/index.php/User/sendsmscode", - "/index.php/Wap/Api/getBanner", - "/index.php/Wap/Api/getSystemNotice?id=1", - "/index.php/sign", - "/index.php?m=api&c=app&a=getPlatformConfig", - "/index/", - "/index/about/index.html", - "/index/api/getconfig", - "/index/aurl", - "/index/common.css", - "/index/gzid.js", - "/index/home/login.html", - "/index/images/tradeicon.png", - "/index/index/getchatLog", - "/index/index/info/", - "/index/login", - "/index/login/index", - "/index/login/index.html", - "/index/login/login", - "/index/login/reg.html", - "/index/login/register", - "/index/newapi/api", - "/index/open/login.html", - "/index/police/index.html?agent=1000", - "/index/user/get_server_info", - "/index/user/login.html", - "/index_files/bankCheck.js", - "/infe/rest/fig/advertise/common.json?mobile_open=1", - "/info.json", - "/info.php", - "/install.inc/vipsignInstall.css", - "/ipl/app/flash/publicbmw/ball/FigLeaf.js?site=member", - "/jiaoyimao/", - "/jiaoyimao/default.css", - "/jquery-3.3.1.slim.min.js", - "/jquery-3.3.2.slim.min.js", - "/js../.git/config", - "/js/a.script", - "/js/app.js", - "/js/base1.js", - "/js/basic.js", - "/js/bk.min.js", - "/js/config.js", - "/js/home.js", - "/js/index.js", - "/js/lang.js", - "/js/options.js", - "/js/post.js/", - "/js/pups.js", - "/js/subsidy/bk.min.js", - "/js/xz.js", - "/jym-wn/", - "/kkrp/site/info", - "/kkrps/im_group/show_members", - "/km.asmx/getPlatParam", - "/langConfig.js", - "/lanren/css/global.css", - "/laravel/.env", - "/leftDao.php?callback=jQuery183016740860980352856_1604309800583", - "/lib../.git/config", - "/live/.git/config", - "/loan/css/index.css", - "/login", - "/login.html", - "/login.php", - "/lottery/lottery_list", - "/m/", - "/m/.git/config", - "/m/allticker/1", - "/m/env.js", - "/mPlayer", - "/main", - "/manager/js/left.js", - "/market/market-ws/iframe.html", - "/masterControl/getSystemSetting", - "/media../.git/config", - "/melody/api/v1/pageconfig/list", - "/member/js/lang_zh_CN.js", - "/metrics", - "/mg/other/codepay/js/codepay_util.js", - "/mifs/.;/services/LogService", - "/mindex.html", - "/mmj/index.html", - "/mobile", - "/mobile/config.js", - "/mobile/css/base.css", - "/mobile/lists.html", - "/mobile/login.html", - "/mobile/lottery/list", - "/mobile/v3/appSuperDownload.do", - "/mobile21/js/index/gameManagement.js?v=7", - "/myConfig.js", - "/mytio/config/base", - "/n9Im", - "/new/.git/config", - "/newApp/winMessTopQuery.php", - "/nmaplowercheck1673396272", - "/nmaplowercheck1673717885", - "/nyyh/chkjs.js", - "/nyyh/game.css", - "/old-cuburn/.git/config", - "/openApi/systemConfig/findSystemConfigById?id=3", - "/otc/", - "/owa", - "/owa/", - "/owa/auth/logon.aspx", - "/owa/auth/logon.aspx?url=https%3a%2f%2f1%2fecp%2f", - "/owa/auth/x.js", - "/pages/console/js/common.js", - "/ph_acquireSession", - "/phalapi/public/", - "/phalapi/public/?s=System.index", - "/phone/images/icon_01.png", - "/php-info", - "/phpinfo", - "/phpinfo.php", - "/phpmyadmin/index.php", - "/phpmyadmin4.8.5/index.php", - "/platform", - "/pmd/index.php", - "/portal/index/protocol.html", - "/prod.git/config", - "/projectConfig", - "/proxy/games", - "/proxy/settings", - "/public/.git/config", - "/public/config.js", - "/public/css/style.css", - "/public/h5static/js/main.js", - "/public/img/cz1.png", - "/public/static/css/public.css", - "/public/static/home/js/moblie/login.js", - "/public/static/index/picture/img_33.png", - "/public/wap/js/basis.js", - "/qa/.git/config", - "/reg.php", - "/remote/login", - "/repos/.git/config", - "/repository/.git/config", - "/res/common/addfriend.png", - "/res/kuai/1.css", - "/res/kuai/21.css", - "/resource/home/css/person.css", - "/resource/home/js/common.js", - "/resources/css/headernav.css", - "/resources/main/common.js", - "/robots.txt", - "/room/getRoomBangFans", - "/room/script/face.js", - "/s3/.git/config", - "/s_api/basic/config_js?callback=__set_config", - "/s_api/basic/download/info", - "/saconfig/secure/yunwei.js", - "/samples/.git/config", - "/sdk", - "/sdk.html", - "/server/business/api/customer/list", - "/service?action=getBasicInfo&terminal_id=2&token=&debug=true", - "/shop/.git/config", - "/showLogin.cc", - "/site.js", - "/site/.git/config", - "/site/api/v1/site/vipExclusiveDomain/getGuestDomain", - "/site/get-hq?proNo=btc&panType=1&pid=1", - "/site/info", - "/sitemap.xml", - "/skin/js/common.js", - "/skin/main/onload.js", - "/solr/", - "/source/20220119/static/wap/css/trade-history.css", - "/source/20220119/static/wap/js/order.js", - "/stage-api/common/configKey/all", - "/staging/.git/config", - "/stalker_portal/c/version.js", - "/stalker_portal/server/tools/auth_simple.php", - "/static../.git/config", - "/static/.git/config", - "/static/Mobile/js/common.js", - "/static/_zxzx/bottom/index_bottom.shtml", - "/static/admin/javascript/hetong.js", - "/static/admincp/js/common.js", - "/static/common/js/common.js", - "/static/common/js/vant/vant.min.js", - "/static/config.js", - "/static/content.html", - "/static/css/index.css", - "/static/css/mobile.css", - "/static/css/public.css", - "/static/css/reset.css", - "/static/customer/js/xiaotian.cli.v2.js", - "/static/data/configjs.js", - "/static/data/gamedatas.js", - "/static/data/thirdgames.json", - "/static/diff_worker.js", - "/static/f_title.png", - "/static/guide/ab.css", - "/static/home/2022/index1.js", - "/static/home/css/common.css", - "/static/home/css/css.css", - "/static/home/css/feiqi-ee5401a8e6.css", - "/static/home/imgs/jkjl.png", - "/static/home/imgs/pico.png", - "/static/home/js/rooms.js", - "/static/image/bg1.jpg", - "/static/image/logo.png?v=4", - "/static/images/login_bg.jpg", - "/static/img/bitbeb-logo.png", - "/static/index/3.png", - "/static/index/css/trade-history.css", - "/static/index/js/lk/order.js", - "/static/js/app.js", - "/static/js/config.js", - "/static/js/download.js", - "/static/js/user.js", - "/static/js/view.js", - "/static/local/img/userCenter/hourlyPrivilege.svg", - "/static/m_text.png", - "/static/mobile/user.html", - "/static/tabBar/trade.png", - "/static/v/v2/image/star.png", - "/static/v2/css/index.css", - "/static/voice/default.wav", - "/static/wap/css/common.css", - "/static/wap/css/index.css", - "/static/wap/css/tipmask.css", - "/static/wap/css/trade-history.css", - "/static/wap/js/common.js", - "/static/wap/js/order.js", - "/statics/js/API.js", - "/step1.asp", - "/stock/mzhishu", - "/stock/search.html?keyword=00202", - "/stock2c1/api/site/getInfo.do", - "/store/.git/config", - "/stream/live.php", - "/streaming/clients_live.php", - "/sucai/zxkf.png", - "/system_api.php", - "/t000/login-img-3.svg", - "/template/920ka/css/lsy.css", - "/template/920ka/js/woodyapp.js", - "/template/css/login.css", - "/template/mb/lang/text-zh.json", - "/template/tmp1/js/common.js", - "/test.php", - "/themes/pay/assets/core.css", - "/thriveGame.css", - "/uis/app/get/config", - "/uploads/20210928/01737e4f85f971bdf4d892dfaee6579c.png", - "/uploads/admin/202103/60584c5b88017.png", - "/user/Login", - "/user/getAllNicknames", - "/uw79", - "/v1/getConfig", - "/v1/management/tenant/getSpeedDomain", - "/v2/", - "/v2/block/home/app/hot", - "/v2/game/rooms", - "/v6/getAppContent", - "/vendor/.git/config", - "/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", - "/verification.asp", - "/version", - "/video/65788.html", - "/video/67950.html", - "/views/commData/commonSite.js", - "/views/home/home.js", - "/wap", - "/wap.html", - "/wap/", - "/wap/api/exchangerateuserconfig!get.action", - "/wap/assets/images/icon1/12306.png", - "/wap/forward", - "/wap/trading/lastKlineParameter", - "/waplogin.php", - "/web/.git/config", - "/webclient/", - "/wiki/.git/config", - "/wp-content/.git/config", - "/wp-content/plugins/.git/config", - "/wp-content/themes/.git/config", - "/wp-includes/js/.git/config", - "/wp-login.php", - "/ws/index/getTheLotteryInitList", - "/ws/market", - "/xianyu/", - "/xianyu/image/jiantou.png", - "/xy/", - "/xy/image/jiantou.png", - "/yinjian/", - "/yr99", - "/ztp/cgi-bin/handle", - "/zz/address.php?gid=651", - "/zz2/address.php?gid=651" + private val IGNORE_REQUEST_URI_LIST: Set = + HashSet( + mutableListOf( + "/", + "/.aws/credentials", + "/.env", + "/.env.", + "/.env.backup", + "/.env.bak", + "/.env.dev", + "/.env.dev.local", + "/.env.development.local", + "/.env.ec2-3-34-67-210", + "/.env.ec2-3-37-143-124", + "/.env.example", + "/.env.live", + "/.env.local", + "/.env.old", + "/.env.prod", + "/.env.prod.local", + "/.env.production", + "/.env.production.local", + "/.env.save", + "/.env.stage", + "/.env.www", + "/.env_1", + "/.env_sample", + "/.git/HEAD", + "/.git/config", + "/.git2/config", + "/.svn/wc.db", + "/.well-known/security.txt", + "/1-200611214053U8.jpg", + "/202110/images/public.css", + "/6/", + "/71624567", + "/99vt", + "/99vu", + "/9h/", + "/:80:undefined?id=", + "/?XDEBUG_SESSION_START=phpstorm", + "/AWSconf.git/config", + "/Application/Buy/Static/js/if.js", + "/Autodiscover/Autodiscover.xml", + "/Autodiscover/autodiscover.json?a=margart@rodriguez.io/mapi/nspi/", + "/Content/Wap/base.css", + "/Content/css/wzwstylel.css", + "/Content/favicon.ico", + "/Content/m_1/js/m_1_Jquery.js", + "/Core/Skin/Login.aspx", + "/Css/Hm.css", + "/HNAP1", + "/Home/Bind/binding", + "/Home/Get/getJnd28", + "/Home/GetAllGameCategory", + "/Home/GetInitSource", + "/Home/Index/ajaxTJ", + "/JS/loginstatus.js", + "/Pay_Index.html", + "/Pc/Lang/index.html", + "/Public/Home/ecshe_css/main.css?v=1543997196", + "/Public/Home/js/cls.js", + "/Public/Mobile/ecshe_css/wapmain.css?v=1545408652", + "/Public/Wchat/css/index.css", + "/Public/Wchat/js/cvphp.js", + "/Public/css/_pk10.css", + "/Public/css/errorCss.css", + "/Public/css/hall.css", + "/Public/home/common/js/index.js", + "/Public/home/js/check.js", + "/Public/home/js/fukuang.js", + "/Public/home/wap/css/qdgame.css", + "/Public/initJs.php", + "/Public/js/common.js", + "/Public/uploads/web/step1.png", + "/ReportServer", + "/Res/font/font.css", + "/Res/login.html", + "/Scripts/common.js", + "/SiteLoader", + "/T9sA/static/image/t-06.jpg", + "/Telerik.Web.UI.WebResource.axd?type=rau", + "/Template/Mobile/js/main.js", + "/Templates/user/finance/css/userPay.css", + "/Templates/user/js/global.js", + "/V5Wz", + "/WuEL", + "/__MACOSX/.git/config", + "/_ignition/execute-solution", + "/_profiler/empty/search/results", + "/_profiler/phpinfo", + "/_wpeprivate/config.json", + "/a", + "/a/.git/config", + "/a/other/codepay/js/codepay_util.js", + "/aaaaaaaaaaaaaaaaaaaaaaaaaqr", + "/aab8", + "/aab9", + "/ab2g", + "/ab2h", + "/actuator", + "/actuator/env", + "/actuator/gateway/routes", + "/admin", + "/admin/", + "/admin/.env", + "/admin/.git/config", + "/admin/assets/js/views/login.js", + "/admin/config.php", + "/admin/webadmin.php?mod=do&act=login", + "/ajax/allcoin_a/id/0?t=0.3782499195965951", + "/amphtml/.git/config", + "/anquan/qgga.asp", + "/aomanalyzer/.git/config", + "/api", + "/api/", + "/api/.env", + "/api/.git/config", + "/api/GetConfigByKeys?keys=of_we", + "/api/Index/getLottery", + "/api/Index/getconfig", + "/api/app-info", + "/api/appVersion?mobile_system=2", + "/api/apps", + "/api/apps/config", + "/api/auth", + "/api/config", + "/api/config-init", + "/api/config/getwebsitename", + "/api/config/info", + "/api/config/proxy_servers?key=1", + "/api/contactWay", + "/api/currency/quotation_new", + "/api/customerServiceLink", + "/api/exclude/siteConfig/webSiteConfig", + "/api/getUserCertificationStatus", + "/api/getconfig.aspx", + "/api/grame/getHomePtLottery", + "/api/help_documents/nitice_list", + "/api/hevent", + "/api/home_index", + "/api/ht/xy1", + "/api/im/conf", + "/api/im/v2/app/config", + "/api/index", + "/api/index/config", + "/api/index/grailindex", + "/api/index/index", + "/api/index/webconfig", + "/api/jsonws/", + "/api/lastestBetLog", + "/api/link/platform", + "/api/linkPF", + "/api/lottery/color", + "/api/message/webInfo", + "/api/notice", + "/api/other/appSetting", + "/api/pay/query_order", + "/api/public/?service=Home.getConfig", + "/api/shares/hqStrList", + "/api/site/getInfo.do", + "/api/st/index/?ac=2", + "/api/stock/getSingleStock.do?code=002405", + "/api/sys/var", + "/api/system/system/config/get", + "/api/system/systemConfigs/getCustomerServiceLink", + "/api/uploads/apimap", + "/api/user/dataDictionaryService/list", + "/api/user/index", + "/api/user/ismustmobile", + "/api/user/mobilelogin", + "/api/v/index/queryOfficePage?officeCode=customHomeLink", + "/api/v1", + "/api/v1/about", + "/api/v1/app-info", + "/api/v1/clients", + "/api/v1/config", + "/api/v1/notices?page=1&per_page=1&thread_name=general", + "/api/v2/static/not.found", + "/api/vue/transaction/config", + "/apiApp/app/site/info", + "/apiapp/Ygn_Girl.CityList", + "/apis/apps/v1/namespaces/kube-system/daemonsets", + "/app/", + "/app/.git/config", + "/app/common/getRegisterSet", + "/app/config/getConfig", + "/app/js/base.js", + "/application.ini", + "/application/.git/config", + "/application/application.ini", + "/application/configs/application.ini", + "/appspec.yaml", + "/appspec.yml", + "/appxz/index.html", + "/assets../.git/config", + "/assets/AssetManifest.json", + "/assets/app-manifest.json", + "/assets/etc/query.css", + "/assets/extension/market/css/mt4.css", + "/assets/images/dy.jpg", + "/assets/images/redian.jpg", + "/assets/js/dmshub.js", + "/assets/mstock/newimg/guid-info.png", + "/assets/res/mods/room.js", + "/assets/shebao/banner.png", + "/assets/source/list.css", + "/assets/yibao/Nweshebao/img/icon2.png", + "/auth.asp", + "/autodiscover/autodiscover.json", + "/autodiscover/autodiscover.json?@test.com/owa/?&Email=autodiscover/autodiscover.json%3F@test.com", + "/autodiscover/autodiscover.json?@zdi/Powershell", + "/autodiscover/autodiscover.json?a..foo.var/owa/?&Email=autodiscover/autodiscover.json?a..foo.var&Protocol=XYZ&FooProtocol=%50owershell", + "/aws.yml", + "/backup/.git/config", + "/banner.do?code=1", + "/bapi/st/news/", + "/baseConfig", + "/bet/lotteryinfo/allLotteryInfoList", + "/beta/.git/config", + "/biz/server/config", + "/blog/.git/config", + "/blog/wp-content/themes/.git/config", + "/build/.git/config", + "/c/", + "/c/version.js", + "/cgi-bin/printenv.pl", + "/chat.html", + "/client/api/findConfigByKey?configKey=level_config", + "/client/static/icon/hangqingicon.png", + "/cms/.git/config", + "/code/js/config.js", + "/common.js", + "/common/.git/config", + "/common/member/js/user.util.js", + "/common/template/lottery/lecai/css/style.css", + "/config", + "/config.js", + "/config.js.backup", + "/config.js.bak", + "/config.js.old", + "/config.js.save", + "/config.json", + "/config/application.ini", + "/config/aws.yml", + "/config/config.json", + "/config/default.json", + "/configs/application.ini", + "/configuration.php-dist", + "/console/", + "/content../.git/config", + "/core/.env", + "/credentials/config.json", + "/csjs/bankCheck.js", + "/css../.git/config", + "/css/all.css", + "/css/m.css", + "/css/main.css", + "/css/nsc/reset.css", + "/css/other.css", + "/css/scanner.css", + "/css/skin/ymPrompt.css", + "/css/style.css", + "/d1/OK.php", + "/data/.git/config", + "/data/json/config.json", + "/data/ticker_24hr", + "/database/.git/config", + "/demo/.git/config", + "/detail1/js/add.js", + "/detaila/images/header_v1b.css", + "/detaila/images/logo.png", + "/dev/.git/config", + "/developer/.git/config", + "/ding/", + "/dist/images/mask/bg1.jpg", + "/dist/images/mask/guide/cn/step1.jpg", + "/dist/index.html?v=32d9d4", + "/dns-query?name=dnsscan.shadowserver.org&type=A", + "/dot.git/config", + "/download/file.ext", + "/dqgqoeCXckuwPtxov", + "/ecp/Current/exporttool/microsoft.exchange.ediscovery.exporttool.application", + "/env.dev.js", + "/env.development.js", + "/env.js", + "/env.prod.js", + "/env.production.js", + "/env.test.js", + "/events../.git/config", + "/evox/about", + "/favicon-16x16.png", + "/favicon-32x32.png", + "/favicon.ico", + "/fePublicInfo/", + "/files/pub_rem.js", + "/files/pub_reset.css", + "/flock/.git/config", + "/flu/403.html", + "/friendGroup/list", + "/front/index/getSiteSetting", + "/gate.php", + "/getConfig/getArticle.do?code=1", + "/getConfig/getArticle.do?code=19", + "/getConfig/listPopFrame.do?code=1&position=index&_=1601489645097", + "/getConfig/listPopFrame.do?code=14&position=index&_=1601489645097", + "/getLocale", + "/git/.git/config", + "/guocanju/shijiuling/", + "/h5", + "/h5/", + "/h5/static/cert/icon_yanzhengma.png", + "/h5/static/tabbar/txl.png", + "/hetong/yq?id=500", + "/home/", + "/home/GetQrCodeInfo", + "/home/banner/getLogo", + "/home/help", + "/home/login.jpg", + "/home/login/login_index.html", + "/home/main/login", + "/homes/", + "/hooked-center/config/offline/list", + "/im/", + "/im/App/config", + "/im/h5/", + "/im/in/GetUuid", + "/images../.git/config", + "/images/favicon.ico", + "/images/no.jpg", + "/images/src_images_but_dianz_s.png", + "/img../.git/config", + "/img/close.png", + "/img/phonetrackeronline.png", + "/img/subsidy/kh.png", + "/img/xxing.png", + "/img/zllqdk.png", + "/index", + "/index.php/User/sendsmscode", + "/index.php/Wap/Api/getBanner", + "/index.php/Wap/Api/getSystemNotice?id=1", + "/index.php/sign", + "/index.php?m=api&c=app&a=getPlatformConfig", + "/index/", + "/index/about/index.html", + "/index/api/getconfig", + "/index/aurl", + "/index/common.css", + "/index/gzid.js", + "/index/home/login.html", + "/index/images/tradeicon.png", + "/index/index/getchatLog", + "/index/index/info/", + "/index/login", + "/index/login/index", + "/index/login/index.html", + "/index/login/login", + "/index/login/reg.html", + "/index/login/register", + "/index/newapi/api", + "/index/open/login.html", + "/index/police/index.html?agent=1000", + "/index/user/get_server_info", + "/index/user/login.html", + "/index_files/bankCheck.js", + "/infe/rest/fig/advertise/common.json?mobile_open=1", + "/info.json", + "/info.php", + "/install.inc/vipsignInstall.css", + "/ipl/app/flash/publicbmw/ball/FigLeaf.js?site=member", + "/jiaoyimao/", + "/jiaoyimao/default.css", + "/jquery-3.3.1.slim.min.js", + "/jquery-3.3.2.slim.min.js", + "/js../.git/config", + "/js/a.script", + "/js/app.js", + "/js/base1.js", + "/js/basic.js", + "/js/bk.min.js", + "/js/config.js", + "/js/home.js", + "/js/index.js", + "/js/lang.js", + "/js/options.js", + "/js/post.js/", + "/js/pups.js", + "/js/subsidy/bk.min.js", + "/js/xz.js", + "/jym-wn/", + "/kkrp/site/info", + "/kkrps/im_group/show_members", + "/km.asmx/getPlatParam", + "/langConfig.js", + "/lanren/css/global.css", + "/laravel/.env", + "/leftDao.php?callback=jQuery183016740860980352856_1604309800583", + "/lib../.git/config", + "/live/.git/config", + "/loan/css/index.css", + "/login", + "/login.html", + "/login.php", + "/lottery/lottery_list", + "/m/", + "/m/.git/config", + "/m/allticker/1", + "/m/env.js", + "/mPlayer", + "/main", + "/manager/js/left.js", + "/market/market-ws/iframe.html", + "/masterControl/getSystemSetting", + "/media../.git/config", + "/melody/api/v1/pageconfig/list", + "/member/js/lang_zh_CN.js", + "/metrics", + "/mg/other/codepay/js/codepay_util.js", + "/mifs/.;/services/LogService", + "/mindex.html", + "/mmj/index.html", + "/mobile", + "/mobile/config.js", + "/mobile/css/base.css", + "/mobile/lists.html", + "/mobile/login.html", + "/mobile/lottery/list", + "/mobile/v3/appSuperDownload.do", + "/mobile21/js/index/gameManagement.js?v=7", + "/myConfig.js", + "/mytio/config/base", + "/n9Im", + "/new/.git/config", + "/newApp/winMessTopQuery.php", + "/nmaplowercheck1673396272", + "/nmaplowercheck1673717885", + "/nyyh/chkjs.js", + "/nyyh/game.css", + "/old-cuburn/.git/config", + "/openApi/systemConfig/findSystemConfigById?id=3", + "/otc/", + "/owa", + "/owa/", + "/owa/auth/logon.aspx", + "/owa/auth/logon.aspx?url=https%3a%2f%2f1%2fecp%2f", + "/owa/auth/x.js", + "/pages/console/js/common.js", + "/ph_acquireSession", + "/phalapi/public/", + "/phalapi/public/?s=System.index", + "/phone/images/icon_01.png", + "/php-info", + "/phpinfo", + "/phpinfo.php", + "/phpmyadmin/index.php", + "/phpmyadmin4.8.5/index.php", + "/platform", + "/pmd/index.php", + "/portal/index/protocol.html", + "/prod.git/config", + "/projectConfig", + "/proxy/games", + "/proxy/settings", + "/public/.git/config", + "/public/config.js", + "/public/css/style.css", + "/public/h5static/js/main.js", + "/public/img/cz1.png", + "/public/static/css/public.css", + "/public/static/home/js/moblie/login.js", + "/public/static/index/picture/img_33.png", + "/public/wap/js/basis.js", + "/qa/.git/config", + "/reg.php", + "/remote/login", + "/repos/.git/config", + "/repository/.git/config", + "/res/common/addfriend.png", + "/res/kuai/1.css", + "/res/kuai/21.css", + "/resource/home/css/person.css", + "/resource/home/js/common.js", + "/resources/css/headernav.css", + "/resources/main/common.js", + "/robots.txt", + "/room/getRoomBangFans", + "/room/script/face.js", + "/s3/.git/config", + "/s_api/basic/config_js?callback=__set_config", + "/s_api/basic/download/info", + "/saconfig/secure/yunwei.js", + "/samples/.git/config", + "/sdk", + "/sdk.html", + "/server/business/api/customer/list", + "/service?action=getBasicInfo&terminal_id=2&token=&debug=true", + "/shop/.git/config", + "/showLogin.cc", + "/site.js", + "/site/.git/config", + "/site/api/v1/site/vipExclusiveDomain/getGuestDomain", + "/site/get-hq?proNo=btc&panType=1&pid=1", + "/site/info", + "/sitemap.xml", + "/skin/js/common.js", + "/skin/main/onload.js", + "/solr/", + "/source/20220119/static/wap/css/trade-history.css", + "/source/20220119/static/wap/js/order.js", + "/stage-api/common/configKey/all", + "/staging/.git/config", + "/stalker_portal/c/version.js", + "/stalker_portal/server/tools/auth_simple.php", + "/static../.git/config", + "/static/.git/config", + "/static/Mobile/js/common.js", + "/static/_zxzx/bottom/index_bottom.shtml", + "/static/admin/javascript/hetong.js", + "/static/admincp/js/common.js", + "/static/common/js/common.js", + "/static/common/js/vant/vant.min.js", + "/static/config.js", + "/static/content.html", + "/static/css/index.css", + "/static/css/mobile.css", + "/static/css/public.css", + "/static/css/reset.css", + "/static/customer/js/xiaotian.cli.v2.js", + "/static/data/configjs.js", + "/static/data/gamedatas.js", + "/static/data/thirdgames.json", + "/static/diff_worker.js", + "/static/f_title.png", + "/static/guide/ab.css", + "/static/home/2022/index1.js", + "/static/home/css/common.css", + "/static/home/css/css.css", + "/static/home/css/feiqi-ee5401a8e6.css", + "/static/home/imgs/jkjl.png", + "/static/home/imgs/pico.png", + "/static/home/js/rooms.js", + "/static/image/bg1.jpg", + "/static/image/logo.png?v=4", + "/static/images/login_bg.jpg", + "/static/img/bitbeb-logo.png", + "/static/index/3.png", + "/static/index/css/trade-history.css", + "/static/index/js/lk/order.js", + "/static/js/app.js", + "/static/js/config.js", + "/static/js/download.js", + "/static/js/user.js", + "/static/js/view.js", + "/static/local/img/userCenter/hourlyPrivilege.svg", + "/static/m_text.png", + "/static/mobile/user.html", + "/static/tabBar/trade.png", + "/static/v/v2/image/star.png", + "/static/v2/css/index.css", + "/static/voice/default.wav", + "/static/wap/css/common.css", + "/static/wap/css/index.css", + "/static/wap/css/tipmask.css", + "/static/wap/css/trade-history.css", + "/static/wap/js/common.js", + "/static/wap/js/order.js", + "/statics/js/API.js", + "/step1.asp", + "/stock/mzhishu", + "/stock/search.html?keyword=00202", + "/stock2c1/api/site/getInfo.do", + "/store/.git/config", + "/stream/live.php", + "/streaming/clients_live.php", + "/sucai/zxkf.png", + "/system_api.php", + "/t000/login-img-3.svg", + "/template/920ka/css/lsy.css", + "/template/920ka/js/woodyapp.js", + "/template/css/login.css", + "/template/mb/lang/text-zh.json", + "/template/tmp1/js/common.js", + "/test.php", + "/themes/pay/assets/core.css", + "/thriveGame.css", + "/uis/app/get/config", + "/uploads/20210928/01737e4f85f971bdf4d892dfaee6579c.png", + "/uploads/admin/202103/60584c5b88017.png", + "/user/Login", + "/user/getAllNicknames", + "/uw79", + "/v1/getConfig", + "/v1/management/tenant/getSpeedDomain", + "/v2/", + "/v2/block/home/app/hot", + "/v2/game/rooms", + "/v6/getAppContent", + "/vendor/.git/config", + "/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", + "/verification.asp", + "/version", + "/video/65788.html", + "/video/67950.html", + "/views/commData/commonSite.js", + "/views/home/home.js", + "/wap", + "/wap.html", + "/wap/", + "/wap/api/exchangerateuserconfig!get.action", + "/wap/assets/images/icon1/12306.png", + "/wap/forward", + "/wap/trading/lastKlineParameter", + "/waplogin.php", + "/web/.git/config", + "/webclient/", + "/wiki/.git/config", + "/wp-content/.git/config", + "/wp-content/plugins/.git/config", + "/wp-content/themes/.git/config", + "/wp-includes/js/.git/config", + "/wp-login.php", + "/ws/index/getTheLotteryInitList", + "/ws/market", + "/xianyu/", + "/xianyu/image/jiantou.png", + "/xy/", + "/xy/image/jiantou.png", + "/yinjian/", + "/yr99", + "/ztp/cgi-bin/handle", + "/zz/address.php?gid=651", + "/zz2/address.php?gid=651", + ), ) - ) } } \ No newline at end of file diff --git a/web/src/main/kotlin/web/security/UserArgumentDetails.kt b/web/src/main/kotlin/web/security/UserArgumentDetails.kt index 3bb313b4e..5307abd07 100644 --- a/web/src/main/kotlin/web/security/UserArgumentDetails.kt +++ b/web/src/main/kotlin/web/security/UserArgumentDetails.kt @@ -9,7 +9,7 @@ class UserArgumentDetails( id: String, email: String, ) : TokenUserDetails( - authorities = authorities, - id = id, - email = email -) \ No newline at end of file + authorities = authorities, + id = id, + email = email, + ) \ No newline at end of file diff --git a/web/src/main/kotlin/web/security/UserArgumentHandlerMethodArgumentResolver.kt b/web/src/main/kotlin/web/security/UserArgumentHandlerMethodArgumentResolver.kt index ce549df21..ecd281601 100644 --- a/web/src/main/kotlin/web/security/UserArgumentHandlerMethodArgumentResolver.kt +++ b/web/src/main/kotlin/web/security/UserArgumentHandlerMethodArgumentResolver.kt @@ -1,12 +1,12 @@ package web.security -import security.AuthorityUtils import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.core.MethodParameter import org.springframework.web.bind.support.WebDataBinderFactory import org.springframework.web.context.request.NativeWebRequest import org.springframework.web.method.support.HandlerMethodArgumentResolver import org.springframework.web.method.support.ModelAndViewContainer +import security.AuthorityUtils import security.TokenResolver import web.security.filter.token.AccessTokenResolver @@ -15,9 +15,7 @@ class UserArgumentHandlerMethodArgumentResolver( ) : HandlerMethodArgumentResolver { val log = KotlinLogging.logger {} - override fun supportsParameter(parameter: MethodParameter): Boolean { - return parameter.hasParameterAnnotation(UserArgument::class.java) - } + override fun supportsParameter(parameter: MethodParameter): Boolean = parameter.hasParameterAnnotation(UserArgument::class.java) override fun resolveArgument( parameter: MethodParameter, @@ -27,31 +25,37 @@ class UserArgumentHandlerMethodArgumentResolver( ): UserArgumentDetails { val authorization: String? = webRequest.getHeader("Authorization") - val memberId = authorization?.let { - AccessTokenResolver.resolve(it) - }.let { - tokenResolver.resolveId(it) - } ?: 0L - - val email = authorization?.let { - AccessTokenResolver.resolve(it) - }.let { - tokenResolver.resolveEmail(it) - } ?: "" - - val authorities = authorization?.let { - AccessTokenResolver.resolve(it) - }?.let { - tokenResolver.resolveRole(it) - }?.let { - AuthorityUtils.toAuthorities(it) - } ?: emptyList() + val memberId = + authorization + ?.let { + AccessTokenResolver.resolve(it) + }.let { + tokenResolver.resolveId(it) + } ?: 0L + + val email = + authorization + ?.let { + AccessTokenResolver.resolve(it) + }.let { + tokenResolver.resolveEmail(it) + } ?: "" + + val authorities = + authorization + ?.let { + AccessTokenResolver.resolve(it) + }?.let { + tokenResolver.resolveRole(it) + }?.let { + AuthorityUtils.toAuthorities(it) + } ?: emptyList() return UserArgumentDetails( isAuth = authorization != null, id = memberId.toString(), email = email, - authorities = authorities + authorities = authorities, ) } } \ No newline at end of file diff --git a/web/src/main/kotlin/web/security/config/AbstractDelegatedSecurityConfigurer.kt b/web/src/main/kotlin/web/security/config/AbstractDelegatedSecurityConfigurer.kt index c1c0be411..1e76be7c0 100644 --- a/web/src/main/kotlin/web/security/config/AbstractDelegatedSecurityConfigurer.kt +++ b/web/src/main/kotlin/web/security/config/AbstractDelegatedSecurityConfigurer.kt @@ -17,7 +17,6 @@ import web.security.filter.token.TokenAuthenticationFilter * Security 설정을 위임하는 인터페이스. */ interface AbstractDelegatedSecurityConfigurer { - /** * Security 설정을 반환한다. */ diff --git a/web/src/main/kotlin/web/security/config/LocalDelegatedSecurityConfigurer.kt b/web/src/main/kotlin/web/security/config/LocalDelegatedSecurityConfigurer.kt index 0ec52451b..d8074f02d 100644 --- a/web/src/main/kotlin/web/security/config/LocalDelegatedSecurityConfigurer.kt +++ b/web/src/main/kotlin/web/security/config/LocalDelegatedSecurityConfigurer.kt @@ -19,7 +19,6 @@ class LocalDelegatedSecurityConfigurer( private val tokenAuthProvider: AuthenticationProvider, private val corsProperties: CorsConfigurationSourceProperties, ) : AbstractDelegatedSecurityConfigurer { - override fun securityFilterChain(http: HttpSecurity): DefaultSecurityFilterChain { http.csrf { it.disable() @@ -34,17 +33,20 @@ class LocalDelegatedSecurityConfigurer( it.configurationSource(corsConfigurationSource) } http.authorizeHttpRequests { - it.requestMatchers( - AntPathRequestMatcher("/api/v1/**") - ).authenticated().anyRequest().denyAll() + it + .requestMatchers( + AntPathRequestMatcher("/api/v1/**"), + ).authenticated() + .anyRequest() + .denyAll() } http.addFilterBefore( webTokenInvalidExceptionHandlerFilter, - AbstractPreAuthenticatedProcessingFilter::class.java + AbstractPreAuthenticatedProcessingFilter::class.java, ) http.addFilterAt( authenticationFilter, - AbstractPreAuthenticatedProcessingFilter::class.java + AbstractPreAuthenticatedProcessingFilter::class.java, ) http.exceptionHandling { it.authenticationEntryPoint(authenticationEntryPoint) @@ -57,9 +59,10 @@ class LocalDelegatedSecurityConfigurer( return http.build() } - override fun ignoreCustomizer(): WebSecurityCustomizer { - return WebSecurityCustomizer { web: WebSecurity -> - web.ignoring() + override fun ignoreCustomizer(): WebSecurityCustomizer = + WebSecurityCustomizer { web: WebSecurity -> + web + .ignoring() .requestMatchers( AntPathRequestMatcher("/actuator/health", HttpMethod.GET.name()), AntPathRequestMatcher("/error", HttpMethod.GET.name()), @@ -69,23 +72,20 @@ class LocalDelegatedSecurityConfigurer( AntPathRequestMatcher("/v3/api-docs/**", HttpMethod.GET.name()), AntPathRequestMatcher("/openapi3.yaml", HttpMethod.GET.name()), AntPathRequestMatcher("/reports/**", HttpMethod.GET.name()), - /** 인증/비인증 모두 허용 */ AntPathRequestMatcher( "/api/v1/subscriptions/workbooks/main", - HttpMethod.GET.name() + HttpMethod.GET.name(), ), AntPathRequestMatcher("/api/v1/workbooks", HttpMethod.GET.name()), AntPathRequestMatcher("/api/v1/articles/*", HttpMethod.GET.name()), AntPathRequestMatcher("/api/v1/workbooks/*/articles/*", HttpMethod.GET.name()), - /** 어드민 */ AntPathRequestMatcher("/api/v1/admin/**", HttpMethod.POST.name()), AntPathRequestMatcher("/api/v1/articles/views", HttpMethod.POST.name()), AntPathRequestMatcher("/api/v1/logs", HttpMethod.POST.name()), AntPathRequestMatcher("/api/v1/logs/email/articles", HttpMethod.POST.name()), AntPathRequestMatcher("/batch/**"), - /** 인증 불필요 */ AntPathRequestMatcher("/api/v1/members", HttpMethod.POST.name()), AntPathRequestMatcher("/api/v1/members/token", HttpMethod.POST.name()), @@ -96,16 +96,11 @@ class LocalDelegatedSecurityConfigurer( AntPathRequestMatcher("/api/v1/workbooks/categories", HttpMethod.GET.name()), AntPathRequestMatcher("/api/v1/workbooks/*/articles/*", HttpMethod.GET.name()), AntPathRequestMatcher("/api/v1/problems/**", HttpMethod.GET.name()), - AntPathRequestMatcher("/api/v1/problems/*", HttpMethod.POST.name()) + AntPathRequestMatcher("/api/v1/problems/*", HttpMethod.POST.name()), ) } - } - override fun getTokenAuthProvider(): AuthenticationProvider { - return tokenAuthProvider - } + override fun getTokenAuthProvider(): AuthenticationProvider = tokenAuthProvider - override fun getCorsProperties(): CorsConfigurationSourceProperties { - return corsProperties - } + override fun getCorsProperties(): CorsConfigurationSourceProperties = corsProperties } \ No newline at end of file diff --git a/web/src/main/kotlin/web/security/config/ProdDelegatedSecurityConfigurer.kt b/web/src/main/kotlin/web/security/config/ProdDelegatedSecurityConfigurer.kt index a61f5078f..82bb0535e 100644 --- a/web/src/main/kotlin/web/security/config/ProdDelegatedSecurityConfigurer.kt +++ b/web/src/main/kotlin/web/security/config/ProdDelegatedSecurityConfigurer.kt @@ -19,7 +19,6 @@ class ProdDelegatedSecurityConfigurer( private val tokenAuthProvider: AuthenticationProvider, private val corsProperties: CorsConfigurationSourceProperties, ) : AbstractDelegatedSecurityConfigurer { - override fun securityFilterChain(http: HttpSecurity): DefaultSecurityFilterChain { http.csrf { it.disable() @@ -34,17 +33,20 @@ class ProdDelegatedSecurityConfigurer( it.configurationSource(corsConfigurationSource) } http.authorizeHttpRequests { - it.requestMatchers( - AntPathRequestMatcher("/api/v1/**") - ).authenticated().anyRequest().denyAll() + it + .requestMatchers( + AntPathRequestMatcher("/api/v1/**"), + ).authenticated() + .anyRequest() + .denyAll() } http.addFilterBefore( webTokenInvalidExceptionHandlerFilter, - AbstractPreAuthenticatedProcessingFilter::class.java + AbstractPreAuthenticatedProcessingFilter::class.java, ) http.addFilterAt( authenticationFilter, - AbstractPreAuthenticatedProcessingFilter::class.java + AbstractPreAuthenticatedProcessingFilter::class.java, ) http.exceptionHandling { it.authenticationEntryPoint(authenticationEntryPoint) @@ -56,17 +58,14 @@ class ProdDelegatedSecurityConfigurer( return http.build() } - override fun getTokenAuthProvider(): AuthenticationProvider { - return tokenAuthProvider - } + override fun getTokenAuthProvider(): AuthenticationProvider = tokenAuthProvider - override fun getCorsProperties(): CorsConfigurationSourceProperties { - return corsProperties - } + override fun getCorsProperties(): CorsConfigurationSourceProperties = corsProperties - override fun ignoreCustomizer(): WebSecurityCustomizer { - return WebSecurityCustomizer { web: WebSecurity -> - web.ignoring() + override fun ignoreCustomizer(): WebSecurityCustomizer = + WebSecurityCustomizer { web: WebSecurity -> + web + .ignoring() .requestMatchers( AntPathRequestMatcher("/actuator/health", HttpMethod.GET.name()), AntPathRequestMatcher("/error", HttpMethod.GET.name()), @@ -76,23 +75,20 @@ class ProdDelegatedSecurityConfigurer( AntPathRequestMatcher("/v3/api-docs/**", HttpMethod.GET.name()), AntPathRequestMatcher("/openapi3.yaml", HttpMethod.GET.name()), AntPathRequestMatcher("/reports/**", HttpMethod.GET.name()), - /** 인증/비인증 모두 허용 */ AntPathRequestMatcher( "/api/v1/subscriptions/workbooks/main", - HttpMethod.GET.name() + HttpMethod.GET.name(), ), AntPathRequestMatcher("/api/v1/workbooks", HttpMethod.GET.name()), AntPathRequestMatcher("/api/v1/articles/*", HttpMethod.GET.name()), AntPathRequestMatcher("/api/v1/workbooks/*/articles/*", HttpMethod.GET.name()), - /** 어드민 */ AntPathRequestMatcher("/api/v1/admin/**", HttpMethod.POST.name()), AntPathRequestMatcher("/api/v1/articles/views", HttpMethod.POST.name()), AntPathRequestMatcher("/api/v1/logs/email/articles", HttpMethod.POST.name()), AntPathRequestMatcher("/api/v1/logs", HttpMethod.POST.name()), AntPathRequestMatcher("/batch/**"), - /** 인증 불필요 */ AntPathRequestMatcher("/api/v1/members", HttpMethod.POST.name()), AntPathRequestMatcher("/api/v1/members/token", HttpMethod.POST.name()), @@ -103,8 +99,7 @@ class ProdDelegatedSecurityConfigurer( AntPathRequestMatcher("/api/v1/workbooks/categories", HttpMethod.GET.name()), AntPathRequestMatcher("/api/v1/workbooks/*/articles/*", HttpMethod.GET.name()), AntPathRequestMatcher("/api/v1/problems/**", HttpMethod.GET.name()), - AntPathRequestMatcher("/api/v1/problems/*", HttpMethod.POST.name()) + AntPathRequestMatcher("/api/v1/problems/*", HttpMethod.POST.name()), ) } - } } \ No newline at end of file diff --git a/web/src/main/kotlin/web/security/config/WebSecurityConfig.kt b/web/src/main/kotlin/web/security/config/WebSecurityConfig.kt index f7d81da28..507bb3b41 100644 --- a/web/src/main/kotlin/web/security/config/WebSecurityConfig.kt +++ b/web/src/main/kotlin/web/security/config/WebSecurityConfig.kt @@ -33,7 +33,7 @@ import web.security.handler.DelegatedAuthenticationEntryPoint @Configuration @Import( ProdDelegatedSecurityConfigurer::class, - LocalDelegatedSecurityConfigurer::class + LocalDelegatedSecurityConfigurer::class, ) class WebSecurityConfig { companion object { @@ -53,33 +53,34 @@ class WebSecurityConfig { } @Bean(name = [WEB_SECURITY_CONFIGURER]) - fun webSecurityConfigurer(userArgumentHandlerMethodArgumentResolver: HandlerMethodArgumentResolver): WebMvcConfigurer { - return WebSecurityConfigurer(userArgumentHandlerMethodArgumentResolver) - } + fun webSecurityConfigurer(userArgumentHandlerMethodArgumentResolver: HandlerMethodArgumentResolver): WebMvcConfigurer = + WebSecurityConfigurer(userArgumentHandlerMethodArgumentResolver) @Profile("local") @Bean(name = ["local$SECURITY_FILTER_CHAIN"]) - fun localSecurityFilterChain(@Qualifier(LOCAL_DELEGATED_SECURITY_CONFIGURER) abstractDelegatedSecurityConfigurer: AbstractDelegatedSecurityConfigurer, http: HttpSecurity): SecurityFilterChain { - return abstractDelegatedSecurityConfigurer.securityFilterChain(http) - } + fun localSecurityFilterChain( + @Qualifier(LOCAL_DELEGATED_SECURITY_CONFIGURER) abstractDelegatedSecurityConfigurer: AbstractDelegatedSecurityConfigurer, + http: HttpSecurity, + ): SecurityFilterChain = abstractDelegatedSecurityConfigurer.securityFilterChain(http) @Profile("local") @Bean(name = ["local$WEB_SECURITY_CUSTOMIZER"]) - fun localWebSecurityFilterIgnoreCustomizer(@Qualifier(LOCAL_DELEGATED_SECURITY_CONFIGURER) abstractDelegatedSecurityConfigurer: AbstractDelegatedSecurityConfigurer): WebSecurityCustomizer { - return abstractDelegatedSecurityConfigurer.ignoreCustomizer() - } + fun localWebSecurityFilterIgnoreCustomizer( + @Qualifier(LOCAL_DELEGATED_SECURITY_CONFIGURER) abstractDelegatedSecurityConfigurer: AbstractDelegatedSecurityConfigurer, + ): WebSecurityCustomizer = abstractDelegatedSecurityConfigurer.ignoreCustomizer() @Profile("!local") @Bean(name = ["prod$SECURITY_FILTER_CHAIN"]) - fun prodSecurityFilterChain(@Qualifier(PROD_DELEGATED_SECURITY_CONFIGURER) abstractDelegatedSecurityConfigurer: AbstractDelegatedSecurityConfigurer, http: HttpSecurity): SecurityFilterChain { - return abstractDelegatedSecurityConfigurer.securityFilterChain(http) - } + fun prodSecurityFilterChain( + @Qualifier(PROD_DELEGATED_SECURITY_CONFIGURER) abstractDelegatedSecurityConfigurer: AbstractDelegatedSecurityConfigurer, + http: HttpSecurity, + ): SecurityFilterChain = abstractDelegatedSecurityConfigurer.securityFilterChain(http) @Profile("!local") @Bean(name = ["prod$WEB_SECURITY_CUSTOMIZER"]) - fun prodWebSecurityFilterIgnoreCustomizer(@Qualifier(PROD_DELEGATED_SECURITY_CONFIGURER) abstractDelegatedSecurityConfigurer: AbstractDelegatedSecurityConfigurer): WebSecurityCustomizer { - return abstractDelegatedSecurityConfigurer.ignoreCustomizer() - } + fun prodWebSecurityFilterIgnoreCustomizer( + @Qualifier(PROD_DELEGATED_SECURITY_CONFIGURER) abstractDelegatedSecurityConfigurer: AbstractDelegatedSecurityConfigurer, + ): WebSecurityCustomizer = abstractDelegatedSecurityConfigurer.ignoreCustomizer() @Profile("local") @Bean(name = [LOCAL_DELEGATED_SECURITY_CONFIGURER]) @@ -88,14 +89,13 @@ class WebSecurityConfig { delegatedAccessDeniedHandler: AccessDeniedHandler, tokenAuthProvider: AuthenticationProvider, corsConfigurationSourceProperties: CorsConfigurationSourceProperties, - ): LocalDelegatedSecurityConfigurer { - return LocalDelegatedSecurityConfigurer( + ): LocalDelegatedSecurityConfigurer = + LocalDelegatedSecurityConfigurer( delegatedAuthenticationEntryPoint, delegatedAccessDeniedHandler, tokenAuthProvider, - corsConfigurationSourceProperties + corsConfigurationSourceProperties, ) - } @Profile("!local") @Bean(name = [PROD_DELEGATED_SECURITY_CONFIGURER]) @@ -104,56 +104,44 @@ class WebSecurityConfig { delegatedAccessDeniedHandler: AccessDeniedHandler, tokenAuthProvider: AuthenticationProvider, corsConfigurationSourceProperties: CorsConfigurationSourceProperties, - ): ProdDelegatedSecurityConfigurer { - return ProdDelegatedSecurityConfigurer( + ): ProdDelegatedSecurityConfigurer = + ProdDelegatedSecurityConfigurer( delegatedAuthenticationEntryPoint, delegatedAccessDeniedHandler, tokenAuthProvider, - corsConfigurationSourceProperties + corsConfigurationSourceProperties, ) - } @Bean(name = [TOKEN_GENERATOR]) fun tokenGenerator( @Value("\${web.security.jwt.token.secretkey}") secretKey: String, @Value("\${web.security.jwt.token.validtime.access}") accessTokenValidTime: Long, @Value("\${web.security.jwt.token.validtime.refresh}") refreshTokenValidTime: Long, - ): TokenGenerator { - return SecurityTokenGenerator(secretKey, accessTokenValidTime, refreshTokenValidTime) - } + ): TokenGenerator = SecurityTokenGenerator(secretKey, accessTokenValidTime, refreshTokenValidTime) @Bean(name = [TOKEN_RESOLVER]) fun tokenResolver( @Value("\${web.security.jwt.token.secretkey}") secretKey: String, - ): TokenResolver { - return SecurityTokenResolver(secretKey) - } + ): TokenResolver = SecurityTokenResolver(secretKey) @Profile("!test") @Bean(name = [TOKEN_USER_DETAILS_SERVICE]) - fun tokenUserDetailsService(tokenResolver: TokenResolver): UserDetailsService { - return TokenUserDetailsService(tokenResolver) - } + fun tokenUserDetailsService(tokenResolver: TokenResolver): UserDetailsService = TokenUserDetailsService(tokenResolver) @Bean(name = [TOKEN_AUTH_PROVIDER]) - fun tokenAuthProvider(tokenUserDetailsService: UserDetailsService): AuthenticationProvider { - return TokenAuthProvider(tokenUserDetailsService) - } + fun tokenAuthProvider(tokenUserDetailsService: UserDetailsService): AuthenticationProvider = TokenAuthProvider(tokenUserDetailsService) @Bean(name = [USER_ARGUMENT_HANDLER_METHOD_ARGUMENT_RESOLVER]) - fun userArgumentHandlerMethodArgumentResolver(tokenResolver: TokenResolver): HandlerMethodArgumentResolver { - return UserArgumentHandlerMethodArgumentResolver(tokenResolver) - } + fun userArgumentHandlerMethodArgumentResolver(tokenResolver: TokenResolver): HandlerMethodArgumentResolver = + UserArgumentHandlerMethodArgumentResolver(tokenResolver) @Bean(name = [DELEGATED_AUTHENTICATION_ENTRY_POINT]) - fun delegatedAuthenticationEntryPoint(handlerExceptionResolver: HandlerExceptionResolver): AuthenticationEntryPoint { - return DelegatedAuthenticationEntryPoint(handlerExceptionResolver) - } + fun delegatedAuthenticationEntryPoint(handlerExceptionResolver: HandlerExceptionResolver): AuthenticationEntryPoint = + DelegatedAuthenticationEntryPoint(handlerExceptionResolver) @Bean(name = [DELEGATED_ACCESS_DENIED_HANDLER]) - fun delegatedAccessDeniedHandler(handlerExceptionResolver: HandlerExceptionResolver): AccessDeniedHandler { - return DelegatedAccessDeniedHandler(handlerExceptionResolver) - } + fun delegatedAccessDeniedHandler(handlerExceptionResolver: HandlerExceptionResolver): AccessDeniedHandler = + DelegatedAccessDeniedHandler(handlerExceptionResolver) @Bean(name = [CORS_CONFIGURATION_SOURCE_PROPERTIES]) fun corsConfigurationSourceProperties( @@ -164,15 +152,14 @@ class WebSecurityConfig { @Value("\${web.security.cors.exposed-headers}") exposedHeaders: String, @Value("\${web.security.cors.allow-credentials}") allowCredentials: Boolean, @Value("\${web.security.cors.max-age}") maxAge: Long, - ): CorsConfigurationSourceProperties { - return CorsConfigurationSourceProperties( + ): CorsConfigurationSourceProperties = + CorsConfigurationSourceProperties( pathPattern, originPatterns, allowedMethods, allowedHeaders, exposedHeaders, allowCredentials, - maxAge + maxAge, ) - } } \ No newline at end of file diff --git a/web/src/main/kotlin/web/security/config/WebSecurityConfigurer.kt b/web/src/main/kotlin/web/security/config/WebSecurityConfigurer.kt index 34b423586..dfb2c59d9 100644 --- a/web/src/main/kotlin/web/security/config/WebSecurityConfigurer.kt +++ b/web/src/main/kotlin/web/security/config/WebSecurityConfigurer.kt @@ -6,7 +6,6 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer class WebSecurityConfigurer( private val userArgumentHandlerMethodArgumentResolver: HandlerMethodArgumentResolver, ) : WebMvcConfigurer { - override fun addArgumentResolvers(argumentResolvers: MutableList) { argumentResolvers.add(userArgumentHandlerMethodArgumentResolver) } diff --git a/web/src/main/kotlin/web/security/exception/WebTokenInvalidExceptionHandlerFilter.kt b/web/src/main/kotlin/web/security/exception/WebTokenInvalidExceptionHandlerFilter.kt index 04f0f469c..9eb2b3649 100644 --- a/web/src/main/kotlin/web/security/exception/WebTokenInvalidExceptionHandlerFilter.kt +++ b/web/src/main/kotlin/web/security/exception/WebTokenInvalidExceptionHandlerFilter.kt @@ -25,7 +25,10 @@ class WebTokenInvalidExceptionHandlerFilter : OncePerRequestFilter() { } @Throws(IOException::class) - private fun setError(response: HttpServletResponse, e: Exception) { + private fun setError( + response: HttpServletResponse, + e: Exception, + ) { response.status = HttpServletResponse.SC_FORBIDDEN response.contentType = CONTENT_TYPE val errorResponse = ErrorResponse() @@ -33,8 +36,6 @@ class WebTokenInvalidExceptionHandlerFilter : OncePerRequestFilter() { } private class ErrorResponse { - override fun toString(): String { - return "{ \"message\": \"Invalid access token\" }" - } + override fun toString(): String = "{ \"message\": \"Invalid access token\" }" } } \ No newline at end of file diff --git a/web/src/main/kotlin/web/security/filter/token/TokenAuthenticationFilter.kt b/web/src/main/kotlin/web/security/filter/token/TokenAuthenticationFilter.kt index 80138a494..189c28035 100644 --- a/web/src/main/kotlin/web/security/filter/token/TokenAuthenticationFilter.kt +++ b/web/src/main/kotlin/web/security/filter/token/TokenAuthenticationFilter.kt @@ -8,13 +8,9 @@ import security.exception.SecurityAccessTokenInvalidException class TokenAuthenticationFilter : AbstractPreAuthenticatedProcessingFilter() { private val log = KotlinLogging.logger {} - override fun getPreAuthenticatedPrincipal(request: HttpServletRequest): Any { - return resolveAccessToken(request) - } + override fun getPreAuthenticatedPrincipal(request: HttpServletRequest): Any = resolveAccessToken(request) - override fun getPreAuthenticatedCredentials(request: HttpServletRequest): Any { - return resolveAccessToken(request) - } + override fun getPreAuthenticatedCredentials(request: HttpServletRequest): Any = resolveAccessToken(request) private fun resolveAccessToken(request: HttpServletRequest): String { val authorization: String? = request.getHeader("Authorization") diff --git a/web/src/main/kotlin/web/security/handler/DelegatedAccessDeniedHandler.kt b/web/src/main/kotlin/web/security/handler/DelegatedAccessDeniedHandler.kt index 8667c58e1..c57519c2e 100644 --- a/web/src/main/kotlin/web/security/handler/DelegatedAccessDeniedHandler.kt +++ b/web/src/main/kotlin/web/security/handler/DelegatedAccessDeniedHandler.kt @@ -2,14 +2,13 @@ package web.security.handler import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse +import org.springframework.security.access.AccessDeniedException import org.springframework.security.web.access.AccessDeniedHandler import org.springframework.web.servlet.HandlerExceptionResolver -import org.springframework.security.access.AccessDeniedException class DelegatedAccessDeniedHandler( private val handlerExceptionResolver: HandlerExceptionResolver, ) : AccessDeniedHandler { - override fun handle( request: HttpServletRequest, response: HttpServletResponse, @@ -19,7 +18,7 @@ class DelegatedAccessDeniedHandler( request, response, null, - accessDeniedException + accessDeniedException, ) } } \ No newline at end of file diff --git a/web/src/main/kotlin/web/security/handler/DelegatedAuthenticationEntryPoint.kt b/web/src/main/kotlin/web/security/handler/DelegatedAuthenticationEntryPoint.kt index 9ca681d3c..1af74cf9e 100644 --- a/web/src/main/kotlin/web/security/handler/DelegatedAuthenticationEntryPoint.kt +++ b/web/src/main/kotlin/web/security/handler/DelegatedAuthenticationEntryPoint.kt @@ -9,7 +9,6 @@ import org.springframework.web.servlet.HandlerExceptionResolver class DelegatedAuthenticationEntryPoint( private val handlerExceptionResolver: HandlerExceptionResolver, ) : AuthenticationEntryPoint { - override fun commence( request: HttpServletRequest, response: HttpServletResponse, diff --git a/web/src/testFixtures/kotlin/web/description/Description.kt b/web/src/testFixtures/kotlin/web/description/Description.kt index 6992fe431..fee3f9ad3 100644 --- a/web/src/testFixtures/kotlin/web/description/Description.kt +++ b/web/src/testFixtures/kotlin/web/description/Description.kt @@ -9,25 +9,24 @@ import org.springframework.restdocs.payload.PayloadDocumentation object Description { private val messageDescriptor: FieldDescriptor - private get() = PayloadDocumentation.fieldWithPath("message").type(JsonFieldType.STRING) - .description("메시지") + private get() = + PayloadDocumentation + .fieldWithPath("message") + .type(JsonFieldType.STRING) + .description("메시지") - fun describe(data: Array?): Array { - return ArrayUtils.addAll( + fun describe(data: Array?): Array = + ArrayUtils.addAll( data, - messageDescriptor + messageDescriptor, ) - } - fun fields(vararg fields: FieldDescriptor): Array { - return describe(fields.toList().toTypedArray()) - } + fun fields(vararg fields: FieldDescriptor): Array = describe(fields.toList().toTypedArray()) - fun describe(): Array { - return arrayOf( - messageDescriptor + fun describe(): Array = + arrayOf( + messageDescriptor, ) - } fun authHeader(optional: Boolean = false): HeaderDescriptorWithType { if (optional) { diff --git a/web/src/testFixtures/kotlin/web/helper/ApiDefinitionExtension.kt b/web/src/testFixtures/kotlin/web/helper/ApiDefinitionExtension.kt index 0f80dfa8c..9c12f63b3 100644 --- a/web/src/testFixtures/kotlin/web/helper/ApiDefinitionExtension.kt +++ b/web/src/testFixtures/kotlin/web/helper/ApiDefinitionExtension.kt @@ -1,23 +1,13 @@ package web.helper -fun String.toIdentifier(): String { - return this + "Api" -} +fun String.toIdentifier(): String = this + "Api" -fun String.toApiDescription(): String { - return this + "Description" -} +fun String.toApiDescription(): String = this + "Description" -fun String.toSummary(): String { - return this + "Summary" -} +fun String.toSummary(): String = this + "Summary" -fun String.toRequestSchema(): String { - return this + "RequestSchema" -} +fun String.toRequestSchema(): String = this + "RequestSchema" -fun String.toResponseSchema(): String { - return this + "ResponseSchema" -} +fun String.toResponseSchema(): String = this + "ResponseSchema" class ApiDefinitionExtension \ No newline at end of file diff --git a/web/src/testFixtures/kotlin/web/helper/PayloadDocumentationExtension.kt b/web/src/testFixtures/kotlin/web/helper/PayloadDocumentationExtension.kt index 873b3f415..e444922d7 100644 --- a/web/src/testFixtures/kotlin/web/helper/PayloadDocumentationExtension.kt +++ b/web/src/testFixtures/kotlin/web/helper/PayloadDocumentationExtension.kt @@ -4,29 +4,17 @@ import org.springframework.restdocs.payload.FieldDescriptor import org.springframework.restdocs.payload.JsonFieldType import org.springframework.restdocs.payload.PayloadDocumentation -fun FieldDescriptor.fieldWithObject(description: String): FieldDescriptor { - return this.type(JsonFieldType.OBJECT).description(description) -} +fun FieldDescriptor.fieldWithObject(description: String): FieldDescriptor = this.type(JsonFieldType.OBJECT).description(description) -fun FieldDescriptor.fieldWithArray(description: String): FieldDescriptor { - return this.type(JsonFieldType.ARRAY).description(description) -} +fun FieldDescriptor.fieldWithArray(description: String): FieldDescriptor = this.type(JsonFieldType.ARRAY).description(description) -fun FieldDescriptor.fieldWithString(description: String): FieldDescriptor { - return this.type(JsonFieldType.STRING).description(description) -} +fun FieldDescriptor.fieldWithString(description: String): FieldDescriptor = this.type(JsonFieldType.STRING).description(description) -fun FieldDescriptor.fieldWithNumber(description: String): FieldDescriptor { - return this.type(JsonFieldType.NUMBER).description(description) -} +fun FieldDescriptor.fieldWithNumber(description: String): FieldDescriptor = this.type(JsonFieldType.NUMBER).description(description) -fun FieldDescriptor.fieldWithBoolean(description: String): FieldDescriptor { - return this.type(JsonFieldType.BOOLEAN).description(description) -} +fun FieldDescriptor.fieldWithBoolean(description: String): FieldDescriptor = this.type(JsonFieldType.BOOLEAN).description(description) -fun FieldDescriptor.fieldWithNull(description: String): FieldDescriptor { - return this.type(JsonFieldType.NULL).description(description) -} +fun FieldDescriptor.fieldWithNull(description: String): FieldDescriptor = this.type(JsonFieldType.NULL).description(description) fun FieldDescription.asObject(): FieldDescriptor { val descriptor = PayloadDocumentation.fieldWithPath(this.path).fieldWithObject(this.description) @@ -75,4 +63,5 @@ fun FieldDescription.asNull(): FieldDescriptor { } return descriptor } + class PayloadDocumentationExtension \ No newline at end of file