From 1b0c2b5bb88ef1c07947067d56b59a17cc2265a3 Mon Sep 17 00:00:00 2001 From: jisu Date: Sun, 27 Jul 2025 19:29:43 +0900 Subject: [PATCH 01/11] chore: add `experiment_post_keywords_log` table SQL file --- ...2507271926__add_experiment_post_keywords_log_table.sql | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 infrastructure/src/main/resources/db/migration/V202507271926__add_experiment_post_keywords_log_table.sql diff --git a/infrastructure/src/main/resources/db/migration/V202507271926__add_experiment_post_keywords_log_table.sql b/infrastructure/src/main/resources/db/migration/V202507271926__add_experiment_post_keywords_log_table.sql new file mode 100644 index 00000000..b9dc0763 --- /dev/null +++ b/infrastructure/src/main/resources/db/migration/V202507271926__add_experiment_post_keywords_log_table.sql @@ -0,0 +1,8 @@ +CREATE TABLE experiment_post_keywords_log ( + experiment_post_keywords_log_id CHAR(13) NOT NULL PRIMARY KEY, + member_id CHAR(13) NOT NULL, + response TEXT NOT NULL, + created_at TIMESTAMP NOT NULL, + CONSTRAINT fk_experiment_post_keywords_log_member + FOREIGN KEY (member_id) REFERENCES member (member_id) +) COLLATE = utf8mb4_unicode_ci; From cd0dd7d6a59a7588f5abacd1b27d4556e22434a7 Mon Sep 17 00:00:00 2001 From: jisu Date: Sun, 27 Jul 2025 19:29:58 +0900 Subject: [PATCH 02/11] feat: add ExperimentPostKeywordsLog Entity and domain model --- .../experiment/ExperimentPostKeywordsLog.kt | 26 +++++++++++ .../ExperimentPostKeywordsLogEntity.kt | 46 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 domain/src/main/kotlin/com/dobby/model/experiment/ExperimentPostKeywordsLog.kt create mode 100644 infrastructure/src/main/kotlin/com/dobby/persistence/entity/experiment/ExperimentPostKeywordsLogEntity.kt diff --git a/domain/src/main/kotlin/com/dobby/model/experiment/ExperimentPostKeywordsLog.kt b/domain/src/main/kotlin/com/dobby/model/experiment/ExperimentPostKeywordsLog.kt new file mode 100644 index 00000000..917a4c22 --- /dev/null +++ b/domain/src/main/kotlin/com/dobby/model/experiment/ExperimentPostKeywordsLog.kt @@ -0,0 +1,26 @@ +package com.dobby.model.experiment + +import com.dobby.model.member.Member +import com.dobby.util.TimeProvider +import java.time.LocalDateTime + +data class ExperimentPostKeywordsLog( + val id: String, + val member: Member, + val response: String, + val createdAt: LocalDateTime +) { + companion object { + fun newExperimentPostKeywordsLog( + id: String, + member: Member, + response: String + ) = ExperimentPostKeywordsLog( + id = id, + member = member, + response = response, + createdAt = TimeProvider.currentDateTime() + ) + } +} + diff --git a/infrastructure/src/main/kotlin/com/dobby/persistence/entity/experiment/ExperimentPostKeywordsLogEntity.kt b/infrastructure/src/main/kotlin/com/dobby/persistence/entity/experiment/ExperimentPostKeywordsLogEntity.kt new file mode 100644 index 00000000..b8a27be3 --- /dev/null +++ b/infrastructure/src/main/kotlin/com/dobby/persistence/entity/experiment/ExperimentPostKeywordsLogEntity.kt @@ -0,0 +1,46 @@ +package com.dobby.persistence.entity.experiment + +import com.dobby.model.experiment.ExperimentPostKeywordsLog +import com.dobby.persistence.entity.member.MemberEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.FetchType +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne +import java.time.LocalDateTime + +@Entity(name = "experiment_post_keywords_log") +class ExperimentPostKeywordsLogEntity( + @Id + @Column(name = "experiment_post_keywords_log_id", columnDefinition = "CHAR(13)") + val id: String, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + val member: MemberEntity, + + @Column(columnDefinition = "TEXT", nullable = false) + val response: String, + + @Column(name = "created_at", nullable = false) + val createdAt: LocalDateTime, +) { + fun toDomain() = ExperimentPostKeywordsLog( + id = id, + member = member.toDomain(), + response = response, + createdAt = createdAt + ) + + companion object { + fun fromDomain(experimentPostKeywordsLog: ExperimentPostKeywordsLog) = with(experimentPostKeywordsLog) { + ExperimentPostKeywordsLogEntity( + id = id, + member = MemberEntity.fromDomain(member), + response = response, + createdAt = createdAt + ) + } + } +} From 01b17b289fda1155d7bf35352a4d476cfd6e228b Mon Sep 17 00:00:00 2001 From: jisu Date: Sun, 27 Jul 2025 19:51:11 +0900 Subject: [PATCH 03/11] feat: add ExperimentPostKeywordsLog Repository interface --- .../ExperimentPostKeywordsLogGateway.kt | 9 +++++++ .../ExperimentPostKeywordsLogGatewayImpl.kt | 27 +++++++++++++++++++ .../ExperimentPostKeywordsLogRepository.kt | 9 +++++++ 3 files changed, 45 insertions(+) create mode 100644 domain/src/main/kotlin/com/dobby/gateway/experiment/ExperimentPostKeywordsLogGateway.kt create mode 100644 infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentPostKeywordsLogGatewayImpl.kt create mode 100644 infrastructure/src/main/kotlin/com/dobby/persistence/repository/ExperimentPostKeywordsLogRepository.kt diff --git a/domain/src/main/kotlin/com/dobby/gateway/experiment/ExperimentPostKeywordsLogGateway.kt b/domain/src/main/kotlin/com/dobby/gateway/experiment/ExperimentPostKeywordsLogGateway.kt new file mode 100644 index 00000000..934484fa --- /dev/null +++ b/domain/src/main/kotlin/com/dobby/gateway/experiment/ExperimentPostKeywordsLogGateway.kt @@ -0,0 +1,9 @@ +package com.dobby.gateway.experiment + +import com.dobby.model.experiment.ExperimentPostKeywordsLog +import java.time.LocalDateTime + +interface ExperimentPostKeywordsLogGateway { + fun save(experimentPostKeywordsLog: ExperimentPostKeywordsLog): ExperimentPostKeywordsLog + fun countByMemberIdAndCreatedAtBetween(memberId: String, start: LocalDateTime, end: LocalDateTime): Int +} diff --git a/infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentPostKeywordsLogGatewayImpl.kt b/infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentPostKeywordsLogGatewayImpl.kt new file mode 100644 index 00000000..57a03acc --- /dev/null +++ b/infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentPostKeywordsLogGatewayImpl.kt @@ -0,0 +1,27 @@ +package com.dobby.external.gateway.experiment + +import com.dobby.gateway.experiment.ExperimentPostKeywordsLogGateway +import com.dobby.model.experiment.ExperimentPostKeywordsLog +import com.dobby.persistence.entity.experiment.ExperimentPostKeywordsLogEntity +import com.dobby.persistence.repository.ExperimentPostKeywordsLogRepository +import org.springframework.stereotype.Component +import java.time.LocalDateTime + +@Component +class ExperimentPostKeywordsLogGatewayImpl( + private val experimentPostKeywordsLogRepository: ExperimentPostKeywordsLogRepository +) : ExperimentPostKeywordsLogGateway { + override fun save(experimentPostKeywordsLog: ExperimentPostKeywordsLog): ExperimentPostKeywordsLog { + val savedEntity = experimentPostKeywordsLogRepository + .save(ExperimentPostKeywordsLogEntity.fromDomain(experimentPostKeywordsLog)) + return savedEntity.toDomain() + } + + override fun countByMemberIdAndCreatedAtBetween( + memberId: String, + start: LocalDateTime, + end: LocalDateTime + ): Int { + return experimentPostKeywordsLogRepository.countByMemberIdAndCreatedAtBetween(memberId, start, end) + } +} diff --git a/infrastructure/src/main/kotlin/com/dobby/persistence/repository/ExperimentPostKeywordsLogRepository.kt b/infrastructure/src/main/kotlin/com/dobby/persistence/repository/ExperimentPostKeywordsLogRepository.kt new file mode 100644 index 00000000..506c05c2 --- /dev/null +++ b/infrastructure/src/main/kotlin/com/dobby/persistence/repository/ExperimentPostKeywordsLogRepository.kt @@ -0,0 +1,9 @@ +package com.dobby.persistence.repository + +import com.dobby.persistence.entity.experiment.ExperimentPostKeywordsLogEntity +import org.springframework.data.jpa.repository.JpaRepository +import java.time.LocalDateTime + +interface ExperimentPostKeywordsLogRepository : JpaRepository { + fun countByMemberIdAndCreatedAtBetween(memberId: String, start: LocalDateTime, end: LocalDateTime): Int +} From 03b2d73565d187043c8f193cf4a9a147713612d9 Mon Sep 17 00:00:00 2001 From: jisu Date: Sun, 27 Jul 2025 20:24:43 +0900 Subject: [PATCH 04/11] feat: serialize `ExperimentPostKeyword` to JSON for database storage --- .../ExtractExperimentPostKeywordsUseCase.kt | 28 ++++++++++++++-- .../experiment/ExperimentPostKeywordsLog.kt | 6 ++-- .../com/dobby/converter/JsonConverter.kt | 13 ++++++++ .../ExperimentPostKeywordsLogGatewayImpl.kt | 12 ++++--- .../mapper/ExperimentPostKeywordsLogMapper.kt | 32 +++++++++++++++++++ .../ExperimentPostKeywordsLogEntity.kt | 21 +----------- 6 files changed, 81 insertions(+), 31 deletions(-) create mode 100644 infrastructure/src/main/kotlin/com/dobby/converter/JsonConverter.kt create mode 100644 infrastructure/src/main/kotlin/com/dobby/mapper/ExperimentPostKeywordsLogMapper.kt diff --git a/application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt b/application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt index 55ecd569..685b83a9 100644 --- a/application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt +++ b/application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt @@ -1,17 +1,39 @@ package com.dobby.usecase.experiment import com.dobby.gateway.experiment.ExperimentKeywordExtractionGateway +import com.dobby.gateway.experiment.ExperimentPostKeywordsLogGateway +import com.dobby.gateway.member.MemberGateway +import com.dobby.model.experiment.ExperimentPostKeywordsLog import com.dobby.model.experiment.keyword.ExperimentPostKeyword import com.dobby.usecase.UseCase +import com.dobby.util.IdGenerator class ExtractExperimentPostKeywordsUseCase( - private val experimentKeywordExtractionGateway: ExperimentKeywordExtractionGateway + private val experimentKeywordExtractionGateway: ExperimentKeywordExtractionGateway, + private val experimentPostKeywordsGateway: ExperimentPostKeywordsLogGateway, + private val memberGateway: MemberGateway, + private val idGenerator: IdGenerator ) : UseCase { - data class Input(val text: String) - data class Output(val experimentPostKeyword: ExperimentPostKeyword) + + data class Input( + val memberId: String, + val text: String + ) + + data class Output( + val experimentPostKeyword: ExperimentPostKeyword + ) override fun execute(input: Input): Output { + val member = memberGateway.getById(input.memberId) val experimentPostKeyword = experimentKeywordExtractionGateway.extractKeywords(input.text) + val log = ExperimentPostKeywordsLog.newExperimentPostKeywordsLog( + id = idGenerator.generateId(), + member = member, + response = experimentPostKeyword, + ) + + experimentPostKeywordsGateway.save(log) return Output(experimentPostKeyword) } } diff --git a/domain/src/main/kotlin/com/dobby/model/experiment/ExperimentPostKeywordsLog.kt b/domain/src/main/kotlin/com/dobby/model/experiment/ExperimentPostKeywordsLog.kt index 917a4c22..b3aa71de 100644 --- a/domain/src/main/kotlin/com/dobby/model/experiment/ExperimentPostKeywordsLog.kt +++ b/domain/src/main/kotlin/com/dobby/model/experiment/ExperimentPostKeywordsLog.kt @@ -1,5 +1,6 @@ package com.dobby.model.experiment +import com.dobby.model.experiment.keyword.ExperimentPostKeyword import com.dobby.model.member.Member import com.dobby.util.TimeProvider import java.time.LocalDateTime @@ -7,14 +8,14 @@ import java.time.LocalDateTime data class ExperimentPostKeywordsLog( val id: String, val member: Member, - val response: String, + val response: ExperimentPostKeyword, val createdAt: LocalDateTime ) { companion object { fun newExperimentPostKeywordsLog( id: String, member: Member, - response: String + response: ExperimentPostKeyword ) = ExperimentPostKeywordsLog( id = id, member = member, @@ -23,4 +24,3 @@ data class ExperimentPostKeywordsLog( ) } } - diff --git a/infrastructure/src/main/kotlin/com/dobby/converter/JsonConverter.kt b/infrastructure/src/main/kotlin/com/dobby/converter/JsonConverter.kt new file mode 100644 index 00000000..d3c7f1b3 --- /dev/null +++ b/infrastructure/src/main/kotlin/com/dobby/converter/JsonConverter.kt @@ -0,0 +1,13 @@ +package com.dobby.converter + +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import org.springframework.stereotype.Component + +@Component +class JsonConverter { + private val objectMapper = jacksonObjectMapper() + + fun toJson(obj: T): String = objectMapper.writeValueAsString(obj) + + fun fromJson(json: String, clazz: Class): T = objectMapper.readValue(json, clazz) +} diff --git a/infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentPostKeywordsLogGatewayImpl.kt b/infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentPostKeywordsLogGatewayImpl.kt index 57a03acc..4e46d600 100644 --- a/infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentPostKeywordsLogGatewayImpl.kt +++ b/infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentPostKeywordsLogGatewayImpl.kt @@ -1,20 +1,22 @@ package com.dobby.external.gateway.experiment import com.dobby.gateway.experiment.ExperimentPostKeywordsLogGateway +import com.dobby.mapper.ExperimentPostKeywordsLogMapper import com.dobby.model.experiment.ExperimentPostKeywordsLog -import com.dobby.persistence.entity.experiment.ExperimentPostKeywordsLogEntity import com.dobby.persistence.repository.ExperimentPostKeywordsLogRepository import org.springframework.stereotype.Component import java.time.LocalDateTime @Component class ExperimentPostKeywordsLogGatewayImpl( - private val experimentPostKeywordsLogRepository: ExperimentPostKeywordsLogRepository + private val experimentPostKeywordsLogRepository: ExperimentPostKeywordsLogRepository, + private val mapper: ExperimentPostKeywordsLogMapper ) : ExperimentPostKeywordsLogGateway { + override fun save(experimentPostKeywordsLog: ExperimentPostKeywordsLog): ExperimentPostKeywordsLog { - val savedEntity = experimentPostKeywordsLogRepository - .save(ExperimentPostKeywordsLogEntity.fromDomain(experimentPostKeywordsLog)) - return savedEntity.toDomain() + val entity = mapper.fromDomain(experimentPostKeywordsLog) + val savedEntity = experimentPostKeywordsLogRepository.save(entity) + return mapper.toDomain(savedEntity) } override fun countByMemberIdAndCreatedAtBetween( diff --git a/infrastructure/src/main/kotlin/com/dobby/mapper/ExperimentPostKeywordsLogMapper.kt b/infrastructure/src/main/kotlin/com/dobby/mapper/ExperimentPostKeywordsLogMapper.kt new file mode 100644 index 00000000..8694b954 --- /dev/null +++ b/infrastructure/src/main/kotlin/com/dobby/mapper/ExperimentPostKeywordsLogMapper.kt @@ -0,0 +1,32 @@ +package com.dobby.mapper + +import com.dobby.converter.JsonConverter +import com.dobby.model.experiment.ExperimentPostKeywordsLog +import com.dobby.model.experiment.keyword.ExperimentPostKeyword +import com.dobby.persistence.entity.experiment.ExperimentPostKeywordsLogEntity +import com.dobby.persistence.entity.member.MemberEntity +import org.springframework.stereotype.Component + +@Component +class ExperimentPostKeywordsLogMapper( + private val jsonConverter: JsonConverter +) { + + fun toDomain(entity: ExperimentPostKeywordsLogEntity): ExperimentPostKeywordsLog { + return ExperimentPostKeywordsLog( + id = entity.id, + member = entity.member.toDomain(), + response = jsonConverter.fromJson(entity.response, ExperimentPostKeyword::class.java), + createdAt = entity.createdAt + ) + } + + fun fromDomain(domain: ExperimentPostKeywordsLog): ExperimentPostKeywordsLogEntity { + return ExperimentPostKeywordsLogEntity( + id = domain.id, + member = MemberEntity.fromDomain(domain.member), + response = jsonConverter.toJson(domain.response), + createdAt = domain.createdAt + ) + } +} diff --git a/infrastructure/src/main/kotlin/com/dobby/persistence/entity/experiment/ExperimentPostKeywordsLogEntity.kt b/infrastructure/src/main/kotlin/com/dobby/persistence/entity/experiment/ExperimentPostKeywordsLogEntity.kt index b8a27be3..e92d5fa6 100644 --- a/infrastructure/src/main/kotlin/com/dobby/persistence/entity/experiment/ExperimentPostKeywordsLogEntity.kt +++ b/infrastructure/src/main/kotlin/com/dobby/persistence/entity/experiment/ExperimentPostKeywordsLogEntity.kt @@ -1,6 +1,5 @@ package com.dobby.persistence.entity.experiment -import com.dobby.model.experiment.ExperimentPostKeywordsLog import com.dobby.persistence.entity.member.MemberEntity import jakarta.persistence.Column import jakarta.persistence.Entity @@ -25,22 +24,4 @@ class ExperimentPostKeywordsLogEntity( @Column(name = "created_at", nullable = false) val createdAt: LocalDateTime, -) { - fun toDomain() = ExperimentPostKeywordsLog( - id = id, - member = member.toDomain(), - response = response, - createdAt = createdAt - ) - - companion object { - fun fromDomain(experimentPostKeywordsLog: ExperimentPostKeywordsLog) = with(experimentPostKeywordsLog) { - ExperimentPostKeywordsLogEntity( - id = id, - member = MemberEntity.fromDomain(member), - response = response, - createdAt = createdAt - ) - } - } -} +) From f4a69f20b91c3919fca599ff2247a1538974a792 Mon Sep 17 00:00:00 2001 From: jisu Date: Sun, 27 Jul 2025 20:30:15 +0900 Subject: [PATCH 05/11] fix: add memberId field to mapper --- .../src/main/kotlin/com/dobby/api/mapper/ExperimentPostMapper.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/presentation/src/main/kotlin/com/dobby/api/mapper/ExperimentPostMapper.kt b/presentation/src/main/kotlin/com/dobby/api/mapper/ExperimentPostMapper.kt index e07ceaf1..a97bae24 100644 --- a/presentation/src/main/kotlin/com/dobby/api/mapper/ExperimentPostMapper.kt +++ b/presentation/src/main/kotlin/com/dobby/api/mapper/ExperimentPostMapper.kt @@ -523,6 +523,7 @@ object ExperimentPostMapper { fun toExtractKeywordUseCaseInput(request: ExtractKeywordRequest): ExtractExperimentPostKeywordsUseCase.Input { return ExtractExperimentPostKeywordsUseCase.Input( + memberId = getCurrentMemberId(), text = request.text ) } From 8f76558d68d7ff8356cca58494eeebd2df493c33 Mon Sep 17 00:00:00 2001 From: jisu Date: Sun, 27 Jul 2025 20:40:13 +0900 Subject: [PATCH 06/11] feat: add daily usage limit validation for experiment post keywords --- .../ExtractExperimentPostKeywordsUseCase.kt | 24 +++++++++++++++++++ .../com/dobby/exception/DobbyException.kt | 1 + 2 files changed, 25 insertions(+) diff --git a/application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt b/application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt index 685b83a9..27ef143a 100644 --- a/application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt +++ b/application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt @@ -1,5 +1,6 @@ package com.dobby.usecase.experiment +import com.dobby.exception.ExperimentPostKeywordsDailyLimitExceededException import com.dobby.gateway.experiment.ExperimentKeywordExtractionGateway import com.dobby.gateway.experiment.ExperimentPostKeywordsLogGateway import com.dobby.gateway.member.MemberGateway @@ -7,6 +8,7 @@ import com.dobby.model.experiment.ExperimentPostKeywordsLog import com.dobby.model.experiment.keyword.ExperimentPostKeyword import com.dobby.usecase.UseCase import com.dobby.util.IdGenerator +import com.dobby.util.TimeProvider class ExtractExperimentPostKeywordsUseCase( private val experimentKeywordExtractionGateway: ExperimentKeywordExtractionGateway, @@ -15,6 +17,10 @@ class ExtractExperimentPostKeywordsUseCase( private val idGenerator: IdGenerator ) : UseCase { + companion object { + private const val DAILY_USAGE_LIMIT = 2 + } + data class Input( val memberId: String, val text: String @@ -26,6 +32,8 @@ class ExtractExperimentPostKeywordsUseCase( override fun execute(input: Input): Output { val member = memberGateway.getById(input.memberId) + validateDailyUsageLimit(input.memberId) + val experimentPostKeyword = experimentKeywordExtractionGateway.extractKeywords(input.text) val log = ExperimentPostKeywordsLog.newExperimentPostKeywordsLog( id = idGenerator.generateId(), @@ -36,4 +44,20 @@ class ExtractExperimentPostKeywordsUseCase( experimentPostKeywordsGateway.save(log) return Output(experimentPostKeyword) } + + private fun validateDailyUsageLimit(memberId: String) { + val today = TimeProvider.currentDateTime().toLocalDate() + val startOfDay = today.atStartOfDay() + val endOfDay = today.plusDays(1).atStartOfDay() + + val todayUsageCount = experimentPostKeywordsGateway.countByMemberIdAndCreatedAtBetween( + memberId = memberId, + start = startOfDay, + end = endOfDay + ) + + if (todayUsageCount >= DAILY_USAGE_LIMIT) { + throw ExperimentPostKeywordsDailyLimitExceededException + } + } } diff --git a/domain/src/main/kotlin/com/dobby/exception/DobbyException.kt b/domain/src/main/kotlin/com/dobby/exception/DobbyException.kt index b9d6edb3..383b06f9 100644 --- a/domain/src/main/kotlin/com/dobby/exception/DobbyException.kt +++ b/domain/src/main/kotlin/com/dobby/exception/DobbyException.kt @@ -68,6 +68,7 @@ data object ExperimentPostRewardException : ClientException("EP0010", "Reward ca data object ExperimentPostContentException : ClientException("EP0011", "Content cannot be null.") data object ExperimentPostCountException : ClientException("EP0012", "Count could be more than zero.") data object ExperimentPostLeadResearcherException : ClientException("EP0013", "Lead Researcher cannot be null.") +data object ExperimentPostKeywordsDailyLimitExceededException : ClientException("EP0014", "Daily usage limit for experiment post keywords extraction has been exceeded.") /** * ServerException: Exceptions caused by internal server issues From 2877e91cecc7ed9ad6a8e663d8fe017f7e521ae0 Mon Sep 17 00:00:00 2001 From: jisu Date: Sun, 27 Jul 2025 21:04:57 +0900 Subject: [PATCH 07/11] chore: add idx_experiment_keywords_log for covering index --- .../V202507271926__add_experiment_post_keywords_log_table.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/infrastructure/src/main/resources/db/migration/V202507271926__add_experiment_post_keywords_log_table.sql b/infrastructure/src/main/resources/db/migration/V202507271926__add_experiment_post_keywords_log_table.sql index b9dc0763..871b39b0 100644 --- a/infrastructure/src/main/resources/db/migration/V202507271926__add_experiment_post_keywords_log_table.sql +++ b/infrastructure/src/main/resources/db/migration/V202507271926__add_experiment_post_keywords_log_table.sql @@ -6,3 +6,5 @@ CREATE TABLE experiment_post_keywords_log ( CONSTRAINT fk_experiment_post_keywords_log_member FOREIGN KEY (member_id) REFERENCES member (member_id) ) COLLATE = utf8mb4_unicode_ci; + +CREATE INDEX idx_experiment_keywords_log ON experiment_post_keywords_log (member_id, created_at); From de2130ae9057a7568d88049a08882b042ad15342 Mon Sep 17 00:00:00 2001 From: jisu Date: Sun, 27 Jul 2025 21:10:23 +0900 Subject: [PATCH 08/11] test: add comprehensive tests for daily usage limit validation --- ...xtractExperimentPostKeywordsUseCaseTest.kt | 78 +++++++++++++++++-- 1 file changed, 72 insertions(+), 6 deletions(-) diff --git a/application/src/test/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCaseTest.kt b/application/src/test/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCaseTest.kt index 007573ee..2df4b7a0 100644 --- a/application/src/test/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCaseTest.kt +++ b/application/src/test/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCaseTest.kt @@ -1,32 +1,98 @@ package com.dobby.usecase.experiment +import com.dobby.exception.ExperimentPostKeywordsDailyLimitExceededException import com.dobby.gateway.experiment.ExperimentKeywordExtractionGateway +import com.dobby.gateway.experiment.ExperimentPostKeywordsLogGateway +import com.dobby.gateway.member.MemberGateway +import com.dobby.model.experiment.ExperimentPostKeywordsLog import com.dobby.model.experiment.keyword.ExperimentPostKeyword +import com.dobby.model.member.Member +import com.dobby.util.IdGenerator +import com.dobby.util.TimeProvider +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 io.mockk.mockkObject +import io.mockk.unmockkAll +import java.time.LocalDateTime class ExtractExperimentPostKeywordsUseCaseTest : BehaviorSpec({ val experimentKeywordExtractionGateway = mockk() - val extractExperimentPostKeywordsUseCase = ExtractExperimentPostKeywordsUseCase(experimentKeywordExtractionGateway) + val experimentPostKeywordsLogGateway = mockk() + val memberGateway = mockk() + val idGenerator = mockk() - given("실험 게시글 텍스트에서 키워드를 추출할 때") { + val extractExperimentPostKeywordsUseCase = ExtractExperimentPostKeywordsUseCase( + experimentKeywordExtractionGateway, + experimentPostKeywordsLogGateway, + memberGateway, + idGenerator + ) + + beforeSpec { + mockkObject(TimeProvider) + } + + afterSpec { + unmockkAll() + } + + given("일일 사용 한도를 초과하지 않은 사용자가") { + val memberId = "test_member_123" val inputText = "남성 20-30대 대상 설문조사 참여자 모집합니다. 시간은 1시간 소요되며 참가비 10,000원 지급합니다." - val input = ExtractExperimentPostKeywordsUseCase.Input(inputText) + val input = ExtractExperimentPostKeywordsUseCase.Input(memberId, inputText) + + val mockMember = mockk() val mockExperimentPostKeyword = mockk() + val mockLog = mockk() + val currentDateTime = LocalDateTime.of(2025, 1, 27, 10, 0, 0) + every { TimeProvider.currentDateTime() } returns currentDateTime + every { memberGateway.getById(memberId) } returns mockMember + every { idGenerator.generateId() } returns "test_log_id" every { experimentKeywordExtractionGateway.extractKeywords(inputText) } returns mockExperimentPostKeyword + every { experimentPostKeywordsLogGateway.save(any()) } returns mockLog + every { + experimentPostKeywordsLogGateway.countByMemberIdAndCreatedAtBetween( + memberId = memberId, + start = currentDateTime.toLocalDate().atStartOfDay(), + end = currentDateTime.toLocalDate().plusDays(1).atStartOfDay() + ) + } returns 1 `when`("키워드 추출을 요청하면") { val result = extractExperimentPostKeywordsUseCase.execute(input) then("추출된 키워드 정보를 반환해야 한다") { result.experimentPostKeyword shouldBe mockExperimentPostKeyword + } + } + } + + given("일일 사용 한도에 도달한 사용자가") { + val memberId = "exceeded_member_456" + val inputText = "실험 참여자 모집" + val input = ExtractExperimentPostKeywordsUseCase.Input(memberId, inputText) - verify(exactly = 1) { - experimentKeywordExtractionGateway.extractKeywords(inputText) + val mockMember = mockk() + val currentDateTime = LocalDateTime.of(2025, 1, 27, 15, 30, 0) + + every { TimeProvider.currentDateTime() } returns currentDateTime + every { memberGateway.getById(memberId) } returns mockMember + every { + experimentPostKeywordsLogGateway.countByMemberIdAndCreatedAtBetween( + memberId = memberId, + start = currentDateTime.toLocalDate().atStartOfDay(), + end = currentDateTime.toLocalDate().plusDays(1).atStartOfDay() + ) + } returns 2 + + `when`("키워드 추출을 요청하면") { + then("DailyLimitExceededException 예외가 발생해야 한다") { + shouldThrow { + extractExperimentPostKeywordsUseCase.execute(input) } } } From e7539c1272a581c1d45fff960c830ef5ba7a4621 Mon Sep 17 00:00:00 2001 From: jisu Date: Sun, 27 Jul 2025 21:11:01 +0900 Subject: [PATCH 09/11] style: apply ktlint formatting --- .../usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt | 2 +- .../entity/experiment/ExperimentPostKeywordsLogEntity.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt b/application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt index 27ef143a..9aa81919 100644 --- a/application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt +++ b/application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt @@ -38,7 +38,7 @@ class ExtractExperimentPostKeywordsUseCase( val log = ExperimentPostKeywordsLog.newExperimentPostKeywordsLog( id = idGenerator.generateId(), member = member, - response = experimentPostKeyword, + response = experimentPostKeyword ) experimentPostKeywordsGateway.save(log) diff --git a/infrastructure/src/main/kotlin/com/dobby/persistence/entity/experiment/ExperimentPostKeywordsLogEntity.kt b/infrastructure/src/main/kotlin/com/dobby/persistence/entity/experiment/ExperimentPostKeywordsLogEntity.kt index e92d5fa6..b2559ec7 100644 --- a/infrastructure/src/main/kotlin/com/dobby/persistence/entity/experiment/ExperimentPostKeywordsLogEntity.kt +++ b/infrastructure/src/main/kotlin/com/dobby/persistence/entity/experiment/ExperimentPostKeywordsLogEntity.kt @@ -23,5 +23,5 @@ class ExperimentPostKeywordsLogEntity( val response: String, @Column(name = "created_at", nullable = false) - val createdAt: LocalDateTime, + val createdAt: LocalDateTime ) From eb3a01f8cc00c3c57a196e76551ce23ec68c441c Mon Sep 17 00:00:00 2001 From: jisu Date: Sun, 27 Jul 2025 21:52:07 +0900 Subject: [PATCH 10/11] style: rename class file for consistency --- .../ExtractExperimentPostKeywordsUseCase.kt | 10 +++++----- .../ExtractExperimentPostKeywordsUseCaseTest.kt | 14 +++++++------- .../ExperimentKeywordExtractionGateway.kt | 7 ------- .../ExperimentPostKeywordsExtractionGateway.kt | 7 +++++++ .../model/experiment/ExperimentPostKeywordsLog.kt | 6 +++--- ...entPostKeyword.kt => ExperimentPostKeywords.kt} | 2 +- ...ExperimentPostKeywordsExtractionGatewayImpl.kt} | 10 +++++----- .../external/prompt/ExperimentPostKeywordMapper.kt | 8 ++++---- .../mapper/ExperimentPostKeywordsLogMapper.kt | 4 ++-- .../response/experiment/ExtractKeywordResponse.kt | 4 ++-- .../com/dobby/api/mapper/ExperimentPostMapper.kt | 2 +- 11 files changed, 37 insertions(+), 37 deletions(-) delete mode 100644 domain/src/main/kotlin/com/dobby/gateway/experiment/ExperimentKeywordExtractionGateway.kt create mode 100644 domain/src/main/kotlin/com/dobby/gateway/experiment/ExperimentPostKeywordsExtractionGateway.kt rename domain/src/main/kotlin/com/dobby/model/experiment/keyword/{ExperimentPostKeyword.kt => ExperimentPostKeywords.kt} (90%) rename infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/{ExperimentKeywordExtractionGatewayImpl.kt => ExperimentPostKeywordsExtractionGatewayImpl.kt} (92%) diff --git a/application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt b/application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt index 9aa81919..3b89e403 100644 --- a/application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt +++ b/application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt @@ -1,17 +1,17 @@ package com.dobby.usecase.experiment import com.dobby.exception.ExperimentPostKeywordsDailyLimitExceededException -import com.dobby.gateway.experiment.ExperimentKeywordExtractionGateway +import com.dobby.gateway.experiment.ExperimentPostKeywordsExtractionGateway import com.dobby.gateway.experiment.ExperimentPostKeywordsLogGateway import com.dobby.gateway.member.MemberGateway import com.dobby.model.experiment.ExperimentPostKeywordsLog -import com.dobby.model.experiment.keyword.ExperimentPostKeyword +import com.dobby.model.experiment.keyword.ExperimentPostKeywords import com.dobby.usecase.UseCase import com.dobby.util.IdGenerator import com.dobby.util.TimeProvider class ExtractExperimentPostKeywordsUseCase( - private val experimentKeywordExtractionGateway: ExperimentKeywordExtractionGateway, + private val experimentPostKeywordsExtractionGateway: ExperimentPostKeywordsExtractionGateway, private val experimentPostKeywordsGateway: ExperimentPostKeywordsLogGateway, private val memberGateway: MemberGateway, private val idGenerator: IdGenerator @@ -27,14 +27,14 @@ class ExtractExperimentPostKeywordsUseCase( ) data class Output( - val experimentPostKeyword: ExperimentPostKeyword + val experimentPostKeywords: ExperimentPostKeywords ) override fun execute(input: Input): Output { val member = memberGateway.getById(input.memberId) validateDailyUsageLimit(input.memberId) - val experimentPostKeyword = experimentKeywordExtractionGateway.extractKeywords(input.text) + val experimentPostKeyword = experimentPostKeywordsExtractionGateway.extractKeywords(input.text) val log = ExperimentPostKeywordsLog.newExperimentPostKeywordsLog( id = idGenerator.generateId(), member = member, diff --git a/application/src/test/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCaseTest.kt b/application/src/test/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCaseTest.kt index 2df4b7a0..eae3007a 100644 --- a/application/src/test/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCaseTest.kt +++ b/application/src/test/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCaseTest.kt @@ -1,11 +1,11 @@ package com.dobby.usecase.experiment import com.dobby.exception.ExperimentPostKeywordsDailyLimitExceededException -import com.dobby.gateway.experiment.ExperimentKeywordExtractionGateway +import com.dobby.gateway.experiment.ExperimentPostKeywordsExtractionGateway import com.dobby.gateway.experiment.ExperimentPostKeywordsLogGateway import com.dobby.gateway.member.MemberGateway import com.dobby.model.experiment.ExperimentPostKeywordsLog -import com.dobby.model.experiment.keyword.ExperimentPostKeyword +import com.dobby.model.experiment.keyword.ExperimentPostKeywords import com.dobby.model.member.Member import com.dobby.util.IdGenerator import com.dobby.util.TimeProvider @@ -19,13 +19,13 @@ import io.mockk.unmockkAll import java.time.LocalDateTime class ExtractExperimentPostKeywordsUseCaseTest : BehaviorSpec({ - val experimentKeywordExtractionGateway = mockk() + val experimentPostKeywordsExtractionGateway = mockk() val experimentPostKeywordsLogGateway = mockk() val memberGateway = mockk() val idGenerator = mockk() val extractExperimentPostKeywordsUseCase = ExtractExperimentPostKeywordsUseCase( - experimentKeywordExtractionGateway, + experimentPostKeywordsExtractionGateway, experimentPostKeywordsLogGateway, memberGateway, idGenerator @@ -45,14 +45,14 @@ class ExtractExperimentPostKeywordsUseCaseTest : BehaviorSpec({ val input = ExtractExperimentPostKeywordsUseCase.Input(memberId, inputText) val mockMember = mockk() - val mockExperimentPostKeyword = mockk() + val mockExperimentPostKeywords = mockk() val mockLog = mockk() val currentDateTime = LocalDateTime.of(2025, 1, 27, 10, 0, 0) every { TimeProvider.currentDateTime() } returns currentDateTime every { memberGateway.getById(memberId) } returns mockMember every { idGenerator.generateId() } returns "test_log_id" - every { experimentKeywordExtractionGateway.extractKeywords(inputText) } returns mockExperimentPostKeyword + every { experimentPostKeywordsExtractionGateway.extractKeywords(inputText) } returns mockExperimentPostKeywords every { experimentPostKeywordsLogGateway.save(any()) } returns mockLog every { experimentPostKeywordsLogGateway.countByMemberIdAndCreatedAtBetween( @@ -66,7 +66,7 @@ class ExtractExperimentPostKeywordsUseCaseTest : BehaviorSpec({ val result = extractExperimentPostKeywordsUseCase.execute(input) then("추출된 키워드 정보를 반환해야 한다") { - result.experimentPostKeyword shouldBe mockExperimentPostKeyword + result.experimentPostKeywords shouldBe mockExperimentPostKeywords } } } diff --git a/domain/src/main/kotlin/com/dobby/gateway/experiment/ExperimentKeywordExtractionGateway.kt b/domain/src/main/kotlin/com/dobby/gateway/experiment/ExperimentKeywordExtractionGateway.kt deleted file mode 100644 index a189843d..00000000 --- a/domain/src/main/kotlin/com/dobby/gateway/experiment/ExperimentKeywordExtractionGateway.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.dobby.gateway.experiment - -import com.dobby.model.experiment.keyword.ExperimentPostKeyword - -interface ExperimentKeywordExtractionGateway { - fun extractKeywords(text: String): ExperimentPostKeyword -} diff --git a/domain/src/main/kotlin/com/dobby/gateway/experiment/ExperimentPostKeywordsExtractionGateway.kt b/domain/src/main/kotlin/com/dobby/gateway/experiment/ExperimentPostKeywordsExtractionGateway.kt new file mode 100644 index 00000000..f530aba1 --- /dev/null +++ b/domain/src/main/kotlin/com/dobby/gateway/experiment/ExperimentPostKeywordsExtractionGateway.kt @@ -0,0 +1,7 @@ +package com.dobby.gateway.experiment + +import com.dobby.model.experiment.keyword.ExperimentPostKeywords + +interface ExperimentPostKeywordsExtractionGateway { + fun extractKeywords(text: String): ExperimentPostKeywords +} diff --git a/domain/src/main/kotlin/com/dobby/model/experiment/ExperimentPostKeywordsLog.kt b/domain/src/main/kotlin/com/dobby/model/experiment/ExperimentPostKeywordsLog.kt index b3aa71de..c0d76df9 100644 --- a/domain/src/main/kotlin/com/dobby/model/experiment/ExperimentPostKeywordsLog.kt +++ b/domain/src/main/kotlin/com/dobby/model/experiment/ExperimentPostKeywordsLog.kt @@ -1,6 +1,6 @@ package com.dobby.model.experiment -import com.dobby.model.experiment.keyword.ExperimentPostKeyword +import com.dobby.model.experiment.keyword.ExperimentPostKeywords import com.dobby.model.member.Member import com.dobby.util.TimeProvider import java.time.LocalDateTime @@ -8,14 +8,14 @@ import java.time.LocalDateTime data class ExperimentPostKeywordsLog( val id: String, val member: Member, - val response: ExperimentPostKeyword, + val response: ExperimentPostKeywords, val createdAt: LocalDateTime ) { companion object { fun newExperimentPostKeywordsLog( id: String, member: Member, - response: ExperimentPostKeyword + response: ExperimentPostKeywords ) = ExperimentPostKeywordsLog( id = id, member = member, diff --git a/domain/src/main/kotlin/com/dobby/model/experiment/keyword/ExperimentPostKeyword.kt b/domain/src/main/kotlin/com/dobby/model/experiment/keyword/ExperimentPostKeywords.kt similarity index 90% rename from domain/src/main/kotlin/com/dobby/model/experiment/keyword/ExperimentPostKeyword.kt rename to domain/src/main/kotlin/com/dobby/model/experiment/keyword/ExperimentPostKeywords.kt index cea8f404..e3419956 100644 --- a/domain/src/main/kotlin/com/dobby/model/experiment/keyword/ExperimentPostKeyword.kt +++ b/domain/src/main/kotlin/com/dobby/model/experiment/keyword/ExperimentPostKeywords.kt @@ -3,7 +3,7 @@ package com.dobby.model.experiment.keyword import com.dobby.enums.MatchType import com.dobby.enums.experiment.TimeSlot -data class ExperimentPostKeyword( +data class ExperimentPostKeywords( val targetGroup: TargetGroupKeyword?, val applyMethod: ApplyMethodKeyword?, val matchType: MatchType?, diff --git a/infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentKeywordExtractionGatewayImpl.kt b/infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentPostKeywordsExtractionGatewayImpl.kt similarity index 92% rename from infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentKeywordExtractionGatewayImpl.kt rename to infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentPostKeywordsExtractionGatewayImpl.kt index 9ee5c29c..e0b05520 100644 --- a/infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentKeywordExtractionGatewayImpl.kt +++ b/infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentPostKeywordsExtractionGatewayImpl.kt @@ -7,19 +7,19 @@ import com.dobby.external.prompt.ExperimentPostKeywordMapper import com.dobby.external.prompt.PromptTemplate import com.dobby.external.prompt.PromptTemplateLoader import com.dobby.external.prompt.dto.ExperimentPostKeywordDto -import com.dobby.gateway.experiment.ExperimentKeywordExtractionGateway -import com.dobby.model.experiment.keyword.ExperimentPostKeyword +import com.dobby.gateway.experiment.ExperimentPostKeywordsExtractionGateway +import com.dobby.model.experiment.keyword.ExperimentPostKeywords import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue import feign.FeignException import org.springframework.stereotype.Component @Component -class ExperimentKeywordExtractionGatewayImpl( +class ExperimentPostKeywordsExtractionGatewayImpl( private val openAiFeignClient: OpenAiFeignClient, private val promptTemplateLoader: PromptTemplateLoader, private val mapper: ExperimentPostKeywordMapper -) : ExperimentKeywordExtractionGateway { +) : ExperimentPostKeywordsExtractionGateway { private val objectMapper = jacksonObjectMapper() @@ -27,7 +27,7 @@ class ExperimentKeywordExtractionGatewayImpl( promptTemplateLoader.loadPrompt("prompts/keyword_extraction_prompt.json") } - override fun extractKeywords(text: String): ExperimentPostKeyword { + override fun extractKeywords(text: String): ExperimentPostKeywords { val promptJson = objectMapper.writeValueAsString(promptTemplate) val prompt = promptJson.replace("{{text}}", escapeJsonString(text)) val messages = listOf( diff --git a/infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt b/infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt index b1986c76..9d739bed 100644 --- a/infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt +++ b/infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt @@ -7,15 +7,15 @@ import com.dobby.external.prompt.dto.ApplyMethodDto import com.dobby.external.prompt.dto.ExperimentPostKeywordDto import com.dobby.external.prompt.dto.TargetGroupDto import com.dobby.model.experiment.keyword.ApplyMethodKeyword -import com.dobby.model.experiment.keyword.ExperimentPostKeyword +import com.dobby.model.experiment.keyword.ExperimentPostKeywords import com.dobby.model.experiment.keyword.TargetGroupKeyword import org.springframework.stereotype.Component @Component class ExperimentPostKeywordMapper { - fun toDomain(dto: ExperimentPostKeywordDto): ExperimentPostKeyword { - return ExperimentPostKeyword( + fun toDomain(dto: ExperimentPostKeywordDto): ExperimentPostKeywords { + return ExperimentPostKeywords( targetGroup = dto.targetGroup?.let { targetGroupDto -> TargetGroupKeyword( startAge = targetGroupDto.startAge ?: 0, @@ -59,7 +59,7 @@ class ExperimentPostKeywordMapper { ) } - fun toDto(domain: ExperimentPostKeyword): ExperimentPostKeywordDto { + fun toDto(domain: ExperimentPostKeywords): ExperimentPostKeywordDto { return ExperimentPostKeywordDto( targetGroup = domain.targetGroup?.let { targetGroupDomain -> TargetGroupDto( diff --git a/infrastructure/src/main/kotlin/com/dobby/mapper/ExperimentPostKeywordsLogMapper.kt b/infrastructure/src/main/kotlin/com/dobby/mapper/ExperimentPostKeywordsLogMapper.kt index 8694b954..2c1eda8a 100644 --- a/infrastructure/src/main/kotlin/com/dobby/mapper/ExperimentPostKeywordsLogMapper.kt +++ b/infrastructure/src/main/kotlin/com/dobby/mapper/ExperimentPostKeywordsLogMapper.kt @@ -2,7 +2,7 @@ package com.dobby.mapper import com.dobby.converter.JsonConverter import com.dobby.model.experiment.ExperimentPostKeywordsLog -import com.dobby.model.experiment.keyword.ExperimentPostKeyword +import com.dobby.model.experiment.keyword.ExperimentPostKeywords import com.dobby.persistence.entity.experiment.ExperimentPostKeywordsLogEntity import com.dobby.persistence.entity.member.MemberEntity import org.springframework.stereotype.Component @@ -16,7 +16,7 @@ class ExperimentPostKeywordsLogMapper( return ExperimentPostKeywordsLog( id = entity.id, member = entity.member.toDomain(), - response = jsonConverter.fromJson(entity.response, ExperimentPostKeyword::class.java), + response = jsonConverter.fromJson(entity.response, ExperimentPostKeywords::class.java), createdAt = entity.createdAt ) } diff --git a/presentation/src/main/kotlin/com/dobby/api/dto/response/experiment/ExtractKeywordResponse.kt b/presentation/src/main/kotlin/com/dobby/api/dto/response/experiment/ExtractKeywordResponse.kt index 978b49a1..b8154070 100644 --- a/presentation/src/main/kotlin/com/dobby/api/dto/response/experiment/ExtractKeywordResponse.kt +++ b/presentation/src/main/kotlin/com/dobby/api/dto/response/experiment/ExtractKeywordResponse.kt @@ -1,9 +1,9 @@ package com.dobby.api.dto.response.experiment -import com.dobby.model.experiment.keyword.ExperimentPostKeyword +import com.dobby.model.experiment.keyword.ExperimentPostKeywords import io.swagger.v3.oas.annotations.media.Schema data class ExtractKeywordResponse( @Schema(description = "추출된 키워드 정보") - val experimentPostKeyword: ExperimentPostKeyword + val experimentPostKeywords: ExperimentPostKeywords ) diff --git a/presentation/src/main/kotlin/com/dobby/api/mapper/ExperimentPostMapper.kt b/presentation/src/main/kotlin/com/dobby/api/mapper/ExperimentPostMapper.kt index a97bae24..5437f42a 100644 --- a/presentation/src/main/kotlin/com/dobby/api/mapper/ExperimentPostMapper.kt +++ b/presentation/src/main/kotlin/com/dobby/api/mapper/ExperimentPostMapper.kt @@ -530,7 +530,7 @@ object ExperimentPostMapper { fun toExtractKeywordResponse(output: ExtractExperimentPostKeywordsUseCase.Output): ExtractKeywordResponse { return ExtractKeywordResponse( - experimentPostKeyword = output.experimentPostKeyword + experimentPostKeywords = output.experimentPostKeywords ) } } From 348b07e1a17557ef1d2988c54cff94d9309ed6ce Mon Sep 17 00:00:00 2001 From: jisu Date: Sun, 27 Jul 2025 21:58:08 +0900 Subject: [PATCH 11/11] refactor: clarify responsibilities by restructuring OpenAI Gateway and renaming classes --- .../experiment/ExtractExperimentPostKeywordsUseCase.kt | 6 +++--- .../ExtractExperimentPostKeywordsUseCaseTest.kt | 8 ++++---- ...tPostKeywordsExtractionGateway.kt => OpenAiGateway.kt} | 4 ++-- .../OpenAiGatewayImpl.kt} | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) rename domain/src/main/kotlin/com/dobby/gateway/{experiment/ExperimentPostKeywordsExtractionGateway.kt => OpenAiGateway.kt} (59%) rename infrastructure/src/main/kotlin/com/dobby/external/{gateway/experiment/ExperimentPostKeywordsExtractionGatewayImpl.kt => openai/OpenAiGatewayImpl.kt} (92%) diff --git a/application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt b/application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt index 3b89e403..15b5b042 100644 --- a/application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt +++ b/application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt @@ -1,7 +1,7 @@ package com.dobby.usecase.experiment import com.dobby.exception.ExperimentPostKeywordsDailyLimitExceededException -import com.dobby.gateway.experiment.ExperimentPostKeywordsExtractionGateway +import com.dobby.gateway.OpenAiGateway import com.dobby.gateway.experiment.ExperimentPostKeywordsLogGateway import com.dobby.gateway.member.MemberGateway import com.dobby.model.experiment.ExperimentPostKeywordsLog @@ -11,7 +11,7 @@ import com.dobby.util.IdGenerator import com.dobby.util.TimeProvider class ExtractExperimentPostKeywordsUseCase( - private val experimentPostKeywordsExtractionGateway: ExperimentPostKeywordsExtractionGateway, + private val openAiGateway: OpenAiGateway, private val experimentPostKeywordsGateway: ExperimentPostKeywordsLogGateway, private val memberGateway: MemberGateway, private val idGenerator: IdGenerator @@ -34,7 +34,7 @@ class ExtractExperimentPostKeywordsUseCase( val member = memberGateway.getById(input.memberId) validateDailyUsageLimit(input.memberId) - val experimentPostKeyword = experimentPostKeywordsExtractionGateway.extractKeywords(input.text) + val experimentPostKeyword = openAiGateway.extractKeywords(input.text) val log = ExperimentPostKeywordsLog.newExperimentPostKeywordsLog( id = idGenerator.generateId(), member = member, diff --git a/application/src/test/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCaseTest.kt b/application/src/test/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCaseTest.kt index eae3007a..095e6076 100644 --- a/application/src/test/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCaseTest.kt +++ b/application/src/test/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCaseTest.kt @@ -1,7 +1,7 @@ package com.dobby.usecase.experiment import com.dobby.exception.ExperimentPostKeywordsDailyLimitExceededException -import com.dobby.gateway.experiment.ExperimentPostKeywordsExtractionGateway +import com.dobby.gateway.OpenAiGateway import com.dobby.gateway.experiment.ExperimentPostKeywordsLogGateway import com.dobby.gateway.member.MemberGateway import com.dobby.model.experiment.ExperimentPostKeywordsLog @@ -19,13 +19,13 @@ import io.mockk.unmockkAll import java.time.LocalDateTime class ExtractExperimentPostKeywordsUseCaseTest : BehaviorSpec({ - val experimentPostKeywordsExtractionGateway = mockk() + val openAiGateway = mockk() val experimentPostKeywordsLogGateway = mockk() val memberGateway = mockk() val idGenerator = mockk() val extractExperimentPostKeywordsUseCase = ExtractExperimentPostKeywordsUseCase( - experimentPostKeywordsExtractionGateway, + openAiGateway, experimentPostKeywordsLogGateway, memberGateway, idGenerator @@ -52,7 +52,7 @@ class ExtractExperimentPostKeywordsUseCaseTest : BehaviorSpec({ every { TimeProvider.currentDateTime() } returns currentDateTime every { memberGateway.getById(memberId) } returns mockMember every { idGenerator.generateId() } returns "test_log_id" - every { experimentPostKeywordsExtractionGateway.extractKeywords(inputText) } returns mockExperimentPostKeywords + every { openAiGateway.extractKeywords(inputText) } returns mockExperimentPostKeywords every { experimentPostKeywordsLogGateway.save(any()) } returns mockLog every { experimentPostKeywordsLogGateway.countByMemberIdAndCreatedAtBetween( diff --git a/domain/src/main/kotlin/com/dobby/gateway/experiment/ExperimentPostKeywordsExtractionGateway.kt b/domain/src/main/kotlin/com/dobby/gateway/OpenAiGateway.kt similarity index 59% rename from domain/src/main/kotlin/com/dobby/gateway/experiment/ExperimentPostKeywordsExtractionGateway.kt rename to domain/src/main/kotlin/com/dobby/gateway/OpenAiGateway.kt index f530aba1..65e4868d 100644 --- a/domain/src/main/kotlin/com/dobby/gateway/experiment/ExperimentPostKeywordsExtractionGateway.kt +++ b/domain/src/main/kotlin/com/dobby/gateway/OpenAiGateway.kt @@ -1,7 +1,7 @@ -package com.dobby.gateway.experiment +package com.dobby.gateway import com.dobby.model.experiment.keyword.ExperimentPostKeywords -interface ExperimentPostKeywordsExtractionGateway { +interface OpenAiGateway { fun extractKeywords(text: String): ExperimentPostKeywords } diff --git a/infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentPostKeywordsExtractionGatewayImpl.kt b/infrastructure/src/main/kotlin/com/dobby/external/openai/OpenAiGatewayImpl.kt similarity index 92% rename from infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentPostKeywordsExtractionGatewayImpl.kt rename to infrastructure/src/main/kotlin/com/dobby/external/openai/OpenAiGatewayImpl.kt index e0b05520..b0cbe9cc 100644 --- a/infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentPostKeywordsExtractionGatewayImpl.kt +++ b/infrastructure/src/main/kotlin/com/dobby/external/openai/OpenAiGatewayImpl.kt @@ -1,4 +1,4 @@ -package com.dobby.external.gateway.experiment +package com.dobby.external.openai import com.dobby.api.dto.request.OpenAiRequest import com.dobby.exception.CustomOpenAiCallException @@ -7,7 +7,7 @@ import com.dobby.external.prompt.ExperimentPostKeywordMapper import com.dobby.external.prompt.PromptTemplate import com.dobby.external.prompt.PromptTemplateLoader import com.dobby.external.prompt.dto.ExperimentPostKeywordDto -import com.dobby.gateway.experiment.ExperimentPostKeywordsExtractionGateway +import com.dobby.gateway.OpenAiGateway import com.dobby.model.experiment.keyword.ExperimentPostKeywords import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue @@ -15,11 +15,11 @@ import feign.FeignException import org.springframework.stereotype.Component @Component -class ExperimentPostKeywordsExtractionGatewayImpl( +class OpenAiGatewayImpl( private val openAiFeignClient: OpenAiFeignClient, private val promptTemplateLoader: PromptTemplateLoader, private val mapper: ExperimentPostKeywordMapper -) : ExperimentPostKeywordsExtractionGateway { +) : OpenAiGateway { private val objectMapper = jacksonObjectMapper()