From 5fe066e7e05db325851377c6b563f4c255256d9e Mon Sep 17 00:00:00 2001 From: sszuev Date: Mon, 18 Mar 2024 11:27:33 +0300 Subject: [PATCH 01/18] common & core & app-ktor: replace CardRepositories & DictionaryRepositories with AppRepositories --- app-ktor/src/main/kotlin/Main.kt | 12 ++--- app-ktor/src/main/kotlin/Repositories.kt | 21 ++++++++ app-ktor/src/main/kotlin/api/Api.kt | 7 ++- .../kotlin/api/controllers/CardController.kt | 22 ++++---- .../api/controllers/DictionaryController.kt | 14 +++--- .../src/main/kotlin/api/controllers/Rest.kt | 9 ++-- .../main/kotlin/config/RepositoriesConfig.kt | 50 ------------------- .../controllers/CardControllerMockkTest.kt | 4 +- ...naryRepositories.kt => AppRepositories.kt} | 22 ++++++-- common/src/commonMain/kotlin/CardContext.kt | 2 +- .../src/commonMain/kotlin/CardRepositories.kt | 42 ---------------- .../commonMain/kotlin/DictionaryContext.kt | 2 +- .../kotlin/model/common/AppContext.kt | 1 + .../kotlin/model/common/AppRepositories.kt | 14 ------ .../kotlin/repositories/AppUserRepository.kt | 14 ++++++ .../kotlin/CardCorProcessorRunCardsTest.kt | 4 +- .../kotlin/CardCorProcessorRunResourceTest.kt | 10 ++-- .../kotlin/DictionaryCorProcessorRunTest.kt | 4 +- 18 files changed, 99 insertions(+), 155 deletions(-) create mode 100644 app-ktor/src/main/kotlin/Repositories.kt delete mode 100644 app-ktor/src/main/kotlin/config/RepositoriesConfig.kt rename common/src/commonMain/kotlin/{DictionaryRepositories.kt => AppRepositories.kt} (70%) delete mode 100644 common/src/commonMain/kotlin/CardRepositories.kt delete mode 100644 common/src/commonMain/kotlin/model/common/AppRepositories.kt create mode 100644 common/src/commonMain/kotlin/repositories/AppUserRepository.kt diff --git a/app-ktor/src/main/kotlin/Main.kt b/app-ktor/src/main/kotlin/Main.kt index 99cc96a9..74f75a97 100644 --- a/app-ktor/src/main/kotlin/Main.kt +++ b/app-ktor/src/main/kotlin/Main.kt @@ -10,7 +10,6 @@ import com.gitlab.sszuev.flashcards.api.cardApiV1 import com.gitlab.sszuev.flashcards.api.dictionaryApiV1 import com.gitlab.sszuev.flashcards.config.ContextConfig import com.gitlab.sszuev.flashcards.config.KeycloakConfig -import com.gitlab.sszuev.flashcards.config.RepositoriesConfig import com.gitlab.sszuev.flashcards.config.RunConfig import com.gitlab.sszuev.flashcards.config.TutorConfig import com.gitlab.sszuev.flashcards.logslib.ExtLogger @@ -81,13 +80,14 @@ fun main(args: Array) = io.ktor.server.jetty.EngineMain.main(args) @KtorExperimentalLocationsAPI @Suppress("unused") fun Application.module( - repositoriesConfig: RepositoriesConfig = RepositoriesConfig(), keycloakConfig: KeycloakConfig = KeycloakConfig(environment.config), runConfig: RunConfig = RunConfig(environment.config), tutorConfig: TutorConfig = TutorConfig(environment.config), ) { logger.info(printGeneralSettings(runConfig, keycloakConfig, tutorConfig)) + val repositories = appRepositories() + val port = environment.config.property("ktor.deployment.port").getString() val keycloakProvider = OAuthServerSettings.OAuth2ServerSettings( @@ -174,12 +174,12 @@ fun Application.module( authenticate("auth-jwt") { this@authenticate.cardApiV1( service = cardService, - repositories = repositoriesConfig.cardRepositories, + repositories = repositories, contextConfig = contextConfig, ) this@authenticate.dictionaryApiV1( service = dictionaryService, - repositories = repositoriesConfig.dictionaryRepositories, + repositories = repositories, contextConfig = contextConfig, ) } @@ -203,12 +203,12 @@ fun Application.module( } else { cardApiV1( service = cardService, - repositories = repositoriesConfig.cardRepositories, + repositories = repositories, contextConfig = contextConfig, ) dictionaryApiV1( service = dictionaryService, - repositories = repositoriesConfig.dictionaryRepositories, + repositories = repositories, contextConfig = contextConfig, ) get("/") { diff --git a/app-ktor/src/main/kotlin/Repositories.kt b/app-ktor/src/main/kotlin/Repositories.kt new file mode 100644 index 00000000..5b12149d --- /dev/null +++ b/app-ktor/src/main/kotlin/Repositories.kt @@ -0,0 +1,21 @@ +package com.gitlab.sszuev.flashcards + +import com.gitlab.sszuev.flashcards.dbmem.MemDbCardRepository +import com.gitlab.sszuev.flashcards.dbmem.MemDbDictionaryRepository +import com.gitlab.sszuev.flashcards.dbmem.MemDbUserRepository +import com.gitlab.sszuev.flashcards.dbpg.PgDbCardRepository +import com.gitlab.sszuev.flashcards.dbpg.PgDbDictionaryRepository +import com.gitlab.sszuev.flashcards.dbpg.PgDbUserRepository +import com.gitlab.sszuev.flashcards.speaker.createDirectTTSResourceRepository +import com.gitlab.sszuev.flashcards.speaker.rabbitmq.RMQTTSResourceRepository + +fun appRepositories() = AppRepositories( + prodTTSClientRepository = RMQTTSResourceRepository(), + testTTSClientRepository = createDirectTTSResourceRepository(), + prodCardRepository = PgDbCardRepository(), + testCardRepository = MemDbCardRepository(), + prodDictionaryRepository = PgDbDictionaryRepository(), + testDictionaryRepository = MemDbDictionaryRepository(), + prodUserRepository = PgDbUserRepository(), + testUserRepository = MemDbUserRepository(), +) \ No newline at end of file diff --git a/app-ktor/src/main/kotlin/api/Api.kt b/app-ktor/src/main/kotlin/api/Api.kt index 12b1d223..99ddb8ea 100644 --- a/app-ktor/src/main/kotlin/api/Api.kt +++ b/app-ktor/src/main/kotlin/api/Api.kt @@ -1,7 +1,6 @@ package com.gitlab.sszuev.flashcards.api -import com.gitlab.sszuev.flashcards.CardRepositories -import com.gitlab.sszuev.flashcards.DictionaryRepositories +import com.gitlab.sszuev.flashcards.AppRepositories import com.gitlab.sszuev.flashcards.api.controllers.cards import com.gitlab.sszuev.flashcards.api.controllers.dictionaries import com.gitlab.sszuev.flashcards.api.controllers.sounds @@ -13,7 +12,7 @@ import io.ktor.server.routing.route internal fun Route.cardApiV1( service: CardService, - repositories: CardRepositories, + repositories: AppRepositories, contextConfig: ContextConfig, ) { route("v1/api") { @@ -24,7 +23,7 @@ internal fun Route.cardApiV1( internal fun Route.dictionaryApiV1( service: DictionaryService, - repositories: DictionaryRepositories, + repositories: AppRepositories, contextConfig: ContextConfig, ) { route("v1/api") { diff --git a/app-ktor/src/main/kotlin/api/controllers/CardController.kt b/app-ktor/src/main/kotlin/api/controllers/CardController.kt index 48b1192d..655c6903 100644 --- a/app-ktor/src/main/kotlin/api/controllers/CardController.kt +++ b/app-ktor/src/main/kotlin/api/controllers/CardController.kt @@ -1,7 +1,7 @@ package com.gitlab.sszuev.flashcards.api.controllers +import com.gitlab.sszuev.flashcards.AppRepositories import com.gitlab.sszuev.flashcards.CardContext -import com.gitlab.sszuev.flashcards.CardRepositories import com.gitlab.sszuev.flashcards.api.services.CardService import com.gitlab.sszuev.flashcards.api.v1.models.BaseRequest import com.gitlab.sszuev.flashcards.api.v1.models.CreateCardRequest @@ -26,7 +26,7 @@ private val logger: ExtLogger = logger("com.gitlab.sszuev.flashcards.api.control suspend fun ApplicationCall.getResource( service: CardService, - repositories: CardRepositories, + repositories: AppRepositories, contextConfig: ContextConfig ) { execute(CardOperation.GET_RESOURCE, repositories, logger, contextConfig) { @@ -36,7 +36,7 @@ suspend fun ApplicationCall.getResource( suspend fun ApplicationCall.createCard( service: CardService, - repositories: CardRepositories, + repositories: AppRepositories, contextConfig: ContextConfig ) { execute(CardOperation.CREATE_CARD, repositories, logger, contextConfig) { @@ -46,7 +46,7 @@ suspend fun ApplicationCall.createCard( suspend fun ApplicationCall.updateCard( service: CardService, - repositories: CardRepositories, + repositories: AppRepositories, contextConfig: ContextConfig ) { execute(CardOperation.UPDATE_CARD, repositories, logger, contextConfig) { @@ -56,7 +56,7 @@ suspend fun ApplicationCall.updateCard( suspend fun ApplicationCall.searchCards( service: CardService, - repositories: CardRepositories, + repositories: AppRepositories, contextConfig: ContextConfig ) { execute(CardOperation.SEARCH_CARDS, repositories, logger, contextConfig) { @@ -66,7 +66,7 @@ suspend fun ApplicationCall.searchCards( suspend fun ApplicationCall.getAllCards( service: CardService, - repositories: CardRepositories, + repositories: AppRepositories, contextConfig: ContextConfig ) { execute(CardOperation.GET_ALL_CARDS, repositories, logger, contextConfig) { @@ -76,7 +76,7 @@ suspend fun ApplicationCall.getAllCards( suspend fun ApplicationCall.getCard( service: CardService, - repositories: CardRepositories, + repositories: AppRepositories, contextConfig: ContextConfig ) { execute(CardOperation.GET_CARD, repositories, logger, contextConfig) { @@ -86,7 +86,7 @@ suspend fun ApplicationCall.getCard( suspend fun ApplicationCall.learnCard( service: CardService, - repositories: CardRepositories, + repositories: AppRepositories, contextConfig: ContextConfig ) { execute(CardOperation.LEARN_CARDS, repositories, logger, contextConfig) { @@ -96,7 +96,7 @@ suspend fun ApplicationCall.learnCard( suspend fun ApplicationCall.resetCard( service: CardService, - repositories: CardRepositories, + repositories: AppRepositories, contextConfig: ContextConfig ) { execute(CardOperation.RESET_CARD, repositories, logger, contextConfig) { @@ -106,7 +106,7 @@ suspend fun ApplicationCall.resetCard( suspend fun ApplicationCall.deleteCard( service: CardService, - repositories: CardRepositories, + repositories: AppRepositories, contextConfig: ContextConfig ) { execute(CardOperation.DELETE_CARD, repositories, logger, contextConfig) { @@ -116,7 +116,7 @@ suspend fun ApplicationCall.deleteCard( private suspend inline fun ApplicationCall.execute( operation: CardOperation, - repositories: CardRepositories, + repositories: AppRepositories, logger: ExtLogger, contextConfig: ContextConfig, noinline exec: suspend CardContext.() -> Unit, diff --git a/app-ktor/src/main/kotlin/api/controllers/DictionaryController.kt b/app-ktor/src/main/kotlin/api/controllers/DictionaryController.kt index 52e07db5..5a7d7e6b 100644 --- a/app-ktor/src/main/kotlin/api/controllers/DictionaryController.kt +++ b/app-ktor/src/main/kotlin/api/controllers/DictionaryController.kt @@ -1,7 +1,7 @@ package com.gitlab.sszuev.flashcards.api.controllers +import com.gitlab.sszuev.flashcards.AppRepositories import com.gitlab.sszuev.flashcards.DictionaryContext -import com.gitlab.sszuev.flashcards.DictionaryRepositories import com.gitlab.sszuev.flashcards.api.services.DictionaryService import com.gitlab.sszuev.flashcards.api.v1.models.BaseRequest import com.gitlab.sszuev.flashcards.api.v1.models.CreateDictionaryRequest @@ -22,7 +22,7 @@ private val logger: ExtLogger = logger("com.gitlab.sszuev.flashcards.api.control suspend fun ApplicationCall.getAllDictionaries( service: DictionaryService, - repositories: DictionaryRepositories, + repositories: AppRepositories, contextConfig: ContextConfig ) { execute( @@ -37,7 +37,7 @@ suspend fun ApplicationCall.getAllDictionaries( suspend fun ApplicationCall.createDictionary( service: DictionaryService, - repositories: DictionaryRepositories, + repositories: AppRepositories, contextConfig: ContextConfig ) { execute( @@ -52,7 +52,7 @@ suspend fun ApplicationCall.createDictionary( suspend fun ApplicationCall.deleteDictionary( service: DictionaryService, - repositories: DictionaryRepositories, + repositories: AppRepositories, contextConfig: ContextConfig ) { execute( @@ -67,7 +67,7 @@ suspend fun ApplicationCall.deleteDictionary( suspend fun ApplicationCall.downloadDictionary( service: DictionaryService, - repositories: DictionaryRepositories, + repositories: AppRepositories, contextConfig: ContextConfig ) { execute( @@ -82,7 +82,7 @@ suspend fun ApplicationCall.downloadDictionary( suspend fun ApplicationCall.uploadDictionary( service: DictionaryService, - repositories: DictionaryRepositories, + repositories: AppRepositories, contextConfig: ContextConfig ) { execute( @@ -97,7 +97,7 @@ suspend fun ApplicationCall.uploadDictionary( private suspend inline fun ApplicationCall.execute( operation: DictionaryOperation, - repositories: DictionaryRepositories, + repositories: AppRepositories, logger: ExtLogger, contextConfig: ContextConfig, noinline exec: suspend DictionaryContext.() -> Unit, diff --git a/app-ktor/src/main/kotlin/api/controllers/Rest.kt b/app-ktor/src/main/kotlin/api/controllers/Rest.kt index 2f0aedc0..e8bbc174 100644 --- a/app-ktor/src/main/kotlin/api/controllers/Rest.kt +++ b/app-ktor/src/main/kotlin/api/controllers/Rest.kt @@ -1,7 +1,6 @@ package com.gitlab.sszuev.flashcards.api.controllers -import com.gitlab.sszuev.flashcards.CardRepositories -import com.gitlab.sszuev.flashcards.DictionaryRepositories +import com.gitlab.sszuev.flashcards.AppRepositories import com.gitlab.sszuev.flashcards.api.services.CardService import com.gitlab.sszuev.flashcards.api.services.DictionaryService import com.gitlab.sszuev.flashcards.config.ContextConfig @@ -12,7 +11,7 @@ import io.ktor.server.routing.route fun Route.cards( service: CardService, - repositories: CardRepositories, + repositories: AppRepositories, contextConfig: ContextConfig, ) { route("cards") { @@ -45,7 +44,7 @@ fun Route.cards( fun Route.sounds( service: CardService, - repositories: CardRepositories, + repositories: AppRepositories, contextConfig: ContextConfig, ) { route("sounds") { @@ -57,7 +56,7 @@ fun Route.sounds( fun Route.dictionaries( service: DictionaryService, - repositories: DictionaryRepositories, + repositories: AppRepositories, contextConfig: ContextConfig, ) { route("dictionaries") { diff --git a/app-ktor/src/main/kotlin/config/RepositoriesConfig.kt b/app-ktor/src/main/kotlin/config/RepositoriesConfig.kt deleted file mode 100644 index 86df23fa..00000000 --- a/app-ktor/src/main/kotlin/config/RepositoriesConfig.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.gitlab.sszuev.flashcards.config - -import com.gitlab.sszuev.flashcards.CardRepositories -import com.gitlab.sszuev.flashcards.DictionaryRepositories -import com.gitlab.sszuev.flashcards.dbmem.MemDbCardRepository -import com.gitlab.sszuev.flashcards.dbmem.MemDbDictionaryRepository -import com.gitlab.sszuev.flashcards.dbmem.MemDbUserRepository -import com.gitlab.sszuev.flashcards.dbpg.PgDbCardRepository -import com.gitlab.sszuev.flashcards.dbpg.PgDbDictionaryRepository -import com.gitlab.sszuev.flashcards.dbpg.PgDbUserRepository -import com.gitlab.sszuev.flashcards.repositories.DbCardRepository -import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository -import com.gitlab.sszuev.flashcards.repositories.DbUserRepository -import com.gitlab.sszuev.flashcards.repositories.TTSResourceRepository -import com.gitlab.sszuev.flashcards.speaker.createDirectTTSResourceRepository -import com.gitlab.sszuev.flashcards.speaker.rabbitmq.RMQTTSResourceRepository - -data class RepositoriesConfig( - val prodTTSClientRepository: TTSResourceRepository = RMQTTSResourceRepository(), - val testTTSClientRepository: TTSResourceRepository = createDirectTTSResourceRepository(), - val prodCardRepository: DbCardRepository = PgDbCardRepository(), - val testCardRepository: DbCardRepository = MemDbCardRepository(), - val prodDictionaryRepository: DbDictionaryRepository = PgDbDictionaryRepository(), - val testDictionaryRepository: DbDictionaryRepository = MemDbDictionaryRepository(), - val prodUserRepository: DbUserRepository = PgDbUserRepository(), - val testUserRepository: DbUserRepository = MemDbUserRepository(), -) { - - val cardRepositories by lazy { - CardRepositories( - prodTTSClientRepository = this.prodTTSClientRepository, - testTTSClientRepository = this.testTTSClientRepository, - prodCardRepository = this.prodCardRepository, - testCardRepository = this.testCardRepository, - prodUserRepository = this.prodUserRepository, - testUserRepository = this.testUserRepository, - ) - } - - val dictionaryRepositories by lazy { - DictionaryRepositories( - prodDictionaryRepository = this.prodDictionaryRepository, - testDictionaryRepository = this.testDictionaryRepository, - prodUserRepository = this.prodUserRepository, - testUserRepository = this.testUserRepository, - prodCardRepository = this.prodCardRepository, - testCardRepository = this.testCardRepository, - ) - } -} \ No newline at end of file diff --git a/app-ktor/src/test/kotlin/api/controllers/CardControllerMockkTest.kt b/app-ktor/src/test/kotlin/api/controllers/CardControllerMockkTest.kt index 06fa8406..b8ab69c4 100644 --- a/app-ktor/src/test/kotlin/api/controllers/CardControllerMockkTest.kt +++ b/app-ktor/src/test/kotlin/api/controllers/CardControllerMockkTest.kt @@ -1,7 +1,7 @@ package com.gitlab.sszuev.flashcards.api.controllers +import com.gitlab.sszuev.flashcards.AppRepositories import com.gitlab.sszuev.flashcards.CardContext -import com.gitlab.sszuev.flashcards.CardRepositories import com.gitlab.sszuev.flashcards.api.services.CardService import com.gitlab.sszuev.flashcards.api.v1.models.BaseRequest import com.gitlab.sszuev.flashcards.api.v1.models.BaseResponse @@ -112,7 +112,7 @@ internal class CardControllerMockkTest { coEvery { service.serviceMethod(any()) } throws TestException(msg) - val repositories = mockk() + val repositories = mockk() val tutorConfig = mockk(relaxed = true) val runConfig = mockk(relaxed = true) diff --git a/common/src/commonMain/kotlin/DictionaryRepositories.kt b/common/src/commonMain/kotlin/AppRepositories.kt similarity index 70% rename from common/src/commonMain/kotlin/DictionaryRepositories.kt rename to common/src/commonMain/kotlin/AppRepositories.kt index b46704b8..307b1719 100644 --- a/common/src/commonMain/kotlin/DictionaryRepositories.kt +++ b/common/src/commonMain/kotlin/AppRepositories.kt @@ -1,27 +1,31 @@ package com.gitlab.sszuev.flashcards import com.gitlab.sszuev.flashcards.model.common.AppMode -import com.gitlab.sszuev.flashcards.model.common.AppRepositories import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository import com.gitlab.sszuev.flashcards.repositories.DbUserRepository import com.gitlab.sszuev.flashcards.repositories.NoOpDbCardRepository import com.gitlab.sszuev.flashcards.repositories.NoOpDbDictionaryRepository import com.gitlab.sszuev.flashcards.repositories.NoOpDbUserRepository +import com.gitlab.sszuev.flashcards.repositories.NoOpTTSResourceRepository +import com.gitlab.sszuev.flashcards.repositories.TTSResourceRepository -data class DictionaryRepositories( +data class AppRepositories( + private val prodTTSClientRepository: TTSResourceRepository = NoOpTTSResourceRepository, + private val testTTSClientRepository: TTSResourceRepository = NoOpTTSResourceRepository, private val prodDictionaryRepository: DbDictionaryRepository = NoOpDbDictionaryRepository, private val testDictionaryRepository: DbDictionaryRepository = NoOpDbDictionaryRepository, private val prodUserRepository: DbUserRepository = NoOpDbUserRepository, private val testUserRepository: DbUserRepository = NoOpDbUserRepository, private val prodCardRepository: DbCardRepository = NoOpDbCardRepository, private val testCardRepository: DbCardRepository = NoOpDbCardRepository, -): AppRepositories { +) { + companion object { - val NO_OP_REPOSITORIES = DictionaryRepositories() + val NO_OP_REPOSITORIES = AppRepositories() } - override fun userRepository(mode: AppMode): DbUserRepository { + fun userRepository(mode: AppMode): DbUserRepository { return when(mode) { AppMode.PROD -> prodUserRepository AppMode.TEST -> testUserRepository @@ -44,4 +48,12 @@ data class DictionaryRepositories( AppMode.STUB -> NoOpDbCardRepository } } + + fun ttsClientRepository(mode: AppMode): TTSResourceRepository { + return when (mode) { + AppMode.PROD -> prodTTSClientRepository + AppMode.TEST -> testTTSClientRepository + AppMode.STUB -> NoOpTTSResourceRepository + } + } } \ No newline at end of file diff --git a/common/src/commonMain/kotlin/CardContext.kt b/common/src/commonMain/kotlin/CardContext.kt index 68c0b1df..b4fef14f 100644 --- a/common/src/commonMain/kotlin/CardContext.kt +++ b/common/src/commonMain/kotlin/CardContext.kt @@ -25,7 +25,7 @@ import kotlinx.datetime.Instant data class CardContext( override val operation: CardOperation = CardOperation.NONE, override val timestamp: Instant = Instant.NONE, - override val repositories: CardRepositories = CardRepositories.NO_OP_REPOSITORIES, + override val repositories: AppRepositories = AppRepositories.NO_OP_REPOSITORIES, override val errors: MutableList = mutableListOf(), override val config: AppConfig = AppConfig.DEFAULT, diff --git a/common/src/commonMain/kotlin/CardRepositories.kt b/common/src/commonMain/kotlin/CardRepositories.kt deleted file mode 100644 index a295bdfc..00000000 --- a/common/src/commonMain/kotlin/CardRepositories.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.gitlab.sszuev.flashcards - -import com.gitlab.sszuev.flashcards.model.common.AppMode -import com.gitlab.sszuev.flashcards.model.common.AppRepositories -import com.gitlab.sszuev.flashcards.repositories.* - -data class CardRepositories( - private val prodTTSClientRepository: TTSResourceRepository = NoOpTTSResourceRepository, - private val testTTSClientRepository: TTSResourceRepository = NoOpTTSResourceRepository, - private val prodCardRepository: DbCardRepository = NoOpDbCardRepository, - private val testCardRepository: DbCardRepository = NoOpDbCardRepository, - private val prodUserRepository: DbUserRepository = NoOpDbUserRepository, - private val testUserRepository: DbUserRepository = NoOpDbUserRepository, -): AppRepositories { - companion object { - val NO_OP_REPOSITORIES = CardRepositories() - } - - override fun userRepository(mode: AppMode): DbUserRepository { - return when(mode) { - AppMode.PROD -> prodUserRepository - AppMode.TEST -> testUserRepository - AppMode.STUB -> NoOpDbUserRepository - } - } - - fun cardRepository(mode: AppMode): DbCardRepository { - return when (mode) { - AppMode.PROD -> prodCardRepository - AppMode.TEST -> testCardRepository - AppMode.STUB -> NoOpDbCardRepository - } - } - - fun ttsClientRepository(mode: AppMode): TTSResourceRepository { - return when(mode) { - AppMode.PROD -> prodTTSClientRepository - AppMode.TEST -> testTTSClientRepository - AppMode.STUB -> NoOpTTSResourceRepository - } - } -} \ No newline at end of file diff --git a/common/src/commonMain/kotlin/DictionaryContext.kt b/common/src/commonMain/kotlin/DictionaryContext.kt index 400a1f30..36b69b62 100644 --- a/common/src/commonMain/kotlin/DictionaryContext.kt +++ b/common/src/commonMain/kotlin/DictionaryContext.kt @@ -16,9 +16,9 @@ import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity import kotlinx.datetime.Instant data class DictionaryContext( - override val repositories: DictionaryRepositories = DictionaryRepositories.NO_OP_REPOSITORIES, override val operation: DictionaryOperation = DictionaryOperation.NONE, override val timestamp: Instant = Instant.NONE, + override val repositories: AppRepositories = AppRepositories.NO_OP_REPOSITORIES, override val errors: MutableList = mutableListOf(), override val config: AppConfig = AppConfig.DEFAULT, diff --git a/common/src/commonMain/kotlin/model/common/AppContext.kt b/common/src/commonMain/kotlin/model/common/AppContext.kt index f1316f26..ba2b6eed 100644 --- a/common/src/commonMain/kotlin/model/common/AppContext.kt +++ b/common/src/commonMain/kotlin/model/common/AppContext.kt @@ -1,6 +1,7 @@ package com.gitlab.sszuev.flashcards.model.common import com.gitlab.sszuev.flashcards.AppConfig +import com.gitlab.sszuev.flashcards.AppRepositories import kotlinx.datetime.Instant interface AppContext { diff --git a/common/src/commonMain/kotlin/model/common/AppRepositories.kt b/common/src/commonMain/kotlin/model/common/AppRepositories.kt deleted file mode 100644 index 1eb9a56e..00000000 --- a/common/src/commonMain/kotlin/model/common/AppRepositories.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.gitlab.sszuev.flashcards.model.common - -interface AppRepositories { - fun userRepository(mode: AppMode): AppUserRepository -} - -interface AppUserRepository { - fun getUser(authId: AppAuthId): AppUserResponse -} - -interface AppUserResponse { - val user: AppUserEntity - val errors: List -} \ No newline at end of file diff --git a/common/src/commonMain/kotlin/repositories/AppUserRepository.kt b/common/src/commonMain/kotlin/repositories/AppUserRepository.kt new file mode 100644 index 00000000..221d2b9b --- /dev/null +++ b/common/src/commonMain/kotlin/repositories/AppUserRepository.kt @@ -0,0 +1,14 @@ +package com.gitlab.sszuev.flashcards.repositories + +import com.gitlab.sszuev.flashcards.model.common.AppAuthId +import com.gitlab.sszuev.flashcards.model.common.AppError +import com.gitlab.sszuev.flashcards.model.common.AppUserEntity + +interface AppUserRepository { + fun getUser(authId: AppAuthId): AppUserResponse +} + +interface AppUserResponse { + val user: AppUserEntity + val errors: List +} \ No newline at end of file diff --git a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt index 49b6a870..2f52cf4b 100644 --- a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt +++ b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt @@ -1,7 +1,7 @@ package com.gitlab.sszuev.flashcards.core +import com.gitlab.sszuev.flashcards.AppRepositories import com.gitlab.sszuev.flashcards.CardContext -import com.gitlab.sszuev.flashcards.CardRepositories import com.gitlab.sszuev.flashcards.dbcommon.mocks.MockDbCardRepository import com.gitlab.sszuev.flashcards.dbcommon.mocks.MockDbUserRepository import com.gitlab.sszuev.flashcards.model.common.AppAuthId @@ -44,7 +44,7 @@ internal class CardCorProcessorRunCardsTest { ): CardContext { val context = CardContext( operation = op, - repositories = CardRepositories().copy( + repositories = AppRepositories().copy( testUserRepository = userRepository, testCardRepository = cardRepository ) diff --git a/core/src/test/kotlin/CardCorProcessorRunResourceTest.kt b/core/src/test/kotlin/CardCorProcessorRunResourceTest.kt index d88aac97..abd94ce3 100644 --- a/core/src/test/kotlin/CardCorProcessorRunResourceTest.kt +++ b/core/src/test/kotlin/CardCorProcessorRunResourceTest.kt @@ -1,13 +1,17 @@ package com.gitlab.sszuev.flashcards.core +import com.gitlab.sszuev.flashcards.AppRepositories import com.gitlab.sszuev.flashcards.CardContext -import com.gitlab.sszuev.flashcards.CardRepositories import com.gitlab.sszuev.flashcards.dbcommon.mocks.MockDbUserRepository import com.gitlab.sszuev.flashcards.model.common.AppAuthId import com.gitlab.sszuev.flashcards.model.common.AppMode import com.gitlab.sszuev.flashcards.model.common.AppRequestId import com.gitlab.sszuev.flashcards.model.common.AppStatus -import com.gitlab.sszuev.flashcards.model.domain.* +import com.gitlab.sszuev.flashcards.model.domain.CardOperation +import com.gitlab.sszuev.flashcards.model.domain.LangId +import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity +import com.gitlab.sszuev.flashcards.model.domain.TTSResourceGet +import com.gitlab.sszuev.flashcards.model.domain.TTSResourceId import com.gitlab.sszuev.flashcards.repositories.TTSResourceEntityResponse import com.gitlab.sszuev.flashcards.repositories.TTSResourceIdResponse import com.gitlab.sszuev.flashcards.repositories.TTSResourceRepository @@ -24,7 +28,7 @@ internal class CardCorProcessorRunResourceTest { private fun testContext(repository: TTSResourceRepository): CardContext { val context = CardContext( operation = CardOperation.GET_RESOURCE, - repositories = CardRepositories().copy( + repositories = AppRepositories().copy( testUserRepository = MockDbUserRepository(), testTTSClientRepository = repository ) diff --git a/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt b/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt index 39ebe417..00f5bc74 100644 --- a/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt +++ b/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt @@ -1,7 +1,7 @@ package com.gitlab.sszuev.flashcards.core +import com.gitlab.sszuev.flashcards.AppRepositories import com.gitlab.sszuev.flashcards.DictionaryContext -import com.gitlab.sszuev.flashcards.DictionaryRepositories import com.gitlab.sszuev.flashcards.core.normalizers.normalize import com.gitlab.sszuev.flashcards.dbcommon.mocks.MockDbCardRepository import com.gitlab.sszuev.flashcards.dbcommon.mocks.MockDbDictionaryRepository @@ -45,7 +45,7 @@ internal class DictionaryCorProcessorRunTest { ): DictionaryContext { val context = DictionaryContext( operation = op, - repositories = DictionaryRepositories().copy( + repositories = AppRepositories().copy( testUserRepository = userRepository, testDictionaryRepository = dictionaryRepository, testCardRepository = cardsRepository, From 6cf056f2a33a0dd584683b23e11586640643c472 Mon Sep 17 00:00:00 2001 From: sszuev Date: Wed, 20 Mar 2024 20:48:55 +0300 Subject: [PATCH 02/18] common & core & db: [#28] replace DbCardRepository#getCard with findCard, add DbDictionaryRepository#findDictionary; move data checks from :db to :core --- .../kotlin/model/domain/DictionaryEntity.kt | 3 + .../kotlin/repositories/DbCardRepository.kt | 4 +- .../repositories/DbDictionaryRepository.kt | 2 + .../repositories/NoOpDbCardRepository.kt | 33 ++-- .../NoOpDbDictionaryRepository.kt | 23 +-- .../kotlin/processes/CardProcessWorkers.kt | 16 +- core/src/main/kotlin/processes/Processes.kt | 48 ++++++ .../kotlin/CardCorProcessorRunCardsTest.kt | 71 ++++++--- .../kotlin/DbCardRepositoryTest.kt | 144 +++++++++--------- .../kotlin/DbDictionaryRepositoryTest.kt | 26 +++- .../kotlin/mocks/MockDbCardRepository.kt | 37 ++--- .../mocks/MockDbDictionaryRepository.kt | 27 ++-- db-mem/src/main/kotlin/MemDbCardRepository.kt | 11 +- .../main/kotlin/MemDbDictionaryRepository.kt | 3 + db-mem/src/main/kotlin/MemDbEntityMapper.kt | 1 + db-pg/src/main/kotlin/PgDbCardRepository.kt | 40 ++--- .../main/kotlin/PgDbDictionaryRepository.kt | 8 +- db-pg/src/main/kotlin/PgDbEntityMapper.kt | 5 +- 18 files changed, 277 insertions(+), 225 deletions(-) diff --git a/common/src/commonMain/kotlin/model/domain/DictionaryEntity.kt b/common/src/commonMain/kotlin/model/domain/DictionaryEntity.kt index 568a6c03..689b790b 100644 --- a/common/src/commonMain/kotlin/model/domain/DictionaryEntity.kt +++ b/common/src/commonMain/kotlin/model/domain/DictionaryEntity.kt @@ -1,7 +1,10 @@ package com.gitlab.sszuev.flashcards.model.domain +import com.gitlab.sszuev.flashcards.model.common.AppUserId + data class DictionaryEntity( val dictionaryId: DictionaryId = DictionaryId.NONE, + val userId: AppUserId = AppUserId.NONE, val name: String = "", val sourceLang: LangEntity = LangEntity.EMPTY, val targetLang: LangEntity = LangEntity.EMPTY, diff --git a/common/src/commonMain/kotlin/repositories/DbCardRepository.kt b/common/src/commonMain/kotlin/repositories/DbCardRepository.kt index 552ec856..d5bd3d44 100644 --- a/common/src/commonMain/kotlin/repositories/DbCardRepository.kt +++ b/common/src/commonMain/kotlin/repositories/DbCardRepository.kt @@ -14,9 +14,9 @@ import com.gitlab.sszuev.flashcards.model.domain.DictionaryId interface DbCardRepository { /** - * Gets card by id. + * Finds card by id returning `null` if nothing found. */ - fun getCard(userId: AppUserId, cardId: CardId): CardDbResponse + fun findCard(cardId: CardId): CardEntity? /** * Gets all cards by dictionaryId. diff --git a/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt b/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt index ab0b3e29..4ac9f492 100644 --- a/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt +++ b/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt @@ -7,6 +7,8 @@ import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity interface DbDictionaryRepository { + fun findDictionary(dictionaryId: DictionaryId): DictionaryEntity? + fun getAllDictionaries(userId: AppUserId): DictionariesDbResponse fun createDictionary(userId: AppUserId, entity: DictionaryEntity): DictionaryDbResponse diff --git a/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt b/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt index 3645c3ef..0d23903f 100644 --- a/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt +++ b/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt @@ -7,41 +7,26 @@ import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.DictionaryId object NoOpDbCardRepository : DbCardRepository { - override fun getCard(userId: AppUserId, cardId: CardId): CardDbResponse { - noOp() - } - override fun getAllCards(userId: AppUserId, dictionaryId: DictionaryId): CardsDbResponse { - noOp() - } + override fun findCard(cardId: CardId): CardEntity = noOp() - override fun searchCard(userId: AppUserId, filter: CardFilter): CardsDbResponse { - noOp() - } + override fun getAllCards(userId: AppUserId, dictionaryId: DictionaryId): CardsDbResponse = noOp() - override fun createCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse { - noOp() - } + override fun searchCard(userId: AppUserId, filter: CardFilter): CardsDbResponse = noOp() - override fun updateCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse { - noOp() - } + override fun createCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse = noOp() + + override fun updateCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse = noOp() override fun updateCards( userId: AppUserId, cardIds: Iterable, update: (CardEntity) -> CardEntity - ): CardsDbResponse { - noOp() - } + ): CardsDbResponse = noOp() - override fun resetCard(userId: AppUserId, cardId: CardId): CardDbResponse { - noOp() - } + override fun resetCard(userId: AppUserId, cardId: CardId): CardDbResponse = noOp() - override fun removeCard(userId: AppUserId, cardId: CardId): RemoveCardDbResponse { - noOp() - } + override fun removeCard(userId: AppUserId, cardId: CardId): RemoveCardDbResponse = noOp() private fun noOp(): Nothing { error("Must not be called.") diff --git a/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt b/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt index 28f0927a..a34a76b4 100644 --- a/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt +++ b/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt @@ -6,25 +6,18 @@ import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity object NoOpDbDictionaryRepository : DbDictionaryRepository { - override fun getAllDictionaries(userId: AppUserId): DictionariesDbResponse { - noOp() - } - override fun createDictionary(userId: AppUserId, entity: DictionaryEntity): DictionaryDbResponse { - noOp() - } + override fun findDictionary(dictionaryId: DictionaryId): DictionaryEntity = noOp() - override fun removeDictionary(userId: AppUserId, dictionaryId: DictionaryId): RemoveDictionaryDbResponse { - noOp() - } + override fun getAllDictionaries(userId: AppUserId): DictionariesDbResponse = noOp() - override fun importDictionary(userId: AppUserId, dictionaryId: DictionaryId): ImportDictionaryDbResponse { - noOp() - } + override fun createDictionary(userId: AppUserId, entity: DictionaryEntity): DictionaryDbResponse = noOp() - override fun exportDictionary(userId: AppUserId, resource: ResourceEntity): DictionaryDbResponse { - noOp() - } + override fun removeDictionary(userId: AppUserId, dictionaryId: DictionaryId): RemoveDictionaryDbResponse = noOp() + + override fun importDictionary(userId: AppUserId, dictionaryId: DictionaryId): ImportDictionaryDbResponse = noOp() + + override fun exportDictionary(userId: AppUserId, resource: ResourceEntity): DictionaryDbResponse = noOp() private fun noOp(): Nothing { error("Must not be called.") diff --git a/core/src/main/kotlin/processes/CardProcessWorkers.kt b/core/src/main/kotlin/processes/CardProcessWorkers.kt index 0af3ce67..f906050e 100644 --- a/core/src/main/kotlin/processes/CardProcessWorkers.kt +++ b/core/src/main/kotlin/processes/CardProcessWorkers.kt @@ -20,8 +20,20 @@ fun ChainDSL.processGetCard() = worker { process { val userId = this.contextUserEntity.id val cardId = this.normalizedRequestCardEntityId - val res = this.repositories.cardRepository(this.workMode).getCard(userId, cardId) - this.postProcess(res) + val card = this.repositories.cardRepository(this.workMode).findCard(cardId) + if (card == null) { + this.errors.add(noCardFoundDataError("getCard", cardId)) + } else { + val dictionary = this.repositories.dictionaryRepository(this.workMode).findDictionary(card.dictionaryId) + if (dictionary == null) { + this.errors.add(noDictionaryFoundDataError("getCard", card.dictionaryId)) + } else if (dictionary.userId != userId) { + this.errors.add(forbiddenEntityDataError("getCard", card.cardId, userId)) + } else { + this.responseCardEntity = card + } + } + this.status = if (this.errors.isNotEmpty()) AppStatus.FAIL else AppStatus.RUN } onException { fail( diff --git a/core/src/main/kotlin/processes/Processes.kt b/core/src/main/kotlin/processes/Processes.kt index b1b417e7..777fcf69 100644 --- a/core/src/main/kotlin/processes/Processes.kt +++ b/core/src/main/kotlin/processes/Processes.kt @@ -5,6 +5,9 @@ import com.gitlab.sszuev.flashcards.model.common.AppContext import com.gitlab.sszuev.flashcards.model.common.AppError import com.gitlab.sszuev.flashcards.model.common.AppOperation import com.gitlab.sszuev.flashcards.model.common.AppStatus +import com.gitlab.sszuev.flashcards.model.common.AppUserId +import com.gitlab.sszuev.flashcards.model.domain.CardId +import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.TTSResourceGet internal fun TTSResourceGet.toFieldName(): String { @@ -15,6 +18,51 @@ internal fun Id.toFieldName(): String { return asString() } +fun forbiddenEntityDataError( + operation: String, + entityId: Id, + userId: AppUserId, +) = dataError( + operation = operation, + fieldName = entityId.asString(), + details = when (entityId) { + is DictionaryId -> "access denied: the dictionary (id=${entityId.asString()}) is not owned by the used (id=${userId.asString()})" + is CardId -> "access denied: the card (id=${entityId.asString()}) is not owned by the the used (id=${userId.asString()})" + else -> throw IllegalArgumentException() + }, +) + +fun noDictionaryFoundDataError( + operation: String, + id: DictionaryId, +) = dataError( + operation = operation, + fieldName = id.asString(), + details = """dictionary with id="${id.toFieldName()}" not found""" +) + +fun noCardFoundDataError( + operation: String, + id: CardId, +) = dataError( + operation = operation, + fieldName = id.toFieldName(), + details = """card with id="${id.asString()}" not found""" +) + +fun dataError( + operation: String, + fieldName: String = "", + details: String = "", + exception: Throwable? = null, +) = AppError( + code = "database::$operation", + field = fieldName, + group = "database", + message = if (details.isBlank()) "Error while $operation" else "Error while $operation: $details", + exception = exception +) + internal fun runError( operation: AppOperation, fieldName: String = "", diff --git a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt index 2f52cf4b..00b68e5a 100644 --- a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt +++ b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt @@ -3,6 +3,7 @@ package com.gitlab.sszuev.flashcards.core import com.gitlab.sszuev.flashcards.AppRepositories import com.gitlab.sszuev.flashcards.CardContext import com.gitlab.sszuev.flashcards.dbcommon.mocks.MockDbCardRepository +import com.gitlab.sszuev.flashcards.dbcommon.mocks.MockDbDictionaryRepository import com.gitlab.sszuev.flashcards.dbcommon.mocks.MockDbUserRepository import com.gitlab.sszuev.flashcards.model.common.AppAuthId import com.gitlab.sszuev.flashcards.model.common.AppError @@ -22,11 +23,13 @@ import com.gitlab.sszuev.flashcards.model.domain.Stage import com.gitlab.sszuev.flashcards.repositories.CardDbResponse import com.gitlab.sszuev.flashcards.repositories.CardsDbResponse import com.gitlab.sszuev.flashcards.repositories.DbCardRepository +import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository import com.gitlab.sszuev.flashcards.repositories.DbUserRepository import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse import com.gitlab.sszuev.flashcards.repositories.UserEntityDbResponse import com.gitlab.sszuev.flashcards.stubs.stubCard import com.gitlab.sszuev.flashcards.stubs.stubCards +import com.gitlab.sszuev.flashcards.stubs.stubDictionary import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test @@ -40,13 +43,15 @@ internal class CardCorProcessorRunCardsTest { private fun testContext( op: CardOperation, cardRepository: DbCardRepository, - userRepository: DbUserRepository = MockDbUserRepository() + userRepository: DbUserRepository = MockDbUserRepository(), + dictionaryRepository: DbDictionaryRepository = MockDbDictionaryRepository(), ): CardContext { val context = CardContext( operation = op, repositories = AppRepositories().copy( testUserRepository = userRepository, - testCardRepository = cardRepository + testCardRepository = cardRepository, + testDictionaryRepository = dictionaryRepository, ) ) context.requestAppAuthId = testUser.authId @@ -78,27 +83,41 @@ internal class CardCorProcessorRunCardsTest { @Test fun `test get-card success`() = runTest { val testId = CardId("42") - val testResponseEntity = stubCard.copy(cardId = testId) + val testResponseCardEntity = stubCard.copy(cardId = testId) + val testResponseDictionaryEntity = stubDictionary + + var findCardIsCalled = false + var findDictionaryIsCalled = false + val cardRepository = MockDbCardRepository( + invokeFindCard = { cardId -> + findCardIsCalled = true + if (cardId == testId) testResponseCardEntity else null + } + ) - var wasCalled = false - val repository = MockDbCardRepository( - invokeGetCard = { _, cardId -> - wasCalled = true - CardDbResponse(if (cardId == testId) testResponseEntity else CardEntity.EMPTY) + val dictionaryRepository = MockDbDictionaryRepository( + invokeFindDictionary = { dictionaryId -> + findDictionaryIsCalled = true + if (dictionaryId == testResponseDictionaryEntity.dictionaryId) testResponseDictionaryEntity else null } ) - val context = testContext(CardOperation.GET_CARD, repository) + val context = testContext( + op = CardOperation.GET_CARD, + cardRepository = cardRepository, + dictionaryRepository = dictionaryRepository, + ) context.requestCardEntityId = testId CardCorProcessor().execute(context) - Assertions.assertTrue(wasCalled) + Assertions.assertTrue(findCardIsCalled) + Assertions.assertTrue(findDictionaryIsCalled) Assertions.assertEquals(requestId(CardOperation.GET_CARD), context.requestId) Assertions.assertEquals(AppStatus.OK, context.status) Assertions.assertTrue(context.errors.isEmpty()) - Assertions.assertEquals(testResponseEntity, context.responseCardEntity) + Assertions.assertEquals(testResponseCardEntity, context.responseCardEntity) } @Test @@ -106,11 +125,13 @@ internal class CardCorProcessorRunCardsTest { val testCardId = CardId("42") var getUserWasCalled = false - var getCardWasCalled = false - val cardRepository = MockDbCardRepository(invokeGetCard = { _, _ -> - getCardWasCalled = true - throw TestException() - }) + var getIsWasCalled = false + val cardRepository = MockDbCardRepository( + invokeFindCard = { _ -> + getIsWasCalled = true + throw TestException() + } + ) val userRepository = MockDbUserRepository( invokeGetUser = { getUserWasCalled = true @@ -119,14 +140,14 @@ internal class CardCorProcessorRunCardsTest { ) val context = - testContext(CardOperation.GET_CARD, cardRepository = cardRepository, userRepository = userRepository) + testContext(op = CardOperation.GET_CARD, cardRepository = cardRepository, userRepository = userRepository) context.requestAppAuthId = testUser.authId context.requestCardEntityId = testCardId CardCorProcessor().execute(context) Assertions.assertTrue(getUserWasCalled) - Assertions.assertTrue(getCardWasCalled) + Assertions.assertTrue(getIsWasCalled) Assertions.assertEquals(requestId(CardOperation.GET_CARD), context.requestId) assertUnknownError(context, CardOperation.GET_CARD) } @@ -484,14 +505,14 @@ internal class CardCorProcessorRunCardsTest { length = 42, ) - var getUserWasCalled = false - var getCardWasCalled = false - val cardRepository = MockDbCardRepository(invokeGetCard = { _, _ -> - getCardWasCalled = true + var getUserIsCalled = false + var getCardIsCalled = false + val cardRepository = MockDbCardRepository(invokeFindCard = { _ -> + getCardIsCalled = true throw TestException() }) val userRepository = MockDbUserRepository(invokeGetUser = { - getUserWasCalled = true + getUserIsCalled = true UserEntityDbResponse(user = AppUserEntity.EMPTY, errors = listOf(testError)) }) @@ -505,8 +526,8 @@ internal class CardCorProcessorRunCardsTest { CardCorProcessor().execute(context) - Assertions.assertTrue(getUserWasCalled) - Assertions.assertFalse(getCardWasCalled) + Assertions.assertTrue(getUserIsCalled) + Assertions.assertFalse(getCardIsCalled) Assertions.assertEquals(requestId(op), context.requestId) val actual = assertSingleError(context, op) Assertions.assertSame(testError, actual) diff --git a/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt b/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt index 00a1735d..a935a830 100644 --- a/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt +++ b/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt @@ -17,7 +17,12 @@ import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse import kotlinx.datetime.Clock import kotlinx.datetime.Instant -import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertNotSame +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.MethodOrderer import org.junit.jupiter.api.Order import org.junit.jupiter.api.Test @@ -149,58 +154,48 @@ abstract class DbCardRepositoryTest { ignoreChangeAt: Boolean = true, ignoreId: Boolean = false ) { - Assertions.assertNotSame(expected, actual) + assertNotSame(expected, actual) var a = actual if (ignoreId) { - Assertions.assertNotEquals(CardId.NONE, actual.cardId) + assertNotEquals(CardId.NONE, actual.cardId) a = a.copy(cardId = CardId.NONE) } else { - Assertions.assertEquals(expected.cardId, actual.cardId) + assertEquals(expected.cardId, actual.cardId) } if (ignoreChangeAt) { - Assertions.assertNotEquals(Instant.NONE, actual.changedAt) + assertNotEquals(Instant.NONE, actual.changedAt) a = a.copy(changedAt = Instant.NONE) } else { - Assertions.assertEquals(expected.changedAt, actual.changedAt) + assertEquals(expected.changedAt, actual.changedAt) } - Assertions.assertNotEquals(Instant.NONE, actual.changedAt) - Assertions.assertEquals(expected, a) + assertNotEquals(Instant.NONE, actual.changedAt) + assertEquals(expected, a) } private fun assertSingleError(res: CardDbResponse, field: String, op: String): AppError { - Assertions.assertEquals(1, res.errors.size) { "Errors: ${res.errors}" } + assertEquals(1, res.errors.size) { "Errors: ${res.errors}" } val error = res.errors[0] - Assertions.assertEquals("database::$op", error.code) { error.toString() } - Assertions.assertEquals(field, error.field) { error.toString() } - Assertions.assertEquals("database", error.group) { error.toString() } - Assertions.assertNull(error.exception) { error.toString() } + assertEquals("database::$op", error.code) { error.toString() } + assertEquals(field, error.field) { error.toString() } + assertEquals("database", error.group) { error.toString() } + assertNull(error.exception) { error.toString() } return error } private fun assertNoErrors(res: CardDbResponse) { - Assertions.assertEquals(0, res.errors.size) { "Has errors: ${res.errors}" } + assertEquals(0, res.errors.size) { "Has errors: ${res.errors}" } } private fun assertNoErrors(res: RemoveCardDbResponse) { - Assertions.assertEquals(0, res.errors.size) { "Has errors: ${res.errors}" } + assertEquals(0, res.errors.size) { "Has errors: ${res.errors}" } } } @Test - fun `test get card error unknown card`() { + fun `test get card not found`() { val id = CardId("42000") - val res = repository.getCard(userId, id) - Assertions.assertEquals(CardEntity.EMPTY, res.card) - Assertions.assertEquals(1, res.errors.size) - val error = res.errors[0] - Assertions.assertEquals("database::getCard", error.code) - Assertions.assertEquals(id.asString(), error.field) - Assertions.assertEquals("database", error.group) - Assertions.assertEquals( - """Error while getCard: card with id="${id.asString()}" not found""", - error.message - ) - Assertions.assertNull(error.exception) + val res = repository.findCard(id) + assertNull(res) } @Order(1) @@ -208,20 +203,20 @@ abstract class DbCardRepositoryTest { fun `test get all cards success`() { // Business dictionary val res1 = repository.getAllCards(userId, DictionaryId("1")) - Assertions.assertEquals(244, res1.cards.size) - Assertions.assertEquals(0, res1.errors.size) - Assertions.assertEquals(1, res1.dictionaries.size) - Assertions.assertEquals("1", res1.dictionaries.single().dictionaryId.asString()) + assertEquals(244, res1.cards.size) + assertEquals(0, res1.errors.size) + assertEquals(1, res1.dictionaries.size) + assertEquals("1", res1.dictionaries.single().dictionaryId.asString()) // Weather dictionary val res2 = repository.getAllCards(userId, DictionaryId("2")) - Assertions.assertEquals(65, res2.cards.size) - Assertions.assertEquals(0, res2.errors.size) - Assertions.assertEquals(1, res2.dictionaries.size) - Assertions.assertEquals("2", res2.dictionaries.single().dictionaryId.asString()) + assertEquals(65, res2.cards.size) + assertEquals(0, res2.errors.size) + assertEquals(1, res2.dictionaries.size) + assertEquals("2", res2.dictionaries.single().dictionaryId.asString()) - Assertions.assertEquals(LangId("en"), res1.dictionaries.single().sourceLang.langId) - Assertions.assertEquals(LangId("en"), res2.dictionaries.single().sourceLang.langId) + assertEquals(LangId("en"), res1.dictionaries.single().sourceLang.langId) + assertEquals(LangId("en"), res2.dictionaries.single().sourceLang.langId) } @Order(2) @@ -229,17 +224,17 @@ abstract class DbCardRepositoryTest { fun `test get all cards error unknown dictionary`() { val dictionaryId = "42" val res = repository.getAllCards(userId, DictionaryId(dictionaryId)) - Assertions.assertEquals(0, res.cards.size) - Assertions.assertEquals(1, res.errors.size) + assertEquals(0, res.cards.size) + assertEquals(1, res.errors.size) val error = res.errors[0] - Assertions.assertEquals("database::getAllCards", error.code) - Assertions.assertEquals(dictionaryId, error.field) - Assertions.assertEquals("database", error.group) - Assertions.assertEquals( + assertEquals("database::getAllCards", error.code) + assertEquals(dictionaryId, error.field) + assertEquals("database", error.group) + assertEquals( """Error while getAllCards: dictionary with id="$dictionaryId" not found""", error.message ) - Assertions.assertNull(error.exception) + assertNull(error.exception) } @Order(4) @@ -258,13 +253,13 @@ abstract class DbCardRepositoryTest { answered = 42, ) val res = repository.createCard(userId, request) - Assertions.assertEquals(CardEntity.EMPTY, res.card) + assertEquals(CardEntity.EMPTY, res.card) val error = assertSingleError(res, dictionaryId, "createCard") - Assertions.assertEquals( + assertEquals( """Error while createCard: dictionary with id="$dictionaryId" not found""", error.message ) - Assertions.assertNull(error.exception) + assertNull(error.exception) } @Order(5) @@ -279,18 +274,18 @@ abstract class DbCardRepositoryTest { val res1 = repository.searchCard(userId, filter) val res2 = repository.searchCard(userId, filter) - Assertions.assertEquals(0, res1.errors.size) - Assertions.assertEquals(0, res2.errors.size) - Assertions.assertEquals(300, res1.cards.size) - Assertions.assertEquals(300, res2.cards.size) - Assertions.assertNotEquals(res1, res2) - Assertions.assertEquals(setOf("1", "2"), res1.cards.map { it.dictionaryId }.map { it.asString() }.toSet()) - Assertions.assertEquals(setOf("1", "2"), res2.cards.map { it.dictionaryId }.map { it.asString() }.toSet()) - Assertions.assertEquals( + assertEquals(0, res1.errors.size) + assertEquals(0, res2.errors.size) + assertEquals(300, res1.cards.size) + assertEquals(300, res2.cards.size) + assertNotEquals(res1, res2) + assertEquals(setOf("1", "2"), res1.cards.map { it.dictionaryId }.map { it.asString() }.toSet()) + assertEquals(setOf("1", "2"), res2.cards.map { it.dictionaryId }.map { it.asString() }.toSet()) + assertEquals( setOf("1", "2"), res1.dictionaries.map { it.dictionaryId }.map { it.asString() }.toSet() ) - Assertions.assertEquals( + assertEquals( setOf("1", "2"), res2.dictionaries.map { it.dictionaryId }.map { it.asString() }.toSet() ) @@ -300,8 +295,9 @@ abstract class DbCardRepositoryTest { @Test fun `test get card & update card success`() { val expected = weatherCardEntity - val prev = repository.getCard(userId, expected.cardId).card - assertCard(expected = expected, actual = prev, ignoreChangeAt = true, ignoreId = false) + val prev = repository.findCard(expected.cardId) + assertNotNull(prev) + assertCard(expected = expected, actual = prev!!, ignoreChangeAt = true, ignoreId = false) val request = climateCardEntity @@ -309,8 +305,9 @@ abstract class DbCardRepositoryTest { assertNoErrors(res) val updated = res.card assertCard(expected = request, actual = updated, ignoreChangeAt = true, ignoreId = false) - val now = repository.getCard(userId, expected.cardId).card - assertCard(expected = request, actual = now, ignoreChangeAt = true, ignoreId = false) + val now = repository.findCard(expected.cardId) + assertNotNull(now) + assertCard(expected = request, actual = now!!, ignoreChangeAt = true, ignoreId = false) } @Order(7) @@ -326,7 +323,7 @@ abstract class DbCardRepositoryTest { ) val res = repository.updateCard(userId, request) val error = assertSingleError(res, id.asString(), "updateCard") - Assertions.assertEquals( + assertEquals( """Error while updateCard: card with id="${id.asString()}" not found""", error.message ) @@ -349,7 +346,7 @@ abstract class DbCardRepositoryTest { ) val res = repository.updateCard(userId, request) val error = assertSingleError(res, dictionaryId.asString(), "updateCard") - Assertions.assertEquals( + assertEquals( """Error while updateCard: dictionary with id="${dictionaryId.asString()}" not found""", error.message ) @@ -359,8 +356,9 @@ abstract class DbCardRepositoryTest { @Test fun `test get card & reset card success`() { val request = snowCardEntity - val prev = repository.getCard(userId, request.cardId).card - assertCard(expected = request, actual = prev, ignoreChangeAt = true, ignoreId = false) + val prev = repository.findCard(request.cardId) + assertNotNull(prev) + assertCard(expected = request, actual = prev!!, ignoreChangeAt = true, ignoreId = false) val expected = request.copy(answered = 0) val res = repository.resetCard(userId, request.cardId) @@ -368,8 +366,9 @@ abstract class DbCardRepositoryTest { val updated = res.card assertCard(expected = expected, actual = updated, ignoreChangeAt = true, ignoreId = false) - val now = repository.getCard(userId, request.cardId).card - assertCard(expected = expected, actual = now, ignoreChangeAt = true, ignoreId = false) + val now = repository.findCard(request.cardId) + assertNotNull(now) + assertCard(expected = expected, actual = now!!, ignoreChangeAt = true, ignoreId = false) } @Order(11) @@ -382,14 +381,14 @@ abstract class DbCardRepositoryTest { ) { it.copy(answered = 42) } - Assertions.assertEquals(0, res.errors.size) - Assertions.assertEquals(3, res.cards.size) + assertEquals(0, res.errors.size) + assertEquals(3, res.cards.size) val actual = res.cards.sortedBy { it.cardId.asLong() } assertCard(expected = drawCardEntity.copy(answered = 42), actual = actual[0], ignoreChangeAt = true) assertCard(expected = forgiveCardEntity.copy(answered = 42), actual = actual[1], ignoreChangeAt = true) assertCard(expected = snowCardEntity.copy(answered = 42), actual = actual[2], ignoreChangeAt = true) actual.forEach { - Assertions.assertTrue(it.changedAt >= now) + assertTrue(it.changedAt >= now) } } @@ -400,7 +399,7 @@ abstract class DbCardRepositoryTest { val res = repository.createCard(userId, request) assertNoErrors(res) assertCard(expected = request, actual = res.card, ignoreChangeAt = true, ignoreId = true) - Assertions.assertTrue(res.card.cardId.asString().matches("\\d+".toRegex())) + assertTrue(res.card.cardId.asString().matches("\\d+".toRegex())) } @Order(42) @@ -410,7 +409,6 @@ abstract class DbCardRepositoryTest { val res = repository.removeCard(userId, id) assertNoErrors(res) - val now = repository.getCard(userId, id).card - Assertions.assertSame(CardEntity.EMPTY, now) + assertNull(repository.findCard(id)) } } \ No newline at end of file diff --git a/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt b/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt index 2be7b4a3..bfff1402 100644 --- a/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt +++ b/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt @@ -1,9 +1,17 @@ package com.gitlab.sszuev.flashcards.dbcommon import com.gitlab.sszuev.flashcards.model.common.AppUserId -import com.gitlab.sszuev.flashcards.model.domain.* +import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity +import com.gitlab.sszuev.flashcards.model.domain.DictionaryId +import com.gitlab.sszuev.flashcards.model.domain.LangEntity +import com.gitlab.sszuev.flashcards.model.domain.LangId +import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository -import org.junit.jupiter.api.* +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.MethodOrderer +import org.junit.jupiter.api.Order +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestMethodOrder @Suppress("FunctionName") @TestMethodOrder(MethodOrderer.OrderAnnotation::class) @@ -44,6 +52,20 @@ abstract class DbDictionaryRepositoryTest { ) } + @Order(1) + @Test + fun `test get dictionary by id`() { + val res1 = repository.findDictionary(DictionaryId("2")) + Assertions.assertNotNull(res1) + Assertions.assertEquals("Weather", res1!!.name) + Assertions.assertEquals("42", res1.userId.asString()) + val res2 = repository.findDictionary(DictionaryId("1")) + Assertions.assertNotNull(res2) + Assertions.assertEquals("Irregular Verbs", res2!!.name) + Assertions.assertEquals("42", res2.userId.asString()) + + } + @Order(1) @Test fun `test get all dictionaries by user-id success`() { diff --git a/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt b/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt index 33938374..df92399d 100644 --- a/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt +++ b/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt @@ -15,7 +15,7 @@ import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse * @see mockk#issue-288 */ class MockDbCardRepository( - private val invokeGetCard: (AppUserId, CardId) -> CardDbResponse = { _, _ -> CardDbResponse.EMPTY }, + private val invokeFindCard: (CardId) -> CardEntity? = { null }, private val invokeGetAllCards: (AppUserId, DictionaryId) -> CardsDbResponse = { _, _ -> CardsDbResponse.EMPTY }, private val invokeSearchCards: (AppUserId, CardFilter) -> CardsDbResponse = { _, _ -> CardsDbResponse.EMPTY }, private val invokeCreateCard: (AppUserId, CardEntity) -> CardDbResponse = { _, _ -> CardDbResponse.EMPTY }, @@ -25,39 +25,26 @@ class MockDbCardRepository( private val invokeDeleteCard: (AppUserId, CardId) -> RemoveCardDbResponse = { _, _ -> RemoveCardDbResponse.EMPTY }, ) : DbCardRepository { - override fun getCard(userId: AppUserId, cardId: CardId): CardDbResponse { - return invokeGetCard(userId, cardId) - } + override fun findCard(cardId: CardId): CardEntity? = invokeFindCard(cardId) - override fun getAllCards(userId: AppUserId, dictionaryId: DictionaryId): CardsDbResponse { - return invokeGetAllCards(userId, dictionaryId) - } + override fun getAllCards(userId: AppUserId, dictionaryId: DictionaryId): CardsDbResponse = + invokeGetAllCards(userId, dictionaryId) - override fun searchCard(userId: AppUserId, filter: CardFilter): CardsDbResponse { - return invokeSearchCards(userId, filter) - } + override fun searchCard(userId: AppUserId, filter: CardFilter): CardsDbResponse = invokeSearchCards(userId, filter) - override fun createCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse { - return invokeCreateCard(userId, cardEntity) - } + override fun createCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse = + invokeCreateCard(userId, cardEntity) - override fun updateCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse { - return invokeUpdateCard(userId, cardEntity) - } + override fun updateCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse = + invokeUpdateCard(userId, cardEntity) override fun updateCards( userId: AppUserId, cardIds: Iterable, update: (CardEntity) -> CardEntity - ): CardsDbResponse { - return invokeUpdateCards(userId, cardIds, update) - } + ): CardsDbResponse = invokeUpdateCards(userId, cardIds, update) - override fun resetCard(userId: AppUserId, cardId: CardId): CardDbResponse { - return invokeResetCard(userId, cardId) - } + override fun resetCard(userId: AppUserId, cardId: CardId): CardDbResponse = invokeResetCard(userId, cardId) - override fun removeCard(userId: AppUserId, cardId: CardId): RemoveCardDbResponse { - return invokeDeleteCard(userId, cardId) - } + override fun removeCard(userId: AppUserId, cardId: CardId): RemoveCardDbResponse = invokeDeleteCard(userId, cardId) } \ No newline at end of file diff --git a/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt b/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt index 84dd3330..8773d11e 100644 --- a/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt +++ b/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt @@ -11,6 +11,7 @@ import com.gitlab.sszuev.flashcards.repositories.ImportDictionaryDbResponse import com.gitlab.sszuev.flashcards.repositories.RemoveDictionaryDbResponse class MockDbDictionaryRepository( + private val invokeFindDictionary: (DictionaryId) -> DictionaryEntity? = { null }, private val invokeGetAllDictionaries: (AppUserId) -> DictionariesDbResponse = { DictionariesDbResponse.EMPTY }, private val invokeCreateDictionary: (AppUserId, DictionaryEntity) -> DictionaryDbResponse = { _, _ -> DictionaryDbResponse.EMPTY }, private val invokeDeleteDictionary: (AppUserId, DictionaryId) -> RemoveDictionaryDbResponse = { _, _ -> RemoveDictionaryDbResponse.EMPTY }, @@ -18,23 +19,19 @@ class MockDbDictionaryRepository( private val invokeUploadDictionary: (AppUserId, ResourceEntity) -> DictionaryDbResponse = { _, _ -> DictionaryDbResponse.EMPTY }, ) : DbDictionaryRepository { - override fun getAllDictionaries(userId: AppUserId): DictionariesDbResponse { - return invokeGetAllDictionaries(userId) - } + override fun findDictionary(dictionaryId: DictionaryId): DictionaryEntity? = invokeFindDictionary(dictionaryId) - override fun createDictionary(userId: AppUserId, entity: DictionaryEntity): DictionaryDbResponse { - return invokeCreateDictionary(userId, entity) - } + override fun getAllDictionaries(userId: AppUserId): DictionariesDbResponse = invokeGetAllDictionaries(userId) - override fun removeDictionary(userId: AppUserId, dictionaryId: DictionaryId): RemoveDictionaryDbResponse { - return invokeDeleteDictionary(userId, dictionaryId) - } + override fun createDictionary(userId: AppUserId, entity: DictionaryEntity): DictionaryDbResponse = + invokeCreateDictionary(userId, entity) - override fun importDictionary(userId: AppUserId, dictionaryId: DictionaryId): ImportDictionaryDbResponse { - return invokeDownloadDictionary(userId, dictionaryId) - } + override fun removeDictionary(userId: AppUserId, dictionaryId: DictionaryId): RemoveDictionaryDbResponse = + invokeDeleteDictionary(userId, dictionaryId) - override fun exportDictionary(userId: AppUserId, resource: ResourceEntity): DictionaryDbResponse { - return invokeUploadDictionary(userId, resource) - } + override fun importDictionary(userId: AppUserId, dictionaryId: DictionaryId): ImportDictionaryDbResponse = + invokeDownloadDictionary(userId, dictionaryId) + + override fun exportDictionary(userId: AppUserId, resource: ResourceEntity): DictionaryDbResponse = + invokeUploadDictionary(userId, resource) } \ No newline at end of file diff --git a/db-mem/src/main/kotlin/MemDbCardRepository.kt b/db-mem/src/main/kotlin/MemDbCardRepository.kt index 3dccc0bd..f7d15198 100644 --- a/db-mem/src/main/kotlin/MemDbCardRepository.kt +++ b/db-mem/src/main/kotlin/MemDbCardRepository.kt @@ -31,16 +31,7 @@ class MemDbCardRepository( ) : DbCardRepository { private val database = MemDatabase.get(dbConfig.dataLocation) - override fun getCard(userId: AppUserId, cardId: CardId): CardDbResponse { - val card = - database.findCardById(cardId.asLong()) ?: return CardDbResponse(noCardFoundDbError("getCard", cardId)) - val errors = mutableListOf() - checkDictionaryUser("getCard", userId, card.dictionaryId.asDictionaryId(), cardId, errors) - if (errors.isNotEmpty()) { - return CardDbResponse(errors = errors) - } - return CardDbResponse(card = card.toCardEntity()) - } + override fun findCard(cardId: CardId): CardEntity? = database.findCardById(cardId.asLong())?.toCardEntity() override fun getAllCards(userId: AppUserId, dictionaryId: DictionaryId): CardsDbResponse { val id = dictionaryId.asLong() diff --git a/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt b/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt index 4fe4cef5..17f3a9b3 100644 --- a/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt +++ b/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt @@ -29,6 +29,9 @@ class MemDbDictionaryRepository( private val database = MemDatabase.get(databaseLocation = dbConfig.dataLocation) + override fun findDictionary(dictionaryId: DictionaryId): DictionaryEntity? = + database.findDictionaryById(dictionaryId.asLong())?.toDictionaryEntity() + override fun getAllDictionaries(userId: AppUserId): DictionariesDbResponse { val dictionaries = this.database.findDictionariesByUserId(userId.asLong()) return DictionariesDbResponse(dictionaries = dictionaries.map { it.toDictionaryEntity() }.toList()) diff --git a/db-mem/src/main/kotlin/MemDbEntityMapper.kt b/db-mem/src/main/kotlin/MemDbEntityMapper.kt index 1a483f70..eb5bcd8f 100644 --- a/db-mem/src/main/kotlin/MemDbEntityMapper.kt +++ b/db-mem/src/main/kotlin/MemDbEntityMapper.kt @@ -131,6 +131,7 @@ internal fun MemDbCard.toDocumentCard(mapStatus: (Int?) -> DocumentCardStatus): internal fun MemDbDictionary.toDictionaryEntity(): DictionaryEntity = DictionaryEntity( dictionaryId = this.id?.asDictionaryId() ?: DictionaryId.NONE, + userId = this.userId?.asUserId() ?: AppUserId.NONE, name = this.name, sourceLang = this.sourceLanguage.toLangEntity(), targetLang = this.targetLanguage.toLangEntity(), diff --git a/db-pg/src/main/kotlin/PgDbCardRepository.kt b/db-pg/src/main/kotlin/PgDbCardRepository.kt index d84ca34a..d860c2eb 100644 --- a/db-pg/src/main/kotlin/PgDbCardRepository.kt +++ b/db-pg/src/main/kotlin/PgDbCardRepository.kt @@ -44,35 +44,23 @@ class PgDbCardRepository( PgDbConnector.connection(dbConfig) } - override fun getCard(userId: AppUserId, cardId: CardId): CardDbResponse { - return connection.execute { - val card = PgDbCard.findById(cardId.asLong()) ?: return@execute CardDbResponse( - noCardFoundDbError(operation = "getCard", id = cardId) - ) - val errors = mutableListOf() - checkDictionaryUser("getCard", userId, checkNotNull(card.dictionaryId).asDictionaryId(), cardId, errors) - if (errors.isNotEmpty()) { - return@execute CardDbResponse(errors = errors) - } - CardDbResponse(card = card.toCardEntity()) - } + override fun findCard(cardId: CardId): CardEntity? = connection.execute { + PgDbCard.findById(cardId.asLong())?.toCardEntity() } - override fun getAllCards(userId: AppUserId, dictionaryId: DictionaryId): CardsDbResponse { - return connection.execute { - val errors = mutableListOf() - val dictionary = checkDictionaryUser("getAllCards", userId, dictionaryId, dictionaryId, errors) - if (errors.isNotEmpty() || dictionary == null) { - return@execute CardsDbResponse(errors = errors) - } - val cards = PgDbCard.find { Cards.dictionaryId eq dictionaryId.asLong() }.map { it.toCardEntity() } - val dictionaries = listOf(dictionary.toDictionaryEntity()) - CardsDbResponse( - cards = cards, - dictionaries = dictionaries, - errors = emptyList(), - ) + override fun getAllCards(userId: AppUserId, dictionaryId: DictionaryId): CardsDbResponse = connection.execute { + val errors = mutableListOf() + val dictionary = checkDictionaryUser("getAllCards", userId, dictionaryId, dictionaryId, errors) + if (errors.isNotEmpty() || dictionary == null) { + return@execute CardsDbResponse(errors = errors) } + val cards = PgDbCard.find { Cards.dictionaryId eq dictionaryId.asLong() }.map { it.toCardEntity() } + val dictionaries = listOf(dictionary.toDictionaryEntity()) + CardsDbResponse( + cards = cards, + dictionaries = dictionaries, + errors = emptyList(), + ) } override fun searchCard(userId: AppUserId, filter: CardFilter): CardsDbResponse { diff --git a/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt b/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt index 47cd33f9..59041e0d 100644 --- a/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt +++ b/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt @@ -32,7 +32,7 @@ import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList import org.jetbrains.exposed.sql.deleteWhere import org.jetbrains.exposed.sql.insertAndGetId -import org.jetbrains.exposed.sql.select +import org.jetbrains.exposed.sql.selectAll class PgDbDictionaryRepository( dbConfig: PgDbConfig = PgDbConfig(), @@ -44,6 +44,10 @@ class PgDbDictionaryRepository( PgDbConnector.connection(dbConfig) } + override fun findDictionary(dictionaryId: DictionaryId): DictionaryEntity? = connection.execute { + PgDbDictionary.findById(dictionaryId.asLong())?.toDictionaryEntity() + } + override fun getAllDictionaries(userId: AppUserId): DictionariesDbResponse { return connection.execute { val dictionaries = @@ -74,7 +78,7 @@ class PgDbDictionaryRepository( return@execute RemoveDictionaryDbResponse(errors = errors) } checkNotNull(found) - val cardIds = Cards.select { + val cardIds = Cards.selectAll().where { Cards.dictionaryId eq found.id }.map { it[Cards.id] diff --git a/db-pg/src/main/kotlin/PgDbEntityMapper.kt b/db-pg/src/main/kotlin/PgDbEntityMapper.kt index 270ca0fa..a68cd127 100644 --- a/db-pg/src/main/kotlin/PgDbEntityMapper.kt +++ b/db-pg/src/main/kotlin/PgDbEntityMapper.kt @@ -10,7 +10,6 @@ import com.gitlab.sszuev.flashcards.common.parseCardWordsJson import com.gitlab.sszuev.flashcards.common.toCardEntityDetails import com.gitlab.sszuev.flashcards.common.toCardEntityStats import com.gitlab.sszuev.flashcards.common.toCardWordEntity -import com.gitlab.sszuev.flashcards.common.toCommonCardDtoDetails import com.gitlab.sszuev.flashcards.common.toCommonWordDtoList import com.gitlab.sszuev.flashcards.common.toJsonString import com.gitlab.sszuev.flashcards.common.wordsAsCommonWordDtoList @@ -29,7 +28,6 @@ import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.LangEntity import com.gitlab.sszuev.flashcards.model.domain.LangId -import com.gitlab.sszuev.flashcards.model.domain.Stage import org.jetbrains.exposed.dao.id.EntityID import java.time.LocalDateTime import java.util.UUID @@ -41,6 +39,7 @@ internal fun PgDbUser.toAppUserEntity(): AppUserEntity = AppUserEntity( internal fun PgDbDictionary.toDictionaryEntity(): DictionaryEntity = DictionaryEntity( dictionaryId = this.id.asDictionaryId(), + userId = this.userId.asUserId(), name = this.name, sourceLang = createLangEntity(this.sourceLang), targetLang = createLangEntity(this.targetLang), @@ -68,8 +67,6 @@ internal fun writeCardEntityToPgDbCard(from: CardEntity, to: PgDbCard, timestamp to.changedAt = timestamp } -internal fun Map.toPgDbCardDetailsJson(): String = toCommonCardDtoDetails().toJsonString() - internal fun CardEntity.toPgDbCardWordsJson(): String = wordsAsCommonWordDtoList().toJsonString() internal fun DocumentCard.toPgDbCardWordsJson(): String = toCommonWordDtoList().toJsonString() From 3338978a74094ffd3a78a3feea2b4f2c60d55118 Mon Sep 17 00:00:00 2001 From: sszuev Date: Thu, 21 Mar 2024 21:53:24 +0300 Subject: [PATCH 03/18] common & core & db: [#28] replace DbCardRepository#getAllCards with findCards --- .../api/controllers/CardControllerRunTest.kt | 3 +- .../kotlin/repositories/DbCardRepository.kt | 4 +- .../repositories/NoOpDbCardRepository.kt | 2 +- .../kotlin/processes/CardProcessWorkers.kt | 58 ++++++++++++++- .../processes/DictionaryProcessWokers.kt | 6 +- .../kotlin/CardCorProcessorRunCardsTest.kt | 71 ++++++++++++++----- .../kotlin/DictionaryCorProcessorRunTest.kt | 5 +- .../kotlin/DbCardRepositoryTest.kt | 34 +++------ .../kotlin/mocks/MockDbCardRepository.kt | 5 +- db-mem/src/main/kotlin/MemDbCardRepository.kt | 17 +---- db-pg/src/main/kotlin/PgDbCardRepository.kt | 15 +--- specs/src/main/kotlin/Stubs.kt | 4 +- 12 files changed, 134 insertions(+), 90 deletions(-) diff --git a/app-ktor/src/test/kotlin/api/controllers/CardControllerRunTest.kt b/app-ktor/src/test/kotlin/api/controllers/CardControllerRunTest.kt index af8dc4ce..1d9a2227 100644 --- a/app-ktor/src/test/kotlin/api/controllers/CardControllerRunTest.kt +++ b/app-ktor/src/test/kotlin/api/controllers/CardControllerRunTest.kt @@ -117,7 +117,8 @@ internal class CardControllerRunTest { "spell of cold weather" ), res.card!!.words!!.single().examples?.map { it.example } ) - Assertions.assertNull(res.card!!.words!!.single().sound) + Assertions.assertEquals("en:weather", res.card!!.words!!.single().sound) + Assertions.assertEquals("en:weather", res.card!!.sound) Assertions.assertNull(res.card!!.answered) Assertions.assertEquals(emptyMap(), res.card!!.details) } diff --git a/common/src/commonMain/kotlin/repositories/DbCardRepository.kt b/common/src/commonMain/kotlin/repositories/DbCardRepository.kt index d5bd3d44..ab69c12c 100644 --- a/common/src/commonMain/kotlin/repositories/DbCardRepository.kt +++ b/common/src/commonMain/kotlin/repositories/DbCardRepository.kt @@ -19,9 +19,9 @@ interface DbCardRepository { fun findCard(cardId: CardId): CardEntity? /** - * Gets all cards by dictionaryId. + * Finds cards by dictionary id. */ - fun getAllCards(userId: AppUserId, dictionaryId: DictionaryId): CardsDbResponse + fun findCards(dictionaryId: DictionaryId): Sequence /** * Searches cards by filter. diff --git a/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt b/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt index 0d23903f..32c5c6f6 100644 --- a/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt +++ b/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt @@ -10,7 +10,7 @@ object NoOpDbCardRepository : DbCardRepository { override fun findCard(cardId: CardId): CardEntity = noOp() - override fun getAllCards(userId: AppUserId, dictionaryId: DictionaryId): CardsDbResponse = noOp() + override fun findCards(dictionaryId: DictionaryId): Sequence = noOp() override fun searchCard(userId: AppUserId, filter: CardFilter): CardsDbResponse = noOp() diff --git a/core/src/main/kotlin/processes/CardProcessWorkers.kt b/core/src/main/kotlin/processes/CardProcessWorkers.kt index f906050e..97a425a1 100644 --- a/core/src/main/kotlin/processes/CardProcessWorkers.kt +++ b/core/src/main/kotlin/processes/CardProcessWorkers.kt @@ -5,7 +5,10 @@ import com.gitlab.sszuev.flashcards.core.normalizers.normalize import com.gitlab.sszuev.flashcards.corlib.ChainDSL import com.gitlab.sszuev.flashcards.corlib.worker import com.gitlab.sszuev.flashcards.model.common.AppStatus +import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardOperation +import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity +import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.TTSResourceGet import com.gitlab.sszuev.flashcards.model.domain.TTSResourceId import com.gitlab.sszuev.flashcards.repositories.CardDbResponse @@ -30,7 +33,7 @@ fun ChainDSL.processGetCard() = worker { } else if (dictionary.userId != userId) { this.errors.add(forbiddenEntityDataError("getCard", card.cardId, userId)) } else { - this.responseCardEntity = card + this.responseCardEntity = postProcess(card) { dictionary } } } this.status = if (this.errors.isNotEmpty()) AppStatus.FAIL else AppStatus.RUN @@ -55,8 +58,18 @@ fun ChainDSL.processGetAllCards() = worker { process { val userId = this.contextUserEntity.id val dictionaryId = this.normalizedRequestDictionaryId - val res = this.repositories.cardRepository(this.workMode).getAllCards(userId, dictionaryId) - this.postProcess(res) + val dictionary = this.repositories.dictionaryRepository(this.workMode).findDictionary(dictionaryId) + if (dictionary == null) { + this.errors.add(noDictionaryFoundDataError("getAllCards", dictionaryId)) + } else if (dictionary.userId != userId) { + this.errors.add(forbiddenEntityDataError("getAllCards", dictionaryId, userId)) + } else { + val cards = postProcess( + this.repositories.cardRepository(this.workMode).findCards(dictionaryId).iterator() + ) { dictionary } + this.responseCardEntityList = cards + } + this.status = if (this.errors.isNotEmpty()) AppStatus.FAIL else AppStatus.RUN } onException { fail( @@ -185,6 +198,45 @@ private suspend fun CardContext.postProcess(res: CardsDbResponse) { this.status = if (this.errors.isNotEmpty()) AppStatus.FAIL else AppStatus.RUN } +private suspend fun CardContext.postProcess( + cardsIterator: Iterator, + dictionary: (DictionaryId) -> DictionaryEntity +): List { + val res = mutableListOf() + while (cardsIterator.hasNext()) { + res.add(postProcess(cardsIterator.next(), dictionary)) + } + return res +} + +private suspend fun CardContext.postProcess( + card: CardEntity, + dictionary: (DictionaryId) -> DictionaryEntity +): CardEntity { + check(card != CardEntity.EMPTY) { "Null card" } + val tts = this.repositories.ttsClientRepository(this.workMode) + val sourceLang = dictionary.invoke(card.dictionaryId).sourceLang.langId + val words = card.words.map { word -> + val wordAudioId = tts.findResourceId(TTSResourceGet(word.word, sourceLang).normalize()) + this.errors.addAll(wordAudioId.errors) + if (wordAudioId.id != TTSResourceId.NONE) { + word.copy(sound = wordAudioId.id) + } else { + word + } + } + + val cardAudioId = if (words.size == 1) { + words.single().sound + } else { + val cardAudioString = card.words.joinToString(",") { it.word } + val findResourceIdResponse = tts.findResourceId(TTSResourceGet(cardAudioString, sourceLang).normalize()) + this.errors.addAll(findResourceIdResponse.errors) + findResourceIdResponse.id + } + return card.copy(words = words, sound = cardAudioId) +} + private fun CardContext.postProcess(res: CardDbResponse) { this.responseCardEntity = res.card if (res.errors.isNotEmpty()) { diff --git a/core/src/main/kotlin/processes/DictionaryProcessWokers.kt b/core/src/main/kotlin/processes/DictionaryProcessWokers.kt index 3da3cc6a..64a0afe3 100644 --- a/core/src/main/kotlin/processes/DictionaryProcessWokers.kt +++ b/core/src/main/kotlin/processes/DictionaryProcessWokers.kt @@ -22,9 +22,9 @@ fun ChainDSL.processGetAllDictionary() = worker { // TODO: temporary solution if (res.errors.isEmpty()) { this.responseDictionaryEntityList = res.dictionaries.map { dictionary -> - val cards = this.repositories.cardRepository(this.workMode).getAllCards(userId, dictionary.dictionaryId) - val total = cards.cards.size - val known = cards.cards.mapNotNull { it.answered }.count { it >= config.numberOfRightAnswers } + val cards = this.repositories.cardRepository(this.workMode).findCards(dictionary.dictionaryId).toList() + val total = cards.size + val known = cards.mapNotNull { it.answered }.count { it >= config.numberOfRightAnswers } dictionary.copy(totalCardsCount = total, learnedCardsCount = known) } } diff --git a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt index 00b68e5a..f91b9a7d 100644 --- a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt +++ b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt @@ -26,7 +26,9 @@ import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository import com.gitlab.sszuev.flashcards.repositories.DbUserRepository import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse +import com.gitlab.sszuev.flashcards.repositories.TTSResourceRepository import com.gitlab.sszuev.flashcards.repositories.UserEntityDbResponse +import com.gitlab.sszuev.flashcards.speaker.MockTTSResourceRepository import com.gitlab.sszuev.flashcards.stubs.stubCard import com.gitlab.sszuev.flashcards.stubs.stubCards import com.gitlab.sszuev.flashcards.stubs.stubDictionary @@ -45,6 +47,7 @@ internal class CardCorProcessorRunCardsTest { cardRepository: DbCardRepository, userRepository: DbUserRepository = MockDbUserRepository(), dictionaryRepository: DbDictionaryRepository = MockDbDictionaryRepository(), + ttsResourceRepository: TTSResourceRepository = MockTTSResourceRepository(), ): CardContext { val context = CardContext( operation = op, @@ -52,6 +55,7 @@ internal class CardCorProcessorRunCardsTest { testUserRepository = userRepository, testCardRepository = cardRepository, testDictionaryRepository = dictionaryRepository, + testTTSClientRepository = ttsResourceRepository, ) ) context.requestAppAuthId = testUser.authId @@ -114,8 +118,8 @@ internal class CardCorProcessorRunCardsTest { Assertions.assertTrue(findCardIsCalled) Assertions.assertTrue(findDictionaryIsCalled) Assertions.assertEquals(requestId(CardOperation.GET_CARD), context.requestId) + Assertions.assertTrue(context.errors.isEmpty()) { context.errors.toString() } Assertions.assertEquals(AppStatus.OK, context.status) - Assertions.assertTrue(context.errors.isEmpty()) Assertions.assertEquals(testResponseCardEntity, context.responseCardEntity) } @@ -155,50 +159,79 @@ internal class CardCorProcessorRunCardsTest { @Test fun `test get-all-cards success`() = runTest { val testDictionaryId = DictionaryId("42") - val testResponseEntities = stubCards + val testDictionary = stubDictionary.copy(dictionaryId = testDictionaryId) + val testCards = stubCards.map { it.copy(dictionaryId = testDictionaryId) } - var wasCalled = false - val repository = MockDbCardRepository( - invokeGetAllCards = { _, id -> - wasCalled = true - CardsDbResponse(if (id == testDictionaryId) testResponseEntities else emptyList()) + var isFindCardsCalled = false + var isFindDictionaryCalled = false + val cardRepository = MockDbCardRepository( + invokeFindCards = { id -> + isFindCardsCalled = true + if (id == testDictionaryId) testCards.asSequence() else emptySequence() + } + ) + val dictionaryRepository = MockDbDictionaryRepository( + invokeFindDictionary = { id -> + isFindDictionaryCalled = true + if (id == testDictionaryId) testDictionary else null } ) - val context = testContext(CardOperation.GET_ALL_CARDS, repository) + val context = testContext( + op = CardOperation.GET_ALL_CARDS, + cardRepository = cardRepository, + dictionaryRepository = dictionaryRepository, + ) context.requestDictionaryId = testDictionaryId CardCorProcessor().execute(context) - Assertions.assertTrue(wasCalled) + Assertions.assertTrue(context.errors.isEmpty()) { context.errors.toString() } + Assertions.assertTrue(isFindCardsCalled) + Assertions.assertTrue(isFindDictionaryCalled) Assertions.assertEquals(requestId(CardOperation.GET_ALL_CARDS), context.requestId) Assertions.assertEquals(AppStatus.OK, context.status) - Assertions.assertTrue(context.errors.isEmpty()) - Assertions.assertEquals(testResponseEntities, context.responseCardEntityList) + Assertions.assertEquals( + testCards.size, + (context.repositories.ttsClientRepository(AppMode.TEST) as MockTTSResourceRepository).findResourceIdCounts.toInt() + ) + + Assertions.assertEquals(testCards, context.responseCardEntityList) } @Test fun `test get-all-cards unexpected fail`() = runTest { val testDictionaryId = DictionaryId("42") + val testDictionary = stubDictionary.copy(dictionaryId = testDictionaryId) val testResponseEntities = stubCards - var wasCalled = false + var isFindCardsCalled = false + var isFindDictionaryCalled = false val repository = MockDbCardRepository( - invokeGetAllCards = { _, id -> - wasCalled = true - CardsDbResponse( - if (id != testDictionaryId) testResponseEntities else throw TestException() - ) + invokeFindCards = { id -> + isFindCardsCalled = true + if (id != testDictionaryId) testResponseEntities.asSequence() else throw TestException() + } + ) + val dictionaryRepository = MockDbDictionaryRepository( + invokeFindDictionary = { id -> + isFindDictionaryCalled = true + if (id == testDictionaryId) testDictionary else null } ) - val context = testContext(CardOperation.GET_ALL_CARDS, repository) + val context = testContext( + op = CardOperation.GET_ALL_CARDS, + cardRepository = repository, + dictionaryRepository = dictionaryRepository, + ) context.requestDictionaryId = testDictionaryId CardCorProcessor().execute(context) - Assertions.assertTrue(wasCalled) + Assertions.assertTrue(isFindDictionaryCalled) + Assertions.assertTrue(isFindCardsCalled) Assertions.assertEquals(requestId(CardOperation.GET_ALL_CARDS), context.requestId) Assertions.assertEquals(0, context.responseCardEntityList.size) assertUnknownError(context, CardOperation.GET_ALL_CARDS) diff --git a/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt b/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt index 00f5bc74..f2848543 100644 --- a/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt +++ b/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt @@ -15,7 +15,6 @@ import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.DictionaryOperation import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity -import com.gitlab.sszuev.flashcards.repositories.CardsDbResponse import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository import com.gitlab.sszuev.flashcards.repositories.DbUserRepository @@ -75,9 +74,9 @@ internal class DictionaryCorProcessorRunTest { } ) val cardsRepository = MockDbCardRepository( - invokeGetAllCards = { _, _ -> + invokeFindCards = { _ -> getAllCardsWasCalled = true - CardsDbResponse.EMPTY + emptySequence() } ) diff --git a/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt b/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt index a935a830..648fd329 100644 --- a/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt +++ b/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt @@ -10,7 +10,6 @@ import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.CardWordEntity import com.gitlab.sszuev.flashcards.model.domain.CardWordExampleEntity import com.gitlab.sszuev.flashcards.model.domain.DictionaryId -import com.gitlab.sszuev.flashcards.model.domain.LangId import com.gitlab.sszuev.flashcards.model.domain.Stage import com.gitlab.sszuev.flashcards.repositories.CardDbResponse import com.gitlab.sszuev.flashcards.repositories.DbCardRepository @@ -202,39 +201,22 @@ abstract class DbCardRepositoryTest { @Test fun `test get all cards success`() { // Business dictionary - val res1 = repository.getAllCards(userId, DictionaryId("1")) - assertEquals(244, res1.cards.size) - assertEquals(0, res1.errors.size) - assertEquals(1, res1.dictionaries.size) - assertEquals("1", res1.dictionaries.single().dictionaryId.asString()) + val res1 = repository.findCards(DictionaryId("1")).toList() + assertEquals(244, res1.size) + assertEquals("1", res1.map { it.dictionaryId.asString() }.toSet().single()) // Weather dictionary - val res2 = repository.getAllCards(userId, DictionaryId("2")) - assertEquals(65, res2.cards.size) - assertEquals(0, res2.errors.size) - assertEquals(1, res2.dictionaries.size) - assertEquals("2", res2.dictionaries.single().dictionaryId.asString()) - - assertEquals(LangId("en"), res1.dictionaries.single().sourceLang.langId) - assertEquals(LangId("en"), res2.dictionaries.single().sourceLang.langId) + val res2 = repository.findCards(DictionaryId("2")).toList() + assertEquals(65, res2.size) + assertEquals("2", res2.map { it.dictionaryId.asString() }.toSet().single()) } @Order(2) @Test fun `test get all cards error unknown dictionary`() { val dictionaryId = "42" - val res = repository.getAllCards(userId, DictionaryId(dictionaryId)) - assertEquals(0, res.cards.size) - assertEquals(1, res.errors.size) - val error = res.errors[0] - assertEquals("database::getAllCards", error.code) - assertEquals(dictionaryId, error.field) - assertEquals("database", error.group) - assertEquals( - """Error while getAllCards: dictionary with id="$dictionaryId" not found""", - error.message - ) - assertNull(error.exception) + val res = repository.findCards(DictionaryId(dictionaryId)).toList() + assertEquals(0, res.size) } @Order(4) diff --git a/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt b/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt index df92399d..1e4517fb 100644 --- a/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt +++ b/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt @@ -16,7 +16,7 @@ import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse */ class MockDbCardRepository( private val invokeFindCard: (CardId) -> CardEntity? = { null }, - private val invokeGetAllCards: (AppUserId, DictionaryId) -> CardsDbResponse = { _, _ -> CardsDbResponse.EMPTY }, + private val invokeFindCards: (DictionaryId) -> Sequence = { emptySequence() }, private val invokeSearchCards: (AppUserId, CardFilter) -> CardsDbResponse = { _, _ -> CardsDbResponse.EMPTY }, private val invokeCreateCard: (AppUserId, CardEntity) -> CardDbResponse = { _, _ -> CardDbResponse.EMPTY }, private val invokeUpdateCard: (AppUserId, CardEntity) -> CardDbResponse = { _, _ -> CardDbResponse.EMPTY }, @@ -27,8 +27,7 @@ class MockDbCardRepository( override fun findCard(cardId: CardId): CardEntity? = invokeFindCard(cardId) - override fun getAllCards(userId: AppUserId, dictionaryId: DictionaryId): CardsDbResponse = - invokeGetAllCards(userId, dictionaryId) + override fun findCards(dictionaryId: DictionaryId): Sequence = invokeFindCards(dictionaryId) override fun searchCard(userId: AppUserId, filter: CardFilter): CardsDbResponse = invokeSearchCards(userId, filter) diff --git a/db-mem/src/main/kotlin/MemDbCardRepository.kt b/db-mem/src/main/kotlin/MemDbCardRepository.kt index f7d15198..a08469fa 100644 --- a/db-mem/src/main/kotlin/MemDbCardRepository.kt +++ b/db-mem/src/main/kotlin/MemDbCardRepository.kt @@ -33,21 +33,8 @@ class MemDbCardRepository( override fun findCard(cardId: CardId): CardEntity? = database.findCardById(cardId.asLong())?.toCardEntity() - override fun getAllCards(userId: AppUserId, dictionaryId: DictionaryId): CardsDbResponse { - val id = dictionaryId.asLong() - val errors = mutableListOf() - val dictionary = checkDictionaryUser("getAllCards", userId, dictionaryId, dictionaryId, errors) - if (errors.isNotEmpty() || dictionary == null) { - return CardsDbResponse(errors = errors) - } - val cards = database.findCardsByDictionaryId(id).map { it.toCardEntity() }.toList() - val dictionaries = listOf(dictionary.toDictionaryEntity()) - return CardsDbResponse( - cards = cards, - dictionaries = dictionaries, - errors = emptyList() - ) - } + override fun findCards(dictionaryId: DictionaryId): Sequence = + database.findCardsByDictionaryId(dictionaryId.asLong()).map { it.toCardEntity() } override fun searchCard(userId: AppUserId, filter: CardFilter): CardsDbResponse { require(filter.length != 0) { "zero length is specified" } diff --git a/db-pg/src/main/kotlin/PgDbCardRepository.kt b/db-pg/src/main/kotlin/PgDbCardRepository.kt index d860c2eb..6053ef2a 100644 --- a/db-pg/src/main/kotlin/PgDbCardRepository.kt +++ b/db-pg/src/main/kotlin/PgDbCardRepository.kt @@ -48,19 +48,8 @@ class PgDbCardRepository( PgDbCard.findById(cardId.asLong())?.toCardEntity() } - override fun getAllCards(userId: AppUserId, dictionaryId: DictionaryId): CardsDbResponse = connection.execute { - val errors = mutableListOf() - val dictionary = checkDictionaryUser("getAllCards", userId, dictionaryId, dictionaryId, errors) - if (errors.isNotEmpty() || dictionary == null) { - return@execute CardsDbResponse(errors = errors) - } - val cards = PgDbCard.find { Cards.dictionaryId eq dictionaryId.asLong() }.map { it.toCardEntity() } - val dictionaries = listOf(dictionary.toDictionaryEntity()) - CardsDbResponse( - cards = cards, - dictionaries = dictionaries, - errors = emptyList(), - ) + override fun findCards(dictionaryId: DictionaryId): Sequence = connection.execute { + PgDbCard.find { Cards.dictionaryId eq dictionaryId.asLong() }.map { it.toCardEntity() }.asSequence() } override fun searchCard(userId: AppUserId, filter: CardFilter): CardsDbResponse { diff --git a/specs/src/main/kotlin/Stubs.kt b/specs/src/main/kotlin/Stubs.kt index 5e744514..ecd7cdfc 100644 --- a/specs/src/main/kotlin/Stubs.kt +++ b/specs/src/main/kotlin/Stubs.kt @@ -47,7 +47,7 @@ val stubCard = CardEntity( word = "stub", partOfSpeech = "noun", transcription = "stʌb", - translations = listOf(listOf("заглушка"), listOf("корешок", "талон", "квитация")), + translations = listOf(listOf("заглушка"), listOf("корешок", "талон", "квитанция")), examples = listOf("That was the last candle stub I had.", "\$500 ticket stub.").map { CardWordExampleEntity( it @@ -57,6 +57,7 @@ val stubCard = CardEntity( ), ), stats = mapOf(Stage.SELF_TEST to 42, Stage.OPTIONS to 21), + sound = TTSResourceId("en:stub"), ) val stubCards = IntRange(1, 3) @@ -70,6 +71,7 @@ val stubCards = IntRange(1, 3) word = "XXX-${it.first}-${it.second}" ), ), + sound = TTSResourceId.NONE, ) } From dc9b177a38388514e02255ec8bebeb859102c01a Mon Sep 17 00:00:00 2001 From: sszuev Date: Fri, 22 Mar 2024 18:35:32 +0300 Subject: [PATCH 04/18] common & core & db: [#28] replace DbCardRepository#searchCards with findCards, another related changes --- .../kotlin/model/domain/CardFilter.kt | 5 +- .../kotlin/repositories/DbCardRepository.kt | 12 +-- .../repositories/DbDictionaryRepository.kt | 3 + .../repositories/NoOpDbCardRepository.kt | 3 - .../kotlin/normalizers/NormalizeWorkers.kt | 2 +- .../kotlin/processes/CardProcessWorkers.kt | 18 ++++- .../kotlin/processes/SearchCardsHelper.kt | 41 +++++------ .../kotlin/CardCorProcessorRunCardsTest.kt | 73 +++++++++++++------ .../test/kotlin/CardCorProcessorStubTest.kt | 26 +++++-- .../kotlin/CardCorProcessorValidationTest.kt | 2 +- .../kotlin/DictionaryCorProcessorRunTest.kt | 2 +- .../kotlin/DbCardRepositoryTest.kt | 30 -------- .../kotlin/mocks/MockDbCardRepository.kt | 11 +-- .../mocks/MockDbDictionaryRepository.kt | 4 + db-mem/src/main/kotlin/MemDbCardRepository.kt | 32 -------- db-pg/src/main/kotlin/PgDbCardRepository.kt | 42 ----------- .../flashcards/logmappers/FromContext.kt | 2 +- .../src/test/kotlin/FromContextTest.kt | 4 +- mappers/src/main/kotlin/FromCardTransport.kt | 2 +- .../src/test/kotlin/FromCardTransportTest.kt | 2 +- 20 files changed, 138 insertions(+), 178 deletions(-) diff --git a/common/src/commonMain/kotlin/model/domain/CardFilter.kt b/common/src/commonMain/kotlin/model/domain/CardFilter.kt index 78a3ebee..d50f03a6 100644 --- a/common/src/commonMain/kotlin/model/domain/CardFilter.kt +++ b/common/src/commonMain/kotlin/model/domain/CardFilter.kt @@ -4,7 +4,10 @@ data class CardFilter( val dictionaryIds: List = emptyList(), val random: Boolean = false, val length: Int = 0, - val withUnknown: Boolean = false, + /** + * `true` to return only unknown words + */ + val onlyUnknown: Boolean = false, ) { companion object { val EMPTY = CardFilter() diff --git a/common/src/commonMain/kotlin/repositories/DbCardRepository.kt b/common/src/commonMain/kotlin/repositories/DbCardRepository.kt index ab69c12c..c07bfe40 100644 --- a/common/src/commonMain/kotlin/repositories/DbCardRepository.kt +++ b/common/src/commonMain/kotlin/repositories/DbCardRepository.kt @@ -3,7 +3,6 @@ package com.gitlab.sszuev.flashcards.repositories import com.gitlab.sszuev.flashcards.model.common.AppError import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.CardEntity -import com.gitlab.sszuev.flashcards.model.domain.CardFilter import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity import com.gitlab.sszuev.flashcards.model.domain.DictionaryId @@ -23,11 +22,6 @@ interface DbCardRepository { */ fun findCards(dictionaryId: DictionaryId): Sequence - /** - * Searches cards by filter. - */ - fun searchCard(userId: AppUserId, filter: CardFilter): CardsDbResponse - /** * Creates card. */ @@ -52,6 +46,12 @@ interface DbCardRepository { * Deletes card by id. */ fun removeCard(userId: AppUserId, cardId: CardId): RemoveCardDbResponse + + /** + * Finds cards by dictionary ids. + */ + fun findCards(dictionaryIds: Iterable): Sequence = + dictionaryIds.asSequence().flatMap { findCards(it) } } data class CardsDbResponse( diff --git a/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt b/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt index 4ac9f492..f039bc5b 100644 --- a/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt +++ b/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt @@ -18,6 +18,9 @@ interface DbDictionaryRepository { fun importDictionary(userId: AppUserId, dictionaryId: DictionaryId): ImportDictionaryDbResponse fun exportDictionary(userId: AppUserId, resource: ResourceEntity): DictionaryDbResponse + + fun findDictionaries(dictionaryIds: Iterable): Sequence = + dictionaryIds.asSequence().mapNotNull { findDictionary(it) } } data class DictionariesDbResponse( diff --git a/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt b/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt index 32c5c6f6..9b92b391 100644 --- a/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt +++ b/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt @@ -2,7 +2,6 @@ package com.gitlab.sszuev.flashcards.repositories import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.CardEntity -import com.gitlab.sszuev.flashcards.model.domain.CardFilter import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.DictionaryId @@ -12,8 +11,6 @@ object NoOpDbCardRepository : DbCardRepository { override fun findCards(dictionaryId: DictionaryId): Sequence = noOp() - override fun searchCard(userId: AppUserId, filter: CardFilter): CardsDbResponse = noOp() - override fun createCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse = noOp() override fun updateCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse = noOp() diff --git a/core/src/main/kotlin/normalizers/NormalizeWorkers.kt b/core/src/main/kotlin/normalizers/NormalizeWorkers.kt index 5a7f831c..5df03fff 100644 --- a/core/src/main/kotlin/normalizers/NormalizeWorkers.kt +++ b/core/src/main/kotlin/normalizers/NormalizeWorkers.kt @@ -111,7 +111,7 @@ fun CardFilter.normalize(): CardFilter { dictionaryIds = this.dictionaryIds.map { it.normalize() }, random = this.random, length = this.length, - withUnknown = this.withUnknown, + onlyUnknown = this.onlyUnknown, ) } diff --git a/core/src/main/kotlin/processes/CardProcessWorkers.kt b/core/src/main/kotlin/processes/CardProcessWorkers.kt index 97a425a1..e4524dff 100644 --- a/core/src/main/kotlin/processes/CardProcessWorkers.kt +++ b/core/src/main/kotlin/processes/CardProcessWorkers.kt @@ -89,7 +89,23 @@ fun ChainDSL.processCardSearch() = worker { this.status == AppStatus.RUN } process { - this.postProcess(this.findCardDeck()) + val userId = this.contextUserEntity.id + val found = this.repositories.dictionaryRepository(this.workMode) + .findDictionaries(this.normalizedRequestCardFilter.dictionaryIds) + .associateBy { it.dictionaryId } + this.normalizedRequestCardFilter.dictionaryIds.filterNot { found.containsKey(it) }.forEach { + this.errors.add(noDictionaryFoundDataError("searchCards", it)) + } + found.values.forEach { dictionary -> + if (dictionary.userId != userId) { + this.errors.add(forbiddenEntityDataError("searchCards", dictionary.dictionaryId, userId)) + } + } + if (errors.isEmpty()) { + val cards = postProcess(findCardDeck().iterator()) { checkNotNull(found[it]) } + this.responseCardEntityList = cards + } + this.status = if (this.errors.isNotEmpty()) AppStatus.FAIL else AppStatus.RUN } onException { this.handleThrowable(CardOperation.SEARCH_CARDS, it) diff --git a/core/src/main/kotlin/processes/SearchCardsHelper.kt b/core/src/main/kotlin/processes/SearchCardsHelper.kt index 775aeb5e..a8048912 100644 --- a/core/src/main/kotlin/processes/SearchCardsHelper.kt +++ b/core/src/main/kotlin/processes/SearchCardsHelper.kt @@ -3,7 +3,6 @@ package com.gitlab.sszuev.flashcards.core.processes import com.gitlab.sszuev.flashcards.CardContext import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardWordEntity -import com.gitlab.sszuev.flashcards.repositories.CardsDbResponse private val comparator: Comparator = Comparator { left, right -> val la = left.answered ?: 0 @@ -16,28 +15,28 @@ private val comparator: Comparator = Comparator { left, /** * Prepares a card deck for a tutor-session. */ -fun CardContext.findCardDeck(): CardsDbResponse { - return if (this.normalizedRequestCardFilter.random) { - // For random mode, do not use the database support since logic is quite complicated. - // We load everything into memory, since the dictionary can hardly contain more than a thousand words, - // i.e., this is relatively small data. - val filter = this.normalizedRequestCardFilter.copy(random = false, length = -1) - val res = this.repositories.cardRepository(this.workMode).searchCard(this.contextUserEntity.id, filter) - if (res.errors.isNotEmpty()) { - return res - } - var cards = res.cards.shuffled().sortedWith(comparator) - if (this.normalizedRequestCardFilter.length > 0) { - val set = mutableSetOf() - collectCardDeck(cards, set, this.normalizedRequestCardFilter.length) - cards = set.toList() - cards = cards.shuffled() +fun CardContext.findCardDeck(): List { + val threshold = config.numberOfRightAnswers + var cards = this.repositories.cardRepository(this.workMode) + .findCards(this.normalizedRequestCardFilter.dictionaryIds) + .filter { !this.normalizedRequestCardFilter.onlyUnknown || (it.answered ?: -1) <= threshold } + if (this.normalizedRequestCardFilter.random) { + cards = cards.shuffled() + } + cards = cards.sortedWith(comparator) + if (!this.normalizedRequestCardFilter.random && this.normalizedRequestCardFilter.length > 0) { + return cards.take(this.normalizedRequestCardFilter.length).toList() + } + var res = cards.toList() + if (this.normalizedRequestCardFilter.length > 0) { + val set = mutableSetOf() + collectCardDeck(res, set, this.normalizedRequestCardFilter.length) + res = set.toList() + if (this.normalizedRequestCardFilter.random) { + res = res.shuffled() } - return res.copy(cards = cards) - } else { - this.repositories.cardRepository(this.workMode) - .searchCard(this.contextUserEntity.id, this.normalizedRequestCardFilter) } + return res } private fun collectCardDeck(all: List, res: MutableSet, num: Int) { diff --git a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt index f91b9a7d..7cf45f84 100644 --- a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt +++ b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt @@ -31,6 +31,7 @@ import com.gitlab.sszuev.flashcards.repositories.UserEntityDbResponse import com.gitlab.sszuev.flashcards.speaker.MockTTSResourceRepository import com.gitlab.sszuev.flashcards.stubs.stubCard import com.gitlab.sszuev.flashcards.stubs.stubCards +import com.gitlab.sszuev.flashcards.stubs.stubDictionaries import com.gitlab.sszuev.flashcards.stubs.stubDictionary import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions @@ -165,7 +166,7 @@ internal class CardCorProcessorRunCardsTest { var isFindCardsCalled = false var isFindDictionaryCalled = false val cardRepository = MockDbCardRepository( - invokeFindCards = { id -> + invokeFindCardsByDictionaryId = { id -> isFindCardsCalled = true if (id == testDictionaryId) testCards.asSequence() else emptySequence() } @@ -209,7 +210,7 @@ internal class CardCorProcessorRunCardsTest { var isFindCardsCalled = false var isFindDictionaryCalled = false val repository = MockDbCardRepository( - invokeFindCards = { id -> + invokeFindCardsByDictionaryId = { id -> isFindCardsCalled = true if (id != testDictionaryId) testResponseEntities.asSequence() else throw TestException() } @@ -289,32 +290,45 @@ internal class CardCorProcessorRunCardsTest { @Test fun `test search-cards success`() = runTest { val testFilter = CardFilter( - dictionaryIds = listOf(DictionaryId("21"), DictionaryId("42")), + dictionaryIds = listOf(DictionaryId("42")), random = false, - withUnknown = true, + onlyUnknown = true, length = 42, ) - val testResponseEntities = stubCards + val testCards = stubCards.filter { it.dictionaryId in testFilter.dictionaryIds } + val testDictionaries = stubDictionaries - var wasCalled = false - val repository = MockDbCardRepository( - invokeSearchCards = { _, it -> - wasCalled = true - CardsDbResponse(if (it == testFilter) testResponseEntities else emptyList()) + var isFindCardsCalled = false + var isFindDictionariesCalled = false + val cardRepository = MockDbCardRepository( + invokeFindCardsByDictionaryIds = { + isFindCardsCalled = true + if (it == testFilter.dictionaryIds) testCards.asSequence() else emptySequence() + } + ) + val dictionaryRepository = MockDbDictionaryRepository( + invokeFindDictionaries = { + isFindDictionariesCalled = true + if (it == testFilter.dictionaryIds) testDictionaries.asSequence() else emptySequence() } ) - val context = testContext(CardOperation.SEARCH_CARDS, repository) + val context = testContext( + op = CardOperation.SEARCH_CARDS, + cardRepository = cardRepository, + dictionaryRepository = dictionaryRepository, + ) context.requestCardFilter = testFilter CardCorProcessor().execute(context) - Assertions.assertTrue(wasCalled) + Assertions.assertTrue(isFindCardsCalled) + Assertions.assertTrue(isFindDictionariesCalled) Assertions.assertEquals(requestId(CardOperation.SEARCH_CARDS), context.requestId) Assertions.assertEquals(AppStatus.OK, context.status) Assertions.assertTrue(context.errors.isEmpty()) - Assertions.assertEquals(testResponseEntities, context.responseCardEntityList) + Assertions.assertEquals(testCards, context.responseCardEntityList) } @Test @@ -322,27 +336,38 @@ internal class CardCorProcessorRunCardsTest { val testFilter = CardFilter( dictionaryIds = listOf(DictionaryId("42")), random = false, - withUnknown = false, + onlyUnknown = false, length = 1, ) - val testResponseEntities = stubCards + val testDictionaries = stubDictionaries + val testCards = stubCards - var wasCalled = false - val repository = MockDbCardRepository( - invokeSearchCards = { _, it -> - wasCalled = true - CardsDbResponse( - if (it != testFilter) testResponseEntities else throw TestException() - ) + var isFindCardsCalled = false + var isFindDictionariesCalled = false + val cardRepository = MockDbCardRepository( + invokeFindCardsByDictionaryIds = { + isFindCardsCalled = true + if (it == testFilter.dictionaryIds) throw TestException() else testCards.asSequence() + } + ) + val dictionaryRepository = MockDbDictionaryRepository( + invokeFindDictionaries = { + isFindDictionariesCalled = true + if (it == testFilter.dictionaryIds) testDictionaries.asSequence() else emptySequence() } ) - val context = testContext(CardOperation.SEARCH_CARDS, repository) + val context = testContext( + op = CardOperation.SEARCH_CARDS, + cardRepository = cardRepository, + dictionaryRepository = dictionaryRepository, + ) context.requestCardFilter = testFilter CardCorProcessor().execute(context) - Assertions.assertTrue(wasCalled) + Assertions.assertTrue(isFindDictionariesCalled) + Assertions.assertTrue(isFindCardsCalled) Assertions.assertEquals(0, context.responseCardEntityList.size) assertUnknownError(context, CardOperation.SEARCH_CARDS) } diff --git a/core/src/test/kotlin/CardCorProcessorStubTest.kt b/core/src/test/kotlin/CardCorProcessorStubTest.kt index 4e9fcb58..a65348d6 100644 --- a/core/src/test/kotlin/CardCorProcessorStubTest.kt +++ b/core/src/test/kotlin/CardCorProcessorStubTest.kt @@ -1,9 +1,25 @@ package com.gitlab.sszuev.flashcards.core import com.gitlab.sszuev.flashcards.CardContext -import com.gitlab.sszuev.flashcards.model.common.* -import com.gitlab.sszuev.flashcards.model.domain.* -import com.gitlab.sszuev.flashcards.stubs.* +import com.gitlab.sszuev.flashcards.model.common.AppError +import com.gitlab.sszuev.flashcards.model.common.AppMode +import com.gitlab.sszuev.flashcards.model.common.AppRequestId +import com.gitlab.sszuev.flashcards.model.common.AppStatus +import com.gitlab.sszuev.flashcards.model.common.AppStub +import com.gitlab.sszuev.flashcards.model.domain.CardEntity +import com.gitlab.sszuev.flashcards.model.domain.CardFilter +import com.gitlab.sszuev.flashcards.model.domain.CardId +import com.gitlab.sszuev.flashcards.model.domain.CardLearn +import com.gitlab.sszuev.flashcards.model.domain.CardOperation +import com.gitlab.sszuev.flashcards.model.domain.DictionaryId +import com.gitlab.sszuev.flashcards.model.domain.LangId +import com.gitlab.sszuev.flashcards.model.domain.Stage +import com.gitlab.sszuev.flashcards.model.domain.TTSResourceGet +import com.gitlab.sszuev.flashcards.stubs.stubAudioResource +import com.gitlab.sszuev.flashcards.stubs.stubCard +import com.gitlab.sszuev.flashcards.stubs.stubCards +import com.gitlab.sszuev.flashcards.stubs.stubError +import com.gitlab.sszuev.flashcards.stubs.stubErrorForCode import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions @@ -12,7 +28,7 @@ import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.EnumSource import org.junit.jupiter.params.provider.MethodSource -import java.util.* +import java.util.UUID @OptIn(ExperimentalCoroutinesApi::class) internal class CardCorProcessorStubTest { @@ -27,7 +43,7 @@ internal class CardCorProcessorStubTest { dictionaryIds = listOf(2, 4, 42).map { DictionaryId(it.toString()) }, length = 42, random = true, - withUnknown = false, + onlyUnknown = false, ) private val testCardLearn = CardLearn( cardId = CardId("42"), diff --git a/core/src/test/kotlin/CardCorProcessorValidationTest.kt b/core/src/test/kotlin/CardCorProcessorValidationTest.kt index f36de508..bb40f9c6 100644 --- a/core/src/test/kotlin/CardCorProcessorValidationTest.kt +++ b/core/src/test/kotlin/CardCorProcessorValidationTest.kt @@ -39,7 +39,7 @@ internal class CardCorProcessorValidationTest { dictionaryIds = listOf(4, 2, 42).map { DictionaryId(it.toString()) }, length = 42, random = false, - withUnknown = true, + onlyUnknown = true, ) private val testCardLearn = CardLearn( cardId = CardId("42"), diff --git a/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt b/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt index f2848543..6537f9d2 100644 --- a/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt +++ b/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt @@ -74,7 +74,7 @@ internal class DictionaryCorProcessorRunTest { } ) val cardsRepository = MockDbCardRepository( - invokeFindCards = { _ -> + invokeFindCardsByDictionaryId = { _ -> getAllCardsWasCalled = true emptySequence() } diff --git a/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt b/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt index 648fd329..bf94d079 100644 --- a/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt +++ b/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt @@ -5,7 +5,6 @@ import com.gitlab.sszuev.flashcards.model.common.AppError import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.common.NONE import com.gitlab.sszuev.flashcards.model.domain.CardEntity -import com.gitlab.sszuev.flashcards.model.domain.CardFilter import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.CardWordEntity import com.gitlab.sszuev.flashcards.model.domain.CardWordExampleEntity @@ -244,35 +243,6 @@ abstract class DbCardRepositoryTest { assertNull(error.exception) } - @Order(5) - @Test - fun `test search cards random with unknown`() { - val filter = CardFilter( - dictionaryIds = listOf(DictionaryId("1"), DictionaryId("2"), DictionaryId("3")), - withUnknown = true, - random = true, - length = 300, - ) - val res1 = repository.searchCard(userId, filter) - val res2 = repository.searchCard(userId, filter) - - assertEquals(0, res1.errors.size) - assertEquals(0, res2.errors.size) - assertEquals(300, res1.cards.size) - assertEquals(300, res2.cards.size) - assertNotEquals(res1, res2) - assertEquals(setOf("1", "2"), res1.cards.map { it.dictionaryId }.map { it.asString() }.toSet()) - assertEquals(setOf("1", "2"), res2.cards.map { it.dictionaryId }.map { it.asString() }.toSet()) - assertEquals( - setOf("1", "2"), - res1.dictionaries.map { it.dictionaryId }.map { it.asString() }.toSet() - ) - assertEquals( - setOf("1", "2"), - res2.dictionaries.map { it.dictionaryId }.map { it.asString() }.toSet() - ) - } - @Order(6) @Test fun `test get card & update card success`() { diff --git a/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt b/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt index 1e4517fb..8ed5e51b 100644 --- a/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt +++ b/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt @@ -2,7 +2,6 @@ package com.gitlab.sszuev.flashcards.dbcommon.mocks import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.CardEntity -import com.gitlab.sszuev.flashcards.model.domain.CardFilter import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.repositories.CardDbResponse @@ -16,8 +15,8 @@ import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse */ class MockDbCardRepository( private val invokeFindCard: (CardId) -> CardEntity? = { null }, - private val invokeFindCards: (DictionaryId) -> Sequence = { emptySequence() }, - private val invokeSearchCards: (AppUserId, CardFilter) -> CardsDbResponse = { _, _ -> CardsDbResponse.EMPTY }, + private val invokeFindCardsByDictionaryId: (DictionaryId) -> Sequence = { emptySequence() }, + private val invokeFindCardsByDictionaryIds: (Iterable) -> Sequence = { emptySequence() }, private val invokeCreateCard: (AppUserId, CardEntity) -> CardDbResponse = { _, _ -> CardDbResponse.EMPTY }, private val invokeUpdateCard: (AppUserId, CardEntity) -> CardDbResponse = { _, _ -> CardDbResponse.EMPTY }, private val invokeUpdateCards: (AppUserId, Iterable, (CardEntity) -> CardEntity) -> CardsDbResponse = { _, _, _ -> CardsDbResponse.EMPTY }, @@ -27,9 +26,11 @@ class MockDbCardRepository( override fun findCard(cardId: CardId): CardEntity? = invokeFindCard(cardId) - override fun findCards(dictionaryId: DictionaryId): Sequence = invokeFindCards(dictionaryId) + override fun findCards(dictionaryId: DictionaryId): Sequence = + invokeFindCardsByDictionaryId(dictionaryId) - override fun searchCard(userId: AppUserId, filter: CardFilter): CardsDbResponse = invokeSearchCards(userId, filter) + override fun findCards(dictionaryIds: Iterable): Sequence = + invokeFindCardsByDictionaryIds(dictionaryIds) override fun createCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse = invokeCreateCard(userId, cardEntity) diff --git a/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt b/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt index 8773d11e..aeeed69c 100644 --- a/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt +++ b/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt @@ -12,6 +12,7 @@ import com.gitlab.sszuev.flashcards.repositories.RemoveDictionaryDbResponse class MockDbDictionaryRepository( private val invokeFindDictionary: (DictionaryId) -> DictionaryEntity? = { null }, + private val invokeFindDictionaries: (Iterable) -> Sequence = { emptySequence() }, private val invokeGetAllDictionaries: (AppUserId) -> DictionariesDbResponse = { DictionariesDbResponse.EMPTY }, private val invokeCreateDictionary: (AppUserId, DictionaryEntity) -> DictionaryDbResponse = { _, _ -> DictionaryDbResponse.EMPTY }, private val invokeDeleteDictionary: (AppUserId, DictionaryId) -> RemoveDictionaryDbResponse = { _, _ -> RemoveDictionaryDbResponse.EMPTY }, @@ -34,4 +35,7 @@ class MockDbDictionaryRepository( override fun exportDictionary(userId: AppUserId, resource: ResourceEntity): DictionaryDbResponse = invokeUploadDictionary(userId, resource) + + override fun findDictionaries(dictionaryIds: Iterable): Sequence = + invokeFindDictionaries(dictionaryIds) } \ No newline at end of file diff --git a/db-mem/src/main/kotlin/MemDbCardRepository.kt b/db-mem/src/main/kotlin/MemDbCardRepository.kt index a08469fa..af9ff626 100644 --- a/db-mem/src/main/kotlin/MemDbCardRepository.kt +++ b/db-mem/src/main/kotlin/MemDbCardRepository.kt @@ -1,13 +1,10 @@ package com.gitlab.sszuev.flashcards.dbmem -import com.gitlab.sszuev.flashcards.common.SysConfig import com.gitlab.sszuev.flashcards.common.asLong import com.gitlab.sszuev.flashcards.common.dbError -import com.gitlab.sszuev.flashcards.common.documents.DocumentCardStatus import com.gitlab.sszuev.flashcards.common.forbiddenEntityDbError import com.gitlab.sszuev.flashcards.common.noCardFoundDbError import com.gitlab.sszuev.flashcards.common.noDictionaryFoundDbError -import com.gitlab.sszuev.flashcards.common.status import com.gitlab.sszuev.flashcards.common.systemNow import com.gitlab.sszuev.flashcards.common.validateCardEntityForCreate import com.gitlab.sszuev.flashcards.common.validateCardEntityForUpdate @@ -16,18 +13,15 @@ import com.gitlab.sszuev.flashcards.model.Id import com.gitlab.sszuev.flashcards.model.common.AppError import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.CardEntity -import com.gitlab.sszuev.flashcards.model.domain.CardFilter import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.repositories.CardDbResponse import com.gitlab.sszuev.flashcards.repositories.CardsDbResponse import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse -import kotlin.random.Random class MemDbCardRepository( dbConfig: MemDbConfig = MemDbConfig(), - private val sysConfig: SysConfig = SysConfig(), ) : DbCardRepository { private val database = MemDatabase.get(dbConfig.dataLocation) @@ -36,32 +30,6 @@ class MemDbCardRepository( override fun findCards(dictionaryId: DictionaryId): Sequence = database.findCardsByDictionaryId(dictionaryId.asLong()).map { it.toCardEntity() } - override fun searchCard(userId: AppUserId, filter: CardFilter): CardsDbResponse { - require(filter.length != 0) { "zero length is specified" } - val ids = filter.dictionaryIds.map { it.asLong() } - val dictionariesFromDb = database.findDictionariesByIds(ids).sortedBy { it.id }.toSet() - if (dictionariesFromDb.isEmpty()) { - return CardsDbResponse() - } - val forbiddenIds = - dictionariesFromDb.filter { it.userId != userId.asLong() }.map { checkNotNull(it.id) }.toSet() - val errors = forbiddenIds.map { forbiddenEntityDbError("searchCards", it.asDictionaryId(), userId) } - if (errors.isNotEmpty()) { - return CardsDbResponse(cards = emptyList(), dictionaries = emptyList(), errors = errors) - } - val dictionaries = dictionariesFromDb.filterNot { it.id in forbiddenIds }.map { it.toDictionaryEntity() } - var cardsFromDb = database.findCardsByDictionaryIds(ids) - if (!filter.withUnknown) { - cardsFromDb = cardsFromDb.filter { sysConfig.status(it.answered) != DocumentCardStatus.LEARNED } - } - if (filter.random) { - cardsFromDb = cardsFromDb.shuffled(Random.Default) - } - val cards = - (if (filter.length < 0) cardsFromDb else cardsFromDb.take(filter.length)).map { it.toCardEntity() }.toList() - return CardsDbResponse(cards = cards, dictionaries = dictionaries) - } - override fun createCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse { validateCardEntityForCreate(cardEntity) val errors = mutableListOf() diff --git a/db-pg/src/main/kotlin/PgDbCardRepository.kt b/db-pg/src/main/kotlin/PgDbCardRepository.kt index 6053ef2a..0d3434b8 100644 --- a/db-pg/src/main/kotlin/PgDbCardRepository.kt +++ b/db-pg/src/main/kotlin/PgDbCardRepository.kt @@ -1,6 +1,5 @@ package com.gitlab.sszuev.flashcards.dbpg -import com.gitlab.sszuev.flashcards.common.SysConfig import com.gitlab.sszuev.flashcards.common.asLong import com.gitlab.sszuev.flashcards.common.dbError import com.gitlab.sszuev.flashcards.common.forbiddenEntityDbError @@ -10,33 +9,23 @@ import com.gitlab.sszuev.flashcards.common.systemNow import com.gitlab.sszuev.flashcards.common.validateCardEntityForCreate import com.gitlab.sszuev.flashcards.common.validateCardEntityForUpdate import com.gitlab.sszuev.flashcards.dbpg.dao.Cards -import com.gitlab.sszuev.flashcards.dbpg.dao.Dictionaries import com.gitlab.sszuev.flashcards.dbpg.dao.PgDbCard import com.gitlab.sszuev.flashcards.dbpg.dao.PgDbDictionary import com.gitlab.sszuev.flashcards.model.Id import com.gitlab.sszuev.flashcards.model.common.AppError import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.CardEntity -import com.gitlab.sszuev.flashcards.model.domain.CardFilter import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.repositories.CardDbResponse import com.gitlab.sszuev.flashcards.repositories.CardsDbResponse import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse -import org.jetbrains.exposed.sql.CustomFunction -import org.jetbrains.exposed.sql.DoubleColumnType -import org.jetbrains.exposed.sql.Op -import org.jetbrains.exposed.sql.SortOrder import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList -import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.deleteWhere -import org.jetbrains.exposed.sql.or class PgDbCardRepository( dbConfig: PgDbConfig = PgDbConfig(), - private val sysConfig: SysConfig = SysConfig(), ) : DbCardRepository { private val connection by lazy { // lazy, to avoid initialization error when there is no real pg-database @@ -52,37 +41,6 @@ class PgDbCardRepository( PgDbCard.find { Cards.dictionaryId eq dictionaryId.asLong() }.map { it.toCardEntity() }.asSequence() } - override fun searchCard(userId: AppUserId, filter: CardFilter): CardsDbResponse { - require(filter.length != 0) { "zero length is specified" } - val dictionaryIds = filter.dictionaryIds.map { it.asLong() } - val learned = sysConfig.numberOfRightAnswers - val random = CustomFunction("random", DoubleColumnType()) - return connection.execute { - val dictionariesFromDb = PgDbDictionary.find(Dictionaries.id inList dictionaryIds) - if (dictionariesFromDb.empty()) { - return@execute CardsDbResponse(cards = emptyList(), dictionaries = emptyList(), errors = emptyList()) - } - val forbiddenIds = - dictionariesFromDb.filter { it.userId.value != userId.asLong() }.map { it.id.value }.toSet() - val errors = forbiddenIds.map { forbiddenEntityDbError("searchCards", it.asDictionaryId(), userId) } - if (errors.isNotEmpty()) { - return@execute CardsDbResponse(cards = emptyList(), errors = errors) - } - val dictionaries = - dictionariesFromDb.filterNot { it.id.value in forbiddenIds }.map { it.toDictionaryEntity() } - var cardsIterable = PgDbCard.find { - Cards.dictionaryId inList dictionaryIds and - (if (filter.withUnknown) Op.TRUE else Cards.answered.isNull() or Cards.answered.lessEq(learned)) - }.orderBy(random to SortOrder.ASC) - .orderBy(Cards.dictionaryId to SortOrder.ASC) - if (filter.length > 0) { - cardsIterable = cardsIterable.limit(filter.length) - } - val cards = cardsIterable.map { it.toCardEntity() } - CardsDbResponse(cards = cards, dictionaries = dictionaries) - } - } - override fun createCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse { return connection.execute { validateCardEntityForCreate(cardEntity) diff --git a/logs-mappers/src/main/kotlin/com/gitlab/sszuev/flashcards/logmappers/FromContext.kt b/logs-mappers/src/main/kotlin/com/gitlab/sszuev/flashcards/logmappers/FromContext.kt index 3d11d7ba..6ceda6b1 100644 --- a/logs-mappers/src/main/kotlin/com/gitlab/sszuev/flashcards/logmappers/FromContext.kt +++ b/logs-mappers/src/main/kotlin/com/gitlab/sszuev/flashcards/logmappers/FromContext.kt @@ -94,7 +94,7 @@ private fun CardWordExampleEntity.toLog() = CardWordExampleLogResource( private fun CardFilter.toLog() = CardFilterLogResource( dictionaryIds = this.dictionaryIds.map { it.asString() }, random = this.random, - unknown = this.withUnknown, + unknown = this.onlyUnknown, length = this.length, ) diff --git a/logs-mappers/src/test/kotlin/FromContextTest.kt b/logs-mappers/src/test/kotlin/FromContextTest.kt index c496ed1b..ecfd8a3f 100644 --- a/logs-mappers/src/test/kotlin/FromContextTest.kt +++ b/logs-mappers/src/test/kotlin/FromContextTest.kt @@ -61,7 +61,7 @@ internal class FromContextTest { Assertions.assertEquals(expected.dictionaryIds.map { it.asString() }, actual.dictionaryIds) Assertions.assertEquals(expected.length, actual.length) Assertions.assertEquals(expected.random, actual.random) - Assertions.assertEquals(expected.withUnknown, actual.unknown) + Assertions.assertEquals(expected.onlyUnknown, actual.unknown) } fun assertCardLearn(expected: CardLearn, actual: CardLearnLogResource) { @@ -114,7 +114,7 @@ internal class FromContextTest { dictionaryIds = listOf(DictionaryId("A"), DictionaryId("B")), length = 42, random = true, - withUnknown = false, + onlyUnknown = false, ) val actual = context.toLogResource("test-log") diff --git a/mappers/src/main/kotlin/FromCardTransport.kt b/mappers/src/main/kotlin/FromCardTransport.kt index d2be3f36..097d1ab7 100644 --- a/mappers/src/main/kotlin/FromCardTransport.kt +++ b/mappers/src/main/kotlin/FromCardTransport.kt @@ -70,7 +70,7 @@ fun CardContext.fromSearchCardsRequest(request: SearchCardsRequest) { dictionaryIds = request.dictionaryIds?.map { toDictionaryId(it) } ?: listOf(), length = request.length ?: 0, random = request.random ?: false, - withUnknown = request.unknown ?: false, + onlyUnknown = request.unknown ?: false, ) } diff --git a/mappers/src/test/kotlin/FromCardTransportTest.kt b/mappers/src/test/kotlin/FromCardTransportTest.kt index f21e9706..df8917b4 100644 --- a/mappers/src/test/kotlin/FromCardTransportTest.kt +++ b/mappers/src/test/kotlin/FromCardTransportTest.kt @@ -107,7 +107,7 @@ internal class FromCardTransportTest { ) Assertions.assertEquals(42, context.requestCardFilter.length) Assertions.assertEquals(true, context.requestCardFilter.random) - Assertions.assertEquals(true, context.requestCardFilter.withUnknown) + Assertions.assertEquals(true, context.requestCardFilter.onlyUnknown) Assertions.assertEquals( listOf("a", "b", "c").map { DictionaryId(it) }, context.requestCardFilter.dictionaryIds From d4cb3025bda6359e9d90fa78e8995236a505d435 Mon Sep 17 00:00:00 2001 From: sszuev Date: Fri, 22 Mar 2024 21:45:22 +0300 Subject: [PATCH 05/18] common & core & db: [#28] new DbCardRepository#createCard method --- .../kotlin/repositories/DbCardRepository.kt | 7 ++- .../kotlin/repositories/DbDataException.kt | 3 + .../repositories/NoOpDbCardRepository.kt | 2 +- .../kotlin/processes/CardProcessWorkers.kt | 13 +++- .../kotlin/CardCorProcessorRunCardsTest.kt | 63 ++++++++++++++----- .../kotlin/DbCardRepositoryTest.kt | 21 +++---- .../kotlin/mocks/MockDbCardRepository.kt | 5 +- db-mem/src/main/kotlin/MemDbCardRepository.kt | 27 ++++---- db-pg/src/main/kotlin/PgDbCardRepository.kt | 33 +++++----- 9 files changed, 112 insertions(+), 62 deletions(-) create mode 100644 common/src/commonMain/kotlin/repositories/DbDataException.kt diff --git a/common/src/commonMain/kotlin/repositories/DbCardRepository.kt b/common/src/commonMain/kotlin/repositories/DbCardRepository.kt index c07bfe40..8e3c6b6d 100644 --- a/common/src/commonMain/kotlin/repositories/DbCardRepository.kt +++ b/common/src/commonMain/kotlin/repositories/DbCardRepository.kt @@ -23,9 +23,12 @@ interface DbCardRepository { fun findCards(dictionaryId: DictionaryId): Sequence /** - * Creates card. + * Creates a new card returning the corresponding new card record from the db. + * @throws IllegalArgumentException if the specified card has ids + * @throws DbDataException in case card cannot be created for some reason, + * i.e., if the corresponding dictionary does not exist */ - fun createCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse + fun createCard(cardEntity: CardEntity): CardEntity /** * Updates. diff --git a/common/src/commonMain/kotlin/repositories/DbDataException.kt b/common/src/commonMain/kotlin/repositories/DbDataException.kt new file mode 100644 index 00000000..f1ee895a --- /dev/null +++ b/common/src/commonMain/kotlin/repositories/DbDataException.kt @@ -0,0 +1,3 @@ +package com.gitlab.sszuev.flashcards.repositories + +class DbDataException(override val message: String, override val cause: Throwable? = null) : Exception(message, cause) \ No newline at end of file diff --git a/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt b/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt index 9b92b391..7f5f78e3 100644 --- a/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt +++ b/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt @@ -11,7 +11,7 @@ object NoOpDbCardRepository : DbCardRepository { override fun findCards(dictionaryId: DictionaryId): Sequence = noOp() - override fun createCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse = noOp() + override fun createCard(cardEntity: CardEntity): CardEntity = noOp() override fun updateCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse = noOp() diff --git a/core/src/main/kotlin/processes/CardProcessWorkers.kt b/core/src/main/kotlin/processes/CardProcessWorkers.kt index e4524dff..f9ca8a9b 100644 --- a/core/src/main/kotlin/processes/CardProcessWorkers.kt +++ b/core/src/main/kotlin/processes/CardProcessWorkers.kt @@ -119,8 +119,17 @@ fun ChainDSL.processCreateCard() = worker { } process { val userId = this.contextUserEntity.id - val res = this.repositories.cardRepository(this.workMode).createCard(userId, this.normalizedRequestCardEntity) - this.postProcess(res) + val dictionaryId = this.normalizedRequestCardEntity.dictionaryId + val dictionary = this.repositories.dictionaryRepository(this.workMode).findDictionary(dictionaryId) + if (dictionary == null) { + this.errors.add(noDictionaryFoundDataError("createCard", dictionaryId)) + } else if (dictionary.userId != userId) { + this.errors.add(forbiddenEntityDataError("createCard", dictionaryId, userId)) + } else { + val res = this.repositories.cardRepository(this.workMode).createCard(this.normalizedRequestCardEntity) + this.responseCardEntity = postProcess(res) { dictionary } + } + this.status = if (this.errors.isNotEmpty()) AppStatus.FAIL else AppStatus.RUN } onException { this.handleThrowable(CardOperation.CREATE_CARD, it) diff --git a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt index 7cf45f84..bdd28d33 100644 --- a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt +++ b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt @@ -20,6 +20,7 @@ import com.gitlab.sszuev.flashcards.model.domain.CardOperation import com.gitlab.sszuev.flashcards.model.domain.CardWordEntity import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.Stage +import com.gitlab.sszuev.flashcards.model.domain.TTSResourceId import com.gitlab.sszuev.flashcards.repositories.CardDbResponse import com.gitlab.sszuev.flashcards.repositories.CardsDbResponse import com.gitlab.sszuev.flashcards.repositories.DbCardRepository @@ -240,23 +241,40 @@ internal class CardCorProcessorRunCardsTest { @Test fun `test create-card success`() = runTest { - val testResponseEntity = stubCard.copy(words = listOf(CardWordEntity("HHH"))) - val testRequestEntity = stubCard.copy(words = listOf(CardWordEntity(word = "XXX")), cardId = CardId.NONE) + val testDictionary = stubDictionary + val testResponseEntity = stubCard.copy(words = listOf(CardWordEntity("HHH")), sound = TTSResourceId.NONE) + val testRequestEntity = stubCard.copy( + words = listOf(CardWordEntity(word = "XXX")), + cardId = CardId.NONE, + dictionaryId = DictionaryId("4200") + ) - var wasCalled = false - val repository = MockDbCardRepository( - invokeCreateCard = { _, it -> - wasCalled = true - CardDbResponse(if (it.words == testRequestEntity.words) testResponseEntity else testRequestEntity) + var isCreateCardCalled = false + var isFindDictionaryCalled = false + val cardRepository = MockDbCardRepository( + invokeCreateCard = { + isCreateCardCalled = true + if (it.words == testRequestEntity.words) testResponseEntity else testRequestEntity + } + ) + val dictionaryRepository = MockDbDictionaryRepository( + invokeFindDictionary = { id -> + isFindDictionaryCalled = true + if (id == testRequestEntity.dictionaryId) testDictionary else null } ) - val context = testContext(CardOperation.CREATE_CARD, repository) + val context = testContext( + op = CardOperation.CREATE_CARD, + cardRepository = cardRepository, + dictionaryRepository = dictionaryRepository, + ) context.requestCardEntity = testRequestEntity CardCorProcessor().execute(context) - Assertions.assertTrue(wasCalled) + Assertions.assertTrue(isCreateCardCalled) + Assertions.assertTrue(isFindDictionaryCalled) Assertions.assertEquals(requestId(CardOperation.CREATE_CARD), context.requestId) Assertions.assertEquals(AppStatus.OK, context.status) { "Errors: ${context.errors}" } Assertions.assertTrue(context.errors.isEmpty()) @@ -266,22 +284,35 @@ internal class CardCorProcessorRunCardsTest { @Test fun `test create-card unexpected fail`() = runTest { + val testDictionary = stubDictionary val testRequestEntity = stubCard.copy(words = listOf(CardWordEntity(word = "XXX")), cardId = CardId.NONE) - var wasCalled = false - val repository = MockDbCardRepository( - invokeCreateCard = { _, it -> - wasCalled = true - CardDbResponse(if (it.words == testRequestEntity.words) throw TestException() else testRequestEntity) + var isCreateCardCalled = false + var isFindDictionaryCalled = false + val cardRepository = MockDbCardRepository( + invokeCreateCard = { + isCreateCardCalled = true + if (it.words == testRequestEntity.words) throw TestException() else testRequestEntity + } + ) + val dictionaryRepository = MockDbDictionaryRepository( + invokeFindDictionary = { id -> + isFindDictionaryCalled = true + if (id == testRequestEntity.dictionaryId) testDictionary else null } ) - val context = testContext(CardOperation.CREATE_CARD, repository) + val context = testContext( + op = CardOperation.CREATE_CARD, + cardRepository = cardRepository, + dictionaryRepository = dictionaryRepository, + ) context.requestCardEntity = testRequestEntity CardCorProcessor().execute(context) - Assertions.assertTrue(wasCalled) + Assertions.assertTrue(isCreateCardCalled) + Assertions.assertTrue(isFindDictionaryCalled) Assertions.assertEquals(requestId(CardOperation.CREATE_CARD), context.requestId) Assertions.assertEquals(CardEntity.EMPTY, context.responseCardEntity) assertUnknownError(context, CardOperation.CREATE_CARD) diff --git a/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt b/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt index bf94d079..aa1595ba 100644 --- a/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt +++ b/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt @@ -12,9 +12,11 @@ import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.Stage import com.gitlab.sszuev.flashcards.repositories.CardDbResponse import com.gitlab.sszuev.flashcards.repositories.DbCardRepository +import com.gitlab.sszuev.flashcards.repositories.DbDataException import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse import kotlinx.datetime.Clock import kotlinx.datetime.Instant +import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotEquals import org.junit.jupiter.api.Assertions.assertNotNull @@ -170,6 +172,7 @@ abstract class DbCardRepositoryTest { assertEquals(expected, a) } + @Suppress("SameParameterValue") private fun assertSingleError(res: CardDbResponse, field: String, op: String): AppError { assertEquals(1, res.errors.size) { "Errors: ${res.errors}" } val error = res.errors[0] @@ -233,14 +236,9 @@ abstract class DbCardRepositoryTest { ), answered = 42, ) - val res = repository.createCard(userId, request) - assertEquals(CardEntity.EMPTY, res.card) - val error = assertSingleError(res, dictionaryId, "createCard") - assertEquals( - """Error while createCard: dictionary with id="$dictionaryId" not found""", - error.message - ) - assertNull(error.exception) + Assertions.assertThrows(DbDataException::class.java) { + repository.createCard(request) + } } @Order(6) @@ -348,10 +346,9 @@ abstract class DbCardRepositoryTest { @Test fun `test create card success`() { val request = newMurkyCardEntity - val res = repository.createCard(userId, request) - assertNoErrors(res) - assertCard(expected = request, actual = res.card, ignoreChangeAt = true, ignoreId = true) - assertTrue(res.card.cardId.asString().matches("\\d+".toRegex())) + val res = repository.createCard(request) + assertCard(expected = request, actual = res, ignoreChangeAt = true, ignoreId = true) + assertTrue(res.cardId.asString().matches("\\d+".toRegex())) } @Order(42) diff --git a/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt b/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt index 8ed5e51b..cea14023 100644 --- a/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt +++ b/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt @@ -17,7 +17,7 @@ class MockDbCardRepository( private val invokeFindCard: (CardId) -> CardEntity? = { null }, private val invokeFindCardsByDictionaryId: (DictionaryId) -> Sequence = { emptySequence() }, private val invokeFindCardsByDictionaryIds: (Iterable) -> Sequence = { emptySequence() }, - private val invokeCreateCard: (AppUserId, CardEntity) -> CardDbResponse = { _, _ -> CardDbResponse.EMPTY }, + private val invokeCreateCard: (CardEntity) -> CardEntity = { CardEntity.EMPTY }, private val invokeUpdateCard: (AppUserId, CardEntity) -> CardDbResponse = { _, _ -> CardDbResponse.EMPTY }, private val invokeUpdateCards: (AppUserId, Iterable, (CardEntity) -> CardEntity) -> CardsDbResponse = { _, _, _ -> CardsDbResponse.EMPTY }, private val invokeResetCard: (AppUserId, CardId) -> CardDbResponse = { _, _ -> CardDbResponse.EMPTY }, @@ -32,8 +32,7 @@ class MockDbCardRepository( override fun findCards(dictionaryIds: Iterable): Sequence = invokeFindCardsByDictionaryIds(dictionaryIds) - override fun createCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse = - invokeCreateCard(userId, cardEntity) + override fun createCard(cardEntity: CardEntity): CardEntity = invokeCreateCard(cardEntity) override fun updateCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse = invokeUpdateCard(userId, cardEntity) diff --git a/db-mem/src/main/kotlin/MemDbCardRepository.kt b/db-mem/src/main/kotlin/MemDbCardRepository.kt index af9ff626..098a9036 100644 --- a/db-mem/src/main/kotlin/MemDbCardRepository.kt +++ b/db-mem/src/main/kotlin/MemDbCardRepository.kt @@ -18,6 +18,7 @@ import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.repositories.CardDbResponse import com.gitlab.sszuev.flashcards.repositories.CardsDbResponse import com.gitlab.sszuev.flashcards.repositories.DbCardRepository +import com.gitlab.sszuev.flashcards.repositories.DbDataException import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse class MemDbCardRepository( @@ -25,22 +26,24 @@ class MemDbCardRepository( ) : DbCardRepository { private val database = MemDatabase.get(dbConfig.dataLocation) - override fun findCard(cardId: CardId): CardEntity? = database.findCardById(cardId.asLong())?.toCardEntity() + override fun findCard(cardId: CardId): CardEntity? { + require(cardId != CardId.NONE) + return database.findCardById(cardId.asLong())?.toCardEntity() + } - override fun findCards(dictionaryId: DictionaryId): Sequence = - database.findCardsByDictionaryId(dictionaryId.asLong()).map { it.toCardEntity() } + override fun findCards(dictionaryId: DictionaryId): Sequence { + require(dictionaryId != DictionaryId.NONE) + return database.findCardsByDictionaryId(dictionaryId.asLong()).map { it.toCardEntity() } + } - override fun createCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse { + override fun createCard(cardEntity: CardEntity): CardEntity { validateCardEntityForCreate(cardEntity) - val errors = mutableListOf() - checkDictionaryUser("createCard", userId, cardEntity.dictionaryId, cardEntity.dictionaryId, errors) - if (errors.isNotEmpty()) { - return CardDbResponse(errors = errors) - } val timestamp = systemNow() - return CardDbResponse( - card = database.saveCard(cardEntity.toMemDbCard().copy(changedAt = timestamp)).toCardEntity() - ) + return try { + database.saveCard(cardEntity.toMemDbCard().copy(changedAt = timestamp)).toCardEntity() + } catch (ex: Exception) { + throw DbDataException("Can't create card $cardEntity", ex) + } } override fun updateCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse { diff --git a/db-pg/src/main/kotlin/PgDbCardRepository.kt b/db-pg/src/main/kotlin/PgDbCardRepository.kt index 0d3434b8..740a576a 100644 --- a/db-pg/src/main/kotlin/PgDbCardRepository.kt +++ b/db-pg/src/main/kotlin/PgDbCardRepository.kt @@ -20,6 +20,7 @@ import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.repositories.CardDbResponse import com.gitlab.sszuev.flashcards.repositories.CardsDbResponse import com.gitlab.sszuev.flashcards.repositories.DbCardRepository +import com.gitlab.sszuev.flashcards.repositories.DbDataException import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.deleteWhere @@ -33,27 +34,31 @@ class PgDbCardRepository( PgDbConnector.connection(dbConfig) } - override fun findCard(cardId: CardId): CardEntity? = connection.execute { - PgDbCard.findById(cardId.asLong())?.toCardEntity() + override fun findCard(cardId: CardId): CardEntity? { + require(cardId != CardId.NONE) + return connection.execute { + PgDbCard.findById(cardId.asLong())?.toCardEntity() + } } - override fun findCards(dictionaryId: DictionaryId): Sequence = connection.execute { - PgDbCard.find { Cards.dictionaryId eq dictionaryId.asLong() }.map { it.toCardEntity() }.asSequence() + override fun findCards(dictionaryId: DictionaryId): Sequence { + require(dictionaryId != DictionaryId.NONE) + return connection.execute { + PgDbCard.find { Cards.dictionaryId eq dictionaryId.asLong() }.map { it.toCardEntity() }.asSequence() + } } - override fun createCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse { + override fun createCard(cardEntity: CardEntity): CardEntity { + validateCardEntityForCreate(cardEntity) return connection.execute { - validateCardEntityForCreate(cardEntity) - val errors = mutableListOf() - checkDictionaryUser("createCard", userId, cardEntity.dictionaryId, cardEntity.dictionaryId, errors) - if (errors.isNotEmpty()) { - return@execute CardDbResponse(errors = errors) - } val timestamp = systemNow() - val res = PgDbCard.new { - writeCardEntityToPgDbCard(from = cardEntity, to = this, timestamp = timestamp) + try { + PgDbCard.new { + writeCardEntityToPgDbCard(from = cardEntity, to = this, timestamp = timestamp) + }.toCardEntity() + } catch (ex: Exception) { + throw DbDataException("Can't create card $cardEntity", ex) } - CardDbResponse(card = res.toCardEntity()) } } From 8628ad9ba408a3d74f5c59d72315c29e9e1a2dda Mon Sep 17 00:00:00 2001 From: sszuev Date: Sat, 23 Mar 2024 16:55:20 +0300 Subject: [PATCH 06/18] common & core & db: [#28] new DbCardRepository#updateCard & find* methods --- .../kotlin/repositories/DbCardRepository.kt | 44 +++-- .../repositories/DbDictionaryRepository.kt | 6 +- .../repositories/NoOpDbCardRepository.kt | 12 +- .../NoOpDbDictionaryRepository.kt | 2 +- .../kotlin/processes/CardProcessWorkers.kt | 78 ++++---- .../processes/DictionaryProcessWokers.kt | 4 +- .../kotlin/processes/SearchCardsHelper.kt | 4 +- .../kotlin/processes/UpdateCardsHelper.kt | 15 +- .../kotlin/CardCorProcessorRunCardsTest.kt | 177 ++++++++++++------ db-common/src/main/kotlin/CommonMappers.kt | 8 +- .../kotlin/DbCardRepositoryTest.kt | 84 ++++----- .../kotlin/DbDictionaryRepositoryTest.kt | 4 +- .../kotlin/mocks/MockDbCardRepository.kt | 29 ++- .../mocks/MockDbDictionaryRepository.kt | 11 +- db-mem/src/main/kotlin/MemDbCardRepository.kt | 66 +------ .../main/kotlin/MemDbDictionaryRepository.kt | 2 +- db-pg/src/main/kotlin/PgDbCardRepository.kt | 109 +++++------ .../main/kotlin/PgDbDictionaryRepository.kt | 2 +- 18 files changed, 320 insertions(+), 337 deletions(-) diff --git a/common/src/commonMain/kotlin/repositories/DbCardRepository.kt b/common/src/commonMain/kotlin/repositories/DbCardRepository.kt index 8e3c6b6d..209e59a4 100644 --- a/common/src/commonMain/kotlin/repositories/DbCardRepository.kt +++ b/common/src/commonMain/kotlin/repositories/DbCardRepository.kt @@ -4,7 +4,6 @@ import com.gitlab.sszuev.flashcards.model.common.AppError import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardId -import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity import com.gitlab.sszuev.flashcards.model.domain.DictionaryId /** @@ -15,30 +14,45 @@ interface DbCardRepository { /** * Finds card by id returning `null` if nothing found. */ - fun findCard(cardId: CardId): CardEntity? + fun findCardById(cardId: CardId): CardEntity? /** * Finds cards by dictionary id. */ - fun findCards(dictionaryId: DictionaryId): Sequence + fun findCardsByDictionaryId(dictionaryId: DictionaryId): Sequence + + /** + * Finds cards by dictionary ids. + */ + fun findCardsByDictionaryIdIn(dictionaryIds: Iterable): Sequence = + dictionaryIds.asSequence().flatMap { findCardsByDictionaryId(it) } + + /** + * Finds cards by card ids. + */ + fun findCardsByIdIn(cardIds: Iterable): Sequence = + cardIds.asSequence().mapNotNull { findCardById(it) } /** * Creates a new card returning the corresponding new card record from the db. - * @throws IllegalArgumentException if the specified card has ids + * @throws IllegalArgumentException if the specified card has card id or illegal structure * @throws DbDataException in case card cannot be created for some reason, * i.e., if the corresponding dictionary does not exist */ fun createCard(cardEntity: CardEntity): CardEntity /** - * Updates. + * Updates the card entity. + * @throws IllegalArgumentException if the specified card has no card id or has illegal structure + * @throws DbDataException in case card cannot be created for some reason, + * i.e., if the corresponding dictionary does not exist */ - fun updateCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse + fun updateCard(cardEntity: CardEntity): CardEntity /** * Performs bulk update. */ - fun updateCards(userId: AppUserId, cardIds: Iterable, update: (CardEntity) -> CardEntity): CardsDbResponse + fun updateCards(cardEntities: Iterable): List = cardEntities.map { updateCard(it) } /** * Resets status. @@ -50,22 +64,6 @@ interface DbCardRepository { */ fun removeCard(userId: AppUserId, cardId: CardId): RemoveCardDbResponse - /** - * Finds cards by dictionary ids. - */ - fun findCards(dictionaryIds: Iterable): Sequence = - dictionaryIds.asSequence().flatMap { findCards(it) } -} - -data class CardsDbResponse( - val cards: List = emptyList(), - val dictionaries: List = emptyList(), - val errors: List = emptyList(), -) { - - companion object { - val EMPTY = CardsDbResponse(cards = emptyList(), dictionaries = emptyList(), errors = emptyList()) - } } data class CardDbResponse( diff --git a/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt b/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt index f039bc5b..7dc31434 100644 --- a/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt +++ b/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt @@ -7,7 +7,7 @@ import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity interface DbDictionaryRepository { - fun findDictionary(dictionaryId: DictionaryId): DictionaryEntity? + fun findDictionaryById(dictionaryId: DictionaryId): DictionaryEntity? fun getAllDictionaries(userId: AppUserId): DictionariesDbResponse @@ -19,8 +19,8 @@ interface DbDictionaryRepository { fun exportDictionary(userId: AppUserId, resource: ResourceEntity): DictionaryDbResponse - fun findDictionaries(dictionaryIds: Iterable): Sequence = - dictionaryIds.asSequence().mapNotNull { findDictionary(it) } + fun findDictionariesByIdIn(dictionaryIds: Iterable): Sequence = + dictionaryIds.asSequence().mapNotNull { findDictionaryById(it) } } data class DictionariesDbResponse( diff --git a/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt b/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt index 7f5f78e3..7e639e3c 100644 --- a/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt +++ b/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt @@ -7,19 +7,13 @@ import com.gitlab.sszuev.flashcards.model.domain.DictionaryId object NoOpDbCardRepository : DbCardRepository { - override fun findCard(cardId: CardId): CardEntity = noOp() + override fun findCardById(cardId: CardId): CardEntity = noOp() - override fun findCards(dictionaryId: DictionaryId): Sequence = noOp() + override fun findCardsByDictionaryId(dictionaryId: DictionaryId): Sequence = noOp() override fun createCard(cardEntity: CardEntity): CardEntity = noOp() - override fun updateCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse = noOp() - - override fun updateCards( - userId: AppUserId, - cardIds: Iterable, - update: (CardEntity) -> CardEntity - ): CardsDbResponse = noOp() + override fun updateCard(cardEntity: CardEntity): CardEntity = noOp() override fun resetCard(userId: AppUserId, cardId: CardId): CardDbResponse = noOp() diff --git a/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt b/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt index a34a76b4..91d12d8b 100644 --- a/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt +++ b/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt @@ -7,7 +7,7 @@ import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity object NoOpDbDictionaryRepository : DbDictionaryRepository { - override fun findDictionary(dictionaryId: DictionaryId): DictionaryEntity = noOp() + override fun findDictionaryById(dictionaryId: DictionaryId): DictionaryEntity = noOp() override fun getAllDictionaries(userId: AppUserId): DictionariesDbResponse = noOp() diff --git a/core/src/main/kotlin/processes/CardProcessWorkers.kt b/core/src/main/kotlin/processes/CardProcessWorkers.kt index f9ca8a9b..55321fe2 100644 --- a/core/src/main/kotlin/processes/CardProcessWorkers.kt +++ b/core/src/main/kotlin/processes/CardProcessWorkers.kt @@ -12,7 +12,6 @@ import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.TTSResourceGet import com.gitlab.sszuev.flashcards.model.domain.TTSResourceId import com.gitlab.sszuev.flashcards.repositories.CardDbResponse -import com.gitlab.sszuev.flashcards.repositories.CardsDbResponse import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse fun ChainDSL.processGetCard() = worker { @@ -23,11 +22,11 @@ fun ChainDSL.processGetCard() = worker { process { val userId = this.contextUserEntity.id val cardId = this.normalizedRequestCardEntityId - val card = this.repositories.cardRepository(this.workMode).findCard(cardId) + val card = this.repositories.cardRepository(this.workMode).findCardById(cardId) if (card == null) { this.errors.add(noCardFoundDataError("getCard", cardId)) } else { - val dictionary = this.repositories.dictionaryRepository(this.workMode).findDictionary(card.dictionaryId) + val dictionary = this.repositories.dictionaryRepository(this.workMode).findDictionaryById(card.dictionaryId) if (dictionary == null) { this.errors.add(noDictionaryFoundDataError("getCard", card.dictionaryId)) } else if (dictionary.userId != userId) { @@ -58,14 +57,14 @@ fun ChainDSL.processGetAllCards() = worker { process { val userId = this.contextUserEntity.id val dictionaryId = this.normalizedRequestDictionaryId - val dictionary = this.repositories.dictionaryRepository(this.workMode).findDictionary(dictionaryId) + val dictionary = this.repositories.dictionaryRepository(this.workMode).findDictionaryById(dictionaryId) if (dictionary == null) { this.errors.add(noDictionaryFoundDataError("getAllCards", dictionaryId)) } else if (dictionary.userId != userId) { this.errors.add(forbiddenEntityDataError("getAllCards", dictionaryId, userId)) } else { val cards = postProcess( - this.repositories.cardRepository(this.workMode).findCards(dictionaryId).iterator() + this.repositories.cardRepository(this.workMode).findCardsByDictionaryId(dictionaryId).iterator() ) { dictionary } this.responseCardEntityList = cards } @@ -91,7 +90,7 @@ fun ChainDSL.processCardSearch() = worker { process { val userId = this.contextUserEntity.id val found = this.repositories.dictionaryRepository(this.workMode) - .findDictionaries(this.normalizedRequestCardFilter.dictionaryIds) + .findDictionariesByIdIn(this.normalizedRequestCardFilter.dictionaryIds) .associateBy { it.dictionaryId } this.normalizedRequestCardFilter.dictionaryIds.filterNot { found.containsKey(it) }.forEach { this.errors.add(noDictionaryFoundDataError("searchCards", it)) @@ -120,7 +119,7 @@ fun ChainDSL.processCreateCard() = worker { process { val userId = this.contextUserEntity.id val dictionaryId = this.normalizedRequestCardEntity.dictionaryId - val dictionary = this.repositories.dictionaryRepository(this.workMode).findDictionary(dictionaryId) + val dictionary = this.repositories.dictionaryRepository(this.workMode).findDictionaryById(dictionaryId) if (dictionary == null) { this.errors.add(noDictionaryFoundDataError("createCard", dictionaryId)) } else if (dictionary.userId != userId) { @@ -143,8 +142,17 @@ fun ChainDSL.processUpdateCard() = worker { } process { val userId = this.contextUserEntity.id - val res = this.repositories.cardRepository(this.workMode).updateCard(userId, this.normalizedRequestCardEntity) - this.postProcess(res) + val dictionaryId = this.normalizedRequestCardEntity.dictionaryId + val dictionary = this.repositories.dictionaryRepository(this.workMode).findDictionaryById(dictionaryId) + if (dictionary == null) { + this.errors.add(noDictionaryFoundDataError("updateCard", dictionaryId)) + } else if (dictionary.userId != userId) { + this.errors.add(forbiddenEntityDataError("updateCard", dictionaryId, userId)) + } else { + val res = this.repositories.cardRepository(this.workMode).updateCard(this.normalizedRequestCardEntity) + this.responseCardEntity = postProcess(res) { dictionary } + } + this.status = if (this.errors.isNotEmpty()) AppStatus.FAIL else AppStatus.RUN } onException { this.handleThrowable(CardOperation.UPDATE_CARD, it) @@ -157,7 +165,28 @@ fun ChainDSL.processLearnCards() = worker { this.status == AppStatus.RUN } process { - this.postProcess(this.learnCards()) + val userId = this.contextUserEntity.id + val cardLearns = this.normalizedRequestCardLearnList.associateBy { it.cardId } + val foundCards = this.repositories.cardRepository(this.workMode).findCardsByIdIn(cardLearns.keys).toSet() + val foundCardIds = foundCards.map { it.cardId }.toSet() + val missedCardIds = cardLearns.keys - foundCardIds + missedCardIds.forEach { + errors.add(noCardFoundDataError("learnCards", it)) + } + val dictionaryIds = foundCards.map { it.dictionaryId }.toSet() + val foundDictionaries = + this.repositories.dictionaryRepository(this.workMode).findDictionariesByIdIn(dictionaryIds) + .associateBy { it.dictionaryId } + foundDictionaries.onEach { + if (it.value.userId != userId) { + errors.add(forbiddenEntityDataError("learnCards", it.key, userId)) + } + } + if (errors.isEmpty()) { + this.responseCardEntityList = + this.postProcess(learnCards(foundCards, cardLearns).iterator()) { checkNotNull(foundDictionaries[it]) } + } + this.status = if (this.errors.isNotEmpty()) AppStatus.FAIL else AppStatus.RUN } onException { this.handleThrowable(CardOperation.LEARN_CARDS, it) @@ -194,35 +223,6 @@ fun ChainDSL.processDeleteCard() = worker { } } -private suspend fun CardContext.postProcess(res: CardsDbResponse) { - check(res != CardsDbResponse.EMPTY) { "Null response" } - this.errors.addAll(res.errors) - val sourceLangByDictionary = res.dictionaries.associate { it.dictionaryId to it.sourceLang.langId } - val tts = this.repositories.ttsClientRepository(this.workMode) - this.responseCardEntityList = res.cards.map { card -> - val sourceLang = sourceLangByDictionary[card.dictionaryId] ?: return@map card - val words = card.words.map { word -> - val wordAudioId = tts.findResourceId(TTSResourceGet(word.word, sourceLang).normalize()) - this.errors.addAll(wordAudioId.errors) - if (wordAudioId.id != TTSResourceId.NONE) { - word.copy(sound = wordAudioId.id) - } else { - word - } - } - val cardAudioString = card.words.joinToString(",") { it.word } - val cardAudioId = tts.findResourceId(TTSResourceGet(cardAudioString, sourceLang).normalize()) - this.errors.addAll(cardAudioId.errors) - val cardSound = if (cardAudioId.id != TTSResourceId.NONE) { - cardAudioId.id - } else { - TTSResourceId.NONE - } - card.copy(words = words, sound = cardSound) - } - this.status = if (this.errors.isNotEmpty()) AppStatus.FAIL else AppStatus.RUN -} - private suspend fun CardContext.postProcess( cardsIterator: Iterator, dictionary: (DictionaryId) -> DictionaryEntity diff --git a/core/src/main/kotlin/processes/DictionaryProcessWokers.kt b/core/src/main/kotlin/processes/DictionaryProcessWokers.kt index 64a0afe3..fcf32155 100644 --- a/core/src/main/kotlin/processes/DictionaryProcessWokers.kt +++ b/core/src/main/kotlin/processes/DictionaryProcessWokers.kt @@ -22,7 +22,9 @@ fun ChainDSL.processGetAllDictionary() = worker { // TODO: temporary solution if (res.errors.isEmpty()) { this.responseDictionaryEntityList = res.dictionaries.map { dictionary -> - val cards = this.repositories.cardRepository(this.workMode).findCards(dictionary.dictionaryId).toList() + val cards = + this.repositories.cardRepository(this.workMode).findCardsByDictionaryId(dictionary.dictionaryId) + .toList() val total = cards.size val known = cards.mapNotNull { it.answered }.count { it >= config.numberOfRightAnswers } dictionary.copy(totalCardsCount = total, learnedCardsCount = known) diff --git a/core/src/main/kotlin/processes/SearchCardsHelper.kt b/core/src/main/kotlin/processes/SearchCardsHelper.kt index a8048912..35a69959 100644 --- a/core/src/main/kotlin/processes/SearchCardsHelper.kt +++ b/core/src/main/kotlin/processes/SearchCardsHelper.kt @@ -15,10 +15,10 @@ private val comparator: Comparator = Comparator { left, /** * Prepares a card deck for a tutor-session. */ -fun CardContext.findCardDeck(): List { +internal fun CardContext.findCardDeck(): List { val threshold = config.numberOfRightAnswers var cards = this.repositories.cardRepository(this.workMode) - .findCards(this.normalizedRequestCardFilter.dictionaryIds) + .findCardsByDictionaryIdIn(this.normalizedRequestCardFilter.dictionaryIds) .filter { !this.normalizedRequestCardFilter.onlyUnknown || (it.answered ?: -1) <= threshold } if (this.normalizedRequestCardFilter.random) { cards = cards.shuffled() diff --git a/core/src/main/kotlin/processes/UpdateCardsHelper.kt b/core/src/main/kotlin/processes/UpdateCardsHelper.kt index 0c721c0c..6830cebc 100644 --- a/core/src/main/kotlin/processes/UpdateCardsHelper.kt +++ b/core/src/main/kotlin/processes/UpdateCardsHelper.kt @@ -1,12 +1,16 @@ package com.gitlab.sszuev.flashcards.core.processes import com.gitlab.sszuev.flashcards.CardContext -import com.gitlab.sszuev.flashcards.repositories.CardsDbResponse +import com.gitlab.sszuev.flashcards.model.domain.CardEntity +import com.gitlab.sszuev.flashcards.model.domain.CardId +import com.gitlab.sszuev.flashcards.model.domain.CardLearn -fun CardContext.learnCards(): CardsDbResponse { - val cards = this.normalizedRequestCardLearnList.associateBy { it.cardId } - return this.repositories.cardRepository(this.workMode).updateCards(this.contextUserEntity.id, cards.keys) { card -> - val learn = checkNotNull(cards[card.cardId]) +internal fun CardContext.learnCards( + foundCards: Iterable, + cardLearns: Map +): List { + val cards = foundCards.map { card -> + val learn = checkNotNull(cardLearns[card.cardId]) var answered = card.answered?.toLong() ?: 0L val details = card.stats.toMutableMap() learn.details.forEach { @@ -19,4 +23,5 @@ fun CardContext.learnCards(): CardsDbResponse { } card.copy(stats = details, answered = answered.toInt()) } + return this.repositories.cardRepository(this.workMode).updateCards(cards) } \ No newline at end of file diff --git a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt index bdd28d33..178c1501 100644 --- a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt +++ b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt @@ -22,7 +22,6 @@ import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.Stage import com.gitlab.sszuev.flashcards.model.domain.TTSResourceId import com.gitlab.sszuev.flashcards.repositories.CardDbResponse -import com.gitlab.sszuev.flashcards.repositories.CardsDbResponse import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository import com.gitlab.sszuev.flashcards.repositories.DbUserRepository @@ -95,14 +94,14 @@ internal class CardCorProcessorRunCardsTest { var findCardIsCalled = false var findDictionaryIsCalled = false val cardRepository = MockDbCardRepository( - invokeFindCard = { cardId -> + invokeFindCardById = { cardId -> findCardIsCalled = true if (cardId == testId) testResponseCardEntity else null } ) val dictionaryRepository = MockDbDictionaryRepository( - invokeFindDictionary = { dictionaryId -> + invokeFindDictionaryById = { dictionaryId -> findDictionaryIsCalled = true if (dictionaryId == testResponseDictionaryEntity.dictionaryId) testResponseDictionaryEntity else null } @@ -133,7 +132,7 @@ internal class CardCorProcessorRunCardsTest { var getUserWasCalled = false var getIsWasCalled = false val cardRepository = MockDbCardRepository( - invokeFindCard = { _ -> + invokeFindCardById = { _ -> getIsWasCalled = true throw TestException() } @@ -173,7 +172,7 @@ internal class CardCorProcessorRunCardsTest { } ) val dictionaryRepository = MockDbDictionaryRepository( - invokeFindDictionary = { id -> + invokeFindDictionaryById = { id -> isFindDictionaryCalled = true if (id == testDictionaryId) testDictionary else null } @@ -217,7 +216,7 @@ internal class CardCorProcessorRunCardsTest { } ) val dictionaryRepository = MockDbDictionaryRepository( - invokeFindDictionary = { id -> + invokeFindDictionaryById = { id -> isFindDictionaryCalled = true if (id == testDictionaryId) testDictionary else null } @@ -258,7 +257,7 @@ internal class CardCorProcessorRunCardsTest { } ) val dictionaryRepository = MockDbDictionaryRepository( - invokeFindDictionary = { id -> + invokeFindDictionaryById = { id -> isFindDictionaryCalled = true if (id == testRequestEntity.dictionaryId) testDictionary else null } @@ -296,7 +295,7 @@ internal class CardCorProcessorRunCardsTest { } ) val dictionaryRepository = MockDbDictionaryRepository( - invokeFindDictionary = { id -> + invokeFindDictionaryById = { id -> isFindDictionaryCalled = true if (id == testRequestEntity.dictionaryId) testDictionary else null } @@ -332,13 +331,13 @@ internal class CardCorProcessorRunCardsTest { var isFindCardsCalled = false var isFindDictionariesCalled = false val cardRepository = MockDbCardRepository( - invokeFindCardsByDictionaryIds = { + invokeFindCardsByDictionaryIdIn = { isFindCardsCalled = true if (it == testFilter.dictionaryIds) testCards.asSequence() else emptySequence() } ) val dictionaryRepository = MockDbDictionaryRepository( - invokeFindDictionaries = { + invokeFindDictionariesByIdIn = { isFindDictionariesCalled = true if (it == testFilter.dictionaryIds) testDictionaries.asSequence() else emptySequence() } @@ -376,13 +375,13 @@ internal class CardCorProcessorRunCardsTest { var isFindCardsCalled = false var isFindDictionariesCalled = false val cardRepository = MockDbCardRepository( - invokeFindCardsByDictionaryIds = { + invokeFindCardsByDictionaryIdIn = { isFindCardsCalled = true if (it == testFilter.dictionaryIds) throw TestException() else testCards.asSequence() } ) val dictionaryRepository = MockDbDictionaryRepository( - invokeFindDictionaries = { + invokeFindDictionariesByIdIn = { isFindDictionariesCalled = true if (it == testFilter.dictionaryIds) testDictionaries.asSequence() else emptySequence() } @@ -406,23 +405,36 @@ internal class CardCorProcessorRunCardsTest { @Test fun `test update-card success`() = runTest { val cardId = CardId("42") + val testDictionary = stubDictionary val testRequestEntity = stubCard.copy(words = listOf(CardWordEntity(word = "XXX")), cardId = cardId) - val testResponseEntity = stubCard.copy(words = listOf(CardWordEntity(word = "HHH"))) + val testResponseEntity = stubCard - var wasCalled = false - val repository = MockDbCardRepository( - invokeUpdateCard = { _, it -> - wasCalled = true - CardDbResponse(if (it.cardId == cardId) testResponseEntity else testRequestEntity) + var isUpdateCardCalled = false + var isFindDictionaryCalled = false + val cardRepository = MockDbCardRepository( + invokeUpdateCard = { + isUpdateCardCalled = true + if (it.cardId == cardId) testResponseEntity else testRequestEntity + } + ) + val dictionaryRepository = MockDbDictionaryRepository( + invokeFindDictionaryById = { + isFindDictionaryCalled = true + if (testDictionary.dictionaryId == it) testDictionary else Assertions.fail() } ) - val context = testContext(CardOperation.UPDATE_CARD, repository) + val context = testContext( + op = CardOperation.UPDATE_CARD, + cardRepository = cardRepository, + dictionaryRepository = dictionaryRepository, + ) context.requestCardEntity = testRequestEntity CardCorProcessor().execute(context) - Assertions.assertTrue(wasCalled) + Assertions.assertTrue(isUpdateCardCalled) + Assertions.assertTrue(isFindDictionaryCalled) Assertions.assertEquals(requestId(CardOperation.UPDATE_CARD), context.requestId) Assertions.assertEquals(AppStatus.OK, context.status) { "Errors: ${context.errors}" } Assertions.assertTrue(context.errors.isEmpty()) @@ -432,22 +444,36 @@ internal class CardCorProcessorRunCardsTest { @Test fun `test update-card unexpected fail`() = runTest { val cardId = CardId("42") + val testDictionary = stubDictionary val testRequestEntity = stubCard.copy(words = listOf(CardWordEntity(word = "XXX")), cardId = cardId) - var wasCalled = false - val repository = MockDbCardRepository( - invokeUpdateCard = { _, it -> - wasCalled = true - CardDbResponse(if (it.words == testRequestEntity.words) throw TestException() else testRequestEntity) + var isUpdateCardCalled = false + var isFindDictionaryCalled = false + val cardRepository = MockDbCardRepository( + invokeUpdateCard = { + isUpdateCardCalled = true + if (it.words == testRequestEntity.words) throw TestException() else testRequestEntity + } + ) + val dictionaryRepository = MockDbDictionaryRepository( + invokeFindDictionaryById = { + isFindDictionaryCalled = true + if (testDictionary.dictionaryId == it) testDictionary else Assertions.fail() } ) - val context = testContext(CardOperation.UPDATE_CARD, repository) + val context = testContext( + op = CardOperation.UPDATE_CARD, + cardRepository = cardRepository, + dictionaryRepository = dictionaryRepository, + ) + context.requestCardEntity = testRequestEntity CardCorProcessor().execute(context) - Assertions.assertTrue(wasCalled) + Assertions.assertTrue(isUpdateCardCalled) + Assertions.assertTrue(isFindDictionaryCalled) Assertions.assertEquals(requestId(CardOperation.UPDATE_CARD), context.requestId) Assertions.assertEquals(CardEntity.EMPTY, context.responseCardEntity) assertUnknownError(context, CardOperation.UPDATE_CARD) @@ -459,28 +485,49 @@ internal class CardCorProcessorRunCardsTest { CardLearn(cardId = stubCard.cardId, details = mapOf(Stage.WRITING to 42)), ) - val testResponseEntities = listOf(stubCard) + val testCards = listOf(stubCard) + val testDictionaries = listOf(stubDictionary) + val expectedCards = listOf( + stubCard.copy( + answered = 42, + stats = stubCard.stats + mapOf(Stage.WRITING to 42) + ) + ) - var wasCalled = false - val repository = MockDbCardRepository( - invokeUpdateCards = { _, givenIds, _ -> - wasCalled = true - CardsDbResponse( - cards = if (givenIds == setOf(stubCard.cardId)) testResponseEntities else emptyList(), - ) + var isUpdateCardsCalled = false + var isFindDictionariesCalled = false + val cardRepository = MockDbCardRepository( + invokeUpdateCards = { givenCards -> + isUpdateCardsCalled = true + if (givenCards == expectedCards) expectedCards else emptyList() + }, + invokeFindCardsByIdIn = { ids -> + if (ids == setOf(stubCard.cardId)) testCards.asSequence() else emptySequence() + } + ) + val dictionaryRepository = MockDbDictionaryRepository( + invokeFindDictionariesByIdIn = { givenDictionaryIds -> + isFindDictionariesCalled = true + if (testDictionaries.map { it.dictionaryId } + .toSet() == givenDictionaryIds) testDictionaries.asSequence() else Assertions.fail() } ) - val context = testContext(CardOperation.LEARN_CARDS, repository) + val context = testContext( + op = CardOperation.LEARN_CARDS, + cardRepository = cardRepository, + dictionaryRepository = dictionaryRepository, + ) context.requestCardLearnList = testLearn CardCorProcessor().execute(context) - Assertions.assertTrue(wasCalled) + Assertions.assertTrue(isUpdateCardsCalled) + Assertions.assertTrue(isFindDictionariesCalled) Assertions.assertEquals(requestId(CardOperation.LEARN_CARDS), context.requestId) Assertions.assertEquals(AppStatus.OK, context.status) Assertions.assertTrue(context.errors.isEmpty()) - Assertions.assertEquals(testResponseEntities, context.responseCardEntityList) + Assertions.assertEquals(expectedCards, context.responseCardEntityList) } @Test @@ -489,34 +536,52 @@ internal class CardCorProcessorRunCardsTest { CardLearn(cardId = CardId("1"), details = mapOf(Stage.SELF_TEST to 42)), CardLearn(cardId = CardId("2"), details = mapOf(Stage.OPTIONS to 2, Stage.MOSAIC to 3)) ) - val ids = testLearn.map { it.cardId }.toSet() + val ids = testLearn.map { it.cardId } - val testResponseEntities = stubCards - val testResponseErrors = listOf( - AppError(code = "test") - ) + val testCards = listOf(stubCard.copy(cardId = CardId("1")), stubCard.copy(cardId = CardId("2"))) + val testDictionaries = listOf(stubDictionary) - var wasCalled = false - val repository = MockDbCardRepository( - invokeUpdateCards = { _, givenIds, _ -> - wasCalled = true - CardsDbResponse( - cards = if (givenIds == ids) testResponseEntities else emptyList(), - errors = if (givenIds == ids) testResponseErrors else emptyList() - ) + var isUpdateCardsCalled = false + var isFindDictionariesCalled = false + val cardRepository = MockDbCardRepository( + invokeUpdateCards = { givenCards -> + isUpdateCardsCalled = true + if (givenCards.map { it.cardId } == ids) { + throw TestException() + } else { + emptyList() + } + }, + invokeFindCardsByIdIn = { givenIds -> + if (givenIds == ids.toSet()) testCards.asSequence() else emptySequence() + } + ) + val dictionaryRepository = MockDbDictionaryRepository( + invokeFindDictionariesByIdIn = { givenDictionaryIds -> + isFindDictionariesCalled = true + if (testDictionaries.map { it.dictionaryId } + .toSet() == givenDictionaryIds) testDictionaries.asSequence() else Assertions.fail() } ) - val context = testContext(CardOperation.LEARN_CARDS, repository) + val context = testContext( + op = CardOperation.LEARN_CARDS, + cardRepository = cardRepository, + dictionaryRepository = dictionaryRepository, + ) context.requestCardLearnList = testLearn CardCorProcessor().execute(context) - Assertions.assertTrue(wasCalled) + Assertions.assertTrue(isUpdateCardsCalled) + Assertions.assertTrue(isFindDictionariesCalled) Assertions.assertEquals(requestId(CardOperation.LEARN_CARDS), context.requestId) Assertions.assertEquals(AppStatus.FAIL, context.status) - Assertions.assertEquals(testResponseErrors, context.errors) - Assertions.assertEquals(testResponseEntities, context.responseCardEntityList) + Assertions.assertEquals(emptyList(), context.responseCardEntityList) + + Assertions.assertEquals(1, context.errors.size) + Assertions.assertEquals("run::LEARN_CARDS", context.errors[0].code) + Assertions.assertInstanceOf(TestException::class.java, context.errors[0].exception) } @Test @@ -596,7 +661,7 @@ internal class CardCorProcessorRunCardsTest { var getUserIsCalled = false var getCardIsCalled = false - val cardRepository = MockDbCardRepository(invokeFindCard = { _ -> + val cardRepository = MockDbCardRepository(invokeFindCardById = { _ -> getCardIsCalled = true throw TestException() }) diff --git a/db-common/src/main/kotlin/CommonMappers.kt b/db-common/src/main/kotlin/CommonMappers.kt index 3e72c13f..f527fe89 100644 --- a/db-common/src/main/kotlin/CommonMappers.kt +++ b/db-common/src/main/kotlin/CommonMappers.kt @@ -5,11 +5,12 @@ import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.KotlinModule import com.gitlab.sszuev.flashcards.model.common.NONE -import com.gitlab.sszuev.flashcards.model.domain.Stage import kotlinx.datetime.toJavaInstant import kotlinx.datetime.toKotlinInstant +import java.time.temporal.ChronoUnit -fun systemNow(): java.time.LocalDateTime = java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC).toLocalDateTime() +fun systemNow(): java.time.LocalDateTime = + java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC).truncatedTo(ChronoUnit.MILLIS).toLocalDateTime() fun kotlinx.datetime.Instant?.asJava(): java.time.LocalDateTime = (this?:kotlinx.datetime.Instant.NONE).toJavaInstant().atOffset(java.time.ZoneOffset.UTC).toLocalDateTime() @@ -56,9 +57,6 @@ fun List.toJsonString(): String { return mapper.writeValueAsString(this) } -fun Map.toCommonCardDtoDetails(): CommonCardDetailsDto = - CommonCardDetailsDto(this.mapKeys { it.key.name }.mapValues { it.value.toString() }) - data class CommonUserDetailsDto(private val content: Map) : Map by content data class CommonDictionaryDetailsDto(private val content: Map) : Map by content diff --git a/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt b/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt index aa1595ba..7395ee72 100644 --- a/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt +++ b/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt @@ -1,7 +1,6 @@ package com.gitlab.sszuev.flashcards.dbcommon import com.gitlab.sszuev.flashcards.common.asLong -import com.gitlab.sszuev.flashcards.model.common.AppError import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.common.NONE import com.gitlab.sszuev.flashcards.model.domain.CardEntity @@ -172,16 +171,6 @@ abstract class DbCardRepositoryTest { assertEquals(expected, a) } - @Suppress("SameParameterValue") - private fun assertSingleError(res: CardDbResponse, field: String, op: String): AppError { - assertEquals(1, res.errors.size) { "Errors: ${res.errors}" } - val error = res.errors[0] - assertEquals("database::$op", error.code) { error.toString() } - assertEquals(field, error.field) { error.toString() } - assertEquals("database", error.group) { error.toString() } - assertNull(error.exception) { error.toString() } - return error - } private fun assertNoErrors(res: CardDbResponse) { assertEquals(0, res.errors.size) { "Has errors: ${res.errors}" } @@ -195,7 +184,7 @@ abstract class DbCardRepositoryTest { @Test fun `test get card not found`() { val id = CardId("42000") - val res = repository.findCard(id) + val res = repository.findCardById(id) assertNull(res) } @@ -203,12 +192,12 @@ abstract class DbCardRepositoryTest { @Test fun `test get all cards success`() { // Business dictionary - val res1 = repository.findCards(DictionaryId("1")).toList() + val res1 = repository.findCardsByDictionaryId(DictionaryId("1")).toList() assertEquals(244, res1.size) assertEquals("1", res1.map { it.dictionaryId.asString() }.toSet().single()) // Weather dictionary - val res2 = repository.findCards(DictionaryId("2")).toList() + val res2 = repository.findCardsByDictionaryId(DictionaryId("2")).toList() assertEquals(65, res2.size) assertEquals("2", res2.map { it.dictionaryId.asString() }.toSet().single()) } @@ -217,7 +206,7 @@ abstract class DbCardRepositoryTest { @Test fun `test get all cards error unknown dictionary`() { val dictionaryId = "42" - val res = repository.findCards(DictionaryId(dictionaryId)).toList() + val res = repository.findCardsByDictionaryId(DictionaryId(dictionaryId)).toList() assertEquals(0, res.size) } @@ -245,17 +234,15 @@ abstract class DbCardRepositoryTest { @Test fun `test get card & update card success`() { val expected = weatherCardEntity - val prev = repository.findCard(expected.cardId) + val prev = repository.findCardById(expected.cardId) assertNotNull(prev) assertCard(expected = expected, actual = prev!!, ignoreChangeAt = true, ignoreId = false) val request = climateCardEntity - val res = repository.updateCard(userId, request) - assertNoErrors(res) - val updated = res.card + val updated = repository.updateCard(request) assertCard(expected = request, actual = updated, ignoreChangeAt = true, ignoreId = false) - val now = repository.findCard(expected.cardId) + val now = repository.findCardById(expected.cardId) assertNotNull(now) assertCard(expected = request, actual = now!!, ignoreChangeAt = true, ignoreId = false) } @@ -271,12 +258,9 @@ abstract class DbCardRepositoryTest { CardWordEntity(word = "XXX", translations = listOf(listOf("xxx"))), ), ) - val res = repository.updateCard(userId, request) - val error = assertSingleError(res, id.asString(), "updateCard") - assertEquals( - """Error while updateCard: card with id="${id.asString()}" not found""", - error.message - ) + Assertions.assertThrows(DbDataException::class.java) { + repository.updateCard(request) + } } @Order(8) @@ -294,19 +278,16 @@ abstract class DbCardRepositoryTest { ), ) ) - val res = repository.updateCard(userId, request) - val error = assertSingleError(res, dictionaryId.asString(), "updateCard") - assertEquals( - """Error while updateCard: dictionary with id="${dictionaryId.asString()}" not found""", - error.message - ) + Assertions.assertThrows(DbDataException::class.java) { + repository.updateCard(request) + } } @Order(10) @Test fun `test get card & reset card success`() { val request = snowCardEntity - val prev = repository.findCard(request.cardId) + val prev = repository.findCardById(request.cardId) assertNotNull(prev) assertCard(expected = request, actual = prev!!, ignoreChangeAt = true, ignoreId = false) @@ -316,30 +297,35 @@ abstract class DbCardRepositoryTest { val updated = res.card assertCard(expected = expected, actual = updated, ignoreChangeAt = true, ignoreId = false) - val now = repository.findCard(request.cardId) + val now = repository.findCardById(request.cardId) assertNotNull(now) assertCard(expected = expected, actual = now!!, ignoreChangeAt = true, ignoreId = false) } @Order(11) @Test - fun `test bulk update success`() { + fun `test bulk update & find by card ids - success`() { val now = Clock.System.now() - val res = repository.updateCards( - userId, - setOf(forgiveCardEntity.cardId, snowCardEntity.cardId, drawCardEntity.cardId), - ) { - it.copy(answered = 42) - } - assertEquals(0, res.errors.size) - assertEquals(3, res.cards.size) - val actual = res.cards.sortedBy { it.cardId.asLong() } - assertCard(expected = drawCardEntity.copy(answered = 42), actual = actual[0], ignoreChangeAt = true) - assertCard(expected = forgiveCardEntity.copy(answered = 42), actual = actual[1], ignoreChangeAt = true) - assertCard(expected = snowCardEntity.copy(answered = 42), actual = actual[2], ignoreChangeAt = true) - actual.forEach { + + val toUpdate = + sequenceOf(forgiveCardEntity, snowCardEntity, drawCardEntity).map { it.copy(answered = 42) }.toSet() + + val updated = repository.updateCards(toUpdate) + assertEquals(3, updated.size) + + val res1 = updated.sortedBy { it.cardId.asLong() } + assertCard(expected = drawCardEntity.copy(answered = 42), actual = res1[0], ignoreChangeAt = true) + assertCard(expected = forgiveCardEntity.copy(answered = 42), actual = res1[1], ignoreChangeAt = true) + assertCard(expected = snowCardEntity.copy(answered = 42), actual = res1[2], ignoreChangeAt = true) + res1.forEach { assertTrue(it.changedAt >= now) } + + val res2 = + repository.findCardsByIdIn(setOf(forgiveCardEntity.cardId, snowCardEntity.cardId, drawCardEntity.cardId)) + .sortedBy { it.cardId.asLong() } + .toList() + assertEquals(res1, res2) } @Order(21) @@ -358,6 +344,6 @@ abstract class DbCardRepositoryTest { val res = repository.removeCard(userId, id) assertNoErrors(res) - assertNull(repository.findCard(id)) + assertNull(repository.findCardById(id)) } } \ No newline at end of file diff --git a/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt b/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt index bfff1402..05875834 100644 --- a/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt +++ b/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt @@ -55,11 +55,11 @@ abstract class DbDictionaryRepositoryTest { @Order(1) @Test fun `test get dictionary by id`() { - val res1 = repository.findDictionary(DictionaryId("2")) + val res1 = repository.findDictionaryById(DictionaryId("2")) Assertions.assertNotNull(res1) Assertions.assertEquals("Weather", res1!!.name) Assertions.assertEquals("42", res1.userId.asString()) - val res2 = repository.findDictionary(DictionaryId("1")) + val res2 = repository.findDictionaryById(DictionaryId("1")) Assertions.assertNotNull(res2) Assertions.assertEquals("Irregular Verbs", res2!!.name) Assertions.assertEquals("42", res2.userId.asString()) diff --git a/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt b/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt index cea14023..3eb916c7 100644 --- a/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt +++ b/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt @@ -5,7 +5,6 @@ import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.repositories.CardDbResponse -import com.gitlab.sszuev.flashcards.repositories.CardsDbResponse import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse @@ -14,34 +13,32 @@ import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse * @see mockk#issue-288 */ class MockDbCardRepository( - private val invokeFindCard: (CardId) -> CardEntity? = { null }, + private val invokeFindCardById: (CardId) -> CardEntity? = { null }, private val invokeFindCardsByDictionaryId: (DictionaryId) -> Sequence = { emptySequence() }, - private val invokeFindCardsByDictionaryIds: (Iterable) -> Sequence = { emptySequence() }, + private val invokeFindCardsByDictionaryIdIn: (Iterable) -> Sequence = { emptySequence() }, + private val invokeFindCardsByIdIn: (Iterable) -> Sequence = { emptySequence() }, private val invokeCreateCard: (CardEntity) -> CardEntity = { CardEntity.EMPTY }, - private val invokeUpdateCard: (AppUserId, CardEntity) -> CardDbResponse = { _, _ -> CardDbResponse.EMPTY }, - private val invokeUpdateCards: (AppUserId, Iterable, (CardEntity) -> CardEntity) -> CardsDbResponse = { _, _, _ -> CardsDbResponse.EMPTY }, + private val invokeUpdateCard: (CardEntity) -> CardEntity = { CardEntity.EMPTY }, + private val invokeUpdateCards: (Iterable) -> List = { _ -> emptyList() }, private val invokeResetCard: (AppUserId, CardId) -> CardDbResponse = { _, _ -> CardDbResponse.EMPTY }, private val invokeDeleteCard: (AppUserId, CardId) -> RemoveCardDbResponse = { _, _ -> RemoveCardDbResponse.EMPTY }, ) : DbCardRepository { - override fun findCard(cardId: CardId): CardEntity? = invokeFindCard(cardId) + override fun findCardById(cardId: CardId): CardEntity? = invokeFindCardById(cardId) - override fun findCards(dictionaryId: DictionaryId): Sequence = + override fun findCardsByDictionaryId(dictionaryId: DictionaryId): Sequence = invokeFindCardsByDictionaryId(dictionaryId) - override fun findCards(dictionaryIds: Iterable): Sequence = - invokeFindCardsByDictionaryIds(dictionaryIds) + override fun findCardsByDictionaryIdIn(dictionaryIds: Iterable): Sequence = + invokeFindCardsByDictionaryIdIn(dictionaryIds) + + override fun findCardsByIdIn(cardIds: Iterable): Sequence = invokeFindCardsByIdIn(cardIds) override fun createCard(cardEntity: CardEntity): CardEntity = invokeCreateCard(cardEntity) - override fun updateCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse = - invokeUpdateCard(userId, cardEntity) + override fun updateCard(cardEntity: CardEntity): CardEntity = invokeUpdateCard(cardEntity) - override fun updateCards( - userId: AppUserId, - cardIds: Iterable, - update: (CardEntity) -> CardEntity - ): CardsDbResponse = invokeUpdateCards(userId, cardIds, update) + override fun updateCards(cardEntities: Iterable): List = invokeUpdateCards(cardEntities) override fun resetCard(userId: AppUserId, cardId: CardId): CardDbResponse = invokeResetCard(userId, cardId) diff --git a/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt b/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt index aeeed69c..cca80b8e 100644 --- a/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt +++ b/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt @@ -11,8 +11,8 @@ import com.gitlab.sszuev.flashcards.repositories.ImportDictionaryDbResponse import com.gitlab.sszuev.flashcards.repositories.RemoveDictionaryDbResponse class MockDbDictionaryRepository( - private val invokeFindDictionary: (DictionaryId) -> DictionaryEntity? = { null }, - private val invokeFindDictionaries: (Iterable) -> Sequence = { emptySequence() }, + private val invokeFindDictionaryById: (DictionaryId) -> DictionaryEntity? = { null }, + private val invokeFindDictionariesByIdIn: (Iterable) -> Sequence = { emptySequence() }, private val invokeGetAllDictionaries: (AppUserId) -> DictionariesDbResponse = { DictionariesDbResponse.EMPTY }, private val invokeCreateDictionary: (AppUserId, DictionaryEntity) -> DictionaryDbResponse = { _, _ -> DictionaryDbResponse.EMPTY }, private val invokeDeleteDictionary: (AppUserId, DictionaryId) -> RemoveDictionaryDbResponse = { _, _ -> RemoveDictionaryDbResponse.EMPTY }, @@ -20,7 +20,8 @@ class MockDbDictionaryRepository( private val invokeUploadDictionary: (AppUserId, ResourceEntity) -> DictionaryDbResponse = { _, _ -> DictionaryDbResponse.EMPTY }, ) : DbDictionaryRepository { - override fun findDictionary(dictionaryId: DictionaryId): DictionaryEntity? = invokeFindDictionary(dictionaryId) + override fun findDictionaryById(dictionaryId: DictionaryId): DictionaryEntity? = + invokeFindDictionaryById(dictionaryId) override fun getAllDictionaries(userId: AppUserId): DictionariesDbResponse = invokeGetAllDictionaries(userId) @@ -36,6 +37,6 @@ class MockDbDictionaryRepository( override fun exportDictionary(userId: AppUserId, resource: ResourceEntity): DictionaryDbResponse = invokeUploadDictionary(userId, resource) - override fun findDictionaries(dictionaryIds: Iterable): Sequence = - invokeFindDictionaries(dictionaryIds) + override fun findDictionariesByIdIn(dictionaryIds: Iterable): Sequence = + invokeFindDictionariesByIdIn(dictionaryIds) } \ No newline at end of file diff --git a/db-mem/src/main/kotlin/MemDbCardRepository.kt b/db-mem/src/main/kotlin/MemDbCardRepository.kt index 098a9036..753c1561 100644 --- a/db-mem/src/main/kotlin/MemDbCardRepository.kt +++ b/db-mem/src/main/kotlin/MemDbCardRepository.kt @@ -1,7 +1,6 @@ package com.gitlab.sszuev.flashcards.dbmem import com.gitlab.sszuev.flashcards.common.asLong -import com.gitlab.sszuev.flashcards.common.dbError import com.gitlab.sszuev.flashcards.common.forbiddenEntityDbError import com.gitlab.sszuev.flashcards.common.noCardFoundDbError import com.gitlab.sszuev.flashcards.common.noDictionaryFoundDbError @@ -16,7 +15,6 @@ import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.repositories.CardDbResponse -import com.gitlab.sszuev.flashcards.repositories.CardsDbResponse import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDataException import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse @@ -26,12 +24,12 @@ class MemDbCardRepository( ) : DbCardRepository { private val database = MemDatabase.get(dbConfig.dataLocation) - override fun findCard(cardId: CardId): CardEntity? { + override fun findCardById(cardId: CardId): CardEntity? { require(cardId != CardId.NONE) return database.findCardById(cardId.asLong())?.toCardEntity() } - override fun findCards(dictionaryId: DictionaryId): Sequence { + override fun findCardsByDictionaryId(dictionaryId: DictionaryId): Sequence { require(dictionaryId != DictionaryId.NONE) return database.findCardsByDictionaryId(dictionaryId.asLong()).map { it.toCardEntity() } } @@ -46,63 +44,15 @@ class MemDbCardRepository( } } - override fun updateCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse { + override fun updateCard(cardEntity: CardEntity): CardEntity { validateCardEntityForUpdate(cardEntity) - val timestamp = systemNow() - val found = database.findCardById(cardEntity.cardId.asLong()) ?: return CardDbResponse( - noCardFoundDbError("updateCard", cardEntity.cardId) - ) - val errors = mutableListOf() - val foundDictionary = - checkDictionaryUser("updateCard", userId, cardEntity.dictionaryId, cardEntity.cardId, errors) - if (foundDictionary != null && foundDictionary.id != cardEntity.dictionaryId.asLong()) { - errors.add( - dbError( - operation = "updateCard", - fieldName = cardEntity.cardId.asString(), - details = "given and found dictionary ids do not match: ${cardEntity.dictionaryId.asString()} != ${found.dictionaryId}" - ) - ) - } - if (errors.isNotEmpty()) { - return CardDbResponse(errors = errors) + val found = database.findCardById(cardEntity.cardId.asLong()) + ?: throw DbDataException("Can't find card, id = ${cardEntity.cardId.asLong()}") + if (found.dictionaryId != cardEntity.dictionaryId.asLong()) { + throw DbDataException("Changing dictionary-id is not allowed; card id = ${cardEntity.cardId.asLong()}") } - return CardDbResponse( - card = database.saveCard(cardEntity.toMemDbCard().copy(changedAt = timestamp)).toCardEntity() - ) - } - - override fun updateCards( - userId: AppUserId, - cardIds: Iterable, - update: (CardEntity) -> CardEntity - ): CardsDbResponse { val timestamp = systemNow() - val ids = cardIds.map { it.asLong() } - val dbCards = database.findCardsById(ids).associateBy { checkNotNull(it.id) } - val errors = mutableListOf() - val dbDictionaries = mutableMapOf() - dbCards.forEach { - val dictionary = dbDictionaries.computeIfAbsent(checkNotNull(it.value.dictionaryId)) { k -> - checkNotNull(database.findDictionaryById(k)) - } - if (dictionary.userId != userId.asLong()) { - errors.add(forbiddenEntityDbError("updateCards", it.key.asCardId(), userId)) - } - } - if (errors.isNotEmpty()) { - return CardsDbResponse(errors = errors) - } - val cards = dbCards.values.map { - val dbCard = update(it.toCardEntity()).toMemDbCard().copy(changedAt = timestamp) - database.saveCard(dbCard).toCardEntity() - } - val dictionaries = dbDictionaries.values.map { it.toDictionaryEntity() } - return CardsDbResponse( - cards = cards, - dictionaries = dictionaries, - errors = emptyList(), - ) + return database.saveCard(cardEntity.toMemDbCard().copy(changedAt = timestamp)).toCardEntity() } override fun resetCard(userId: AppUserId, cardId: CardId): CardDbResponse { diff --git a/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt b/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt index 17f3a9b3..4ffb16ce 100644 --- a/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt +++ b/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt @@ -29,7 +29,7 @@ class MemDbDictionaryRepository( private val database = MemDatabase.get(databaseLocation = dbConfig.dataLocation) - override fun findDictionary(dictionaryId: DictionaryId): DictionaryEntity? = + override fun findDictionaryById(dictionaryId: DictionaryId): DictionaryEntity? = database.findDictionaryById(dictionaryId.asLong())?.toDictionaryEntity() override fun getAllDictionaries(userId: AppUserId): DictionariesDbResponse { diff --git a/db-pg/src/main/kotlin/PgDbCardRepository.kt b/db-pg/src/main/kotlin/PgDbCardRepository.kt index 740a576a..b7c32cc2 100644 --- a/db-pg/src/main/kotlin/PgDbCardRepository.kt +++ b/db-pg/src/main/kotlin/PgDbCardRepository.kt @@ -1,11 +1,13 @@ package com.gitlab.sszuev.flashcards.dbpg +import com.gitlab.sszuev.flashcards.common.asKotlin import com.gitlab.sszuev.flashcards.common.asLong -import com.gitlab.sszuev.flashcards.common.dbError +import com.gitlab.sszuev.flashcards.common.detailsAsCommonCardDetailsDto import com.gitlab.sszuev.flashcards.common.forbiddenEntityDbError import com.gitlab.sszuev.flashcards.common.noCardFoundDbError import com.gitlab.sszuev.flashcards.common.noDictionaryFoundDbError import com.gitlab.sszuev.flashcards.common.systemNow +import com.gitlab.sszuev.flashcards.common.toJsonString import com.gitlab.sszuev.flashcards.common.validateCardEntityForCreate import com.gitlab.sszuev.flashcards.common.validateCardEntityForUpdate import com.gitlab.sszuev.flashcards.dbpg.dao.Cards @@ -18,12 +20,13 @@ import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.repositories.CardDbResponse -import com.gitlab.sszuev.flashcards.repositories.CardsDbResponse import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDataException import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.deleteWhere +import org.jetbrains.exposed.sql.statements.BatchUpdateStatement +import org.jetbrains.exposed.sql.transactions.TransactionManager class PgDbCardRepository( dbConfig: PgDbConfig = PgDbConfig(), @@ -34,20 +37,37 @@ class PgDbCardRepository( PgDbConnector.connection(dbConfig) } - override fun findCard(cardId: CardId): CardEntity? { + override fun findCardById(cardId: CardId): CardEntity? { require(cardId != CardId.NONE) return connection.execute { PgDbCard.findById(cardId.asLong())?.toCardEntity() } } - override fun findCards(dictionaryId: DictionaryId): Sequence { + override fun findCardsByDictionaryId(dictionaryId: DictionaryId): Sequence { require(dictionaryId != DictionaryId.NONE) return connection.execute { PgDbCard.find { Cards.dictionaryId eq dictionaryId.asLong() }.map { it.toCardEntity() }.asSequence() } } + override fun findCardsByDictionaryIdIn(dictionaryIds: Iterable): Sequence { + return connection.execute { + PgDbCard.find { + Cards.dictionaryId inList + dictionaryIds.onEach { require(it != DictionaryId.NONE) }.map { it.asRecordId() }.toSet() + }.map { it.toCardEntity() }.asSequence() + } + } + + override fun findCardsByIdIn(cardIds: Iterable): Sequence { + return connection.execute { + PgDbCard.find { + Cards.id inList cardIds.onEach { require(it != CardId.NONE) }.map { it.asRecordId() }.toSet() + }.map { it.toCardEntity() }.asSequence() + } + } + override fun createCard(cardEntity: CardEntity): CardEntity { validateCardEntityForCreate(cardEntity) return connection.execute { @@ -62,71 +82,38 @@ class PgDbCardRepository( } } - override fun updateCard(userId: AppUserId, cardEntity: CardEntity): CardDbResponse { + override fun updateCard(cardEntity: CardEntity): CardEntity { + validateCardEntityForUpdate(cardEntity) return connection.execute { - validateCardEntityForUpdate(cardEntity) - val timestamp = systemNow() - val found = PgDbCard.findById(cardEntity.cardId.asRecordId()) ?: return@execute CardDbResponse( - noCardFoundDbError("updateCard", cardEntity.cardId) - ) - val errors = mutableListOf() - val foundDictionary = - checkDictionaryUser("updateCard", userId, cardEntity.dictionaryId, cardEntity.cardId, errors) - if (foundDictionary != null && foundDictionary.id.value != cardEntity.dictionaryId.asLong()) { - errors.add( - dbError( - operation = "updateCard", - fieldName = cardEntity.cardId.asString(), - details = "given and found dictionary ids do not match: ${cardEntity.dictionaryId.asString()} != ${found.dictionaryId.value}" - ) - ) - } - if (errors.isNotEmpty()) { - return@execute CardDbResponse(errors = errors) + val found = PgDbCard.findById(cardEntity.cardId.asRecordId()) + ?: throw DbDataException("Can't find card id = ${cardEntity.cardId.asLong()}") + if (found.dictionaryId.value != cardEntity.dictionaryId.asLong()) { + throw DbDataException("Changing dictionary-id is not allowed; card id = ${cardEntity.cardId.asLong()}") } + val timestamp = systemNow() writeCardEntityToPgDbCard(from = cardEntity, to = found, timestamp = timestamp) - return@execute CardDbResponse(card = found.toCardEntity()) + found.toCardEntity() } } - override fun updateCards( - userId: AppUserId, - cardIds: Iterable, - update: (CardEntity) -> CardEntity - ): CardsDbResponse { - return connection.execute { - val timestamp = systemNow() - val ids = cardIds.map { it.asLong() } - val dbCards = PgDbCard.find { Cards.id inList ids }.associateBy { it.id.value } - val errors = mutableListOf() - ids.filterNot { it in dbCards.keys }.forEach { - errors.add(noCardFoundDbError(operation = "updateCards", id = it.asCardId())) - } - val dbDictionaries = mutableMapOf() - dbCards.forEach { - val dictionary = dbDictionaries.computeIfAbsent(it.value.dictionaryId.value) { k -> - checkNotNull(PgDbDictionary.findById(k)) - } - if (dictionary.userId.value != userId.asLong()) { - errors.add(forbiddenEntityDbError("updateCards", it.key.asCardId(), userId)) - } - } - if (errors.isNotEmpty()) { - return@execute CardsDbResponse(errors = errors) - } - val cards = dbCards.values.onEach { - val new = update(it.toCardEntity()) - writeCardEntityToPgDbCard(from = new, to = it, timestamp = timestamp) - }.map { - it.toCardEntity() + override fun updateCards(cardEntities: Iterable): List = connection.execute { + val res = mutableListOf() + val timestamp = systemNow() + BatchUpdateStatement(Cards).apply { + cardEntities.onEach { + validateCardEntityForUpdate(it) + addBatch(it.cardId.asRecordId()) + this[Cards.dictionaryId] = it.dictionaryId.asRecordId() + this[Cards.words] = it.toPgDbCardWordsJson() + this[Cards.answered] = it.answered + this[Cards.details] = it.detailsAsCommonCardDetailsDto().toJsonString() + this[Cards.changedAt] = timestamp + }.forEach { + res.add(it.copy(changedAt = timestamp.asKotlin())) } - val dictionaries = dbDictionaries.values.map { it.toDictionaryEntity() } - CardsDbResponse( - cards = cards, - dictionaries = dictionaries, - errors = emptyList(), - ) + execute(TransactionManager.current()) } + res } override fun resetCard(userId: AppUserId, cardId: CardId): CardDbResponse { diff --git a/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt b/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt index 59041e0d..70e8b8e6 100644 --- a/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt +++ b/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt @@ -44,7 +44,7 @@ class PgDbDictionaryRepository( PgDbConnector.connection(dbConfig) } - override fun findDictionary(dictionaryId: DictionaryId): DictionaryEntity? = connection.execute { + override fun findDictionaryById(dictionaryId: DictionaryId): DictionaryEntity? = connection.execute { PgDbDictionary.findById(dictionaryId.asLong())?.toDictionaryEntity() } From cb70723e4ae1e2261318752812dd2e616e3cd351 Mon Sep 17 00:00:00 2001 From: sszuev Date: Mon, 25 Mar 2024 22:55:32 +0300 Subject: [PATCH 07/18] common & core & db: [#28] remove DbCardRepository#resetCard --- .../kotlin/repositories/DbCardRepository.kt | 16 -------- .../repositories/NoOpDbCardRepository.kt | 6 ++- core/src/main/kotlin/CardCorProcessor.kt | 4 +- .../kotlin/processes/CardProcessWorkers.kt | 33 ++++++++------- .../kotlin/CardCorProcessorRunCardsTest.kt | 40 ++++++++++++------- .../kotlin/DbCardRepositoryTest.kt | 25 ------------ .../kotlin/mocks/MockDbCardRepository.kt | 4 -- db-mem/src/main/kotlin/MemDbCardRepository.kt | 15 +------ db-pg/src/main/kotlin/PgDbCardRepository.kt | 19 +-------- 9 files changed, 54 insertions(+), 108 deletions(-) diff --git a/common/src/commonMain/kotlin/repositories/DbCardRepository.kt b/common/src/commonMain/kotlin/repositories/DbCardRepository.kt index 209e59a4..5796c539 100644 --- a/common/src/commonMain/kotlin/repositories/DbCardRepository.kt +++ b/common/src/commonMain/kotlin/repositories/DbCardRepository.kt @@ -54,11 +54,6 @@ interface DbCardRepository { */ fun updateCards(cardEntities: Iterable): List = cardEntities.map { updateCard(it) } - /** - * Resets status. - */ - fun resetCard(userId: AppUserId, cardId: CardId): CardDbResponse - /** * Deletes card by id. */ @@ -66,17 +61,6 @@ interface DbCardRepository { } -data class CardDbResponse( - val card: CardEntity = CardEntity.EMPTY, - val errors: List = emptyList(), -) { - constructor(error: AppError) : this(errors = listOf(error)) - - companion object { - val EMPTY = CardDbResponse(card = CardEntity.EMPTY) - } -} - data class RemoveCardDbResponse( val card: CardEntity = CardEntity.EMPTY, val errors: List = emptyList(), diff --git a/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt b/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt index 7e639e3c..4a25fff0 100644 --- a/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt +++ b/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt @@ -11,11 +11,15 @@ object NoOpDbCardRepository : DbCardRepository { override fun findCardsByDictionaryId(dictionaryId: DictionaryId): Sequence = noOp() + override fun findCardsByDictionaryIdIn(dictionaryIds: Iterable): Sequence = noOp() + + override fun findCardsByIdIn(cardIds: Iterable): Sequence = noOp() + override fun createCard(cardEntity: CardEntity): CardEntity = noOp() override fun updateCard(cardEntity: CardEntity): CardEntity = noOp() - override fun resetCard(userId: AppUserId, cardId: CardId): CardDbResponse = noOp() + override fun updateCards(cardEntities: Iterable): List = noOp() override fun removeCard(userId: AppUserId, cardId: CardId): RemoveCardDbResponse = noOp() diff --git a/core/src/main/kotlin/CardCorProcessor.kt b/core/src/main/kotlin/CardCorProcessor.kt index cbc8b10e..c50531c1 100644 --- a/core/src/main/kotlin/CardCorProcessor.kt +++ b/core/src/main/kotlin/CardCorProcessor.kt @@ -9,7 +9,7 @@ import com.gitlab.sszuev.flashcards.core.processes.processFindUser import com.gitlab.sszuev.flashcards.core.processes.processGetAllCards import com.gitlab.sszuev.flashcards.core.processes.processGetCard import com.gitlab.sszuev.flashcards.core.processes.processLearnCards -import com.gitlab.sszuev.flashcards.core.processes.processResetCards +import com.gitlab.sszuev.flashcards.core.processes.processResetCard import com.gitlab.sszuev.flashcards.core.processes.processResource import com.gitlab.sszuev.flashcards.core.processes.processUpdateCard import com.gitlab.sszuev.flashcards.core.stubs.cardStubSuccess @@ -219,7 +219,7 @@ class CardCorProcessor { } runs(CardOperation.RESET_CARD) { processFindUser(CardOperation.RESET_CARD) - processResetCards() + processResetCard() } } diff --git a/core/src/main/kotlin/processes/CardProcessWorkers.kt b/core/src/main/kotlin/processes/CardProcessWorkers.kt index 55321fe2..0556e4c5 100644 --- a/core/src/main/kotlin/processes/CardProcessWorkers.kt +++ b/core/src/main/kotlin/processes/CardProcessWorkers.kt @@ -11,7 +11,6 @@ import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.TTSResourceGet import com.gitlab.sszuev.flashcards.model.domain.TTSResourceId -import com.gitlab.sszuev.flashcards.repositories.CardDbResponse import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse fun ChainDSL.processGetCard() = worker { @@ -183,8 +182,7 @@ fun ChainDSL.processLearnCards() = worker { } } if (errors.isEmpty()) { - this.responseCardEntityList = - this.postProcess(learnCards(foundCards, cardLearns).iterator()) { checkNotNull(foundDictionaries[it]) } + this.responseCardEntityList = learnCards(foundCards, cardLearns) } this.status = if (this.errors.isNotEmpty()) AppStatus.FAIL else AppStatus.RUN } @@ -193,15 +191,30 @@ fun ChainDSL.processLearnCards() = worker { } } -fun ChainDSL.processResetCards() = worker { +fun ChainDSL.processResetCard() = worker { this.name = "process reset-cards request" test { this.status == AppStatus.RUN } process { val userId = this.contextUserEntity.id - val res = this.repositories.cardRepository(this.workMode).resetCard(userId, this.normalizedRequestCardEntityId) - this.postProcess(res) + val cardId = this.normalizedRequestCardEntityId + val card = this.repositories.cardRepository(this.workMode).findCardById(cardId) + if (card == null) { + this.errors.add(noCardFoundDataError("resetCard", cardId)) + } else { + val dictionaryId = card.dictionaryId + val dictionary = this.repositories.dictionaryRepository(this.workMode).findDictionaryById(dictionaryId) + if (dictionary == null) { + this.errors.add(noDictionaryFoundDataError("resetCard", dictionaryId)) + } else if (dictionary.userId != userId) { + this.errors.add(forbiddenEntityDataError("resetCard", dictionaryId, userId)) + } else { + val res = this.repositories.cardRepository(this.workMode).updateCard(card.copy(answered = 0)) + this.responseCardEntity = res + } + } + this.status = if (this.errors.isNotEmpty()) AppStatus.FAIL else AppStatus.RUN } onException { this.handleThrowable(CardOperation.RESET_CARD, it) @@ -262,14 +275,6 @@ private suspend fun CardContext.postProcess( return card.copy(words = words, sound = cardAudioId) } -private fun CardContext.postProcess(res: CardDbResponse) { - this.responseCardEntity = res.card - if (res.errors.isNotEmpty()) { - this.errors.addAll(res.errors) - } - this.status = if (this.errors.isNotEmpty()) AppStatus.FAIL else AppStatus.RUN -} - private fun CardContext.postProcess(res: RemoveCardDbResponse) { if (res.errors.isNotEmpty()) { this.errors.addAll(res.errors) diff --git a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt index 178c1501..5150c501 100644 --- a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt +++ b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt @@ -21,7 +21,6 @@ import com.gitlab.sszuev.flashcards.model.domain.CardWordEntity import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.Stage import com.gitlab.sszuev.flashcards.model.domain.TTSResourceId -import com.gitlab.sszuev.flashcards.repositories.CardDbResponse import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository import com.gitlab.sszuev.flashcards.repositories.DbUserRepository @@ -586,29 +585,42 @@ internal class CardCorProcessorRunCardsTest { @Test fun `test reset-card success`() = runTest { - val testId = CardId("42") - val testResponseEntity = stubCard.copy(cardId = testId) + val testDictionaryId = DictionaryId("42") + val testCardId = CardId("42") + val testDictionary = stubDictionary.copy(dictionaryId = testDictionaryId) + val testCard = stubCard.copy(cardId = testCardId, answered = 42, dictionaryId = testDictionaryId) + val expectedCard = testCard.copy(answered = 0) - var wasCalled = false - val repository = MockDbCardRepository( - invokeResetCard = { _, it -> - wasCalled = true - CardDbResponse( - card = if (it == testId) testResponseEntity else CardEntity.EMPTY, - ) + var isUpdateCardCalled = false + val cardRepository = MockDbCardRepository( + invokeUpdateCard = { + isUpdateCardCalled = true + if (it.cardId == testCardId) it else Assertions.fail() + }, + invokeFindCardById = { + if (it == testCardId) testCard else null + } + ) + val dictionaryRepository = MockDbDictionaryRepository( + invokeFindDictionaryById = { + if (it == testDictionaryId) testDictionary else null } ) - val context = testContext(CardOperation.RESET_CARD, repository) - context.requestCardEntityId = testId + val context = testContext( + op = CardOperation.RESET_CARD, + cardRepository = cardRepository, + dictionaryRepository = dictionaryRepository, + ) + context.requestCardEntityId = testCardId CardCorProcessor().execute(context) - Assertions.assertTrue(wasCalled) + Assertions.assertTrue(isUpdateCardCalled) Assertions.assertEquals(requestId(CardOperation.RESET_CARD), context.requestId) Assertions.assertEquals(AppStatus.OK, context.status) Assertions.assertTrue(context.errors.isEmpty()) - Assertions.assertEquals(testResponseEntity, context.responseCardEntity) + Assertions.assertEquals(expectedCard, context.responseCardEntity) } @Test diff --git a/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt b/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt index 7395ee72..15b5f4ac 100644 --- a/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt +++ b/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt @@ -9,7 +9,6 @@ import com.gitlab.sszuev.flashcards.model.domain.CardWordEntity import com.gitlab.sszuev.flashcards.model.domain.CardWordExampleEntity import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.Stage -import com.gitlab.sszuev.flashcards.repositories.CardDbResponse import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDataException import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse @@ -171,11 +170,6 @@ abstract class DbCardRepositoryTest { assertEquals(expected, a) } - - private fun assertNoErrors(res: CardDbResponse) { - assertEquals(0, res.errors.size) { "Has errors: ${res.errors}" } - } - private fun assertNoErrors(res: RemoveCardDbResponse) { assertEquals(0, res.errors.size) { "Has errors: ${res.errors}" } } @@ -283,25 +277,6 @@ abstract class DbCardRepositoryTest { } } - @Order(10) - @Test - fun `test get card & reset card success`() { - val request = snowCardEntity - val prev = repository.findCardById(request.cardId) - assertNotNull(prev) - assertCard(expected = request, actual = prev!!, ignoreChangeAt = true, ignoreId = false) - - val expected = request.copy(answered = 0) - val res = repository.resetCard(userId, request.cardId) - assertNoErrors(res) - val updated = res.card - assertCard(expected = expected, actual = updated, ignoreChangeAt = true, ignoreId = false) - - val now = repository.findCardById(request.cardId) - assertNotNull(now) - assertCard(expected = expected, actual = now!!, ignoreChangeAt = true, ignoreId = false) - } - @Order(11) @Test fun `test bulk update & find by card ids - success`() { diff --git a/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt b/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt index 3eb916c7..ed8e0916 100644 --- a/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt +++ b/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt @@ -4,7 +4,6 @@ import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.DictionaryId -import com.gitlab.sszuev.flashcards.repositories.CardDbResponse import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse @@ -20,7 +19,6 @@ class MockDbCardRepository( private val invokeCreateCard: (CardEntity) -> CardEntity = { CardEntity.EMPTY }, private val invokeUpdateCard: (CardEntity) -> CardEntity = { CardEntity.EMPTY }, private val invokeUpdateCards: (Iterable) -> List = { _ -> emptyList() }, - private val invokeResetCard: (AppUserId, CardId) -> CardDbResponse = { _, _ -> CardDbResponse.EMPTY }, private val invokeDeleteCard: (AppUserId, CardId) -> RemoveCardDbResponse = { _, _ -> RemoveCardDbResponse.EMPTY }, ) : DbCardRepository { @@ -40,7 +38,5 @@ class MockDbCardRepository( override fun updateCards(cardEntities: Iterable): List = invokeUpdateCards(cardEntities) - override fun resetCard(userId: AppUserId, cardId: CardId): CardDbResponse = invokeResetCard(userId, cardId) - override fun removeCard(userId: AppUserId, cardId: CardId): RemoveCardDbResponse = invokeDeleteCard(userId, cardId) } \ No newline at end of file diff --git a/db-mem/src/main/kotlin/MemDbCardRepository.kt b/db-mem/src/main/kotlin/MemDbCardRepository.kt index 753c1561..6ac68e29 100644 --- a/db-mem/src/main/kotlin/MemDbCardRepository.kt +++ b/db-mem/src/main/kotlin/MemDbCardRepository.kt @@ -14,7 +14,6 @@ import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.DictionaryId -import com.gitlab.sszuev.flashcards.repositories.CardDbResponse import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDataException import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse @@ -55,18 +54,6 @@ class MemDbCardRepository( return database.saveCard(cardEntity.toMemDbCard().copy(changedAt = timestamp)).toCardEntity() } - override fun resetCard(userId: AppUserId, cardId: CardId): CardDbResponse { - val timestamp = systemNow() - val card = - database.findCardById(cardId.asLong()) ?: return CardDbResponse(noCardFoundDbError("resetCard", cardId)) - val errors = mutableListOf() - checkDictionaryUser("resetCard", userId, card.dictionaryId.asDictionaryId(), cardId, errors) - if (errors.isNotEmpty()) { - return CardDbResponse(errors = errors) - } - return CardDbResponse(card = database.saveCard(card.copy(answered = 0, changedAt = timestamp)).toCardEntity()) - } - override fun removeCard(userId: AppUserId, cardId: CardId): RemoveCardDbResponse { val timestamp = systemNow() val card = database.findCardById(cardId.asLong()) ?: return RemoveCardDbResponse( @@ -85,7 +72,7 @@ class MemDbCardRepository( @Suppress("DuplicatedCode") private fun checkDictionaryUser( - operation: String, + @Suppress("SameParameterValue") operation: String, userId: AppUserId, dictionaryId: DictionaryId, entityId: Id, diff --git a/db-pg/src/main/kotlin/PgDbCardRepository.kt b/db-pg/src/main/kotlin/PgDbCardRepository.kt index b7c32cc2..a6072c97 100644 --- a/db-pg/src/main/kotlin/PgDbCardRepository.kt +++ b/db-pg/src/main/kotlin/PgDbCardRepository.kt @@ -19,7 +19,6 @@ import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.DictionaryId -import com.gitlab.sszuev.flashcards.repositories.CardDbResponse import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDataException import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse @@ -116,22 +115,6 @@ class PgDbCardRepository( res } - override fun resetCard(userId: AppUserId, cardId: CardId): CardDbResponse { - return connection.execute { - val timestamp = systemNow() - val found = PgDbCard.findById(cardId.asLong()) ?: return@execute CardDbResponse( - noCardFoundDbError("resetCard", cardId) - ) - val errors = mutableListOf() - checkDictionaryUser("resetCard", userId, found.dictionaryId.asDictionaryId(), cardId, errors) - if (errors.isNotEmpty()) { - return@execute CardDbResponse(errors = errors) - } - writeCardEntityToPgDbCard(from = found.toCardEntity().copy(answered = 0), to = found, timestamp = timestamp) - return@execute CardDbResponse(card = found.toCardEntity()) - } - } - override fun removeCard(userId: AppUserId, cardId: CardId): RemoveCardDbResponse { return connection.execute { val card = PgDbCard.findById(cardId.asLong())?.toCardEntity() ?: return@execute RemoveCardDbResponse( @@ -151,7 +134,7 @@ class PgDbCardRepository( @Suppress("DuplicatedCode") private fun checkDictionaryUser( - operation: String, + @Suppress("SameParameterValue") operation: String, userId: AppUserId, dictionaryId: DictionaryId, entityId: Id, From d9a135d43171904970c279a875e1c173c83f8313 Mon Sep 17 00:00:00 2001 From: sszuev Date: Tue, 26 Mar 2024 23:37:13 +0300 Subject: [PATCH 08/18] common & core & db: [#28] replace DbCardRepository#resetCard with deleteCard --- .../kotlin/repositories/DbCardRepository.kt | 17 +------ .../repositories/NoOpDbCardRepository.kt | 3 +- .../kotlin/processes/CardProcessWorkers.kt | 25 ++++++----- .../kotlin/CardCorProcessorRunCardsTest.kt | 35 ++++++++++----- .../kotlin/DbCardRepositoryTest.kt | 11 +---- .../kotlin/mocks/MockDbCardRepository.kt | 6 +-- db-mem/src/main/kotlin/MemDbCardRepository.kt | 45 +++---------------- db-pg/src/main/kotlin/PgDbCardRepository.kt | 45 +++---------------- 8 files changed, 57 insertions(+), 130 deletions(-) diff --git a/common/src/commonMain/kotlin/repositories/DbCardRepository.kt b/common/src/commonMain/kotlin/repositories/DbCardRepository.kt index 5796c539..af4558da 100644 --- a/common/src/commonMain/kotlin/repositories/DbCardRepository.kt +++ b/common/src/commonMain/kotlin/repositories/DbCardRepository.kt @@ -1,7 +1,5 @@ package com.gitlab.sszuev.flashcards.repositories -import com.gitlab.sszuev.flashcards.model.common.AppError -import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.DictionaryId @@ -55,19 +53,8 @@ interface DbCardRepository { fun updateCards(cardEntities: Iterable): List = cardEntities.map { updateCard(it) } /** - * Deletes card by id. + * Deletes the card from the database, returning records that were deleted. */ - fun removeCard(userId: AppUserId, cardId: CardId): RemoveCardDbResponse + fun deleteCard(cardId: CardId): CardEntity -} - -data class RemoveCardDbResponse( - val card: CardEntity = CardEntity.EMPTY, - val errors: List = emptyList(), -) { - constructor(error: AppError) : this(errors = listOf(error)) - - companion object { - val EMPTY = RemoveCardDbResponse(errors = emptyList()) - } } \ No newline at end of file diff --git a/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt b/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt index 4a25fff0..cd7ea45c 100644 --- a/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt +++ b/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt @@ -1,6 +1,5 @@ package com.gitlab.sszuev.flashcards.repositories -import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.DictionaryId @@ -21,7 +20,7 @@ object NoOpDbCardRepository : DbCardRepository { override fun updateCards(cardEntities: Iterable): List = noOp() - override fun removeCard(userId: AppUserId, cardId: CardId): RemoveCardDbResponse = noOp() + override fun deleteCard(cardId: CardId): CardEntity = noOp() private fun noOp(): Nothing { error("Must not be called.") diff --git a/core/src/main/kotlin/processes/CardProcessWorkers.kt b/core/src/main/kotlin/processes/CardProcessWorkers.kt index 0556e4c5..6715e409 100644 --- a/core/src/main/kotlin/processes/CardProcessWorkers.kt +++ b/core/src/main/kotlin/processes/CardProcessWorkers.kt @@ -11,7 +11,6 @@ import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.TTSResourceGet import com.gitlab.sszuev.flashcards.model.domain.TTSResourceId -import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse fun ChainDSL.processGetCard() = worker { this.name = "process get-card request" @@ -228,8 +227,21 @@ fun ChainDSL.processDeleteCard() = worker { } process { val userId = this.contextUserEntity.id - val res = this.repositories.cardRepository(this.workMode).removeCard(userId, this.normalizedRequestCardEntityId) - this.postProcess(res) + + val cardId = this.normalizedRequestCardEntityId + val card = this.repositories.cardRepository(this.workMode).findCardById(cardId) + if (card == null) { + this.errors.add(noCardFoundDataError("deleteCard", cardId)) + } else { + val dictionary = this.repositories.dictionaryRepository(this.workMode).findDictionaryById(card.dictionaryId) + if (dictionary == null) { + this.errors.add(noDictionaryFoundDataError("deleteCard", card.dictionaryId)) + } else if (dictionary.userId != userId) { + this.errors.add(forbiddenEntityDataError("deleteCard", card.cardId, userId)) + } else { + this.repositories.cardRepository(this.workMode).deleteCard(this.normalizedRequestCardEntityId) + } + } } onException { this.handleThrowable(CardOperation.DELETE_CARD, it) @@ -274,10 +286,3 @@ private suspend fun CardContext.postProcess( } return card.copy(words = words, sound = cardAudioId) } - -private fun CardContext.postProcess(res: RemoveCardDbResponse) { - if (res.errors.isNotEmpty()) { - this.errors.addAll(res.errors) - } - this.status = if (this.errors.isNotEmpty()) AppStatus.FAIL else AppStatus.RUN -} \ No newline at end of file diff --git a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt index 5150c501..16d3597e 100644 --- a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt +++ b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt @@ -24,7 +24,6 @@ import com.gitlab.sszuev.flashcards.model.domain.TTSResourceId import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository import com.gitlab.sszuev.flashcards.repositories.DbUserRepository -import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse import com.gitlab.sszuev.flashcards.repositories.TTSResourceRepository import com.gitlab.sszuev.flashcards.repositories.UserEntityDbResponse import com.gitlab.sszuev.flashcards.speaker.MockTTSResourceRepository @@ -625,23 +624,37 @@ internal class CardCorProcessorRunCardsTest { @Test fun `test delete-card success`() = runTest { - val testId = CardId("42") - val response = RemoveCardDbResponse() + val testDictionaryId = DictionaryId("42") + val testCardId = CardId("42") + val testCard = stubCard.copy(cardId = testCardId, dictionaryId = testDictionaryId) + val testDictionary = stubDictionary.copy(dictionaryId = testDictionaryId) - var wasCalled = false - val repository = MockDbCardRepository( - invokeDeleteCard = { _, it -> - wasCalled = true - if (it == testId) response else throw TestException() + var isDeleteCardCalled = false + val cardRepository = MockDbCardRepository( + invokeDeleteCard = { + isDeleteCardCalled = true + if (it == testCardId) testCard else Assertions.fail() + }, + invokeFindCardById = { + if (it == testCardId) testCard else Assertions.fail() + } + ) + val dictionaryRepository = MockDbDictionaryRepository( + invokeFindDictionaryById = { + if (it == testDictionaryId) testDictionary else Assertions.fail() } ) - val context = testContext(CardOperation.DELETE_CARD, repository) - context.requestCardEntityId = testId + val context = testContext( + op = CardOperation.DELETE_CARD, + cardRepository = cardRepository, + dictionaryRepository = dictionaryRepository, + ) + context.requestCardEntityId = testCardId CardCorProcessor().execute(context) - Assertions.assertTrue(wasCalled) + Assertions.assertTrue(isDeleteCardCalled) Assertions.assertEquals(requestId(CardOperation.DELETE_CARD), context.requestId) Assertions.assertEquals(AppStatus.OK, context.status) Assertions.assertTrue(context.errors.isEmpty()) diff --git a/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt b/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt index 15b5f4ac..dc8cd2dc 100644 --- a/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt +++ b/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt @@ -1,7 +1,6 @@ package com.gitlab.sszuev.flashcards.dbcommon import com.gitlab.sszuev.flashcards.common.asLong -import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.common.NONE import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardId @@ -11,7 +10,6 @@ import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.Stage import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDataException -import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse import kotlinx.datetime.Clock import kotlinx.datetime.Instant import org.junit.jupiter.api.Assertions @@ -36,7 +34,6 @@ abstract class DbCardRepositoryTest { abstract val repository: DbCardRepository companion object { - private val userId = AppUserId("42") private val drawCardEntity = CardEntity( cardId = CardId("38"), @@ -169,10 +166,6 @@ abstract class DbCardRepositoryTest { assertNotEquals(Instant.NONE, actual.changedAt) assertEquals(expected, a) } - - private fun assertNoErrors(res: RemoveCardDbResponse) { - assertEquals(0, res.errors.size) { "Has errors: ${res.errors}" } - } } @Test @@ -316,8 +309,8 @@ abstract class DbCardRepositoryTest { @Test fun `test get card & delete card success`() { val id = CardId("300") - val res = repository.removeCard(userId, id) - assertNoErrors(res) + val res = repository.deleteCard(id) + assertEquals(id, res.cardId) assertNull(repository.findCardById(id)) } diff --git a/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt b/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt index ed8e0916..dfbff616 100644 --- a/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt +++ b/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt @@ -1,11 +1,9 @@ package com.gitlab.sszuev.flashcards.dbcommon.mocks -import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.repositories.DbCardRepository -import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse /** * Does not work with `io.mockk:mockk` @@ -19,7 +17,7 @@ class MockDbCardRepository( private val invokeCreateCard: (CardEntity) -> CardEntity = { CardEntity.EMPTY }, private val invokeUpdateCard: (CardEntity) -> CardEntity = { CardEntity.EMPTY }, private val invokeUpdateCards: (Iterable) -> List = { _ -> emptyList() }, - private val invokeDeleteCard: (AppUserId, CardId) -> RemoveCardDbResponse = { _, _ -> RemoveCardDbResponse.EMPTY }, + private val invokeDeleteCard: (CardId) -> CardEntity = { _ -> CardEntity.EMPTY }, ) : DbCardRepository { override fun findCardById(cardId: CardId): CardEntity? = invokeFindCardById(cardId) @@ -38,5 +36,5 @@ class MockDbCardRepository( override fun updateCards(cardEntities: Iterable): List = invokeUpdateCards(cardEntities) - override fun removeCard(userId: AppUserId, cardId: CardId): RemoveCardDbResponse = invokeDeleteCard(userId, cardId) + override fun deleteCard(cardId: CardId): CardEntity = invokeDeleteCard(cardId) } \ No newline at end of file diff --git a/db-mem/src/main/kotlin/MemDbCardRepository.kt b/db-mem/src/main/kotlin/MemDbCardRepository.kt index 6ac68e29..eec7b605 100644 --- a/db-mem/src/main/kotlin/MemDbCardRepository.kt +++ b/db-mem/src/main/kotlin/MemDbCardRepository.kt @@ -1,22 +1,14 @@ package com.gitlab.sszuev.flashcards.dbmem import com.gitlab.sszuev.flashcards.common.asLong -import com.gitlab.sszuev.flashcards.common.forbiddenEntityDbError -import com.gitlab.sszuev.flashcards.common.noCardFoundDbError -import com.gitlab.sszuev.flashcards.common.noDictionaryFoundDbError import com.gitlab.sszuev.flashcards.common.systemNow import com.gitlab.sszuev.flashcards.common.validateCardEntityForCreate import com.gitlab.sszuev.flashcards.common.validateCardEntityForUpdate -import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbDictionary -import com.gitlab.sszuev.flashcards.model.Id -import com.gitlab.sszuev.flashcards.model.common.AppError -import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDataException -import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse class MemDbCardRepository( dbConfig: MemDbConfig = MemDbConfig(), @@ -54,40 +46,13 @@ class MemDbCardRepository( return database.saveCard(cardEntity.toMemDbCard().copy(changedAt = timestamp)).toCardEntity() } - override fun removeCard(userId: AppUserId, cardId: CardId): RemoveCardDbResponse { + override fun deleteCard(cardId: CardId): CardEntity { val timestamp = systemNow() - val card = database.findCardById(cardId.asLong()) ?: return RemoveCardDbResponse( - noCardFoundDbError("removeCard", cardId) - ) - val errors = mutableListOf() - checkDictionaryUser("removeCard", userId, card.dictionaryId.asDictionaryId(), cardId, errors) - if (errors.isNotEmpty()) { - return RemoveCardDbResponse(errors = errors) - } + val found = database.findCardById(cardId.asLong()) + ?: throw DbDataException("Can't find card, id = ${cardId.asLong()}") if (!database.deleteCardById(cardId.asLong())) { - return RemoveCardDbResponse(noCardFoundDbError("removeCard", cardId)) - } - return RemoveCardDbResponse(card = card.copy(changedAt = timestamp).toCardEntity()) - } - - @Suppress("DuplicatedCode") - private fun checkDictionaryUser( - @Suppress("SameParameterValue") operation: String, - userId: AppUserId, - dictionaryId: DictionaryId, - entityId: Id, - errors: MutableList - ): MemDbDictionary? { - val dictionary = database.findDictionaryById(dictionaryId.asLong()) - if (dictionary == null) { - errors.add(noDictionaryFoundDbError(operation, dictionaryId)) - return null - } - - if (dictionary.userId == userId.asLong()) { - return dictionary + throw DbDataException("Can't delete card, id = ${cardId.asLong()}") } - errors.add(forbiddenEntityDbError(operation, entityId, userId)) - return null + return found.copy(changedAt = timestamp).toCardEntity() } } \ No newline at end of file diff --git a/db-pg/src/main/kotlin/PgDbCardRepository.kt b/db-pg/src/main/kotlin/PgDbCardRepository.kt index a6072c97..d7748af5 100644 --- a/db-pg/src/main/kotlin/PgDbCardRepository.kt +++ b/db-pg/src/main/kotlin/PgDbCardRepository.kt @@ -3,25 +3,17 @@ package com.gitlab.sszuev.flashcards.dbpg import com.gitlab.sszuev.flashcards.common.asKotlin import com.gitlab.sszuev.flashcards.common.asLong import com.gitlab.sszuev.flashcards.common.detailsAsCommonCardDetailsDto -import com.gitlab.sszuev.flashcards.common.forbiddenEntityDbError -import com.gitlab.sszuev.flashcards.common.noCardFoundDbError -import com.gitlab.sszuev.flashcards.common.noDictionaryFoundDbError import com.gitlab.sszuev.flashcards.common.systemNow import com.gitlab.sszuev.flashcards.common.toJsonString import com.gitlab.sszuev.flashcards.common.validateCardEntityForCreate import com.gitlab.sszuev.flashcards.common.validateCardEntityForUpdate import com.gitlab.sszuev.flashcards.dbpg.dao.Cards import com.gitlab.sszuev.flashcards.dbpg.dao.PgDbCard -import com.gitlab.sszuev.flashcards.dbpg.dao.PgDbDictionary -import com.gitlab.sszuev.flashcards.model.Id -import com.gitlab.sszuev.flashcards.model.common.AppError -import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDataException -import com.gitlab.sszuev.flashcards.repositories.RemoveCardDbResponse import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.deleteWhere import org.jetbrains.exposed.sql.statements.BatchUpdateStatement @@ -115,40 +107,15 @@ class PgDbCardRepository( res } - override fun removeCard(userId: AppUserId, cardId: CardId): RemoveCardDbResponse { + override fun deleteCard(cardId: CardId): CardEntity { return connection.execute { - val card = PgDbCard.findById(cardId.asLong())?.toCardEntity() ?: return@execute RemoveCardDbResponse( - noCardFoundDbError("removeCard", cardId) - ) - val errors = mutableListOf() - checkDictionaryUser("removeCard", userId, card.dictionaryId, cardId, errors) - if (errors.isNotEmpty()) { - return@execute RemoveCardDbResponse(errors = errors) - } + val timestamp = systemNow() + val card = PgDbCard.findById(cardId.asLong())?.toCardEntity() + ?: throw DbDataException("Can't find card, id = ${cardId.asLong()}") if (Cards.deleteWhere { this.id eq cardId.asLong() } == 0) { - return@execute RemoveCardDbResponse(noCardFoundDbError("removeCard", cardId)) + throw DbDataException("Can't delete card, id = ${cardId.asLong()}") } - RemoveCardDbResponse(card = card) - } - } - - @Suppress("DuplicatedCode") - private fun checkDictionaryUser( - @Suppress("SameParameterValue") operation: String, - userId: AppUserId, - dictionaryId: DictionaryId, - entityId: Id, - errors: MutableList - ): PgDbDictionary? { - val dictionary = PgDbDictionary.findById(dictionaryId.asLong()) - if (dictionary == null) { - errors.add(noDictionaryFoundDbError(operation, dictionaryId)) - return null - } - if (dictionary.userId.value == userId.asLong()) { - return dictionary + card.copy(changedAt = timestamp.asKotlin()) } - errors.add(forbiddenEntityDbError(operation, entityId, userId)) - return null } } \ No newline at end of file From 1e269933cf04c21396c5a805e0e54146bdd9bb4b Mon Sep 17 00:00:00 2001 From: sszuev Date: Sat, 30 Mar 2024 22:55:19 +0300 Subject: [PATCH 09/18] common & core & db: [#28] replace CardEntity with DbCard in DbCardRepository --- .../commonMain/kotlin/repositories/DbCard.kt | 59 +++++++ .../kotlin/repositories/DbCardRepository.kt | 20 +-- .../repositories/NoOpDbCardRepository.kt | 20 +-- core/src/main/kotlin/mappers/DbMappers.kt | 51 ++++++ .../kotlin/processes/CardProcessWorkers.kt | 31 ++-- .../processes/DictionaryProcessWokers.kt | 3 +- .../kotlin/processes/SearchCardsHelper.kt | 4 +- .../kotlin/processes/UpdateCardsHelper.kt | 6 +- .../kotlin/CardCorProcessorRunCardsTest.kt | 105 +++++++++--- .../src/main/kotlin/DomainModelMappers.kt | 45 ++--- .../kotlin/DbCardRepositoryTest.kt | 157 ++++++++++-------- .../kotlin/mocks/MockDbCardRepository.kt | 36 ++-- db-mem/src/main/kotlin/MemDbCardRepository.kt | 39 ++--- db-mem/src/main/kotlin/MemDbEntityMapper.kt | 21 +-- db-pg/src/main/kotlin/PgDbCardRepository.kt | 55 +++--- db-pg/src/main/kotlin/PgDbEntityMapper.kt | 27 ++- specs/src/main/kotlin/Stubs.kt | 10 +- 17 files changed, 423 insertions(+), 266 deletions(-) create mode 100644 common/src/commonMain/kotlin/repositories/DbCard.kt create mode 100644 core/src/main/kotlin/mappers/DbMappers.kt diff --git a/common/src/commonMain/kotlin/repositories/DbCard.kt b/common/src/commonMain/kotlin/repositories/DbCard.kt new file mode 100644 index 00000000..f0760863 --- /dev/null +++ b/common/src/commonMain/kotlin/repositories/DbCard.kt @@ -0,0 +1,59 @@ +package com.gitlab.sszuev.flashcards.repositories + +import kotlinx.datetime.Instant + +private val none = Instant.fromEpochMilliseconds(Long.MIN_VALUE) +val Instant.Companion.NONE + get() = none + +data class DbCard( + val cardId: String, + val dictionaryId: String, + val words: List, + val stats: Map, + val details: Map, + val answered: Int?, + val changedAt: Instant, +) { + data class Word( + val word: String, + val transcription: String?, + val partOfSpeech: String?, + val examples: List, + val translations: List>, + ) { + data class Example( + val text: String, + val translation: String?, + ) { + companion object { + val NULL = Example( + text = "", + translation = null, + ) + } + } + + companion object { + val NULL = Word( + word = "", + transcription = null, + partOfSpeech = null, + examples = emptyList(), + translations = emptyList(), + ) + } + } + + companion object { + val NULL = DbCard( + cardId = "", + dictionaryId = "", + changedAt = Instant.NONE, + details = emptyMap(), + stats = emptyMap(), + answered = null, + words = emptyList(), + ) + } +} diff --git a/common/src/commonMain/kotlin/repositories/DbCardRepository.kt b/common/src/commonMain/kotlin/repositories/DbCardRepository.kt index af4558da..1298a34f 100644 --- a/common/src/commonMain/kotlin/repositories/DbCardRepository.kt +++ b/common/src/commonMain/kotlin/repositories/DbCardRepository.kt @@ -1,9 +1,5 @@ package com.gitlab.sszuev.flashcards.repositories -import com.gitlab.sszuev.flashcards.model.domain.CardEntity -import com.gitlab.sszuev.flashcards.model.domain.CardId -import com.gitlab.sszuev.flashcards.model.domain.DictionaryId - /** * Database repository to work with cards. */ @@ -12,23 +8,23 @@ interface DbCardRepository { /** * Finds card by id returning `null` if nothing found. */ - fun findCardById(cardId: CardId): CardEntity? + fun findCardById(cardId: String): DbCard? /** * Finds cards by dictionary id. */ - fun findCardsByDictionaryId(dictionaryId: DictionaryId): Sequence + fun findCardsByDictionaryId(dictionaryId: String): Sequence /** * Finds cards by dictionary ids. */ - fun findCardsByDictionaryIdIn(dictionaryIds: Iterable): Sequence = + fun findCardsByDictionaryIdIn(dictionaryIds: Iterable): Sequence = dictionaryIds.asSequence().flatMap { findCardsByDictionaryId(it) } /** * Finds cards by card ids. */ - fun findCardsByIdIn(cardIds: Iterable): Sequence = + fun findCardsByIdIn(cardIds: Iterable): Sequence = cardIds.asSequence().mapNotNull { findCardById(it) } /** @@ -37,7 +33,7 @@ interface DbCardRepository { * @throws DbDataException in case card cannot be created for some reason, * i.e., if the corresponding dictionary does not exist */ - fun createCard(cardEntity: CardEntity): CardEntity + fun createCard(cardEntity: DbCard): DbCard /** * Updates the card entity. @@ -45,16 +41,16 @@ interface DbCardRepository { * @throws DbDataException in case card cannot be created for some reason, * i.e., if the corresponding dictionary does not exist */ - fun updateCard(cardEntity: CardEntity): CardEntity + fun updateCard(cardEntity: DbCard): DbCard /** * Performs bulk update. */ - fun updateCards(cardEntities: Iterable): List = cardEntities.map { updateCard(it) } + fun updateCards(cardEntities: Iterable): List = cardEntities.map { updateCard(it) } /** * Deletes the card from the database, returning records that were deleted. */ - fun deleteCard(cardId: CardId): CardEntity + fun deleteCard(cardId: String): DbCard } \ No newline at end of file diff --git a/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt b/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt index cd7ea45c..26788a47 100644 --- a/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt +++ b/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt @@ -1,26 +1,22 @@ package com.gitlab.sszuev.flashcards.repositories -import com.gitlab.sszuev.flashcards.model.domain.CardEntity -import com.gitlab.sszuev.flashcards.model.domain.CardId -import com.gitlab.sszuev.flashcards.model.domain.DictionaryId - object NoOpDbCardRepository : DbCardRepository { - override fun findCardById(cardId: CardId): CardEntity = noOp() + override fun findCardById(cardId: String): DbCard = noOp() - override fun findCardsByDictionaryId(dictionaryId: DictionaryId): Sequence = noOp() + override fun findCardsByDictionaryId(dictionaryId: String): Sequence = noOp() - override fun findCardsByDictionaryIdIn(dictionaryIds: Iterable): Sequence = noOp() + override fun findCardsByDictionaryIdIn(dictionaryIds: Iterable): Sequence = noOp() - override fun findCardsByIdIn(cardIds: Iterable): Sequence = noOp() + override fun findCardsByIdIn(cardIds: Iterable): Sequence = noOp() - override fun createCard(cardEntity: CardEntity): CardEntity = noOp() + override fun createCard(cardEntity: DbCard): DbCard = noOp() - override fun updateCard(cardEntity: CardEntity): CardEntity = noOp() + override fun updateCard(cardEntity: DbCard): DbCard = noOp() - override fun updateCards(cardEntities: Iterable): List = noOp() + override fun updateCards(cardEntities: Iterable): List = noOp() - override fun deleteCard(cardId: CardId): CardEntity = noOp() + override fun deleteCard(cardId: String): DbCard = noOp() private fun noOp(): Nothing { error("Must not be called.") diff --git a/core/src/main/kotlin/mappers/DbMappers.kt b/core/src/main/kotlin/mappers/DbMappers.kt new file mode 100644 index 00000000..d552e737 --- /dev/null +++ b/core/src/main/kotlin/mappers/DbMappers.kt @@ -0,0 +1,51 @@ +package com.gitlab.sszuev.flashcards.core.mappers + +import com.gitlab.sszuev.flashcards.model.domain.CardEntity +import com.gitlab.sszuev.flashcards.model.domain.CardId +import com.gitlab.sszuev.flashcards.model.domain.CardWordEntity +import com.gitlab.sszuev.flashcards.model.domain.CardWordExampleEntity +import com.gitlab.sszuev.flashcards.model.domain.DictionaryId +import com.gitlab.sszuev.flashcards.model.domain.Stage +import com.gitlab.sszuev.flashcards.repositories.DbCard + +fun CardEntity.toDbCard() = DbCard( + cardId = this.cardId.asString(), + dictionaryId = this.dictionaryId.asString(), + answered = this.answered, + changedAt = this.changedAt, + stats = this.stats.mapKeys { it.key.name }, + words = this.words.map { it.toDbCardWord() }, + details = this.details, +) + +fun DbCard.toCardEntity() = CardEntity( + cardId = CardId(this.cardId), + dictionaryId = DictionaryId(this.dictionaryId), + answered = this.answered, + changedAt = this.changedAt, + stats = this.stats.mapKeys { Stage.valueOf(it.key) }, + words = this.words.map { it.toCardWordEntity() }, + details = this.details, +) + +private fun CardWordEntity.toDbCardWord() = DbCard.Word( + word = this.word, + transcription = this.transcription, + partOfSpeech = this.partOfSpeech, + examples = this.examples.map { it.toDbCardWordExample() }, + translations = this.translations, +) + +private fun CardWordExampleEntity.toDbCardWordExample() = + DbCard.Word.Example(text = this.text, translation = this.translation) + +private fun DbCard.Word.toCardWordEntity() = CardWordEntity( + word = this.word, + transcription = this.transcription, + partOfSpeech = this.partOfSpeech, + examples = this.examples.map { it.toCardWordExampleEntity() }, + translations = this.translations, +) + +private fun DbCard.Word.Example.toCardWordExampleEntity() = + CardWordExampleEntity(text = this.text, translation = this.translation) \ No newline at end of file diff --git a/core/src/main/kotlin/processes/CardProcessWorkers.kt b/core/src/main/kotlin/processes/CardProcessWorkers.kt index 6715e409..cd873c2c 100644 --- a/core/src/main/kotlin/processes/CardProcessWorkers.kt +++ b/core/src/main/kotlin/processes/CardProcessWorkers.kt @@ -1,6 +1,8 @@ package com.gitlab.sszuev.flashcards.core.processes import com.gitlab.sszuev.flashcards.CardContext +import com.gitlab.sszuev.flashcards.core.mappers.toCardEntity +import com.gitlab.sszuev.flashcards.core.mappers.toDbCard import com.gitlab.sszuev.flashcards.core.normalizers.normalize import com.gitlab.sszuev.flashcards.corlib.ChainDSL import com.gitlab.sszuev.flashcards.corlib.worker @@ -20,7 +22,7 @@ fun ChainDSL.processGetCard() = worker { process { val userId = this.contextUserEntity.id val cardId = this.normalizedRequestCardEntityId - val card = this.repositories.cardRepository(this.workMode).findCardById(cardId) + val card = this.repositories.cardRepository(this.workMode).findCardById(cardId.asString())?.toCardEntity() if (card == null) { this.errors.add(noCardFoundDataError("getCard", cardId)) } else { @@ -62,7 +64,8 @@ fun ChainDSL.processGetAllCards() = worker { this.errors.add(forbiddenEntityDataError("getAllCards", dictionaryId, userId)) } else { val cards = postProcess( - this.repositories.cardRepository(this.workMode).findCardsByDictionaryId(dictionaryId).iterator() + this.repositories.cardRepository(this.workMode) + .findCardsByDictionaryId(dictionaryId.asString()).map { it.toCardEntity() }.iterator() ) { dictionary } this.responseCardEntityList = cards } @@ -123,7 +126,8 @@ fun ChainDSL.processCreateCard() = worker { } else if (dictionary.userId != userId) { this.errors.add(forbiddenEntityDataError("createCard", dictionaryId, userId)) } else { - val res = this.repositories.cardRepository(this.workMode).createCard(this.normalizedRequestCardEntity) + val res = this.repositories.cardRepository(this.workMode) + .createCard(this.normalizedRequestCardEntity.toDbCard()).toCardEntity() this.responseCardEntity = postProcess(res) { dictionary } } this.status = if (this.errors.isNotEmpty()) AppStatus.FAIL else AppStatus.RUN @@ -147,7 +151,8 @@ fun ChainDSL.processUpdateCard() = worker { } else if (dictionary.userId != userId) { this.errors.add(forbiddenEntityDataError("updateCard", dictionaryId, userId)) } else { - val res = this.repositories.cardRepository(this.workMode).updateCard(this.normalizedRequestCardEntity) + val res = this.repositories.cardRepository(this.workMode) + .updateCard(this.normalizedRequestCardEntity.toDbCard()).toCardEntity() this.responseCardEntity = postProcess(res) { dictionary } } this.status = if (this.errors.isNotEmpty()) AppStatus.FAIL else AppStatus.RUN @@ -165,7 +170,8 @@ fun ChainDSL.processLearnCards() = worker { process { val userId = this.contextUserEntity.id val cardLearns = this.normalizedRequestCardLearnList.associateBy { it.cardId } - val foundCards = this.repositories.cardRepository(this.workMode).findCardsByIdIn(cardLearns.keys).toSet() + val foundCards = this.repositories.cardRepository(this.workMode) + .findCardsByIdIn(cardLearns.keys.map { it.asString() }).map { it.toCardEntity() }.toSet() val foundCardIds = foundCards.map { it.cardId }.toSet() val missedCardIds = cardLearns.keys - foundCardIds missedCardIds.forEach { @@ -181,7 +187,9 @@ fun ChainDSL.processLearnCards() = worker { } } if (errors.isEmpty()) { - this.responseCardEntityList = learnCards(foundCards, cardLearns) + this.responseCardEntityList = postProcess(learnCards(foundCards, cardLearns).iterator()) { + checkNotNull(foundDictionaries[it]) + } } this.status = if (this.errors.isNotEmpty()) AppStatus.FAIL else AppStatus.RUN } @@ -198,7 +206,7 @@ fun ChainDSL.processResetCard() = worker { process { val userId = this.contextUserEntity.id val cardId = this.normalizedRequestCardEntityId - val card = this.repositories.cardRepository(this.workMode).findCardById(cardId) + val card = this.repositories.cardRepository(this.workMode).findCardById(cardId.asString())?.toCardEntity() if (card == null) { this.errors.add(noCardFoundDataError("resetCard", cardId)) } else { @@ -209,8 +217,8 @@ fun ChainDSL.processResetCard() = worker { } else if (dictionary.userId != userId) { this.errors.add(forbiddenEntityDataError("resetCard", dictionaryId, userId)) } else { - val res = this.repositories.cardRepository(this.workMode).updateCard(card.copy(answered = 0)) - this.responseCardEntity = res + val res = this.repositories.cardRepository(this.workMode).updateCard(card.copy(answered = 0).toDbCard()) + this.responseCardEntity = postProcess(res.toCardEntity()) { dictionary } } } this.status = if (this.errors.isNotEmpty()) AppStatus.FAIL else AppStatus.RUN @@ -229,7 +237,7 @@ fun ChainDSL.processDeleteCard() = worker { val userId = this.contextUserEntity.id val cardId = this.normalizedRequestCardEntityId - val card = this.repositories.cardRepository(this.workMode).findCardById(cardId) + val card = this.repositories.cardRepository(this.workMode).findCardById(cardId.asString())?.toCardEntity() if (card == null) { this.errors.add(noCardFoundDataError("deleteCard", cardId)) } else { @@ -239,7 +247,8 @@ fun ChainDSL.processDeleteCard() = worker { } else if (dictionary.userId != userId) { this.errors.add(forbiddenEntityDataError("deleteCard", card.cardId, userId)) } else { - this.repositories.cardRepository(this.workMode).deleteCard(this.normalizedRequestCardEntityId) + this.repositories.cardRepository(this.workMode) + .deleteCard(this.normalizedRequestCardEntityId.asString()) } } } diff --git a/core/src/main/kotlin/processes/DictionaryProcessWokers.kt b/core/src/main/kotlin/processes/DictionaryProcessWokers.kt index fcf32155..88bf8e1d 100644 --- a/core/src/main/kotlin/processes/DictionaryProcessWokers.kt +++ b/core/src/main/kotlin/processes/DictionaryProcessWokers.kt @@ -23,7 +23,8 @@ fun ChainDSL.processGetAllDictionary() = worker { if (res.errors.isEmpty()) { this.responseDictionaryEntityList = res.dictionaries.map { dictionary -> val cards = - this.repositories.cardRepository(this.workMode).findCardsByDictionaryId(dictionary.dictionaryId) + this.repositories.cardRepository(this.workMode) + .findCardsByDictionaryId(dictionary.dictionaryId.asString()) .toList() val total = cards.size val known = cards.mapNotNull { it.answered }.count { it >= config.numberOfRightAnswers } diff --git a/core/src/main/kotlin/processes/SearchCardsHelper.kt b/core/src/main/kotlin/processes/SearchCardsHelper.kt index 35a69959..a1c0397f 100644 --- a/core/src/main/kotlin/processes/SearchCardsHelper.kt +++ b/core/src/main/kotlin/processes/SearchCardsHelper.kt @@ -1,6 +1,7 @@ package com.gitlab.sszuev.flashcards.core.processes import com.gitlab.sszuev.flashcards.CardContext +import com.gitlab.sszuev.flashcards.core.mappers.toCardEntity import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardWordEntity @@ -18,8 +19,9 @@ private val comparator: Comparator = Comparator { left, internal fun CardContext.findCardDeck(): List { val threshold = config.numberOfRightAnswers var cards = this.repositories.cardRepository(this.workMode) - .findCardsByDictionaryIdIn(this.normalizedRequestCardFilter.dictionaryIds) + .findCardsByDictionaryIdIn(this.normalizedRequestCardFilter.dictionaryIds.map { it.asString() }) .filter { !this.normalizedRequestCardFilter.onlyUnknown || (it.answered ?: -1) <= threshold } + .map { it.toCardEntity() } if (this.normalizedRequestCardFilter.random) { cards = cards.shuffled() } diff --git a/core/src/main/kotlin/processes/UpdateCardsHelper.kt b/core/src/main/kotlin/processes/UpdateCardsHelper.kt index 6830cebc..d5f4d030 100644 --- a/core/src/main/kotlin/processes/UpdateCardsHelper.kt +++ b/core/src/main/kotlin/processes/UpdateCardsHelper.kt @@ -1,6 +1,8 @@ package com.gitlab.sszuev.flashcards.core.processes import com.gitlab.sszuev.flashcards.CardContext +import com.gitlab.sszuev.flashcards.core.mappers.toCardEntity +import com.gitlab.sszuev.flashcards.core.mappers.toDbCard import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.CardLearn @@ -21,7 +23,7 @@ internal fun CardContext.learnCards( } details.merge(it.key, it.value) { a, b -> a + b } } - card.copy(stats = details, answered = answered.toInt()) + card.copy(stats = details, answered = answered.toInt()).toDbCard() } - return this.repositories.cardRepository(this.workMode).updateCards(cards) + return this.repositories.cardRepository(this.workMode).updateCards(cards).map { it.toCardEntity() } } \ No newline at end of file diff --git a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt index 16d3597e..00eb9247 100644 --- a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt +++ b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt @@ -2,6 +2,7 @@ package com.gitlab.sszuev.flashcards.core import com.gitlab.sszuev.flashcards.AppRepositories import com.gitlab.sszuev.flashcards.CardContext +import com.gitlab.sszuev.flashcards.core.mappers.toDbCard import com.gitlab.sszuev.flashcards.dbcommon.mocks.MockDbCardRepository import com.gitlab.sszuev.flashcards.dbcommon.mocks.MockDbDictionaryRepository import com.gitlab.sszuev.flashcards.dbcommon.mocks.MockDbUserRepository @@ -24,6 +25,7 @@ import com.gitlab.sszuev.flashcards.model.domain.TTSResourceId import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository import com.gitlab.sszuev.flashcards.repositories.DbUserRepository +import com.gitlab.sszuev.flashcards.repositories.TTSResourceIdResponse import com.gitlab.sszuev.flashcards.repositories.TTSResourceRepository import com.gitlab.sszuev.flashcards.repositories.UserEntityDbResponse import com.gitlab.sszuev.flashcards.speaker.MockTTSResourceRepository @@ -46,7 +48,9 @@ internal class CardCorProcessorRunCardsTest { cardRepository: DbCardRepository, userRepository: DbUserRepository = MockDbUserRepository(), dictionaryRepository: DbDictionaryRepository = MockDbDictionaryRepository(), - ttsResourceRepository: TTSResourceRepository = MockTTSResourceRepository(), + ttsResourceRepository: TTSResourceRepository = MockTTSResourceRepository(invokeFindResourceId = { + TTSResourceIdResponse.EMPTY.copy(TTSResourceId(it.lang.asString() + ":" + it.word)) + }), ): CardContext { val context = CardContext( operation = op, @@ -94,7 +98,7 @@ internal class CardCorProcessorRunCardsTest { val cardRepository = MockDbCardRepository( invokeFindCardById = { cardId -> findCardIsCalled = true - if (cardId == testId) testResponseCardEntity else null + if (cardId == testId.asString()) testResponseCardEntity.toDbCard() else null } ) @@ -166,7 +170,7 @@ internal class CardCorProcessorRunCardsTest { val cardRepository = MockDbCardRepository( invokeFindCardsByDictionaryId = { id -> isFindCardsCalled = true - if (id == testDictionaryId) testCards.asSequence() else emptySequence() + if (id == testDictionaryId.asString()) testCards.map { it.toDbCard() }.asSequence() else emptySequence() } ) val dictionaryRepository = MockDbDictionaryRepository( @@ -210,7 +214,9 @@ internal class CardCorProcessorRunCardsTest { val repository = MockDbCardRepository( invokeFindCardsByDictionaryId = { id -> isFindCardsCalled = true - if (id != testDictionaryId) testResponseEntities.asSequence() else throw TestException() + if (id != testDictionaryId.asString()) { + testResponseEntities.map { it.toDbCard() }.asSequence() + } else throw TestException() } ) val dictionaryRepository = MockDbDictionaryRepository( @@ -239,7 +245,10 @@ internal class CardCorProcessorRunCardsTest { @Test fun `test create-card success`() = runTest { val testDictionary = stubDictionary - val testResponseEntity = stubCard.copy(words = listOf(CardWordEntity("HHH")), sound = TTSResourceId.NONE) + val testResponseEntity = stubCard.copy( + words = listOf(CardWordEntity(word = "HHH", sound = TTSResourceId("sl:HHH"))), + sound = TTSResourceId("sl:HHH") + ) val testRequestEntity = stubCard.copy( words = listOf(CardWordEntity(word = "XXX")), cardId = CardId.NONE, @@ -249,9 +258,13 @@ internal class CardCorProcessorRunCardsTest { var isCreateCardCalled = false var isFindDictionaryCalled = false val cardRepository = MockDbCardRepository( - invokeCreateCard = { + invokeCreateCard = { card -> isCreateCardCalled = true - if (it.words == testRequestEntity.words) testResponseEntity else testRequestEntity + if (card.words.map { it.word } == testRequestEntity.words.map { it.word }) { + testResponseEntity.toDbCard() + } else { + testRequestEntity.toDbCard() + } } ) val dictionaryRepository = MockDbDictionaryRepository( @@ -287,9 +300,13 @@ internal class CardCorProcessorRunCardsTest { var isCreateCardCalled = false var isFindDictionaryCalled = false val cardRepository = MockDbCardRepository( - invokeCreateCard = { + invokeCreateCard = { card -> isCreateCardCalled = true - if (it.words == testRequestEntity.words) throw TestException() else testRequestEntity + if (card.words.map { it.word } == testRequestEntity.words.map { it.word }) { + throw TestException() + } else { + testRequestEntity.toDbCard() + } } ) val dictionaryRepository = MockDbDictionaryRepository( @@ -329,9 +346,13 @@ internal class CardCorProcessorRunCardsTest { var isFindCardsCalled = false var isFindDictionariesCalled = false val cardRepository = MockDbCardRepository( - invokeFindCardsByDictionaryIdIn = { + invokeFindCardsByDictionaryIdIn = { ids -> isFindCardsCalled = true - if (it == testFilter.dictionaryIds) testCards.asSequence() else emptySequence() + if (ids == testFilter.dictionaryIds.map { it.asString() }) { + testCards.asSequence().map { it.toDbCard() } + } else { + emptySequence() + } } ) val dictionaryRepository = MockDbDictionaryRepository( @@ -373,9 +394,13 @@ internal class CardCorProcessorRunCardsTest { var isFindCardsCalled = false var isFindDictionariesCalled = false val cardRepository = MockDbCardRepository( - invokeFindCardsByDictionaryIdIn = { + invokeFindCardsByDictionaryIdIn = { ids -> isFindCardsCalled = true - if (it == testFilter.dictionaryIds) throw TestException() else testCards.asSequence() + if (ids == testFilter.dictionaryIds.map { it.asString() }) { + throw TestException() + } else { + testCards.asSequence().map { it.toDbCard() } + } } ) val dictionaryRepository = MockDbDictionaryRepository( @@ -412,7 +437,11 @@ internal class CardCorProcessorRunCardsTest { val cardRepository = MockDbCardRepository( invokeUpdateCard = { isUpdateCardCalled = true - if (it.cardId == cardId) testResponseEntity else testRequestEntity + if (it.cardId == cardId.asString()) { + testResponseEntity.toDbCard() + } else { + testRequestEntity.toDbCard() + } } ) val dictionaryRepository = MockDbDictionaryRepository( @@ -450,7 +479,11 @@ internal class CardCorProcessorRunCardsTest { val cardRepository = MockDbCardRepository( invokeUpdateCard = { isUpdateCardCalled = true - if (it.words == testRequestEntity.words) throw TestException() else testRequestEntity + if (it.cardId == testRequestEntity.cardId.asString()) { + throw TestException() + } else { + testRequestEntity.toDbCard() + } } ) val dictionaryRepository = MockDbDictionaryRepository( @@ -497,17 +530,28 @@ internal class CardCorProcessorRunCardsTest { val cardRepository = MockDbCardRepository( invokeUpdateCards = { givenCards -> isUpdateCardsCalled = true - if (givenCards == expectedCards) expectedCards else emptyList() + if (givenCards.map { it.cardId } == expectedCards.map { it.cardId.asString() }) { + expectedCards.map { it.toDbCard() } + } else { + emptyList() + } }, invokeFindCardsByIdIn = { ids -> - if (ids == setOf(stubCard.cardId)) testCards.asSequence() else emptySequence() + if (ids == listOf(stubCard.cardId.asString())) { + testCards.asSequence().map { it.toDbCard() } + } else { + emptySequence() + } } ) val dictionaryRepository = MockDbDictionaryRepository( invokeFindDictionariesByIdIn = { givenDictionaryIds -> isFindDictionariesCalled = true - if (testDictionaries.map { it.dictionaryId } - .toSet() == givenDictionaryIds) testDictionaries.asSequence() else Assertions.fail() + if (testDictionaries.map { it.dictionaryId }.toSet() == givenDictionaryIds) { + testDictionaries.asSequence() + } else { + Assertions.fail() + } } ) @@ -534,7 +578,7 @@ internal class CardCorProcessorRunCardsTest { CardLearn(cardId = CardId("1"), details = mapOf(Stage.SELF_TEST to 42)), CardLearn(cardId = CardId("2"), details = mapOf(Stage.OPTIONS to 2, Stage.MOSAIC to 3)) ) - val ids = testLearn.map { it.cardId } + val ids = testLearn.map { it.cardId }.map { it.asString() } val testCards = listOf(stubCard.copy(cardId = CardId("1")), stubCard.copy(cardId = CardId("2"))) val testDictionaries = listOf(stubDictionary) @@ -551,14 +595,21 @@ internal class CardCorProcessorRunCardsTest { } }, invokeFindCardsByIdIn = { givenIds -> - if (givenIds == ids.toSet()) testCards.asSequence() else emptySequence() + if (givenIds == ids) { + testCards.asSequence().map { it.toDbCard() } + } else { + emptySequence() + } } ) val dictionaryRepository = MockDbDictionaryRepository( invokeFindDictionariesByIdIn = { givenDictionaryIds -> isFindDictionariesCalled = true - if (testDictionaries.map { it.dictionaryId } - .toSet() == givenDictionaryIds) testDictionaries.asSequence() else Assertions.fail() + if (testDictionaries.map { it.dictionaryId }.toSet() == givenDictionaryIds) { + testDictionaries.asSequence() + } else { + Assertions.fail() + } } ) @@ -594,10 +645,10 @@ internal class CardCorProcessorRunCardsTest { val cardRepository = MockDbCardRepository( invokeUpdateCard = { isUpdateCardCalled = true - if (it.cardId == testCardId) it else Assertions.fail() + if (it.cardId == testCardId.asString()) it else Assertions.fail() }, invokeFindCardById = { - if (it == testCardId) testCard else null + if (it == testCardId.asString()) testCard.toDbCard() else null } ) val dictionaryRepository = MockDbDictionaryRepository( @@ -633,10 +684,10 @@ internal class CardCorProcessorRunCardsTest { val cardRepository = MockDbCardRepository( invokeDeleteCard = { isDeleteCardCalled = true - if (it == testCardId) testCard else Assertions.fail() + if (it == testCardId.asString()) testCard.toDbCard() else Assertions.fail() }, invokeFindCardById = { - if (it == testCardId) testCard else Assertions.fail() + if (it == testCardId.asString()) testCard.toDbCard() else Assertions.fail() } ) val dictionaryRepository = MockDbDictionaryRepository( diff --git a/db-common/src/main/kotlin/DomainModelMappers.kt b/db-common/src/main/kotlin/DomainModelMappers.kt index bd24bf36..514981c7 100644 --- a/db-common/src/main/kotlin/DomainModelMappers.kt +++ b/db-common/src/main/kotlin/DomainModelMappers.kt @@ -1,18 +1,13 @@ package com.gitlab.sszuev.flashcards.common import com.gitlab.sszuev.flashcards.model.Id -import com.gitlab.sszuev.flashcards.model.domain.CardEntity -import com.gitlab.sszuev.flashcards.model.domain.CardId -import com.gitlab.sszuev.flashcards.model.domain.CardLearn -import com.gitlab.sszuev.flashcards.model.domain.CardWordEntity -import com.gitlab.sszuev.flashcards.model.domain.CardWordExampleEntity -import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.Stage +import com.gitlab.sszuev.flashcards.repositories.DbCard -fun validateCardEntityForCreate(entity: CardEntity) { +fun validateCardEntityForCreate(entity: DbCard) { val errors = mutableListOf() - if (entity.cardId != CardId.NONE) { - errors.add("no card-id specified") + if (entity.cardId.isNotBlank()) { + errors.add("card-id specified") } errors.addAll(validateCardEntity(entity)) require(errors.isEmpty()) { @@ -20,9 +15,9 @@ fun validateCardEntityForCreate(entity: CardEntity) { } } -fun validateCardEntityForUpdate(entity: CardEntity) { +fun validateCardEntityForUpdate(entity: DbCard) { val errors = mutableListOf() - if (entity.cardId == CardId.NONE) { + if (entity.cardId.isBlank()) { errors.add("no card-id specified.") } errors.addAll(validateCardEntity(entity)) @@ -31,9 +26,9 @@ fun validateCardEntityForUpdate(entity: CardEntity) { } } -private fun validateCardEntity(entity: CardEntity): List { +private fun validateCardEntity(entity: DbCard): List { val errors = mutableListOf() - if (entity.dictionaryId == DictionaryId.NONE) { + if (entity.dictionaryId.isBlank()) { errors.add("no dictionary-id specified") } if (entity.words.isEmpty()) { @@ -43,7 +38,7 @@ private fun validateCardEntity(entity: CardEntity): List { return errors } -private fun validateCardWords(words: List): List { +private fun validateCardWords(words: List): List { val errors = mutableListOf() val translations = words.flatMap { it.translations.flatten() }.filter { it.isNotBlank() } if (translations.isEmpty()) { @@ -52,14 +47,9 @@ private fun validateCardWords(words: List): List { return errors } -fun validateCardLearns(learns: Collection) { - val ids = learns.groupBy { it.cardId }.filter { it.value.size > 1 }.map { it.key } - require(ids.isEmpty()) { "Duplicate card ids: $ids" } -} - -fun CardEntity.wordsAsCommonWordDtoList(): List = words.map { it.toCommonWordDto() } +fun DbCard.wordsAsCommonWordDtoList(): List = words.map { it.toCommonWordDto() } -fun CardWordEntity.toCommonWordDto(): CommonWordDto = CommonWordDto( +fun DbCard.Word.toCommonWordDto(): CommonWordDto = CommonWordDto( word = this.word, transcription = this.transcription, partOfSpeech = this.partOfSpeech, @@ -67,7 +57,7 @@ fun CardWordEntity.toCommonWordDto(): CommonWordDto = CommonWordDto( translations = this.translations, ) -fun CommonWordDto.toCardWordEntity(): CardWordEntity = CardWordEntity( +fun CommonWordDto.toCardWordEntity(): DbCard.Word = DbCard.Word( word = word, transcription = transcription, translations = translations, @@ -75,23 +65,22 @@ fun CommonWordDto.toCardWordEntity(): CardWordEntity = CardWordEntity( examples = examples.map { it.toCardWordExampleEntity() } ) -private fun CommonExampleDto.toCardWordExampleEntity(): CardWordExampleEntity = CardWordExampleEntity( +private fun CommonExampleDto.toCardWordExampleEntity(): DbCard.Word.Example = DbCard.Word.Example( text = text, translation = translation, ) -fun CardEntity.detailsAsCommonCardDetailsDto(): CommonCardDetailsDto { - return CommonCardDetailsDto(this.details + this.stats.mapKeys { it.key.name }) +fun DbCard.detailsAsCommonCardDetailsDto(): CommonCardDetailsDto { + return CommonCardDetailsDto(this.details + this.stats.mapKeys { it.key }) } -fun CardWordExampleEntity.toCommonExampleDto(): CommonExampleDto = CommonExampleDto( +fun DbCard.Word.Example.toCommonExampleDto(): CommonExampleDto = CommonExampleDto( text = this.text, translation = this.translation, ) -fun CommonCardDetailsDto.toCardEntityStats(): Map = +fun CommonCardDetailsDto.toCardEntityStats(): Map = this.filterKeys { Stage.entries.map { s -> s.name }.contains(it) } - .mapKeys { Stage.valueOf(it.key) } .mapValues { it.value.toString().toLong() } fun CommonCardDetailsDto.toCardEntityDetails(): Map = diff --git a/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt b/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt index dc8cd2dc..be63a570 100644 --- a/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt +++ b/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt @@ -1,13 +1,8 @@ package com.gitlab.sszuev.flashcards.dbcommon -import com.gitlab.sszuev.flashcards.common.asLong import com.gitlab.sszuev.flashcards.model.common.NONE -import com.gitlab.sszuev.flashcards.model.domain.CardEntity -import com.gitlab.sszuev.flashcards.model.domain.CardId -import com.gitlab.sszuev.flashcards.model.domain.CardWordEntity -import com.gitlab.sszuev.flashcards.model.domain.CardWordExampleEntity -import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.Stage +import com.gitlab.sszuev.flashcards.repositories.DbCard import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDataException import kotlinx.datetime.Clock @@ -35,125 +30,147 @@ abstract class DbCardRepositoryTest { companion object { - private val drawCardEntity = CardEntity( - cardId = CardId("38"), - dictionaryId = DictionaryId("1"), + private val drawCardEntity = DbCard( + cardId = "38", + dictionaryId = "1", + changedAt = Instant.NONE, + details = emptyMap(), + stats = emptyMap(), + answered = null, words = listOf( - CardWordEntity( + DbCard.Word.NULL.copy( word = "draw", partOfSpeech = "verb", translations = listOf(listOf("рисовать"), listOf("чертить")), examples = emptyList(), ), - CardWordEntity( + DbCard.Word.NULL.copy( word = "drew", ), - CardWordEntity( + DbCard.Word.NULL.copy( word = "drawn", ), ), ) - private val forgiveCardEntity = CardEntity( - cardId = CardId("58"), - dictionaryId = DictionaryId("1"), + private val forgiveCardEntity = DbCard( + cardId = "58", + dictionaryId = "1", + changedAt = Instant.NONE, + details = emptyMap(), + stats = emptyMap(), + answered = null, words = listOf( - CardWordEntity( + DbCard.Word.NULL.copy( word = "forgive", partOfSpeech = "verb", translations = listOf(listOf("прощать")), examples = emptyList(), ), - CardWordEntity( + DbCard.Word.NULL.copy( word = "forgave", ), - CardWordEntity( + DbCard.Word.NULL.copy( word = "forgiven", ), ), ) - private val weatherCardEntity = CardEntity( - cardId = CardId("246"), - dictionaryId = DictionaryId("2"), + private val weatherCardEntity = DbCard( + cardId = "246", + dictionaryId = "2", + changedAt = Instant.NONE, + details = emptyMap(), + stats = emptyMap(), + answered = null, words = listOf( - CardWordEntity( + DbCard.Word( word = "weather", transcription = "'weðə", partOfSpeech = "noun", translations = listOf(listOf("погода")), examples = listOf( - CardWordExampleEntity(text = "weather forecast", translation = "прогноз погоды"), - CardWordExampleEntity(text = "weather bureau", translation = "бюро погоды"), - CardWordExampleEntity(text = "nasty weather", translation = "ненастная погода"), - CardWordExampleEntity(text = "spell of cold weather", translation = "похолодание"), + DbCard.Word.Example(text = "weather forecast", translation = "прогноз погоды"), + DbCard.Word.Example(text = "weather bureau", translation = "бюро погоды"), + DbCard.Word.Example(text = "nasty weather", translation = "ненастная погода"), + DbCard.Word.Example(text = "spell of cold weather", translation = "похолодание"), ), ), ), ) - private val climateCardEntity = CardEntity( + private val climateCardEntity = DbCard( cardId = weatherCardEntity.cardId, - dictionaryId = DictionaryId("2"), + dictionaryId = "2", + changedAt = Instant.NONE, + details = emptyMap(), + stats = mapOf(Stage.SELF_TEST.name to 3), + answered = null, words = listOf( - CardWordEntity( + DbCard.Word.NULL.copy( word = "climate", transcription = "ˈklaɪmɪt", partOfSpeech = "noun", translations = listOf(listOf("климат", "атмосфера", "обстановка"), listOf("климатические условия")), examples = listOf( - CardWordExampleEntity("Create a climate of fear, and it's easy to keep the borders closed."), - CardWordExampleEntity("The clock of climate change is ticking in these magnificent landscapes."), + DbCard.Word.Example.NULL.copy(text = "Create a climate of fear, and it's easy to keep the borders closed."), + DbCard.Word.Example.NULL.copy(text = "The clock of climate change is ticking in these magnificent landscapes."), ), ), ), - stats = mapOf(Stage.SELF_TEST to 3), ) - private val snowCardEntity = CardEntity( - cardId = CardId("247"), - dictionaryId = DictionaryId("2"), + private val snowCardEntity = DbCard( + cardId = "247", + dictionaryId = "2", + changedAt = Instant.NONE, + details = emptyMap(), + stats = emptyMap(), + answered = null, words = listOf( - CardWordEntity( + DbCard.Word.NULL.copy( word = "snow", transcription = "snəu", partOfSpeech = "noun", translations = listOf(listOf("снег")), examples = listOf( - CardWordExampleEntity(text = "It snows.", translation = "Идет снег."), - CardWordExampleEntity(text = "a flake of snow", translation = "снежинка"), - CardWordExampleEntity(text = "snow depth", translation = "высота снежного покрова"), + DbCard.Word.Example(text = "It snows.", translation = "Идет снег."), + DbCard.Word.Example(text = "a flake of snow", translation = "снежинка"), + DbCard.Word.Example(text = "snow depth", translation = "высота снежного покрова"), ), ), ), ) - private val newMurkyCardEntity = CardEntity( - dictionaryId = DictionaryId("2"), + private val newMurkyCardEntity = DbCard( + cardId = "", + dictionaryId = "2", + changedAt = Instant.NONE, + details = emptyMap(), + stats = mapOf(Stage.OPTIONS.name to 0), + answered = 42, words = listOf( - CardWordEntity( + DbCard.Word.NULL.copy( word = "murky", transcription = "ˈmɜːkɪ", partOfSpeech = "adjective", translations = listOf(listOf("темный"), listOf("пасмурный")), - examples = listOf(CardWordExampleEntity("Well, that's a murky issue, isn't it?")), + examples = listOf(DbCard.Word.Example.NULL.copy(text = "Well, that's a murky issue, isn't it?")), ), ), - stats = mapOf(Stage.OPTIONS to 0), - answered = 42, ) @Suppress("SameParameterValue") private fun assertCard( - expected: CardEntity, - actual: CardEntity, + expected: DbCard, + actual: DbCard, ignoreChangeAt: Boolean = true, ignoreId: Boolean = false ) { assertNotSame(expected, actual) var a = actual if (ignoreId) { - assertNotEquals(CardId.NONE, actual.cardId) - a = a.copy(cardId = CardId.NONE) + assertNotEquals("", actual.cardId) + a = a.copy(cardId = "") } else { assertEquals(expected.cardId, actual.cardId) } @@ -170,7 +187,7 @@ abstract class DbCardRepositoryTest { @Test fun `test get card not found`() { - val id = CardId("42000") + val id = "42000" val res = repository.findCardById(id) assertNull(res) } @@ -179,21 +196,21 @@ abstract class DbCardRepositoryTest { @Test fun `test get all cards success`() { // Business dictionary - val res1 = repository.findCardsByDictionaryId(DictionaryId("1")).toList() + val res1 = repository.findCardsByDictionaryId("1").toList() assertEquals(244, res1.size) - assertEquals("1", res1.map { it.dictionaryId.asString() }.toSet().single()) + assertEquals("1", res1.map { it.dictionaryId }.toSet().single()) // Weather dictionary - val res2 = repository.findCardsByDictionaryId(DictionaryId("2")).toList() + val res2 = repository.findCardsByDictionaryId("2").toList() assertEquals(65, res2.size) - assertEquals("2", res2.map { it.dictionaryId.asString() }.toSet().single()) + assertEquals("2", res2.map { it.dictionaryId }.toSet().single()) } @Order(2) @Test fun `test get all cards error unknown dictionary`() { val dictionaryId = "42" - val res = repository.findCardsByDictionaryId(DictionaryId(dictionaryId)).toList() + val res = repository.findCardsByDictionaryId(dictionaryId).toList() assertEquals(0, res.size) } @@ -201,10 +218,10 @@ abstract class DbCardRepositoryTest { @Test fun `test create card error unknown dictionary`() { val dictionaryId = "42" - val request = CardEntity( - dictionaryId = DictionaryId(dictionaryId), + val request = DbCard.NULL.copy( + dictionaryId = dictionaryId, words = listOf( - CardWordEntity( + DbCard.Word.NULL.copy( word = "xxx", transcription = "xxx", translations = listOf(listOf("xxx")), @@ -237,12 +254,12 @@ abstract class DbCardRepositoryTest { @Order(7) @Test fun `test update card error unknown card`() { - val id = CardId("4200") - val request = CardEntity.EMPTY.copy( + val id = "4200" + val request = DbCard.NULL.copy( cardId = id, - dictionaryId = DictionaryId("2"), + dictionaryId = "2", words = listOf( - CardWordEntity(word = "XXX", translations = listOf(listOf("xxx"))), + DbCard.Word.NULL.copy(word = "XXX", translations = listOf(listOf("xxx"))), ), ) Assertions.assertThrows(DbDataException::class.java) { @@ -253,13 +270,13 @@ abstract class DbCardRepositoryTest { @Order(8) @Test fun `test update card error unknown dictionary`() { - val cardId = CardId("42") - val dictionaryId = DictionaryId("4200") - val request = CardEntity.EMPTY.copy( + val cardId = "42" + val dictionaryId = "4200" + val request = DbCard.NULL.copy( cardId = cardId, dictionaryId = dictionaryId, words = listOf( - CardWordEntity( + DbCard.Word.NULL.copy( word = "XXX", translations = listOf(listOf("xxx")), ), @@ -281,7 +298,7 @@ abstract class DbCardRepositoryTest { val updated = repository.updateCards(toUpdate) assertEquals(3, updated.size) - val res1 = updated.sortedBy { it.cardId.asLong() } + val res1 = updated.sortedBy { it.cardId.toLong() } assertCard(expected = drawCardEntity.copy(answered = 42), actual = res1[0], ignoreChangeAt = true) assertCard(expected = forgiveCardEntity.copy(answered = 42), actual = res1[1], ignoreChangeAt = true) assertCard(expected = snowCardEntity.copy(answered = 42), actual = res1[2], ignoreChangeAt = true) @@ -291,7 +308,7 @@ abstract class DbCardRepositoryTest { val res2 = repository.findCardsByIdIn(setOf(forgiveCardEntity.cardId, snowCardEntity.cardId, drawCardEntity.cardId)) - .sortedBy { it.cardId.asLong() } + .sortedBy { it.cardId.toLong() } .toList() assertEquals(res1, res2) } @@ -302,13 +319,13 @@ abstract class DbCardRepositoryTest { val request = newMurkyCardEntity val res = repository.createCard(request) assertCard(expected = request, actual = res, ignoreChangeAt = true, ignoreId = true) - assertTrue(res.cardId.asString().matches("\\d+".toRegex())) + assertTrue(res.cardId.matches("\\d+".toRegex())) } @Order(42) @Test fun `test get card & delete card success`() { - val id = CardId("300") + val id = "300" val res = repository.deleteCard(id) assertEquals(id, res.cardId) diff --git a/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt b/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt index dfbff616..a305872c 100644 --- a/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt +++ b/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt @@ -1,8 +1,6 @@ package com.gitlab.sszuev.flashcards.dbcommon.mocks -import com.gitlab.sszuev.flashcards.model.domain.CardEntity -import com.gitlab.sszuev.flashcards.model.domain.CardId -import com.gitlab.sszuev.flashcards.model.domain.DictionaryId +import com.gitlab.sszuev.flashcards.repositories.DbCard import com.gitlab.sszuev.flashcards.repositories.DbCardRepository /** @@ -10,31 +8,31 @@ import com.gitlab.sszuev.flashcards.repositories.DbCardRepository * @see mockk#issue-288 */ class MockDbCardRepository( - private val invokeFindCardById: (CardId) -> CardEntity? = { null }, - private val invokeFindCardsByDictionaryId: (DictionaryId) -> Sequence = { emptySequence() }, - private val invokeFindCardsByDictionaryIdIn: (Iterable) -> Sequence = { emptySequence() }, - private val invokeFindCardsByIdIn: (Iterable) -> Sequence = { emptySequence() }, - private val invokeCreateCard: (CardEntity) -> CardEntity = { CardEntity.EMPTY }, - private val invokeUpdateCard: (CardEntity) -> CardEntity = { CardEntity.EMPTY }, - private val invokeUpdateCards: (Iterable) -> List = { _ -> emptyList() }, - private val invokeDeleteCard: (CardId) -> CardEntity = { _ -> CardEntity.EMPTY }, + private val invokeFindCardById: (String) -> DbCard? = { null }, + private val invokeFindCardsByDictionaryId: (String) -> Sequence = { emptySequence() }, + private val invokeFindCardsByDictionaryIdIn: (Iterable) -> Sequence = { emptySequence() }, + private val invokeFindCardsByIdIn: (Iterable) -> Sequence = { emptySequence() }, + private val invokeCreateCard: (DbCard) -> DbCard = { DbCard.NULL }, + private val invokeUpdateCard: (DbCard) -> DbCard = { DbCard.NULL }, + private val invokeUpdateCards: (Iterable) -> List = { _ -> emptyList() }, + private val invokeDeleteCard: (String) -> DbCard = { _ -> DbCard.NULL }, ) : DbCardRepository { - override fun findCardById(cardId: CardId): CardEntity? = invokeFindCardById(cardId) + override fun findCardById(cardId: String): DbCard? = invokeFindCardById(cardId) - override fun findCardsByDictionaryId(dictionaryId: DictionaryId): Sequence = + override fun findCardsByDictionaryId(dictionaryId: String): Sequence = invokeFindCardsByDictionaryId(dictionaryId) - override fun findCardsByDictionaryIdIn(dictionaryIds: Iterable): Sequence = + override fun findCardsByDictionaryIdIn(dictionaryIds: Iterable): Sequence = invokeFindCardsByDictionaryIdIn(dictionaryIds) - override fun findCardsByIdIn(cardIds: Iterable): Sequence = invokeFindCardsByIdIn(cardIds) + override fun findCardsByIdIn(cardIds: Iterable): Sequence = invokeFindCardsByIdIn(cardIds) - override fun createCard(cardEntity: CardEntity): CardEntity = invokeCreateCard(cardEntity) + override fun createCard(cardEntity: DbCard): DbCard = invokeCreateCard(cardEntity) - override fun updateCard(cardEntity: CardEntity): CardEntity = invokeUpdateCard(cardEntity) + override fun updateCard(cardEntity: DbCard): DbCard = invokeUpdateCard(cardEntity) - override fun updateCards(cardEntities: Iterable): List = invokeUpdateCards(cardEntities) + override fun updateCards(cardEntities: Iterable): List = invokeUpdateCards(cardEntities) - override fun deleteCard(cardId: CardId): CardEntity = invokeDeleteCard(cardId) + override fun deleteCard(cardId: String): DbCard = invokeDeleteCard(cardId) } \ No newline at end of file diff --git a/db-mem/src/main/kotlin/MemDbCardRepository.kt b/db-mem/src/main/kotlin/MemDbCardRepository.kt index eec7b605..7b8a2678 100644 --- a/db-mem/src/main/kotlin/MemDbCardRepository.kt +++ b/db-mem/src/main/kotlin/MemDbCardRepository.kt @@ -1,12 +1,9 @@ package com.gitlab.sszuev.flashcards.dbmem -import com.gitlab.sszuev.flashcards.common.asLong import com.gitlab.sszuev.flashcards.common.systemNow import com.gitlab.sszuev.flashcards.common.validateCardEntityForCreate import com.gitlab.sszuev.flashcards.common.validateCardEntityForUpdate -import com.gitlab.sszuev.flashcards.model.domain.CardEntity -import com.gitlab.sszuev.flashcards.model.domain.CardId -import com.gitlab.sszuev.flashcards.model.domain.DictionaryId +import com.gitlab.sszuev.flashcards.repositories.DbCard import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDataException @@ -15,17 +12,17 @@ class MemDbCardRepository( ) : DbCardRepository { private val database = MemDatabase.get(dbConfig.dataLocation) - override fun findCardById(cardId: CardId): CardEntity? { - require(cardId != CardId.NONE) - return database.findCardById(cardId.asLong())?.toCardEntity() + override fun findCardById(cardId: String): DbCard? { + require(cardId.isNotBlank()) + return database.findCardById(cardId.toLong())?.toCardEntity() } - override fun findCardsByDictionaryId(dictionaryId: DictionaryId): Sequence { - require(dictionaryId != DictionaryId.NONE) - return database.findCardsByDictionaryId(dictionaryId.asLong()).map { it.toCardEntity() } + override fun findCardsByDictionaryId(dictionaryId: String): Sequence { + require(dictionaryId.isNotBlank()) + return database.findCardsByDictionaryId(dictionaryId.toLong()).map { it.toCardEntity() } } - override fun createCard(cardEntity: CardEntity): CardEntity { + override fun createCard(cardEntity: DbCard): DbCard { validateCardEntityForCreate(cardEntity) val timestamp = systemNow() return try { @@ -35,23 +32,23 @@ class MemDbCardRepository( } } - override fun updateCard(cardEntity: CardEntity): CardEntity { + override fun updateCard(cardEntity: DbCard): DbCard { validateCardEntityForUpdate(cardEntity) - val found = database.findCardById(cardEntity.cardId.asLong()) - ?: throw DbDataException("Can't find card, id = ${cardEntity.cardId.asLong()}") - if (found.dictionaryId != cardEntity.dictionaryId.asLong()) { - throw DbDataException("Changing dictionary-id is not allowed; card id = ${cardEntity.cardId.asLong()}") + val found = database.findCardById(cardEntity.cardId.toLong()) + ?: throw DbDataException("Can't find card, id = ${cardEntity.cardId.toLong()}") + if (found.dictionaryId != cardEntity.dictionaryId.toLong()) { + throw DbDataException("Changing dictionary-id is not allowed; card id = ${cardEntity.cardId.toLong()}") } val timestamp = systemNow() return database.saveCard(cardEntity.toMemDbCard().copy(changedAt = timestamp)).toCardEntity() } - override fun deleteCard(cardId: CardId): CardEntity { + override fun deleteCard(cardId: String): DbCard { val timestamp = systemNow() - val found = database.findCardById(cardId.asLong()) - ?: throw DbDataException("Can't find card, id = ${cardId.asLong()}") - if (!database.deleteCardById(cardId.asLong())) { - throw DbDataException("Can't delete card, id = ${cardId.asLong()}") + val found = database.findCardById(cardId.toLong()) + ?: throw DbDataException("Can't find card, id = ${cardId.toLong()}") + if (!database.deleteCardById(cardId.toLong())) { + throw DbDataException("Can't delete card, id = ${cardId.toLong()}") } return found.copy(changedAt = timestamp).toCardEntity() } diff --git a/db-mem/src/main/kotlin/MemDbEntityMapper.kt b/db-mem/src/main/kotlin/MemDbEntityMapper.kt index eb5bcd8f..78e4ea09 100644 --- a/db-mem/src/main/kotlin/MemDbEntityMapper.kt +++ b/db-mem/src/main/kotlin/MemDbEntityMapper.kt @@ -34,12 +34,11 @@ import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbWord import com.gitlab.sszuev.flashcards.model.common.AppAuthId import com.gitlab.sszuev.flashcards.model.common.AppUserEntity import com.gitlab.sszuev.flashcards.model.common.AppUserId -import com.gitlab.sszuev.flashcards.model.domain.CardEntity -import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.LangEntity import com.gitlab.sszuev.flashcards.model.domain.LangId +import com.gitlab.sszuev.flashcards.repositories.DbCard import java.util.UUID internal fun MemDbUser.detailsAsJsonString(): String { @@ -145,11 +144,11 @@ internal fun DictionaryEntity.toMemDbDictionary(): MemDbDictionary = MemDbDictio details = emptyMap(), ) -internal fun MemDbCard.toCardEntity(): CardEntity { +internal fun MemDbCard.toCardEntity(): DbCard { val details: CommonCardDetailsDto = this.detailsAsCommonCardDetailsDto() - return CardEntity( - cardId = id?.asCardId() ?: CardId.NONE, - dictionaryId = dictionaryId?.asDictionaryId() ?: DictionaryId.NONE, + return DbCard( + cardId = id?.toString() ?: "", + dictionaryId = dictionaryId?.toString() ?: "", words = this.words.map { it.toCommonWordDto() }.map { it.toCardWordEntity() }, details = details.toCardEntityDetails(), stats = details.toCardEntityStats(), @@ -158,10 +157,10 @@ internal fun MemDbCard.toCardEntity(): CardEntity { ) } -internal fun CardEntity.toMemDbCard(): MemDbCard { - val dictionaryId = dictionaryId.asLong() +internal fun DbCard.toMemDbCard(): MemDbCard { + val dictionaryId = dictionaryId.toLong() return MemDbCard( - id = if (this.cardId == CardId.NONE) null else this.cardId.asLong(), + id = if (this.cardId.isBlank()) null else this.cardId.toLong(), dictionaryId = dictionaryId, words = this.wordsAsCommonWordDtoList().map { it.toMemDbWord() }, details = this.detailsAsCommonCardDetailsDto().toMemDbCardDetails(), @@ -219,10 +218,6 @@ private fun Long.asUserId(): AppUserId = AppUserId(toString()) private fun String.asLangId(): LangId = LangId(this) -internal fun Long.asCardId(): CardId = CardId(toString()) - internal fun Long.asDictionaryId(): DictionaryId = DictionaryId(toString()) -internal fun Long?.asDictionaryId(): DictionaryId = DictionaryId(checkNotNull(this).toString()) - private fun UUID.asAppAuthId(): AppAuthId = AppAuthId(toString()) \ No newline at end of file diff --git a/db-pg/src/main/kotlin/PgDbCardRepository.kt b/db-pg/src/main/kotlin/PgDbCardRepository.kt index d7748af5..9427881f 100644 --- a/db-pg/src/main/kotlin/PgDbCardRepository.kt +++ b/db-pg/src/main/kotlin/PgDbCardRepository.kt @@ -1,7 +1,6 @@ package com.gitlab.sszuev.flashcards.dbpg import com.gitlab.sszuev.flashcards.common.asKotlin -import com.gitlab.sszuev.flashcards.common.asLong import com.gitlab.sszuev.flashcards.common.detailsAsCommonCardDetailsDto import com.gitlab.sszuev.flashcards.common.systemNow import com.gitlab.sszuev.flashcards.common.toJsonString @@ -9,9 +8,7 @@ import com.gitlab.sszuev.flashcards.common.validateCardEntityForCreate import com.gitlab.sszuev.flashcards.common.validateCardEntityForUpdate import com.gitlab.sszuev.flashcards.dbpg.dao.Cards import com.gitlab.sszuev.flashcards.dbpg.dao.PgDbCard -import com.gitlab.sszuev.flashcards.model.domain.CardEntity -import com.gitlab.sszuev.flashcards.model.domain.CardId -import com.gitlab.sszuev.flashcards.model.domain.DictionaryId +import com.gitlab.sszuev.flashcards.repositories.DbCard import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDataException import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq @@ -28,38 +25,38 @@ class PgDbCardRepository( PgDbConnector.connection(dbConfig) } - override fun findCardById(cardId: CardId): CardEntity? { - require(cardId != CardId.NONE) + override fun findCardById(cardId: String): DbCard? { + require(cardId.isNotBlank()) return connection.execute { - PgDbCard.findById(cardId.asLong())?.toCardEntity() + PgDbCard.findById(cardId.toLong())?.toCardEntity() } } - override fun findCardsByDictionaryId(dictionaryId: DictionaryId): Sequence { - require(dictionaryId != DictionaryId.NONE) + override fun findCardsByDictionaryId(dictionaryId: String): Sequence { + require(dictionaryId.isNotBlank()) return connection.execute { - PgDbCard.find { Cards.dictionaryId eq dictionaryId.asLong() }.map { it.toCardEntity() }.asSequence() + PgDbCard.find { Cards.dictionaryId eq dictionaryId.toLong() }.map { it.toCardEntity() }.asSequence() } } - override fun findCardsByDictionaryIdIn(dictionaryIds: Iterable): Sequence { + override fun findCardsByDictionaryIdIn(dictionaryIds: Iterable): Sequence { return connection.execute { PgDbCard.find { Cards.dictionaryId inList - dictionaryIds.onEach { require(it != DictionaryId.NONE) }.map { it.asRecordId() }.toSet() + dictionaryIds.onEach { require(it.isNotBlank()) }.map { it.toDictionariesId() }.toSet() }.map { it.toCardEntity() }.asSequence() } } - override fun findCardsByIdIn(cardIds: Iterable): Sequence { + override fun findCardsByIdIn(cardIds: Iterable): Sequence { return connection.execute { PgDbCard.find { - Cards.id inList cardIds.onEach { require(it != CardId.NONE) }.map { it.asRecordId() }.toSet() + Cards.id inList cardIds.onEach { require(it.isNotBlank()) }.map { it.toCardsId() }.toSet() }.map { it.toCardEntity() }.asSequence() } } - override fun createCard(cardEntity: CardEntity): CardEntity { + override fun createCard(cardEntity: DbCard): DbCard { validateCardEntityForCreate(cardEntity) return connection.execute { val timestamp = systemNow() @@ -73,13 +70,13 @@ class PgDbCardRepository( } } - override fun updateCard(cardEntity: CardEntity): CardEntity { + override fun updateCard(cardEntity: DbCard): DbCard { validateCardEntityForUpdate(cardEntity) return connection.execute { - val found = PgDbCard.findById(cardEntity.cardId.asRecordId()) - ?: throw DbDataException("Can't find card id = ${cardEntity.cardId.asLong()}") - if (found.dictionaryId.value != cardEntity.dictionaryId.asLong()) { - throw DbDataException("Changing dictionary-id is not allowed; card id = ${cardEntity.cardId.asLong()}") + val found = PgDbCard.findById(cardEntity.cardId.toCardsId()) + ?: throw DbDataException("Can't find card id = ${cardEntity.cardId}") + if (found.dictionaryId.value != cardEntity.dictionaryId.toLong()) { + throw DbDataException("Changing dictionary-id is not allowed; card id = ${cardEntity.cardId}") } val timestamp = systemNow() writeCardEntityToPgDbCard(from = cardEntity, to = found, timestamp = timestamp) @@ -87,14 +84,14 @@ class PgDbCardRepository( } } - override fun updateCards(cardEntities: Iterable): List = connection.execute { - val res = mutableListOf() + override fun updateCards(cardEntities: Iterable): List = connection.execute { + val res = mutableListOf() val timestamp = systemNow() BatchUpdateStatement(Cards).apply { cardEntities.onEach { validateCardEntityForUpdate(it) - addBatch(it.cardId.asRecordId()) - this[Cards.dictionaryId] = it.dictionaryId.asRecordId() + addBatch(it.cardId.toCardsId()) + this[Cards.dictionaryId] = it.dictionaryId.toDictionariesId() this[Cards.words] = it.toPgDbCardWordsJson() this[Cards.answered] = it.answered this[Cards.details] = it.detailsAsCommonCardDetailsDto().toJsonString() @@ -107,13 +104,13 @@ class PgDbCardRepository( res } - override fun deleteCard(cardId: CardId): CardEntity { + override fun deleteCard(cardId: String): DbCard { return connection.execute { val timestamp = systemNow() - val card = PgDbCard.findById(cardId.asLong())?.toCardEntity() - ?: throw DbDataException("Can't find card, id = ${cardId.asLong()}") - if (Cards.deleteWhere { this.id eq cardId.asLong() } == 0) { - throw DbDataException("Can't delete card, id = ${cardId.asLong()}") + val card = PgDbCard.findById(cardId.toCardsId())?.toCardEntity() + ?: throw DbDataException("Can't find card, id = $cardId") + if (Cards.deleteWhere { this.id eq cardId.toLong() } == 0) { + throw DbDataException("Can't delete card, id = $cardId") } card.copy(changedAt = timestamp.asKotlin()) } diff --git a/db-pg/src/main/kotlin/PgDbEntityMapper.kt b/db-pg/src/main/kotlin/PgDbEntityMapper.kt index a68cd127..b03418d6 100644 --- a/db-pg/src/main/kotlin/PgDbEntityMapper.kt +++ b/db-pg/src/main/kotlin/PgDbEntityMapper.kt @@ -22,12 +22,11 @@ import com.gitlab.sszuev.flashcards.dbpg.dao.Users import com.gitlab.sszuev.flashcards.model.common.AppAuthId import com.gitlab.sszuev.flashcards.model.common.AppUserEntity import com.gitlab.sszuev.flashcards.model.common.AppUserId -import com.gitlab.sszuev.flashcards.model.domain.CardEntity -import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.LangEntity import com.gitlab.sszuev.flashcards.model.domain.LangId +import com.gitlab.sszuev.flashcards.repositories.DbCard import org.jetbrains.exposed.dao.id.EntityID import java.time.LocalDateTime import java.util.UUID @@ -45,12 +44,12 @@ internal fun PgDbDictionary.toDictionaryEntity(): DictionaryEntity = DictionaryE targetLang = createLangEntity(this.targetLang), ) -internal fun PgDbCard.toCardEntity(): CardEntity { +internal fun PgDbCard.toCardEntity(): DbCard { val words = parseCardWordsJson(this.words) val details = parseCardDetailsJson(this.details) - return CardEntity( - cardId = this.id.asCardId(), - dictionaryId = this.dictionaryId.asDictionaryId(), + return DbCard( + cardId = this.id.value.toString(), + dictionaryId = this.dictionaryId.value.toString(), words = words.map { it.toCardWordEntity() }, details = details.toCardEntityDetails(), stats = details.toCardEntityStats(), @@ -59,15 +58,15 @@ internal fun PgDbCard.toCardEntity(): CardEntity { ) } -internal fun writeCardEntityToPgDbCard(from: CardEntity, to: PgDbCard, timestamp: LocalDateTime) { - to.dictionaryId = from.dictionaryId.asRecordId() +internal fun writeCardEntityToPgDbCard(from: DbCard, to: PgDbCard, timestamp: LocalDateTime) { + to.dictionaryId = from.dictionaryId.toDictionariesId() to.words = from.toPgDbCardWordsJson() to.answered = from.answered to.details = from.detailsAsCommonCardDetailsDto().toJsonString() to.changedAt = timestamp } -internal fun CardEntity.toPgDbCardWordsJson(): String = wordsAsCommonWordDtoList().toJsonString() +internal fun DbCard.toPgDbCardWordsJson(): String = wordsAsCommonWordDtoList().toJsonString() internal fun DocumentCard.toPgDbCardWordsJson(): String = toCommonWordDtoList().toJsonString() @@ -75,13 +74,11 @@ internal fun EntityID.asUserId(): AppUserId = AppUserId(value.toString()) internal fun EntityID.asDictionaryId(): DictionaryId = value.asDictionaryId() -internal fun EntityID.asCardId(): CardId = value.asCardId() - internal fun AppUserId.asRecordId(): EntityID = EntityID(asLong(), Users) -internal fun DictionaryId.asRecordId(): EntityID = EntityID(asLong(), Dictionaries) +internal fun String.toDictionariesId(): EntityID = EntityID(toLong(), Dictionaries) -internal fun CardId.asRecordId(): EntityID = EntityID(asLong(), Cards) +internal fun String.toCardsId(): EntityID = EntityID(toLong(), Cards) internal fun createLangEntity(tag: String) = LangEntity( langId = LangId(tag), @@ -90,6 +87,4 @@ internal fun createLangEntity(tag: String) = LangEntity( private fun UUID.asAppAuthId(): AppAuthId = AppAuthId(toString()) -internal fun Long.asDictionaryId(): DictionaryId = DictionaryId(toString()) - -internal fun Long.asCardId(): CardId = CardId(toString()) \ No newline at end of file +internal fun Long.asDictionaryId(): DictionaryId = DictionaryId(toString()) \ No newline at end of file diff --git a/specs/src/main/kotlin/Stubs.kt b/specs/src/main/kotlin/Stubs.kt index ecd7cdfc..b2eda14d 100644 --- a/specs/src/main/kotlin/Stubs.kt +++ b/specs/src/main/kotlin/Stubs.kt @@ -53,25 +53,27 @@ val stubCard = CardEntity( it ) }, - sound = TTSResourceId("en:stub"), + sound = TTSResourceId("sl:stub"), ), ), stats = mapOf(Stage.SELF_TEST to 42, Stage.OPTIONS to 21), - sound = TTSResourceId("en:stub"), + sound = TTSResourceId("sl:stub"), ) val stubCards = IntRange(1, 3) .flatMap { dictionaryId -> IntRange(1, 42).map { cardId -> dictionaryId to cardId } } .map { + val word = "XXX-${it.first}-${it.second}" stubCard.copy( cardId = CardId(it.second.toString()), dictionaryId = DictionaryId(it.first.toString()), words = listOf( CardWordEntity( - word = "XXX-${it.first}-${it.second}" + word = word, + sound = TTSResourceId("sl:$word"), ), ), - sound = TTSResourceId.NONE, + sound = TTSResourceId("sl:$word"), ) } From 712569a4cbd4af5d9b448de781f8ea910f418257 Mon Sep 17 00:00:00 2001 From: sszuev Date: Tue, 2 Apr 2024 22:48:11 +0300 Subject: [PATCH 10/18] common & core & db: [#28] replace DictionaryEntity with DbDictionary in find operations --- .../kotlin/repositories/DbDictionary.kt | 19 +++++++ .../repositories/DbDictionaryRepository.kt | 7 ++- .../commonMain/kotlin/repositories/DbLang.kt | 10 ++++ .../NoOpDbDictionaryRepository.kt | 3 +- core/src/main/kotlin/mappers/DbMappers.kt | 34 +++++++++++- .../kotlin/processes/CardProcessWorkers.kt | 27 ++++++--- .../kotlin/CardCorProcessorRunCardsTest.kt | 55 +++++++++++++------ .../kotlin/DbDictionaryRepositoryTest.kt | 9 ++- .../mocks/MockDbDictionaryRepository.kt | 10 ++-- .../main/kotlin/MemDbDictionaryRepository.kt | 5 +- db-mem/src/main/kotlin/MemDbEntityMapper.kt | 15 +++++ .../main/kotlin/PgDbDictionaryRepository.kt | 5 +- db-pg/src/main/kotlin/PgDbEntityMapper.kt | 15 +++++ 13 files changed, 169 insertions(+), 45 deletions(-) create mode 100644 common/src/commonMain/kotlin/repositories/DbDictionary.kt create mode 100644 common/src/commonMain/kotlin/repositories/DbLang.kt diff --git a/common/src/commonMain/kotlin/repositories/DbDictionary.kt b/common/src/commonMain/kotlin/repositories/DbDictionary.kt new file mode 100644 index 00000000..56bf6f37 --- /dev/null +++ b/common/src/commonMain/kotlin/repositories/DbDictionary.kt @@ -0,0 +1,19 @@ +package com.gitlab.sszuev.flashcards.repositories + +data class DbDictionary( + val dictionaryId: String, + val userId: String, + val name: String, + val sourceLang: DbLang, + val targetLang: DbLang, +) { + companion object { + val NULL = DbDictionary( + dictionaryId = "", + userId = "", + name = "", + sourceLang = DbLang.NULL, + targetLang = DbLang.NULL, + ) + } +} \ No newline at end of file diff --git a/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt b/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt index 7dc31434..228a5c32 100644 --- a/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt +++ b/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt @@ -7,7 +7,10 @@ import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity interface DbDictionaryRepository { - fun findDictionaryById(dictionaryId: DictionaryId): DictionaryEntity? + fun findDictionaryById(dictionaryId: String): DbDictionary? + + fun findDictionariesByIdIn(dictionaryIds: Iterable): Sequence = + dictionaryIds.asSequence().mapNotNull { findDictionaryById(it) } fun getAllDictionaries(userId: AppUserId): DictionariesDbResponse @@ -19,8 +22,6 @@ interface DbDictionaryRepository { fun exportDictionary(userId: AppUserId, resource: ResourceEntity): DictionaryDbResponse - fun findDictionariesByIdIn(dictionaryIds: Iterable): Sequence = - dictionaryIds.asSequence().mapNotNull { findDictionaryById(it) } } data class DictionariesDbResponse( diff --git a/common/src/commonMain/kotlin/repositories/DbLang.kt b/common/src/commonMain/kotlin/repositories/DbLang.kt new file mode 100644 index 00000000..6ad93781 --- /dev/null +++ b/common/src/commonMain/kotlin/repositories/DbLang.kt @@ -0,0 +1,10 @@ +package com.gitlab.sszuev.flashcards.repositories + +data class DbLang( + val langId: String, + val partsOfSpeech: List = emptyList(), +) { + companion object { + val NULL = DbLang(langId = "", partsOfSpeech = emptyList()) + } +} \ No newline at end of file diff --git a/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt b/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt index 91d12d8b..3f763d6e 100644 --- a/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt +++ b/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt @@ -6,8 +6,7 @@ import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity object NoOpDbDictionaryRepository : DbDictionaryRepository { - - override fun findDictionaryById(dictionaryId: DictionaryId): DictionaryEntity = noOp() + override fun findDictionaryById(dictionaryId: String): DbDictionary = noOp() override fun getAllDictionaries(userId: AppUserId): DictionariesDbResponse = noOp() diff --git a/core/src/main/kotlin/mappers/DbMappers.kt b/core/src/main/kotlin/mappers/DbMappers.kt index d552e737..85e7ba81 100644 --- a/core/src/main/kotlin/mappers/DbMappers.kt +++ b/core/src/main/kotlin/mappers/DbMappers.kt @@ -1,12 +1,18 @@ package com.gitlab.sszuev.flashcards.core.mappers +import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.CardWordEntity import com.gitlab.sszuev.flashcards.model.domain.CardWordExampleEntity +import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity import com.gitlab.sszuev.flashcards.model.domain.DictionaryId +import com.gitlab.sszuev.flashcards.model.domain.LangEntity +import com.gitlab.sszuev.flashcards.model.domain.LangId import com.gitlab.sszuev.flashcards.model.domain.Stage import com.gitlab.sszuev.flashcards.repositories.DbCard +import com.gitlab.sszuev.flashcards.repositories.DbDictionary +import com.gitlab.sszuev.flashcards.repositories.DbLang fun CardEntity.toDbCard() = DbCard( cardId = this.cardId.asString(), @@ -28,6 +34,22 @@ fun DbCard.toCardEntity() = CardEntity( details = this.details, ) +fun DictionaryEntity.toDbDictionary() = DbDictionary( + dictionaryId = dictionaryId.asString(), + name = name, + userId = userId.asString(), + sourceLang = sourceLang.toDbLang(), + targetLang = targetLang.toDbLang(), +) + +fun DbDictionary.toDictionaryEntity() = DictionaryEntity( + dictionaryId = DictionaryId(dictionaryId), + name = name, + userId = AppUserId(userId), + sourceLang = sourceLang.toLangEntity(), + targetLang = targetLang.toLangEntity(), +) + private fun CardWordEntity.toDbCardWord() = DbCard.Word( word = this.word, transcription = this.transcription, @@ -48,4 +70,14 @@ private fun DbCard.Word.toCardWordEntity() = CardWordEntity( ) private fun DbCard.Word.Example.toCardWordExampleEntity() = - CardWordExampleEntity(text = this.text, translation = this.translation) \ No newline at end of file + CardWordExampleEntity(text = this.text, translation = this.translation) + +private fun DbLang.toLangEntity() = LangEntity( + langId = LangId(langId), + partsOfSpeech = partsOfSpeech, +) + +private fun LangEntity.toDbLang() = DbLang( + langId = langId.asString(), + partsOfSpeech = partsOfSpeech, +) \ No newline at end of file diff --git a/core/src/main/kotlin/processes/CardProcessWorkers.kt b/core/src/main/kotlin/processes/CardProcessWorkers.kt index cd873c2c..d4148370 100644 --- a/core/src/main/kotlin/processes/CardProcessWorkers.kt +++ b/core/src/main/kotlin/processes/CardProcessWorkers.kt @@ -3,6 +3,7 @@ package com.gitlab.sszuev.flashcards.core.processes import com.gitlab.sszuev.flashcards.CardContext import com.gitlab.sszuev.flashcards.core.mappers.toCardEntity import com.gitlab.sszuev.flashcards.core.mappers.toDbCard +import com.gitlab.sszuev.flashcards.core.mappers.toDictionaryEntity import com.gitlab.sszuev.flashcards.core.normalizers.normalize import com.gitlab.sszuev.flashcards.corlib.ChainDSL import com.gitlab.sszuev.flashcards.corlib.worker @@ -26,7 +27,8 @@ fun ChainDSL.processGetCard() = worker { if (card == null) { this.errors.add(noCardFoundDataError("getCard", cardId)) } else { - val dictionary = this.repositories.dictionaryRepository(this.workMode).findDictionaryById(card.dictionaryId) + val dictionary = this.repositories.dictionaryRepository(this.workMode) + .findDictionaryById(card.dictionaryId.asString())?.toDictionaryEntity() if (dictionary == null) { this.errors.add(noDictionaryFoundDataError("getCard", card.dictionaryId)) } else if (dictionary.userId != userId) { @@ -57,7 +59,9 @@ fun ChainDSL.processGetAllCards() = worker { process { val userId = this.contextUserEntity.id val dictionaryId = this.normalizedRequestDictionaryId - val dictionary = this.repositories.dictionaryRepository(this.workMode).findDictionaryById(dictionaryId) + val dictionary = + this.repositories.dictionaryRepository(this.workMode).findDictionaryById(dictionaryId.asString()) + ?.toDictionaryEntity() if (dictionary == null) { this.errors.add(noDictionaryFoundDataError("getAllCards", dictionaryId)) } else if (dictionary.userId != userId) { @@ -91,7 +95,8 @@ fun ChainDSL.processCardSearch() = worker { process { val userId = this.contextUserEntity.id val found = this.repositories.dictionaryRepository(this.workMode) - .findDictionariesByIdIn(this.normalizedRequestCardFilter.dictionaryIds) + .findDictionariesByIdIn(this.normalizedRequestCardFilter.dictionaryIds.map { it.asString() }) + .map { it.toDictionaryEntity() } .associateBy { it.dictionaryId } this.normalizedRequestCardFilter.dictionaryIds.filterNot { found.containsKey(it) }.forEach { this.errors.add(noDictionaryFoundDataError("searchCards", it)) @@ -120,7 +125,8 @@ fun ChainDSL.processCreateCard() = worker { process { val userId = this.contextUserEntity.id val dictionaryId = this.normalizedRequestCardEntity.dictionaryId - val dictionary = this.repositories.dictionaryRepository(this.workMode).findDictionaryById(dictionaryId) + val dictionary = this.repositories.dictionaryRepository(this.workMode) + .findDictionaryById(dictionaryId.asString())?.toDictionaryEntity() if (dictionary == null) { this.errors.add(noDictionaryFoundDataError("createCard", dictionaryId)) } else if (dictionary.userId != userId) { @@ -145,7 +151,8 @@ fun ChainDSL.processUpdateCard() = worker { process { val userId = this.contextUserEntity.id val dictionaryId = this.normalizedRequestCardEntity.dictionaryId - val dictionary = this.repositories.dictionaryRepository(this.workMode).findDictionaryById(dictionaryId) + val dictionary = this.repositories.dictionaryRepository(this.workMode) + .findDictionaryById(dictionaryId.asString())?.toDictionaryEntity() if (dictionary == null) { this.errors.add(noDictionaryFoundDataError("updateCard", dictionaryId)) } else if (dictionary.userId != userId) { @@ -179,7 +186,9 @@ fun ChainDSL.processLearnCards() = worker { } val dictionaryIds = foundCards.map { it.dictionaryId }.toSet() val foundDictionaries = - this.repositories.dictionaryRepository(this.workMode).findDictionariesByIdIn(dictionaryIds) + this.repositories.dictionaryRepository(this.workMode) + .findDictionariesByIdIn(dictionaryIds.map { it.asString() }) + .map { it.toDictionaryEntity() } .associateBy { it.dictionaryId } foundDictionaries.onEach { if (it.value.userId != userId) { @@ -211,7 +220,8 @@ fun ChainDSL.processResetCard() = worker { this.errors.add(noCardFoundDataError("resetCard", cardId)) } else { val dictionaryId = card.dictionaryId - val dictionary = this.repositories.dictionaryRepository(this.workMode).findDictionaryById(dictionaryId) + val dictionary = this.repositories.dictionaryRepository(this.workMode) + .findDictionaryById(dictionaryId.asString())?.toDictionaryEntity() if (dictionary == null) { this.errors.add(noDictionaryFoundDataError("resetCard", dictionaryId)) } else if (dictionary.userId != userId) { @@ -241,7 +251,8 @@ fun ChainDSL.processDeleteCard() = worker { if (card == null) { this.errors.add(noCardFoundDataError("deleteCard", cardId)) } else { - val dictionary = this.repositories.dictionaryRepository(this.workMode).findDictionaryById(card.dictionaryId) + val dictionary = this.repositories.dictionaryRepository(this.workMode) + .findDictionaryById(card.dictionaryId.asString())?.toDictionaryEntity() if (dictionary == null) { this.errors.add(noDictionaryFoundDataError("deleteCard", card.dictionaryId)) } else if (dictionary.userId != userId) { diff --git a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt index 00eb9247..d51417af 100644 --- a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt +++ b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt @@ -3,6 +3,7 @@ package com.gitlab.sszuev.flashcards.core import com.gitlab.sszuev.flashcards.AppRepositories import com.gitlab.sszuev.flashcards.CardContext import com.gitlab.sszuev.flashcards.core.mappers.toDbCard +import com.gitlab.sszuev.flashcards.core.mappers.toDbDictionary import com.gitlab.sszuev.flashcards.dbcommon.mocks.MockDbCardRepository import com.gitlab.sszuev.flashcards.dbcommon.mocks.MockDbDictionaryRepository import com.gitlab.sszuev.flashcards.dbcommon.mocks.MockDbUserRepository @@ -105,7 +106,11 @@ internal class CardCorProcessorRunCardsTest { val dictionaryRepository = MockDbDictionaryRepository( invokeFindDictionaryById = { dictionaryId -> findDictionaryIsCalled = true - if (dictionaryId == testResponseDictionaryEntity.dictionaryId) testResponseDictionaryEntity else null + if (dictionaryId == testResponseDictionaryEntity.dictionaryId.asString()) { + testResponseDictionaryEntity.toDbDictionary() + } else { + null + } } ) @@ -176,7 +181,7 @@ internal class CardCorProcessorRunCardsTest { val dictionaryRepository = MockDbDictionaryRepository( invokeFindDictionaryById = { id -> isFindDictionaryCalled = true - if (id == testDictionaryId) testDictionary else null + if (id == testDictionaryId.asString()) testDictionary.toDbDictionary() else null } ) @@ -222,7 +227,7 @@ internal class CardCorProcessorRunCardsTest { val dictionaryRepository = MockDbDictionaryRepository( invokeFindDictionaryById = { id -> isFindDictionaryCalled = true - if (id == testDictionaryId) testDictionary else null + if (id == testDictionaryId.asString()) testDictionary.toDbDictionary() else null } ) @@ -270,7 +275,7 @@ internal class CardCorProcessorRunCardsTest { val dictionaryRepository = MockDbDictionaryRepository( invokeFindDictionaryById = { id -> isFindDictionaryCalled = true - if (id == testRequestEntity.dictionaryId) testDictionary else null + if (id == testRequestEntity.dictionaryId.asString()) testDictionary.toDbDictionary() else null } ) @@ -312,7 +317,7 @@ internal class CardCorProcessorRunCardsTest { val dictionaryRepository = MockDbDictionaryRepository( invokeFindDictionaryById = { id -> isFindDictionaryCalled = true - if (id == testRequestEntity.dictionaryId) testDictionary else null + if (id == testRequestEntity.dictionaryId.asString()) testDictionary.toDbDictionary() else null } ) @@ -356,9 +361,13 @@ internal class CardCorProcessorRunCardsTest { } ) val dictionaryRepository = MockDbDictionaryRepository( - invokeFindDictionariesByIdIn = { + invokeFindDictionariesByIdIn = { givenDictionaryIds -> isFindDictionariesCalled = true - if (it == testFilter.dictionaryIds) testDictionaries.asSequence() else emptySequence() + if (givenDictionaryIds == testFilter.dictionaryIds.map { it.asString() }) { + testDictionaries.asSequence().map { it.toDbDictionary() } + } else { + emptySequence() + } } ) @@ -404,9 +413,13 @@ internal class CardCorProcessorRunCardsTest { } ) val dictionaryRepository = MockDbDictionaryRepository( - invokeFindDictionariesByIdIn = { + invokeFindDictionariesByIdIn = { givenDictionaryIds -> isFindDictionariesCalled = true - if (it == testFilter.dictionaryIds) testDictionaries.asSequence() else emptySequence() + if (givenDictionaryIds == testFilter.dictionaryIds.map { it.asString() }) { + testDictionaries.asSequence().map { it.toDbDictionary() } + } else { + emptySequence() + } } ) @@ -447,7 +460,11 @@ internal class CardCorProcessorRunCardsTest { val dictionaryRepository = MockDbDictionaryRepository( invokeFindDictionaryById = { isFindDictionaryCalled = true - if (testDictionary.dictionaryId == it) testDictionary else Assertions.fail() + if (testDictionary.dictionaryId.asString() == it) { + testDictionary.toDbDictionary() + } else { + Assertions.fail() + } } ) @@ -489,7 +506,11 @@ internal class CardCorProcessorRunCardsTest { val dictionaryRepository = MockDbDictionaryRepository( invokeFindDictionaryById = { isFindDictionaryCalled = true - if (testDictionary.dictionaryId == it) testDictionary else Assertions.fail() + if (testDictionary.dictionaryId.asString() == it) { + testDictionary.toDbDictionary() + } else { + Assertions.fail() + } } ) @@ -547,8 +568,8 @@ internal class CardCorProcessorRunCardsTest { val dictionaryRepository = MockDbDictionaryRepository( invokeFindDictionariesByIdIn = { givenDictionaryIds -> isFindDictionariesCalled = true - if (testDictionaries.map { it.dictionaryId }.toSet() == givenDictionaryIds) { - testDictionaries.asSequence() + if (testDictionaries.map { it.dictionaryId.asString() } == givenDictionaryIds) { + testDictionaries.asSequence().map { it.toDbDictionary() } } else { Assertions.fail() } @@ -605,8 +626,8 @@ internal class CardCorProcessorRunCardsTest { val dictionaryRepository = MockDbDictionaryRepository( invokeFindDictionariesByIdIn = { givenDictionaryIds -> isFindDictionariesCalled = true - if (testDictionaries.map { it.dictionaryId }.toSet() == givenDictionaryIds) { - testDictionaries.asSequence() + if (testDictionaries.map { it.dictionaryId.asString() } == givenDictionaryIds) { + testDictionaries.asSequence().map { it.toDbDictionary() } } else { Assertions.fail() } @@ -653,7 +674,7 @@ internal class CardCorProcessorRunCardsTest { ) val dictionaryRepository = MockDbDictionaryRepository( invokeFindDictionaryById = { - if (it == testDictionaryId) testDictionary else null + if (it == testDictionaryId.asString()) testDictionary.toDbDictionary() else null } ) @@ -692,7 +713,7 @@ internal class CardCorProcessorRunCardsTest { ) val dictionaryRepository = MockDbDictionaryRepository( invokeFindDictionaryById = { - if (it == testDictionaryId) testDictionary else Assertions.fail() + if (it == testDictionaryId.asString()) testDictionary.toDbDictionary() else Assertions.fail() } ) diff --git a/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt b/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt index 05875834..ceaaaa64 100644 --- a/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt +++ b/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt @@ -55,15 +55,14 @@ abstract class DbDictionaryRepositoryTest { @Order(1) @Test fun `test get dictionary by id`() { - val res1 = repository.findDictionaryById(DictionaryId("2")) + val res1 = repository.findDictionaryById("2") Assertions.assertNotNull(res1) Assertions.assertEquals("Weather", res1!!.name) - Assertions.assertEquals("42", res1.userId.asString()) - val res2 = repository.findDictionaryById(DictionaryId("1")) + Assertions.assertEquals("42", res1.userId) + val res2 = repository.findDictionaryById("1") Assertions.assertNotNull(res2) Assertions.assertEquals("Irregular Verbs", res2!!.name) - Assertions.assertEquals("42", res2.userId.asString()) - + Assertions.assertEquals("42", res2.userId) } @Order(1) diff --git a/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt b/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt index cca80b8e..090e6054 100644 --- a/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt +++ b/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt @@ -4,6 +4,7 @@ import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity +import com.gitlab.sszuev.flashcards.repositories.DbDictionary import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository import com.gitlab.sszuev.flashcards.repositories.DictionariesDbResponse import com.gitlab.sszuev.flashcards.repositories.DictionaryDbResponse @@ -11,8 +12,8 @@ import com.gitlab.sszuev.flashcards.repositories.ImportDictionaryDbResponse import com.gitlab.sszuev.flashcards.repositories.RemoveDictionaryDbResponse class MockDbDictionaryRepository( - private val invokeFindDictionaryById: (DictionaryId) -> DictionaryEntity? = { null }, - private val invokeFindDictionariesByIdIn: (Iterable) -> Sequence = { emptySequence() }, + private val invokeFindDictionaryById: (String) -> DbDictionary? = { null }, + private val invokeFindDictionariesByIdIn: (Iterable) -> Sequence = { emptySequence() }, private val invokeGetAllDictionaries: (AppUserId) -> DictionariesDbResponse = { DictionariesDbResponse.EMPTY }, private val invokeCreateDictionary: (AppUserId, DictionaryEntity) -> DictionaryDbResponse = { _, _ -> DictionaryDbResponse.EMPTY }, private val invokeDeleteDictionary: (AppUserId, DictionaryId) -> RemoveDictionaryDbResponse = { _, _ -> RemoveDictionaryDbResponse.EMPTY }, @@ -20,8 +21,7 @@ class MockDbDictionaryRepository( private val invokeUploadDictionary: (AppUserId, ResourceEntity) -> DictionaryDbResponse = { _, _ -> DictionaryDbResponse.EMPTY }, ) : DbDictionaryRepository { - override fun findDictionaryById(dictionaryId: DictionaryId): DictionaryEntity? = - invokeFindDictionaryById(dictionaryId) + override fun findDictionaryById(dictionaryId: String): DbDictionary? = invokeFindDictionaryById(dictionaryId) override fun getAllDictionaries(userId: AppUserId): DictionariesDbResponse = invokeGetAllDictionaries(userId) @@ -37,6 +37,6 @@ class MockDbDictionaryRepository( override fun exportDictionary(userId: AppUserId, resource: ResourceEntity): DictionaryDbResponse = invokeUploadDictionary(userId, resource) - override fun findDictionariesByIdIn(dictionaryIds: Iterable): Sequence = + override fun findDictionariesByIdIn(dictionaryIds: Iterable): Sequence = invokeFindDictionariesByIdIn(dictionaryIds) } \ No newline at end of file diff --git a/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt b/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt index 4ffb16ce..83f26e5b 100644 --- a/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt +++ b/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt @@ -16,6 +16,7 @@ import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity +import com.gitlab.sszuev.flashcards.repositories.DbDictionary import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository import com.gitlab.sszuev.flashcards.repositories.DictionariesDbResponse import com.gitlab.sszuev.flashcards.repositories.DictionaryDbResponse @@ -29,8 +30,8 @@ class MemDbDictionaryRepository( private val database = MemDatabase.get(databaseLocation = dbConfig.dataLocation) - override fun findDictionaryById(dictionaryId: DictionaryId): DictionaryEntity? = - database.findDictionaryById(dictionaryId.asLong())?.toDictionaryEntity() + override fun findDictionaryById(dictionaryId: String): DbDictionary? = + database.findDictionaryById(dictionaryId.toLong())?.toDbDictionary() override fun getAllDictionaries(userId: AppUserId): DictionariesDbResponse { val dictionaries = this.database.findDictionariesByUserId(userId.asLong()) diff --git a/db-mem/src/main/kotlin/MemDbEntityMapper.kt b/db-mem/src/main/kotlin/MemDbEntityMapper.kt index 78e4ea09..c2b32fe0 100644 --- a/db-mem/src/main/kotlin/MemDbEntityMapper.kt +++ b/db-mem/src/main/kotlin/MemDbEntityMapper.kt @@ -39,6 +39,8 @@ import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.LangEntity import com.gitlab.sszuev.flashcards.model.domain.LangId import com.gitlab.sszuev.flashcards.repositories.DbCard +import com.gitlab.sszuev.flashcards.repositories.DbDictionary +import com.gitlab.sszuev.flashcards.repositories.DbLang import java.util.UUID internal fun MemDbUser.detailsAsJsonString(): String { @@ -136,6 +138,14 @@ internal fun MemDbDictionary.toDictionaryEntity(): DictionaryEntity = Dictionary targetLang = this.targetLanguage.toLangEntity(), ) +internal fun MemDbDictionary.toDbDictionary() = DbDictionary( + dictionaryId = this.id?.toString() ?: "", + userId = this.userId?.toString() ?: "", + name = this.name, + sourceLang = this.sourceLanguage.toDbLang(), + targetLang = this.targetLanguage.toDbLang(), +) + internal fun DictionaryEntity.toMemDbDictionary(): MemDbDictionary = MemDbDictionary( id = if (this.dictionaryId == DictionaryId.NONE) null else this.dictionaryId.asLong(), name = this.name, @@ -184,6 +194,11 @@ internal fun LangEntity.toMemDbLanguage(): MemDbLanguage = MemDbLanguage( partsOfSpeech = this.partsOfSpeech, ) +internal fun MemDbLanguage.toDbLang(): DbLang = DbLang( + langId = this.id, + partsOfSpeech = this.partsOfSpeech, +) + private fun MemDbWord.toCommonWordDto(): CommonWordDto = CommonWordDto( word = word, transcription = transcription, diff --git a/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt b/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt index 70e8b8e6..c737f310 100644 --- a/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt +++ b/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt @@ -23,6 +23,7 @@ import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity +import com.gitlab.sszuev.flashcards.repositories.DbDictionary import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository import com.gitlab.sszuev.flashcards.repositories.DictionariesDbResponse import com.gitlab.sszuev.flashcards.repositories.DictionaryDbResponse @@ -44,8 +45,8 @@ class PgDbDictionaryRepository( PgDbConnector.connection(dbConfig) } - override fun findDictionaryById(dictionaryId: DictionaryId): DictionaryEntity? = connection.execute { - PgDbDictionary.findById(dictionaryId.asLong())?.toDictionaryEntity() + override fun findDictionaryById(dictionaryId: String): DbDictionary? = connection.execute { + PgDbDictionary.findById(dictionaryId.toLong())?.toDbDictionary() } override fun getAllDictionaries(userId: AppUserId): DictionariesDbResponse { diff --git a/db-pg/src/main/kotlin/PgDbEntityMapper.kt b/db-pg/src/main/kotlin/PgDbEntityMapper.kt index b03418d6..a3b35075 100644 --- a/db-pg/src/main/kotlin/PgDbEntityMapper.kt +++ b/db-pg/src/main/kotlin/PgDbEntityMapper.kt @@ -27,6 +27,8 @@ import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.LangEntity import com.gitlab.sszuev.flashcards.model.domain.LangId import com.gitlab.sszuev.flashcards.repositories.DbCard +import com.gitlab.sszuev.flashcards.repositories.DbDictionary +import com.gitlab.sszuev.flashcards.repositories.DbLang import org.jetbrains.exposed.dao.id.EntityID import java.time.LocalDateTime import java.util.UUID @@ -44,6 +46,14 @@ internal fun PgDbDictionary.toDictionaryEntity(): DictionaryEntity = DictionaryE targetLang = createLangEntity(this.targetLang), ) +internal fun PgDbDictionary.toDbDictionary(): DbDictionary = DbDictionary( + dictionaryId = this.id.value.toString(), + userId = this.userId.value.toString(), + name = this.name, + sourceLang = createDbLang(this.sourceLang), + targetLang = createDbLang(this.targetLang), +) + internal fun PgDbCard.toCardEntity(): DbCard { val words = parseCardWordsJson(this.words) val details = parseCardDetailsJson(this.details) @@ -85,6 +95,11 @@ internal fun createLangEntity(tag: String) = LangEntity( partsOfSpeech = LanguageRepository.partsOfSpeech(tag) ) +internal fun createDbLang(tag: String) = DbLang( + langId = tag, + partsOfSpeech = LanguageRepository.partsOfSpeech(tag) +) + private fun UUID.asAppAuthId(): AppAuthId = AppAuthId(toString()) internal fun Long.asDictionaryId(): DictionaryId = DictionaryId(toString()) \ No newline at end of file From 0e01b55df2065dc921ee015250f9a7c0471409d4 Mon Sep 17 00:00:00 2001 From: sszuev Date: Wed, 3 Apr 2024 11:20:26 +0300 Subject: [PATCH 11/18] common & core & db: [#28] DbDictionaryRepository#getAllDictionaries -> findDictionariesByUserId --- .../repositories/DbDictionaryRepository.kt | 11 +--- .../NoOpDbDictionaryRepository.kt | 2 +- .../processes/DictionaryProcessWokers.kt | 37 ++++++------ .../kotlin/DictionaryCorProcessorRunTest.kt | 10 +++- .../kotlin/DbDictionaryRepositoryTest.kt | 59 ++++++++++++++----- .../mocks/MockDbDictionaryRepository.kt | 10 ++-- .../main/kotlin/MemDbDictionaryRepository.kt | 7 +-- .../main/kotlin/PgDbDictionaryRepository.kt | 9 +-- db-pg/src/main/kotlin/PgDbEntityMapper.kt | 14 +---- 9 files changed, 84 insertions(+), 75 deletions(-) diff --git a/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt b/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt index 228a5c32..8672c0ec 100644 --- a/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt +++ b/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt @@ -12,7 +12,7 @@ interface DbDictionaryRepository { fun findDictionariesByIdIn(dictionaryIds: Iterable): Sequence = dictionaryIds.asSequence().mapNotNull { findDictionaryById(it) } - fun getAllDictionaries(userId: AppUserId): DictionariesDbResponse + fun findDictionariesByUserId(userId: String): Sequence fun createDictionary(userId: AppUserId, entity: DictionaryEntity): DictionaryDbResponse @@ -24,15 +24,6 @@ interface DbDictionaryRepository { } -data class DictionariesDbResponse( - val dictionaries: List, - val errors: List = emptyList() -) { - companion object { - val EMPTY = DictionariesDbResponse(dictionaries = emptyList(), errors = emptyList()) - } -} - data class RemoveDictionaryDbResponse( val dictionary: DictionaryEntity = DictionaryEntity.EMPTY, val errors: List = emptyList(), diff --git a/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt b/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt index 3f763d6e..ca70220f 100644 --- a/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt +++ b/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt @@ -8,7 +8,7 @@ import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity object NoOpDbDictionaryRepository : DbDictionaryRepository { override fun findDictionaryById(dictionaryId: String): DbDictionary = noOp() - override fun getAllDictionaries(userId: AppUserId): DictionariesDbResponse = noOp() + override fun findDictionariesByUserId(userId: String): Sequence = noOp() override fun createDictionary(userId: AppUserId, entity: DictionaryEntity): DictionaryDbResponse = noOp() diff --git a/core/src/main/kotlin/processes/DictionaryProcessWokers.kt b/core/src/main/kotlin/processes/DictionaryProcessWokers.kt index 88bf8e1d..c718d2e1 100644 --- a/core/src/main/kotlin/processes/DictionaryProcessWokers.kt +++ b/core/src/main/kotlin/processes/DictionaryProcessWokers.kt @@ -1,6 +1,7 @@ package com.gitlab.sszuev.flashcards.core.processes import com.gitlab.sszuev.flashcards.DictionaryContext +import com.gitlab.sszuev.flashcards.core.mappers.toDictionaryEntity import com.gitlab.sszuev.flashcards.core.validators.fail import com.gitlab.sszuev.flashcards.corlib.ChainDSL import com.gitlab.sszuev.flashcards.corlib.worker @@ -14,22 +15,17 @@ fun ChainDSL.processGetAllDictionary() = worker { } process { val userId = this.contextUserEntity.id - val res = this.repositories.dictionaryRepository(this.workMode).getAllDictionaries(userId) - if (res.errors.isNotEmpty()) { - this.errors.addAll(res.errors) - } - - // TODO: temporary solution - if (res.errors.isEmpty()) { - this.responseDictionaryEntityList = res.dictionaries.map { dictionary -> - val cards = - this.repositories.cardRepository(this.workMode) - .findCardsByDictionaryId(dictionary.dictionaryId.asString()) - .toList() - val total = cards.size - val known = cards.mapNotNull { it.answered }.count { it >= config.numberOfRightAnswers } - dictionary.copy(totalCardsCount = total, learnedCardsCount = known) - } + val res = this.repositories.dictionaryRepository(this.workMode) + .findDictionariesByUserId(userId.asString()) + .map { it.toDictionaryEntity() }.toList() + this.responseDictionaryEntityList = res.map { dictionary -> + val cards = + this.repositories.cardRepository(this.workMode) + .findCardsByDictionaryId(dictionary.dictionaryId.asString()) + .toList() + val total = cards.size + val known = cards.mapNotNull { it.answered }.count { it >= config.numberOfRightAnswers } + dictionary.copy(totalCardsCount = total, learnedCardsCount = known) } this.status = if (this.errors.isNotEmpty()) AppStatus.FAIL else AppStatus.RUN @@ -54,7 +50,8 @@ fun ChainDSL.processCreateDictionary() = worker { process { val userId = this.contextUserEntity.id val res = - this.repositories.dictionaryRepository(this.workMode).createDictionary(userId, this.normalizedRequestDictionaryEntity) + this.repositories.dictionaryRepository(this.workMode) + .createDictionary(userId, this.normalizedRequestDictionaryEntity) this.responseDictionaryEntity = res.dictionary if (res.errors.isNotEmpty()) { this.errors.addAll(res.errors) @@ -74,7 +71,8 @@ fun ChainDSL.processDeleteDictionary() = worker { process { val userId = this.contextUserEntity.id val res = - this.repositories.dictionaryRepository(this.workMode).removeDictionary(userId, this.normalizedRequestDictionaryId) + this.repositories.dictionaryRepository(this.workMode) + .removeDictionary(userId, this.normalizedRequestDictionaryId) if (res.errors.isNotEmpty()) { this.errors.addAll(res.errors) } @@ -93,7 +91,8 @@ fun ChainDSL.processDownloadDictionary() = worker { process { val userId = this.contextUserEntity.id val res = - this.repositories.dictionaryRepository(this.workMode).importDictionary(userId, this.normalizedRequestDictionaryId) + this.repositories.dictionaryRepository(this.workMode) + .importDictionary(userId, this.normalizedRequestDictionaryId) this.responseDictionaryResourceEntity = res.resource if (res.errors.isNotEmpty()) { this.errors.addAll(res.errors) diff --git a/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt b/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt index 6537f9d2..44a40db9 100644 --- a/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt +++ b/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt @@ -2,6 +2,7 @@ package com.gitlab.sszuev.flashcards.core import com.gitlab.sszuev.flashcards.AppRepositories import com.gitlab.sszuev.flashcards.DictionaryContext +import com.gitlab.sszuev.flashcards.core.mappers.toDbDictionary import com.gitlab.sszuev.flashcards.core.normalizers.normalize import com.gitlab.sszuev.flashcards.dbcommon.mocks.MockDbCardRepository import com.gitlab.sszuev.flashcards.dbcommon.mocks.MockDbDictionaryRepository @@ -18,7 +19,6 @@ import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository import com.gitlab.sszuev.flashcards.repositories.DbUserRepository -import com.gitlab.sszuev.flashcards.repositories.DictionariesDbResponse import com.gitlab.sszuev.flashcards.repositories.DictionaryDbResponse import com.gitlab.sszuev.flashcards.repositories.ImportDictionaryDbResponse import com.gitlab.sszuev.flashcards.repositories.RemoveDictionaryDbResponse @@ -68,9 +68,13 @@ internal class DictionaryCorProcessorRunTest { var getAllDictionariesWasCalled = false var getAllCardsWasCalled = false val dictionaryRepository = MockDbDictionaryRepository( - invokeGetAllDictionaries = { + invokeGetAllDictionaries = { userId -> getAllDictionariesWasCalled = true - DictionariesDbResponse(if (it == testUser.id) testResponseEntities else emptyList()) + if (userId == testUser.id.asString()) { + testResponseEntities.asSequence().map { it.toDbDictionary() } + } else { + emptySequence() + } } ) val cardsRepository = MockDbCardRepository( diff --git a/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt b/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt index ceaaaa64..957d9994 100644 --- a/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt +++ b/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt @@ -7,6 +7,7 @@ import com.gitlab.sszuev.flashcards.model.domain.LangEntity import com.gitlab.sszuev.flashcards.model.domain.LangId import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository +import com.gitlab.sszuev.flashcards.repositories.DbLang import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.MethodOrderer import org.junit.jupiter.api.Order @@ -50,6 +51,38 @@ abstract class DbDictionaryRepositoryTest { "междометие" ) ) + + private val _EN = DbLang( + langId = "en", + partsOfSpeech = listOf( + "noun", + "verb", + "adjective", + "adverb", + "pronoun", + "preposition", + "conjunction", + "interjection", + "article" + ) + ) + private val _RU = DbLang( + langId = "ru", + partsOfSpeech = listOf( + "существительное", + "прилагательное", + "числительное", + "местоимение", + "глагол", + "наречие", + "причастие", + "предлог", + "союз", + "частица", + "междометие" + ) + ) + } @Order(1) @@ -68,28 +101,26 @@ abstract class DbDictionaryRepositoryTest { @Order(1) @Test fun `test get all dictionaries by user-id success`() { - val res = repository.getAllDictionaries(AppUserId("42")) - Assertions.assertTrue(res.errors.isEmpty()) - Assertions.assertEquals(2, res.dictionaries.size) + val res = repository.findDictionariesByUserId("42").toList() + Assertions.assertEquals(2, res.size) - val businessDictionary = res.dictionaries[0] - Assertions.assertEquals(DictionaryId("1"), businessDictionary.dictionaryId) + val businessDictionary = res[0] + Assertions.assertEquals("1", businessDictionary.dictionaryId) Assertions.assertEquals("Irregular Verbs", businessDictionary.name) - Assertions.assertEquals(EN, businessDictionary.sourceLang) - Assertions.assertEquals(RU, businessDictionary.targetLang) - val weatherDictionary = res.dictionaries[1] - Assertions.assertEquals(DictionaryId("2"), weatherDictionary.dictionaryId) + Assertions.assertEquals(_EN, businessDictionary.sourceLang) + Assertions.assertEquals(_RU, businessDictionary.targetLang) + val weatherDictionary = res[1] + Assertions.assertEquals("2", weatherDictionary.dictionaryId) Assertions.assertEquals("Weather", weatherDictionary.name) - Assertions.assertEquals(EN, weatherDictionary.sourceLang) - Assertions.assertEquals(RU, weatherDictionary.targetLang) + Assertions.assertEquals(_EN, weatherDictionary.sourceLang) + Assertions.assertEquals(_RU, weatherDictionary.targetLang) } @Order(2) @Test fun `test get all dictionaries by user-id nothing found`() { - val res = repository.getAllDictionaries(AppUserId("42000")) - Assertions.assertEquals(0, res.dictionaries.size) - Assertions.assertTrue(res.errors.isEmpty()) + val res = repository.findDictionariesByUserId("42000").toList() + Assertions.assertEquals(0, res.size) } @Order(3) diff --git a/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt b/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt index 090e6054..e625ba27 100644 --- a/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt +++ b/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt @@ -6,7 +6,6 @@ import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity import com.gitlab.sszuev.flashcards.repositories.DbDictionary import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository -import com.gitlab.sszuev.flashcards.repositories.DictionariesDbResponse import com.gitlab.sszuev.flashcards.repositories.DictionaryDbResponse import com.gitlab.sszuev.flashcards.repositories.ImportDictionaryDbResponse import com.gitlab.sszuev.flashcards.repositories.RemoveDictionaryDbResponse @@ -14,7 +13,7 @@ import com.gitlab.sszuev.flashcards.repositories.RemoveDictionaryDbResponse class MockDbDictionaryRepository( private val invokeFindDictionaryById: (String) -> DbDictionary? = { null }, private val invokeFindDictionariesByIdIn: (Iterable) -> Sequence = { emptySequence() }, - private val invokeGetAllDictionaries: (AppUserId) -> DictionariesDbResponse = { DictionariesDbResponse.EMPTY }, + private val invokeGetAllDictionaries: (String) -> Sequence = { emptySequence() }, private val invokeCreateDictionary: (AppUserId, DictionaryEntity) -> DictionaryDbResponse = { _, _ -> DictionaryDbResponse.EMPTY }, private val invokeDeleteDictionary: (AppUserId, DictionaryId) -> RemoveDictionaryDbResponse = { _, _ -> RemoveDictionaryDbResponse.EMPTY }, private val invokeDownloadDictionary: (AppUserId, DictionaryId) -> ImportDictionaryDbResponse = { _, _ -> ImportDictionaryDbResponse.EMPTY }, @@ -23,7 +22,10 @@ class MockDbDictionaryRepository( override fun findDictionaryById(dictionaryId: String): DbDictionary? = invokeFindDictionaryById(dictionaryId) - override fun getAllDictionaries(userId: AppUserId): DictionariesDbResponse = invokeGetAllDictionaries(userId) + override fun findDictionariesByIdIn(dictionaryIds: Iterable): Sequence = + invokeFindDictionariesByIdIn(dictionaryIds) + + override fun findDictionariesByUserId(userId: String): Sequence = invokeGetAllDictionaries(userId) override fun createDictionary(userId: AppUserId, entity: DictionaryEntity): DictionaryDbResponse = invokeCreateDictionary(userId, entity) @@ -37,6 +39,4 @@ class MockDbDictionaryRepository( override fun exportDictionary(userId: AppUserId, resource: ResourceEntity): DictionaryDbResponse = invokeUploadDictionary(userId, resource) - override fun findDictionariesByIdIn(dictionaryIds: Iterable): Sequence = - invokeFindDictionariesByIdIn(dictionaryIds) } \ No newline at end of file diff --git a/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt b/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt index 83f26e5b..1dae1371 100644 --- a/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt +++ b/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt @@ -18,7 +18,6 @@ import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity import com.gitlab.sszuev.flashcards.repositories.DbDictionary import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository -import com.gitlab.sszuev.flashcards.repositories.DictionariesDbResponse import com.gitlab.sszuev.flashcards.repositories.DictionaryDbResponse import com.gitlab.sszuev.flashcards.repositories.ImportDictionaryDbResponse import com.gitlab.sszuev.flashcards.repositories.RemoveDictionaryDbResponse @@ -33,9 +32,9 @@ class MemDbDictionaryRepository( override fun findDictionaryById(dictionaryId: String): DbDictionary? = database.findDictionaryById(dictionaryId.toLong())?.toDbDictionary() - override fun getAllDictionaries(userId: AppUserId): DictionariesDbResponse { - val dictionaries = this.database.findDictionariesByUserId(userId.asLong()) - return DictionariesDbResponse(dictionaries = dictionaries.map { it.toDictionaryEntity() }.toList()) + override fun findDictionariesByUserId(userId: String): Sequence { + val dictionaries = this.database.findDictionariesByUserId(userId.toLong()) + return dictionaries.map { it.toDbDictionary() } } override fun createDictionary(userId: AppUserId, entity: DictionaryEntity): DictionaryDbResponse { diff --git a/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt b/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt index c737f310..39383f15 100644 --- a/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt +++ b/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt @@ -25,7 +25,6 @@ import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity import com.gitlab.sszuev.flashcards.repositories.DbDictionary import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository -import com.gitlab.sszuev.flashcards.repositories.DictionariesDbResponse import com.gitlab.sszuev.flashcards.repositories.DictionaryDbResponse import com.gitlab.sszuev.flashcards.repositories.ImportDictionaryDbResponse import com.gitlab.sszuev.flashcards.repositories.RemoveDictionaryDbResponse @@ -49,12 +48,8 @@ class PgDbDictionaryRepository( PgDbDictionary.findById(dictionaryId.toLong())?.toDbDictionary() } - override fun getAllDictionaries(userId: AppUserId): DictionariesDbResponse { - return connection.execute { - val dictionaries = - PgDbDictionary.find(Dictionaries.userId eq userId.asRecordId()).map { it.toDictionaryEntity() } - DictionariesDbResponse(dictionaries = dictionaries) - } + override fun findDictionariesByUserId(userId: String): Sequence = connection.execute { + PgDbDictionary.find(Dictionaries.userId eq userId.toUserId()).map { it.toDbDictionary() }.asSequence() } override fun createDictionary(userId: AppUserId, entity: DictionaryEntity): DictionaryDbResponse { diff --git a/db-pg/src/main/kotlin/PgDbEntityMapper.kt b/db-pg/src/main/kotlin/PgDbEntityMapper.kt index a3b35075..7cfb8c75 100644 --- a/db-pg/src/main/kotlin/PgDbEntityMapper.kt +++ b/db-pg/src/main/kotlin/PgDbEntityMapper.kt @@ -2,7 +2,6 @@ package com.gitlab.sszuev.flashcards.dbpg import com.gitlab.sszuev.flashcards.common.LanguageRepository import com.gitlab.sszuev.flashcards.common.asKotlin -import com.gitlab.sszuev.flashcards.common.asLong import com.gitlab.sszuev.flashcards.common.detailsAsCommonCardDetailsDto import com.gitlab.sszuev.flashcards.common.documents.DocumentCard import com.gitlab.sszuev.flashcards.common.parseCardDetailsJson @@ -22,7 +21,6 @@ import com.gitlab.sszuev.flashcards.dbpg.dao.Users import com.gitlab.sszuev.flashcards.model.common.AppAuthId import com.gitlab.sszuev.flashcards.model.common.AppUserEntity import com.gitlab.sszuev.flashcards.model.common.AppUserId -import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.LangEntity import com.gitlab.sszuev.flashcards.model.domain.LangId @@ -38,14 +36,6 @@ internal fun PgDbUser.toAppUserEntity(): AppUserEntity = AppUserEntity( authId = this.uuid.asAppAuthId(), ) -internal fun PgDbDictionary.toDictionaryEntity(): DictionaryEntity = DictionaryEntity( - dictionaryId = this.id.asDictionaryId(), - userId = this.userId.asUserId(), - name = this.name, - sourceLang = createLangEntity(this.sourceLang), - targetLang = createLangEntity(this.targetLang), -) - internal fun PgDbDictionary.toDbDictionary(): DbDictionary = DbDictionary( dictionaryId = this.id.value.toString(), userId = this.userId.value.toString(), @@ -84,12 +74,12 @@ internal fun EntityID.asUserId(): AppUserId = AppUserId(value.toString()) internal fun EntityID.asDictionaryId(): DictionaryId = value.asDictionaryId() -internal fun AppUserId.asRecordId(): EntityID = EntityID(asLong(), Users) - internal fun String.toDictionariesId(): EntityID = EntityID(toLong(), Dictionaries) internal fun String.toCardsId(): EntityID = EntityID(toLong(), Cards) +internal fun String.toUserId(): EntityID = EntityID(toLong(), Users) + internal fun createLangEntity(tag: String) = LangEntity( langId = LangId(tag), partsOfSpeech = LanguageRepository.partsOfSpeech(tag) From 8a3b81b4ed4b647bb437c1806ce2f5ce1ea6c607 Mon Sep 17 00:00:00 2001 From: sszuev Date: Thu, 4 Apr 2024 14:01:03 +0300 Subject: [PATCH 12/18] common & core & db: [#28] refactoring DbDictionaryRepository#createDictionary --- .../repositories/DbDictionaryRepository.kt | 2 +- .../NoOpDbDictionaryRepository.kt | 3 +- .../src/main/kotlin/DictionaryCorProcessor.kt | 13 ++++++- .../processes/DictionaryProcessWokers.kt | 12 +++--- .../kotlin/CardCorProcessorRunCardsTest.kt | 8 +++- .../kotlin/DictionaryCorProcessorRunTest.kt | 4 +- .../kotlin/DbDictionaryRepositoryTest.kt | 18 ++++----- .../mocks/MockDbDictionaryRepository.kt | 6 +-- db-mem/src/main/kotlin/MemDatabase.kt | 38 ++++++++++--------- db-mem/src/main/kotlin/MemDbCardRepository.kt | 27 +++++++------ .../main/kotlin/MemDbDictionaryRepository.kt | 15 ++------ db-mem/src/main/kotlin/MemDbEntityMapper.kt | 12 +++--- .../main/kotlin/PgDbDictionaryRepository.kt | 10 ++--- specs/src/main/kotlin/Stubs.kt | 2 + 14 files changed, 91 insertions(+), 79 deletions(-) diff --git a/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt b/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt index 8672c0ec..7e874287 100644 --- a/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt +++ b/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt @@ -14,7 +14,7 @@ interface DbDictionaryRepository { fun findDictionariesByUserId(userId: String): Sequence - fun createDictionary(userId: AppUserId, entity: DictionaryEntity): DictionaryDbResponse + fun createDictionary(entity: DbDictionary): DbDictionary fun removeDictionary(userId: AppUserId, dictionaryId: DictionaryId): RemoveDictionaryDbResponse diff --git a/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt b/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt index ca70220f..b39150b2 100644 --- a/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt +++ b/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt @@ -1,7 +1,6 @@ package com.gitlab.sszuev.flashcards.repositories import com.gitlab.sszuev.flashcards.model.common.AppUserId -import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity @@ -10,7 +9,7 @@ object NoOpDbDictionaryRepository : DbDictionaryRepository { override fun findDictionariesByUserId(userId: String): Sequence = noOp() - override fun createDictionary(userId: AppUserId, entity: DictionaryEntity): DictionaryDbResponse = noOp() + override fun createDictionary(entity: DbDictionary): DbDictionary = noOp() override fun removeDictionary(userId: AppUserId, dictionaryId: DictionaryId): RemoveDictionaryDbResponse = noOp() diff --git a/core/src/main/kotlin/DictionaryCorProcessor.kt b/core/src/main/kotlin/DictionaryCorProcessor.kt index 356271b9..614a4ea1 100644 --- a/core/src/main/kotlin/DictionaryCorProcessor.kt +++ b/core/src/main/kotlin/DictionaryCorProcessor.kt @@ -2,10 +2,19 @@ package com.gitlab.sszuev.flashcards.core import com.gitlab.sszuev.flashcards.DictionaryContext import com.gitlab.sszuev.flashcards.core.normalizers.normalizers -import com.gitlab.sszuev.flashcards.core.processes.* +import com.gitlab.sszuev.flashcards.core.processes.processCreateDictionary +import com.gitlab.sszuev.flashcards.core.processes.processDeleteDictionary +import com.gitlab.sszuev.flashcards.core.processes.processDownloadDictionary +import com.gitlab.sszuev.flashcards.core.processes.processFindUser +import com.gitlab.sszuev.flashcards.core.processes.processGetAllDictionary +import com.gitlab.sszuev.flashcards.core.processes.processUploadDictionary import com.gitlab.sszuev.flashcards.core.stubs.dictionaryStubSuccess import com.gitlab.sszuev.flashcards.core.stubs.stubError -import com.gitlab.sszuev.flashcards.core.validators.* +import com.gitlab.sszuev.flashcards.core.validators.validateDictionaryEntityHasNoCardId +import com.gitlab.sszuev.flashcards.core.validators.validateDictionaryId +import com.gitlab.sszuev.flashcards.core.validators.validateDictionaryLangId +import com.gitlab.sszuev.flashcards.core.validators.validateDictionaryResource +import com.gitlab.sszuev.flashcards.core.validators.validateUserId import com.gitlab.sszuev.flashcards.corlib.chain import com.gitlab.sszuev.flashcards.model.domain.DictionaryOperation import com.gitlab.sszuev.flashcards.stubs.stubDictionaries diff --git a/core/src/main/kotlin/processes/DictionaryProcessWokers.kt b/core/src/main/kotlin/processes/DictionaryProcessWokers.kt index c718d2e1..df515c2c 100644 --- a/core/src/main/kotlin/processes/DictionaryProcessWokers.kt +++ b/core/src/main/kotlin/processes/DictionaryProcessWokers.kt @@ -1,6 +1,7 @@ package com.gitlab.sszuev.flashcards.core.processes import com.gitlab.sszuev.flashcards.DictionaryContext +import com.gitlab.sszuev.flashcards.core.mappers.toDbDictionary import com.gitlab.sszuev.flashcards.core.mappers.toDictionaryEntity import com.gitlab.sszuev.flashcards.core.validators.fail import com.gitlab.sszuev.flashcards.corlib.ChainDSL @@ -49,13 +50,10 @@ fun ChainDSL.processCreateDictionary() = worker { } process { val userId = this.contextUserEntity.id - val res = - this.repositories.dictionaryRepository(this.workMode) - .createDictionary(userId, this.normalizedRequestDictionaryEntity) - this.responseDictionaryEntity = res.dictionary - if (res.errors.isNotEmpty()) { - this.errors.addAll(res.errors) - } + val res = this.repositories.dictionaryRepository(this.workMode) + .createDictionary(this.normalizedRequestDictionaryEntity.copy(userId = userId).toDbDictionary()) + .toDictionaryEntity() + this.responseDictionaryEntity = res this.status = if (this.errors.isNotEmpty()) AppStatus.FAIL else AppStatus.RUN } onException { diff --git a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt index d51417af..fedb7965 100644 --- a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt +++ b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt @@ -47,7 +47,11 @@ internal class CardCorProcessorRunCardsTest { private fun testContext( op: CardOperation, cardRepository: DbCardRepository, - userRepository: DbUserRepository = MockDbUserRepository(), + userRepository: DbUserRepository = MockDbUserRepository( + invokeGetUser = { + if (it == testUser.authId) UserEntityDbResponse(user = testUser) else Assertions.fail() + } + ), dictionaryRepository: DbDictionaryRepository = MockDbDictionaryRepository(), ttsResourceRepository: TTSResourceRepository = MockTTSResourceRepository(invokeFindResourceId = { TTSResourceIdResponse.EMPTY.copy(TTSResourceId(it.lang.asString() + ":" + it.word)) @@ -60,7 +64,7 @@ internal class CardCorProcessorRunCardsTest { testCardRepository = cardRepository, testDictionaryRepository = dictionaryRepository, testTTSClientRepository = ttsResourceRepository, - ) + ), ) context.requestAppAuthId = testUser.authId context.workMode = AppMode.TEST diff --git a/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt b/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt index 44a40db9..202c73ed 100644 --- a/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt +++ b/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt @@ -115,9 +115,9 @@ internal class DictionaryCorProcessorRunTest { var wasCalled = false val repository = MockDbDictionaryRepository( - invokeCreateDictionary = { _, d -> + invokeCreateDictionary = { d -> wasCalled = true - DictionaryDbResponse(dictionary = d.copy(testDictionaryId)) + d.copy(dictionaryId = testDictionaryId.asString()) } ) diff --git a/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt b/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt index 957d9994..0a4152fd 100644 --- a/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt +++ b/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt @@ -1,11 +1,11 @@ package com.gitlab.sszuev.flashcards.dbcommon import com.gitlab.sszuev.flashcards.model.common.AppUserId -import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.LangEntity import com.gitlab.sszuev.flashcards.model.domain.LangId import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity +import com.gitlab.sszuev.flashcards.repositories.DbDictionary import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository import com.gitlab.sszuev.flashcards.repositories.DbLang import org.junit.jupiter.api.Assertions @@ -200,13 +200,13 @@ abstract class DbDictionaryRepositoryTest { @Order(6) @Test fun `test create dictionary success`() { - val given = DictionaryEntity(name = "test-dictionary", sourceLang = RU, targetLang = EN) - val res = repository.createDictionary(AppUserId("42"), given) - Assertions.assertEquals(0, res.errors.size) { "Errors: ${res.errors}" } - Assertions.assertEquals(given.name, res.dictionary.name) - Assertions.assertEquals(RU, res.dictionary.sourceLang) - Assertions.assertEquals(EN, res.dictionary.targetLang) - Assertions.assertNotEquals(DictionaryId.NONE, res.dictionary.dictionaryId) - Assertions.assertTrue(res.dictionary.dictionaryId.asString().matches("\\d+".toRegex())) + val given = + DbDictionary(name = "test-dictionary", sourceLang = _RU, targetLang = _EN, userId = "42", dictionaryId = "") + val res = repository.createDictionary(given) + Assertions.assertEquals(given.name, res.name) + Assertions.assertEquals(_RU, res.sourceLang) + Assertions.assertEquals(_EN, res.targetLang) + Assertions.assertFalse(res.dictionaryId.isBlank()) + Assertions.assertTrue(res.dictionaryId.matches("\\d+".toRegex())) } } \ No newline at end of file diff --git a/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt b/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt index e625ba27..41eae219 100644 --- a/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt +++ b/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt @@ -1,7 +1,6 @@ package com.gitlab.sszuev.flashcards.dbcommon.mocks import com.gitlab.sszuev.flashcards.model.common.AppUserId -import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity import com.gitlab.sszuev.flashcards.repositories.DbDictionary @@ -14,7 +13,7 @@ class MockDbDictionaryRepository( private val invokeFindDictionaryById: (String) -> DbDictionary? = { null }, private val invokeFindDictionariesByIdIn: (Iterable) -> Sequence = { emptySequence() }, private val invokeGetAllDictionaries: (String) -> Sequence = { emptySequence() }, - private val invokeCreateDictionary: (AppUserId, DictionaryEntity) -> DictionaryDbResponse = { _, _ -> DictionaryDbResponse.EMPTY }, + private val invokeCreateDictionary: (DbDictionary) -> DbDictionary = { DbDictionary.NULL }, private val invokeDeleteDictionary: (AppUserId, DictionaryId) -> RemoveDictionaryDbResponse = { _, _ -> RemoveDictionaryDbResponse.EMPTY }, private val invokeDownloadDictionary: (AppUserId, DictionaryId) -> ImportDictionaryDbResponse = { _, _ -> ImportDictionaryDbResponse.EMPTY }, private val invokeUploadDictionary: (AppUserId, ResourceEntity) -> DictionaryDbResponse = { _, _ -> DictionaryDbResponse.EMPTY }, @@ -27,8 +26,7 @@ class MockDbDictionaryRepository( override fun findDictionariesByUserId(userId: String): Sequence = invokeGetAllDictionaries(userId) - override fun createDictionary(userId: AppUserId, entity: DictionaryEntity): DictionaryDbResponse = - invokeCreateDictionary(userId, entity) + override fun createDictionary(entity: DbDictionary): DbDictionary = invokeCreateDictionary(entity) override fun removeDictionary(userId: AppUserId, dictionaryId: DictionaryId): RemoveDictionaryDbResponse = invokeDeleteDictionary(userId, dictionaryId) diff --git a/db-mem/src/main/kotlin/MemDatabase.kt b/db-mem/src/main/kotlin/MemDatabase.kt index d09e1541..041c9883 100644 --- a/db-mem/src/main/kotlin/MemDatabase.kt +++ b/db-mem/src/main/kotlin/MemDatabase.kt @@ -79,10 +79,14 @@ class MemDatabase private constructor( } fun saveDictionary(dictionary: MemDbDictionary): MemDbDictionary { + val userId = requireNotNull(dictionary.userId) { "User id is required" } val resource = - requireNotNull(resources[dictionary.userId]) { "Unknown user ${dictionary.userId}" } + requireNotNull(resources[userId]) { "Unknown user ${dictionary.userId}" } val id = dictionary.id ?: idGenerator.nextDictionaryId() - val res = dictionary.copy(id = id, changedAt = dictionary.changedAt ?: OffsetDateTime.now(ZoneOffset.UTC).toLocalDateTime()) + val res = dictionary.copy( + id = id, + changedAt = dictionary.changedAt ?: OffsetDateTime.now(ZoneOffset.UTC).toLocalDateTime() + ) resource.dictionaries[id] = DictionaryResource(res) dictionariesChanged = true return res @@ -167,21 +171,21 @@ class MemDatabase private constructor( } if (usersChanged) { val users = users().sortedBy { it.id }.toList() - Paths.get(databaseHomeDirectory).resolve(usersDbFile).outputStream().use { + Paths.get(databaseHomeDirectory).resolve(USERS_DB_FILE).outputStream().use { writeUsers(users, it) } usersChanged = false } if (cardsChanged) { val cards = cards().sortedBy { it.id }.toList() - Paths.get(databaseHomeDirectory).resolve(cardsDbFile).outputStream().use { + Paths.get(databaseHomeDirectory).resolve(CARDS_DB_FILE).outputStream().use { writeCards(cards, it) } cardsChanged = false } if (dictionariesChanged) { val dictionaries = dictionaryResources().map { it.dictionary }.sortedBy { it.id }.toList() - Paths.get(databaseHomeDirectory).resolve(dictionariesDbFile).outputStream().use { + Paths.get(databaseHomeDirectory).resolve(DICTIONARY_DB_FILE).outputStream().use { writeDictionaries(dictionaries, it) } dictionariesChanged = false @@ -199,11 +203,11 @@ class MemDatabase private constructor( ) companion object { - private const val usersDbFile = "users.csv" - private const val dictionariesDbFile = "dictionaries.csv" - private const val cardsDbFile = "cards.csv" + private const val USERS_DB_FILE = "users.csv" + private const val DICTIONARY_DB_FILE = "dictionaries.csv" + private const val CARDS_DB_FILE = "cards.csv" + private const val CLASSPATH_PREFIX = "classpath:" - private const val classpathPrefix = "classpath:" private val logger = LoggerFactory.getLogger(MemDatabase::class.java) /** @@ -240,7 +244,7 @@ class MemDatabase private constructor( * Loads dictionary store from classpath or directory. */ internal fun load(databaseLocation: String): MemDatabase { - val fromClassPath = databaseLocation.startsWith(classpathPrefix) + val fromClassPath = databaseLocation.startsWith(CLASSPATH_PREFIX) val res = if (fromClassPath) { loadDatabaseResourcesFromClassPath(databaseLocation) } else { @@ -267,9 +271,9 @@ class MemDatabase private constructor( private fun loadDatabaseResourcesFromDirectory( directoryDbLocation: String, ): MutableMap { - val usersFile = Paths.get(directoryDbLocation).resolve(usersDbFile).toRealPath() - val cardsFile = Paths.get(directoryDbLocation).resolve(cardsDbFile).toRealPath() - val dictionariesFile = Paths.get(directoryDbLocation).resolve(dictionariesDbFile).toRealPath() + val usersFile = Paths.get(directoryDbLocation).resolve(USERS_DB_FILE).toRealPath() + val cardsFile = Paths.get(directoryDbLocation).resolve(CARDS_DB_FILE).toRealPath() + val dictionariesFile = Paths.get(directoryDbLocation).resolve(DICTIONARY_DB_FILE).toRealPath() logger.info("Load users data from file: <$usersFile>.") val users = usersFile.inputStream().use { readUsers(it) @@ -288,9 +292,9 @@ class MemDatabase private constructor( private fun loadDatabaseResourcesFromClassPath( classpathDbLocation: String, ): MutableMap { - val usersFile = resolveClasspathResource(classpathDbLocation, usersDbFile) - val cardsFile = resolveClasspathResource(classpathDbLocation, cardsDbFile) - val dictionariesFile = resolveClasspathResource(classpathDbLocation, dictionariesDbFile) + val usersFile = resolveClasspathResource(classpathDbLocation, USERS_DB_FILE) + val cardsFile = resolveClasspathResource(classpathDbLocation, CARDS_DB_FILE) + val dictionariesFile = resolveClasspathResource(classpathDbLocation, DICTIONARY_DB_FILE) logger.info("Load users data from classpath: <$usersFile>.") val users = checkNotNull(MemDatabase::class.java.getResourceAsStream(usersFile)).use { readUsers(it) @@ -489,7 +493,7 @@ class MemDatabase private constructor( } private fun resolveClasspathResource(classpathDir: String, classpathFilename: String): String { - return "${classpathDir.substringAfter(classpathPrefix)}/$classpathFilename".replace("//", "/") + return "${classpathDir.substringAfter(CLASSPATH_PREFIX)}/$classpathFilename".replace("//", "/") } } diff --git a/db-mem/src/main/kotlin/MemDbCardRepository.kt b/db-mem/src/main/kotlin/MemDbCardRepository.kt index 7b8a2678..ce5b9fab 100644 --- a/db-mem/src/main/kotlin/MemDbCardRepository.kt +++ b/db-mem/src/main/kotlin/MemDbCardRepository.kt @@ -12,21 +12,26 @@ class MemDbCardRepository( ) : DbCardRepository { private val database = MemDatabase.get(dbConfig.dataLocation) - override fun findCardById(cardId: String): DbCard? { - require(cardId.isNotBlank()) - return database.findCardById(cardId.toLong())?.toCardEntity() - } + override fun findCardById(cardId: String): DbCard? = + database.findCardById(require(cardId.isNotBlank()).run { cardId.toLong() })?.toDbCard() - override fun findCardsByDictionaryId(dictionaryId: String): Sequence { - require(dictionaryId.isNotBlank()) - return database.findCardsByDictionaryId(dictionaryId.toLong()).map { it.toCardEntity() } - } + override fun findCardsByDictionaryId(dictionaryId: String): Sequence = + database.findCardsByDictionaryId(require(dictionaryId.isNotBlank()).run { dictionaryId.toLong() }) + .map { it.toDbCard() } + + override fun findCardsByDictionaryIdIn(dictionaryIds: Iterable): Sequence = + database.findCardsByDictionaryIds(dictionaryIds.onEach { require(it.isNotBlank()) }.map { it.toLong() }) + .map { it.toDbCard() } + + override fun findCardsByIdIn(cardIds: Iterable): Sequence = + database.findCardsById(cardIds.onEach { require(it.isNotBlank()) }.map { it.toLong() }) + .map { it.toDbCard() } override fun createCard(cardEntity: DbCard): DbCard { validateCardEntityForCreate(cardEntity) val timestamp = systemNow() return try { - database.saveCard(cardEntity.toMemDbCard().copy(changedAt = timestamp)).toCardEntity() + database.saveCard(cardEntity.toMemDbCard().copy(changedAt = timestamp)).toDbCard() } catch (ex: Exception) { throw DbDataException("Can't create card $cardEntity", ex) } @@ -40,7 +45,7 @@ class MemDbCardRepository( throw DbDataException("Changing dictionary-id is not allowed; card id = ${cardEntity.cardId.toLong()}") } val timestamp = systemNow() - return database.saveCard(cardEntity.toMemDbCard().copy(changedAt = timestamp)).toCardEntity() + return database.saveCard(cardEntity.toMemDbCard().copy(changedAt = timestamp)).toDbCard() } override fun deleteCard(cardId: String): DbCard { @@ -50,6 +55,6 @@ class MemDbCardRepository( if (!database.deleteCardById(cardId.toLong())) { throw DbDataException("Can't delete card, id = ${cardId.toLong()}") } - return found.copy(changedAt = timestamp).toCardEntity() + return found.copy(changedAt = timestamp).toDbCard() } } \ No newline at end of file diff --git a/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt b/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt index 1dae1371..550fc451 100644 --- a/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt +++ b/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt @@ -13,7 +13,6 @@ import com.gitlab.sszuev.flashcards.common.wrongResourceDbError import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbDictionary import com.gitlab.sszuev.flashcards.model.common.AppError import com.gitlab.sszuev.flashcards.model.common.AppUserId -import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity import com.gitlab.sszuev.flashcards.repositories.DbDictionary @@ -32,17 +31,11 @@ class MemDbDictionaryRepository( override fun findDictionaryById(dictionaryId: String): DbDictionary? = database.findDictionaryById(dictionaryId.toLong())?.toDbDictionary() - override fun findDictionariesByUserId(userId: String): Sequence { - val dictionaries = this.database.findDictionariesByUserId(userId.toLong()) - return dictionaries.map { it.toDbDictionary() } - } + override fun findDictionariesByUserId(userId: String): Sequence = + this.database.findDictionariesByUserId(userId.toLong()).map { it.toDbDictionary() } - override fun createDictionary(userId: AppUserId, entity: DictionaryEntity): DictionaryDbResponse { - val timestamp = systemNow() - val dictionary = - database.saveDictionary(entity.toMemDbDictionary().copy(userId = userId.asLong(), changedAt = timestamp)) - return DictionaryDbResponse(dictionary = dictionary.toDictionaryEntity()) - } + override fun createDictionary(entity: DbDictionary): DbDictionary = + database.saveDictionary(entity.toMemDbDictionary().copy(changedAt = systemNow())).toDbDictionary() override fun removeDictionary(userId: AppUserId, dictionaryId: DictionaryId): RemoveDictionaryDbResponse { val timestamp = systemNow() diff --git a/db-mem/src/main/kotlin/MemDbEntityMapper.kt b/db-mem/src/main/kotlin/MemDbEntityMapper.kt index c2b32fe0..3353f9a5 100644 --- a/db-mem/src/main/kotlin/MemDbEntityMapper.kt +++ b/db-mem/src/main/kotlin/MemDbEntityMapper.kt @@ -8,7 +8,6 @@ import com.gitlab.sszuev.flashcards.common.CommonWordDto import com.gitlab.sszuev.flashcards.common.LanguageRepository import com.gitlab.sszuev.flashcards.common.asJava import com.gitlab.sszuev.flashcards.common.asKotlin -import com.gitlab.sszuev.flashcards.common.asLong import com.gitlab.sszuev.flashcards.common.detailsAsCommonCardDetailsDto import com.gitlab.sszuev.flashcards.common.documents.DocumentCard import com.gitlab.sszuev.flashcards.common.documents.DocumentCardStatus @@ -146,15 +145,16 @@ internal fun MemDbDictionary.toDbDictionary() = DbDictionary( targetLang = this.targetLanguage.toDbLang(), ) -internal fun DictionaryEntity.toMemDbDictionary(): MemDbDictionary = MemDbDictionary( - id = if (this.dictionaryId == DictionaryId.NONE) null else this.dictionaryId.asLong(), +internal fun DbDictionary.toMemDbDictionary(): MemDbDictionary = MemDbDictionary( + id = if (this.dictionaryId.isBlank()) null else this.dictionaryId.toLong(), name = this.name, sourceLanguage = this.sourceLang.toMemDbLanguage(), targetLanguage = this.targetLang.toMemDbLanguage(), details = emptyMap(), + userId = if (this.userId.isBlank()) null else this.userId.toLong() ) -internal fun MemDbCard.toCardEntity(): DbCard { +internal fun MemDbCard.toDbCard(): DbCard { val details: CommonCardDetailsDto = this.detailsAsCommonCardDetailsDto() return DbCard( cardId = id?.toString() ?: "", @@ -189,8 +189,8 @@ internal fun createMemDbLanguage(tag: String): MemDbLanguage = MemDbLanguage( partsOfSpeech = LanguageRepository.partsOfSpeech(tag) ) -internal fun LangEntity.toMemDbLanguage(): MemDbLanguage = MemDbLanguage( - id = this.langId.asString(), +internal fun DbLang.toMemDbLanguage(): MemDbLanguage = MemDbLanguage( + id = this.langId, partsOfSpeech = this.partsOfSpeech, ) diff --git a/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt b/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt index 39383f15..ecbcbad2 100644 --- a/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt +++ b/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt @@ -52,17 +52,17 @@ class PgDbDictionaryRepository( PgDbDictionary.find(Dictionaries.userId eq userId.toUserId()).map { it.toDbDictionary() }.asSequence() } - override fun createDictionary(userId: AppUserId, entity: DictionaryEntity): DictionaryDbResponse { + override fun createDictionary(entity: DbDictionary): DbDictionary { return connection.execute { val timestamp = systemNow() val dictionaryId = Dictionaries.insertAndGetId { - it[sourceLanguage] = entity.sourceLang.langId.asString() - it[targetLanguage] = entity.targetLang.langId.asString() + it[sourceLanguage] = entity.sourceLang.langId + it[targetLanguage] = entity.targetLang.langId it[name] = entity.name - it[Dictionaries.userId] = userId.asLong() + it[userId] = entity.userId.toLong() it[changedAt] = timestamp } - DictionaryDbResponse(dictionary = entity.copy(dictionaryId = dictionaryId.asDictionaryId())) + entity.copy(dictionaryId = dictionaryId.value.toString()) } } diff --git a/specs/src/main/kotlin/Stubs.kt b/specs/src/main/kotlin/Stubs.kt index b2eda14d..7b057e56 100644 --- a/specs/src/main/kotlin/Stubs.kt +++ b/specs/src/main/kotlin/Stubs.kt @@ -2,6 +2,7 @@ package com.gitlab.sszuev.flashcards.stubs import com.gitlab.sszuev.flashcards.model.common.AppError import com.gitlab.sszuev.flashcards.model.common.AppStub +import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.CardLearn @@ -22,6 +23,7 @@ val stubDictionary = DictionaryEntity( name = "Stub-dictionary", sourceLang = LangEntity(LangId("SL"), listOf("A", "B", "C")), targetLang = LangEntity(LangId("TL"), listOf("X", "Y")), + userId = AppUserId("42"), ) val stubDictionaries = listOf(stubDictionary) From 19420f9442d72e04ae496bab93e7f65eb1843015 Mon Sep 17 00:00:00 2001 From: sszuev Date: Thu, 4 Apr 2024 15:52:56 +0300 Subject: [PATCH 13/18] common & core & db: [#28] refactoring DbDictionaryRepository#removeDictionary -> DbDictionaryRepository#deleteDictionary --- .../repositories/DbDictionaryRepository.kt | 31 +++++---- .../NoOpDbDictionaryRepository.kt | 2 +- .../processes/DictionaryProcessWokers.kt | 13 ++-- .../kotlin/DictionaryCorProcessorRunTest.kt | 16 +++-- .../kotlin/DbDictionaryRepositoryTest.kt | 23 +++---- .../mocks/MockDbDictionaryRepository.kt | 6 +- .../main/kotlin/MemDbDictionaryRepository.kt | 24 +++---- .../main/kotlin/PgDbDictionaryRepository.kt | 66 ++++++++----------- 8 files changed, 88 insertions(+), 93 deletions(-) diff --git a/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt b/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt index 7e874287..a4fc3e8e 100644 --- a/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt +++ b/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt @@ -7,16 +7,34 @@ import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity interface DbDictionaryRepository { + /** + * Finds dictionary by id. + */ fun findDictionaryById(dictionaryId: String): DbDictionary? + /** + * Finds dictionaries by their id. + */ fun findDictionariesByIdIn(dictionaryIds: Iterable): Sequence = dictionaryIds.asSequence().mapNotNull { findDictionaryById(it) } + /** + * Finds dictionaries by user id. + */ fun findDictionariesByUserId(userId: String): Sequence + /** + * Creates dictionary. + * @throws IllegalArgumentException if the specified dictionary has illegal structure + */ fun createDictionary(entity: DbDictionary): DbDictionary - fun removeDictionary(userId: AppUserId, dictionaryId: DictionaryId): RemoveDictionaryDbResponse + /** + * Deletes dictionary by id. + * @throws IllegalArgumentException wrong [dictionaryId] + * @throws DbDataException dictionary not found. + */ + fun deleteDictionary(dictionaryId: String): DbDictionary fun importDictionary(userId: AppUserId, dictionaryId: DictionaryId): ImportDictionaryDbResponse @@ -24,17 +42,6 @@ interface DbDictionaryRepository { } -data class RemoveDictionaryDbResponse( - val dictionary: DictionaryEntity = DictionaryEntity.EMPTY, - val errors: List = emptyList(), -) { - constructor(error: AppError) : this(errors = listOf(error)) - - companion object { - val EMPTY = RemoveDictionaryDbResponse() - } -} - data class ImportDictionaryDbResponse( val resource: ResourceEntity = ResourceEntity.DUMMY, val errors: List = emptyList() diff --git a/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt b/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt index b39150b2..2182210a 100644 --- a/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt +++ b/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt @@ -11,7 +11,7 @@ object NoOpDbDictionaryRepository : DbDictionaryRepository { override fun createDictionary(entity: DbDictionary): DbDictionary = noOp() - override fun removeDictionary(userId: AppUserId, dictionaryId: DictionaryId): RemoveDictionaryDbResponse = noOp() + override fun deleteDictionary(dictionaryId: String): DbDictionary = noOp() override fun importDictionary(userId: AppUserId, dictionaryId: DictionaryId): ImportDictionaryDbResponse = noOp() diff --git a/core/src/main/kotlin/processes/DictionaryProcessWokers.kt b/core/src/main/kotlin/processes/DictionaryProcessWokers.kt index df515c2c..42d85172 100644 --- a/core/src/main/kotlin/processes/DictionaryProcessWokers.kt +++ b/core/src/main/kotlin/processes/DictionaryProcessWokers.kt @@ -68,11 +68,16 @@ fun ChainDSL.processDeleteDictionary() = worker { } process { val userId = this.contextUserEntity.id - val res = + val dictionaryId = this.normalizedRequestDictionaryId + val dictionary = this.repositories.dictionaryRepository(this.workMode) + .findDictionaryById(dictionaryId.asString())?.toDictionaryEntity() + if (dictionary == null) { + this.errors.add(noDictionaryFoundDataError("deleteDictionary", dictionaryId)) + } else if (dictionary.userId != userId) { + this.errors.add(forbiddenEntityDataError("deleteDictionary", dictionaryId, userId)) + } else { this.repositories.dictionaryRepository(this.workMode) - .removeDictionary(userId, this.normalizedRequestDictionaryId) - if (res.errors.isNotEmpty()) { - this.errors.addAll(res.errors) + .deleteDictionary(this.normalizedRequestDictionaryId.asString()) } this.status = if (this.errors.isNotEmpty()) AppStatus.FAIL else AppStatus.RUN } diff --git a/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt b/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt index 202c73ed..f65866ad 100644 --- a/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt +++ b/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt @@ -21,7 +21,6 @@ import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository import com.gitlab.sszuev.flashcards.repositories.DbUserRepository import com.gitlab.sszuev.flashcards.repositories.DictionaryDbResponse import com.gitlab.sszuev.flashcards.repositories.ImportDictionaryDbResponse -import com.gitlab.sszuev.flashcards.repositories.RemoveDictionaryDbResponse import com.gitlab.sszuev.flashcards.repositories.UserEntityDbResponse import com.gitlab.sszuev.flashcards.stubs.stubDictionaries import com.gitlab.sszuev.flashcards.stubs.stubDictionary @@ -137,13 +136,16 @@ internal class DictionaryCorProcessorRunTest { @Test fun `test delete-dictionary success`() = runTest { val testId = DictionaryId("42") - val response = RemoveDictionaryDbResponse() + val response = stubDictionary - var wasCalled = false + var isDeleteDictionaryCalled = false val repository = MockDbDictionaryRepository( - invokeDeleteDictionary = { _, it -> - wasCalled = true - if (it == testId) response else throw AssertionError() + invokeDeleteDictionary = { + isDeleteDictionaryCalled = true + if (it == testId.asString()) response.toDbDictionary() else Assertions.fail() + }, + invokeFindDictionaryById = { + if (it == testId.asString()) response.toDbDictionary() else Assertions.fail() } ) @@ -152,7 +154,7 @@ internal class DictionaryCorProcessorRunTest { DictionaryCorProcessor().execute(context) - Assertions.assertTrue(wasCalled) + Assertions.assertTrue(isDeleteDictionaryCalled) Assertions.assertEquals(requestId(DictionaryOperation.DELETE_DICTIONARY), context.requestId) Assertions.assertEquals(AppStatus.OK, context.status) Assertions.assertTrue(context.errors.isEmpty()) diff --git a/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt b/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt index 0a4152fd..c4977707 100644 --- a/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt +++ b/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt @@ -5,6 +5,7 @@ import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.LangEntity import com.gitlab.sszuev.flashcards.model.domain.LangId import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity +import com.gitlab.sszuev.flashcards.repositories.DbDataException import com.gitlab.sszuev.flashcards.repositories.DbDictionary import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository import com.gitlab.sszuev.flashcards.repositories.DbLang @@ -14,7 +15,6 @@ import org.junit.jupiter.api.Order import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestMethodOrder -@Suppress("FunctionName") @TestMethodOrder(MethodOrderer.OrderAnnotation::class) abstract class DbDictionaryRepositoryTest { abstract val repository: DbDictionaryRepository @@ -139,24 +139,19 @@ abstract class DbDictionaryRepositoryTest { @Test fun `test delete dictionary success`() { // Business vocabulary (Job) - val res = repository.removeDictionary(userId, DictionaryId("1")) - Assertions.assertTrue(res.errors.isEmpty()) + val res = repository.deleteDictionary("1") + Assertions.assertEquals("1", res.dictionaryId) + Assertions.assertEquals("Irregular Verbs", res.name) + Assertions.assertNull(repository.findDictionaryById("1")) } @Order(4) @Test fun `test delete dictionary not found`() { - val id = DictionaryId("42") - val res = repository.removeDictionary(userId, id) - Assertions.assertEquals(1, res.errors.size) - val error = res.errors[0] - Assertions.assertEquals("database::removeDictionary", error.code) - Assertions.assertEquals(id.asString(), error.field) - Assertions.assertEquals("database", error.group) - Assertions.assertEquals( - """Error while removeDictionary: dictionary with id="${id.asString()}" not found""", - error.message - ) + val id = "42" + Assertions.assertThrows(DbDataException::class.java) { + repository.deleteDictionary(id) + } } @Order(5) diff --git a/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt b/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt index 41eae219..5db67492 100644 --- a/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt +++ b/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt @@ -7,14 +7,13 @@ import com.gitlab.sszuev.flashcards.repositories.DbDictionary import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository import com.gitlab.sszuev.flashcards.repositories.DictionaryDbResponse import com.gitlab.sszuev.flashcards.repositories.ImportDictionaryDbResponse -import com.gitlab.sszuev.flashcards.repositories.RemoveDictionaryDbResponse class MockDbDictionaryRepository( private val invokeFindDictionaryById: (String) -> DbDictionary? = { null }, private val invokeFindDictionariesByIdIn: (Iterable) -> Sequence = { emptySequence() }, private val invokeGetAllDictionaries: (String) -> Sequence = { emptySequence() }, private val invokeCreateDictionary: (DbDictionary) -> DbDictionary = { DbDictionary.NULL }, - private val invokeDeleteDictionary: (AppUserId, DictionaryId) -> RemoveDictionaryDbResponse = { _, _ -> RemoveDictionaryDbResponse.EMPTY }, + private val invokeDeleteDictionary: (String) -> DbDictionary = { DbDictionary.NULL }, private val invokeDownloadDictionary: (AppUserId, DictionaryId) -> ImportDictionaryDbResponse = { _, _ -> ImportDictionaryDbResponse.EMPTY }, private val invokeUploadDictionary: (AppUserId, ResourceEntity) -> DictionaryDbResponse = { _, _ -> DictionaryDbResponse.EMPTY }, ) : DbDictionaryRepository { @@ -28,8 +27,7 @@ class MockDbDictionaryRepository( override fun createDictionary(entity: DbDictionary): DbDictionary = invokeCreateDictionary(entity) - override fun removeDictionary(userId: AppUserId, dictionaryId: DictionaryId): RemoveDictionaryDbResponse = - invokeDeleteDictionary(userId, dictionaryId) + override fun deleteDictionary(dictionaryId: String): DbDictionary = invokeDeleteDictionary(dictionaryId) override fun importDictionary(userId: AppUserId, dictionaryId: DictionaryId): ImportDictionaryDbResponse = invokeDownloadDictionary(userId, dictionaryId) diff --git a/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt b/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt index 550fc451..bc3916f7 100644 --- a/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt +++ b/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt @@ -15,11 +15,11 @@ import com.gitlab.sszuev.flashcards.model.common.AppError import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity +import com.gitlab.sszuev.flashcards.repositories.DbDataException import com.gitlab.sszuev.flashcards.repositories.DbDictionary import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository import com.gitlab.sszuev.flashcards.repositories.DictionaryDbResponse import com.gitlab.sszuev.flashcards.repositories.ImportDictionaryDbResponse -import com.gitlab.sszuev.flashcards.repositories.RemoveDictionaryDbResponse class MemDbDictionaryRepository( dbConfig: MemDbConfig = MemDbConfig(), @@ -37,19 +37,15 @@ class MemDbDictionaryRepository( override fun createDictionary(entity: DbDictionary): DbDictionary = database.saveDictionary(entity.toMemDbDictionary().copy(changedAt = systemNow())).toDbDictionary() - override fun removeDictionary(userId: AppUserId, dictionaryId: DictionaryId): RemoveDictionaryDbResponse { - val timestamp = systemNow() - val errors = mutableListOf() - val found = checkDictionaryUser("removeDictionary", userId, dictionaryId, errors) - if (errors.isNotEmpty()) { - return RemoveDictionaryDbResponse(errors = errors) + override fun deleteDictionary(dictionaryId: String): DbDictionary { + require(dictionaryId.isNotBlank()) + val id = dictionaryId.toLong() + val found = database.findDictionaryById(id)?.toDbDictionary() + ?: throw DbDataException("Can't find dictionary $id") + if (!database.deleteDictionaryById(id)) { + throw DbDataException("Can't delete dictionary $id") } - if (!database.deleteDictionaryById(dictionaryId.asLong())) { - return RemoveDictionaryDbResponse(noDictionaryFoundDbError("removeDictionary", dictionaryId)) - } - return RemoveDictionaryDbResponse( - dictionary = checkNotNull(found).copy(changedAt = timestamp).toDictionaryEntity() - ) + return found } override fun importDictionary( @@ -88,7 +84,7 @@ class MemDbDictionaryRepository( return DictionaryDbResponse(dictionary = dictionary.toDictionaryEntity()) } - @Suppress("DuplicatedCode") + @Suppress("DuplicatedCode", "SameParameterValue") private fun checkDictionaryUser( operation: String, userId: AppUserId, diff --git a/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt b/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt index ecbcbad2..29576bf6 100644 --- a/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt +++ b/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt @@ -23,11 +23,11 @@ import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity +import com.gitlab.sszuev.flashcards.repositories.DbDataException import com.gitlab.sszuev.flashcards.repositories.DbDictionary import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository import com.gitlab.sszuev.flashcards.repositories.DictionaryDbResponse import com.gitlab.sszuev.flashcards.repositories.ImportDictionaryDbResponse -import com.gitlab.sszuev.flashcards.repositories.RemoveDictionaryDbResponse import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList import org.jetbrains.exposed.sql.deleteWhere @@ -52,45 +52,37 @@ class PgDbDictionaryRepository( PgDbDictionary.find(Dictionaries.userId eq userId.toUserId()).map { it.toDbDictionary() }.asSequence() } - override fun createDictionary(entity: DbDictionary): DbDictionary { - return connection.execute { - val timestamp = systemNow() - val dictionaryId = Dictionaries.insertAndGetId { - it[sourceLanguage] = entity.sourceLang.langId - it[targetLanguage] = entity.targetLang.langId - it[name] = entity.name - it[userId] = entity.userId.toLong() - it[changedAt] = timestamp - } - entity.copy(dictionaryId = dictionaryId.value.toString()) + override fun createDictionary(entity: DbDictionary): DbDictionary = connection.execute { + val timestamp = systemNow() + val dictionaryId = Dictionaries.insertAndGetId { + it[sourceLanguage] = entity.sourceLang.langId + it[targetLanguage] = entity.targetLang.langId + it[name] = entity.name + it[userId] = entity.userId.toLong() + it[changedAt] = timestamp } + entity.copy(dictionaryId = dictionaryId.value.toString()) } - override fun removeDictionary(userId: AppUserId, dictionaryId: DictionaryId): RemoveDictionaryDbResponse { - return connection.execute { - val errors = mutableListOf() - val found = checkDictionaryUser("removeDictionary", userId, dictionaryId, errors) - if (errors.isNotEmpty()) { - return@execute RemoveDictionaryDbResponse(errors = errors) - } - checkNotNull(found) - val cardIds = Cards.selectAll().where { - Cards.dictionaryId eq found.id - }.map { - it[Cards.id] - } - Cards.deleteWhere { - this.id inList cardIds - } - val res = Dictionaries.deleteWhere { - Dictionaries.id eq found.id - } - RemoveDictionaryDbResponse( - errors = if (res == 0) - listOf(noDictionaryFoundDbError(operation = "removeDictionary", id = dictionaryId)) - else emptyList() - ) + override fun deleteDictionary(dictionaryId: String): DbDictionary = connection.execute { + require(dictionaryId.isNotBlank()) + val id = dictionaryId.toLong() + val found = PgDbDictionary.findById(id)?.toDbDictionary() ?: throw DbDataException("Can't find dictionary $id") + val cardIds = Cards.selectAll().where { + Cards.dictionaryId eq id + }.map { + it[Cards.id] + } + Cards.deleteWhere { + this.id inList cardIds + } + val res = Dictionaries.deleteWhere { + Dictionaries.id eq id + } + if (res != 1) { + throw DbDataException("Can't delete dictionary $id") } + found } override fun importDictionary(userId: AppUserId, dictionaryId: DictionaryId): ImportDictionaryDbResponse { @@ -164,7 +156,7 @@ class PgDbDictionaryRepository( } } - @Suppress("DuplicatedCode") + @Suppress("DuplicatedCode", "SameParameterValue") private fun checkDictionaryUser( operation: String, userId: AppUserId, From 4ecb9eaf80b14e412d530c83040af0cdd690c2ae Mon Sep 17 00:00:00 2001 From: sszuev Date: Fri, 5 Apr 2024 12:05:36 +0300 Subject: [PATCH 14/18] common & core & db: [#28] move import\export logic to `:core`; remove `DbDictionaryRepository#[import|export]Dictionary`; other related changes --- .../kotlin/repositories/DbCardRepository.kt | 5 + .../repositories/DbDictionaryRepository.kt | 32 ---- .../NoOpDbDictionaryRepository.kt | 8 - core/build.gradle.kts | 1 + core/src/main/kotlin/mappers/DocMappers.kt | 147 ++++++++++++++++++ .../processes/DictionaryProcessWokers.kt | 58 +++++-- .../kotlin/DictionaryCorProcessorRunTest.kt | 121 ++++++++++---- .../src/test/kotlin/mappers/DocMappersTest.kt | 100 ++++++++++++ db-common/src/main/kotlin/DocumentMappers.kt | 107 ------------- .../kotlin/{documents => }/IdGenerator.kt | 2 +- .../src/test/kotlin/EntityMappersTest.kt | 36 ----- .../kotlin/DbDictionaryRepositoryTest.kt | 104 ++----------- .../kotlin/mocks/MockDbCardRepository.kt | 5 +- .../mocks/MockDbDictionaryRepository.kt | 13 -- db-mem/src/main/kotlin/IdSequences.kt | 2 +- .../main/kotlin/MemDbDictionaryRepository.kt | 71 --------- db-mem/src/main/kotlin/MemDbEntityMapper.kt | 77 --------- db-mem/src/test/kotlin/EntityMapperTest.kt | 65 -------- .../main/kotlin/PgDbDictionaryRepository.kt | 111 ------------- db-pg/src/main/kotlin/PgDbEntityMapper.kt | 16 -- 20 files changed, 408 insertions(+), 673 deletions(-) create mode 100644 core/src/main/kotlin/mappers/DocMappers.kt create mode 100644 core/src/test/kotlin/mappers/DocMappersTest.kt delete mode 100644 db-common/src/main/kotlin/DocumentMappers.kt rename db-common/src/main/kotlin/{documents => }/IdGenerator.kt (61%) delete mode 100644 db-common/src/test/kotlin/EntityMappersTest.kt delete mode 100644 db-mem/src/test/kotlin/EntityMapperTest.kt diff --git a/common/src/commonMain/kotlin/repositories/DbCardRepository.kt b/common/src/commonMain/kotlin/repositories/DbCardRepository.kt index 1298a34f..f4a251b0 100644 --- a/common/src/commonMain/kotlin/repositories/DbCardRepository.kt +++ b/common/src/commonMain/kotlin/repositories/DbCardRepository.kt @@ -35,6 +35,11 @@ interface DbCardRepository { */ fun createCard(cardEntity: DbCard): DbCard + /** + * Performs bulk create. + */ + fun createCards(cardEntities: Iterable): List = cardEntities.map { createCard(it) } + /** * Updates the card entity. * @throws IllegalArgumentException if the specified card has no card id or has illegal structure diff --git a/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt b/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt index a4fc3e8e..239e8dde 100644 --- a/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt +++ b/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt @@ -1,11 +1,5 @@ package com.gitlab.sszuev.flashcards.repositories -import com.gitlab.sszuev.flashcards.model.common.AppError -import com.gitlab.sszuev.flashcards.model.common.AppUserId -import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity -import com.gitlab.sszuev.flashcards.model.domain.DictionaryId -import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity - interface DbDictionaryRepository { /** * Finds dictionary by id. @@ -36,30 +30,4 @@ interface DbDictionaryRepository { */ fun deleteDictionary(dictionaryId: String): DbDictionary - fun importDictionary(userId: AppUserId, dictionaryId: DictionaryId): ImportDictionaryDbResponse - - fun exportDictionary(userId: AppUserId, resource: ResourceEntity): DictionaryDbResponse - -} - -data class ImportDictionaryDbResponse( - val resource: ResourceEntity = ResourceEntity.DUMMY, - val errors: List = emptyList() -) { - constructor(error: AppError) : this(errors = listOf(error)) - - companion object { - val EMPTY = ImportDictionaryDbResponse() - } -} - -data class DictionaryDbResponse( - val dictionary: DictionaryEntity = DictionaryEntity.EMPTY, - val errors: List = emptyList() -) { - constructor(error: AppError) : this(errors = listOf(error)) - - companion object { - val EMPTY = DictionaryDbResponse() - } } \ No newline at end of file diff --git a/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt b/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt index 2182210a..74d96d56 100644 --- a/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt +++ b/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt @@ -1,9 +1,5 @@ package com.gitlab.sszuev.flashcards.repositories -import com.gitlab.sszuev.flashcards.model.common.AppUserId -import com.gitlab.sszuev.flashcards.model.domain.DictionaryId -import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity - object NoOpDbDictionaryRepository : DbDictionaryRepository { override fun findDictionaryById(dictionaryId: String): DbDictionary = noOp() @@ -13,10 +9,6 @@ object NoOpDbDictionaryRepository : DbDictionaryRepository { override fun deleteDictionary(dictionaryId: String): DbDictionary = noOp() - override fun importDictionary(userId: AppUserId, dictionaryId: DictionaryId): ImportDictionaryDbResponse = noOp() - - override fun exportDictionary(userId: AppUserId, resource: ResourceEntity): DictionaryDbResponse = noOp() - private fun noOp(): Nothing { error("Must not be called.") } diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 7a499be3..54bf8901 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -10,6 +10,7 @@ dependencies { val kotlinCoroutinesVersion: String by project implementation(project(":cor-lib")) + implementation(project(":db-common")) implementation(project(":common")) implementation(project(":specs")) diff --git a/core/src/main/kotlin/mappers/DocMappers.kt b/core/src/main/kotlin/mappers/DocMappers.kt new file mode 100644 index 00000000..b7f8ebf6 --- /dev/null +++ b/core/src/main/kotlin/mappers/DocMappers.kt @@ -0,0 +1,147 @@ +package com.gitlab.sszuev.flashcards.core.mappers + +import com.gitlab.sszuev.flashcards.AppConfig +import com.gitlab.sszuev.flashcards.common.LanguageRepository +import com.gitlab.sszuev.flashcards.common.documents.DocumentCard +import com.gitlab.sszuev.flashcards.common.documents.DocumentCardStatus +import com.gitlab.sszuev.flashcards.common.documents.DocumentDictionary +import com.gitlab.sszuev.flashcards.model.domain.CardEntity +import com.gitlab.sszuev.flashcards.model.domain.CardWordEntity +import com.gitlab.sszuev.flashcards.model.domain.CardWordExampleEntity +import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity +import com.gitlab.sszuev.flashcards.model.domain.LangEntity +import com.gitlab.sszuev.flashcards.model.domain.LangId + +fun DocumentDictionary.toDictionaryEntity(): DictionaryEntity = DictionaryEntity( + name = this.name, + sourceLang = createLangEntity(this.sourceLang), + targetLang = createLangEntity(this.targetLang), +) + +fun DictionaryEntity.toDocumentDictionary(): DocumentDictionary = DocumentDictionary( + name = this.name, + sourceLang = this.sourceLang.langId.asString(), + targetLang = this.targetLang.langId.asString(), + cards = emptyList(), +) + +fun DocumentCard.toCardEntity(config: AppConfig): CardEntity = CardEntity( + words = this.toCardWordEntity(), + details = emptyMap(), + answered = config.answered(this.status), +) + +fun CardEntity.toDocumentCard(config: AppConfig): DocumentCard { + val word = this.words.first() + return DocumentCard( + text = word.word, + transcription = word.transcription, + partOfSpeech = word.partOfSpeech, + translations = word.toDocumentTranslations(), + examples = word.toDocumentExamples(), + status = config.status(this.answered), + ) +} + +internal fun createLangEntity(documentTag: String): LangEntity = LangEntity( + langId = LangId(documentTag), + partsOfSpeech = LanguageRepository.partsOfSpeech(documentTag) +) + +private fun CardWordEntity.toDocumentTranslations(): List = translations.map { it.joinToString(",") } + +private fun CardWordEntity.toDocumentExamples(): List = + examples.map { if (it.translation != null) "${it.text} -- ${it.translation}" else it.text } + +private fun DocumentCard.toCardWordEntity(): List { + val forms = this.text.split(",").map { it.trim() }.filter { it.isNotEmpty() } + val primaryTranslations = this.translations.map { + fromDocumentCardTranslationToCommonWordDtoTranslation(it) + } + val primaryExamples = this.examples.map { example -> + val parts = example.split(" -- ").filter { it.isNotEmpty() } + val (e, t) = if (parts.size == 2) { + parts[0] to parts[1] + } else { + example to null + } + CardWordExampleEntity(text = e, translation = t) + } + return forms.mapIndexed { i, word -> + val examples = if (i == 0) primaryExamples else emptyList() + val translations = if (i == 0) primaryTranslations else emptyList() + val transcription = if (i == 0) this.transcription ?: "" else null + val pos = if (i == 0) this.partOfSpeech ?: "" else null + CardWordEntity( + word = word, + transcription = transcription, + partOfSpeech = pos, + translations = translations, + examples = examples, + ) + } +} + +/** + * Splits the given `phrase` using comma (i.e. '`,`') as separator. + * Commas inside the parentheses (e.g. "`(x,y)`") are not considered. + * + * @param [phrase] + * @return [List] + */ +internal fun fromDocumentCardTranslationToCommonWordDtoTranslation(phrase: String): List { + val parts = phrase.split(",") + val res = mutableListOf() + var i = 0 + while (i < parts.size) { + val pi = parts[i].trim() + if (pi.isEmpty()) { + i++ + continue + } + if (!pi.contains("(") || pi.contains(")")) { + res.add(pi) + i++ + continue + } + val sb = StringBuilder(pi) + var j = i + 1 + while (j < parts.size) { + val pj = parts[j].trim { it <= ' ' } + if (pj.isEmpty()) { + j++ + continue + } + sb.append(", ").append(pj) + if (pj.contains(")")) { + break + } + j++ + } + if (sb.lastIndexOf(")") == -1) { + res.add(pi) + i++ + continue + } + res.add(sb.toString()) + i = j + i++ + } + return res +} + +private fun AppConfig.status(answered: Int?): DocumentCardStatus = if (answered == null) { + DocumentCardStatus.UNKNOWN +} else { + if (answered >= this.numberOfRightAnswers) { + DocumentCardStatus.LEARNED + } else { + DocumentCardStatus.IN_PROCESS + } +} + +private fun AppConfig.answered(status: DocumentCardStatus): Int = when (status) { + DocumentCardStatus.UNKNOWN -> 0 + DocumentCardStatus.IN_PROCESS -> 1 + DocumentCardStatus.LEARNED -> this.numberOfRightAnswers +} \ No newline at end of file diff --git a/core/src/main/kotlin/processes/DictionaryProcessWokers.kt b/core/src/main/kotlin/processes/DictionaryProcessWokers.kt index 42d85172..6940c7ab 100644 --- a/core/src/main/kotlin/processes/DictionaryProcessWokers.kt +++ b/core/src/main/kotlin/processes/DictionaryProcessWokers.kt @@ -1,13 +1,20 @@ package com.gitlab.sszuev.flashcards.core.processes import com.gitlab.sszuev.flashcards.DictionaryContext +import com.gitlab.sszuev.flashcards.common.documents.createReader +import com.gitlab.sszuev.flashcards.common.documents.createWriter +import com.gitlab.sszuev.flashcards.core.mappers.toCardEntity +import com.gitlab.sszuev.flashcards.core.mappers.toDbCard import com.gitlab.sszuev.flashcards.core.mappers.toDbDictionary import com.gitlab.sszuev.flashcards.core.mappers.toDictionaryEntity +import com.gitlab.sszuev.flashcards.core.mappers.toDocumentCard +import com.gitlab.sszuev.flashcards.core.mappers.toDocumentDictionary import com.gitlab.sszuev.flashcards.core.validators.fail import com.gitlab.sszuev.flashcards.corlib.ChainDSL import com.gitlab.sszuev.flashcards.corlib.worker import com.gitlab.sszuev.flashcards.model.common.AppStatus import com.gitlab.sszuev.flashcards.model.domain.DictionaryOperation +import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity fun ChainDSL.processGetAllDictionary() = worker { this.name = "process get-all-dictionary request" @@ -93,12 +100,26 @@ fun ChainDSL.processDownloadDictionary() = worker { } process { val userId = this.contextUserEntity.id - val res = - this.repositories.dictionaryRepository(this.workMode) - .importDictionary(userId, this.normalizedRequestDictionaryId) - this.responseDictionaryResourceEntity = res.resource - if (res.errors.isNotEmpty()) { - this.errors.addAll(res.errors) + val dictionaryId = this.normalizedRequestDictionaryId + val dictionary = this.repositories.dictionaryRepository(this.workMode) + .findDictionaryById(dictionaryId.asString())?.toDictionaryEntity() + if (dictionary == null) { + this.errors.add(noDictionaryFoundDataError("downloadDictionary", dictionaryId)) + } else if (dictionary.userId != userId) { + this.errors.add(forbiddenEntityDataError("downloadDictionary", dictionaryId, userId)) + } else { + val cards = this.repositories.cardRepository(this.workMode) + .findCardsByDictionaryId(dictionaryId.asString()) + .map { it.toCardEntity() } + .map { it.toDocumentCard(this.config) } + .toList() + val document = dictionary.toDocumentDictionary().copy(cards = cards) + try { + val res = createWriter().write(document) + this.responseDictionaryResourceEntity = ResourceEntity(resourceId = dictionaryId, data = res) + } catch (ex: Exception) { + handleThrowable(DictionaryOperation.DOWNLOAD_DICTIONARY, ex) + } } this.status = if (this.errors.isNotEmpty()) AppStatus.FAIL else AppStatus.RUN } @@ -113,13 +134,24 @@ fun ChainDSL.processUploadDictionary() = worker { this.status == AppStatus.RUN } process { - val res = this.repositories.dictionaryRepository(this.workMode).exportDictionary( - userId = this.contextUserEntity.id, - resource = this.requestDictionaryResourceEntity - ) - this.responseDictionaryEntity = res.dictionary - if (res.errors.isNotEmpty()) { - this.errors.addAll(res.errors) + try { + val document = createReader().parse(this.requestDictionaryResourceEntity.data) + val dictionary = this.repositories.dictionaryRepository(this.workMode) + .createDictionary( + document.toDictionaryEntity().copy(userId = this.contextUserEntity.id).toDbDictionary() + ) + .toDictionaryEntity() + val cards = document.cards.asSequence() + .map { it.toCardEntity(this.config) } + .map { it.copy(dictionaryId = dictionary.dictionaryId) } + .map { it.toDbCard() } + .toList() + if (cards.isNotEmpty()) { + this.repositories.cardRepository(this.workMode).createCards(cards) + } + this.responseDictionaryEntity = dictionary + } catch (ex: Exception) { + handleThrowable(DictionaryOperation.UPLOAD_DICTIONARY, ex) } this.status = if (this.errors.isNotEmpty()) AppStatus.FAIL else AppStatus.RUN } diff --git a/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt b/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt index f65866ad..11787edc 100644 --- a/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt +++ b/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt @@ -2,6 +2,7 @@ package com.gitlab.sszuev.flashcards.core import com.gitlab.sszuev.flashcards.AppRepositories import com.gitlab.sszuev.flashcards.DictionaryContext +import com.gitlab.sszuev.flashcards.core.mappers.toDbCard import com.gitlab.sszuev.flashcards.core.mappers.toDbDictionary import com.gitlab.sszuev.flashcards.core.normalizers.normalize import com.gitlab.sszuev.flashcards.dbcommon.mocks.MockDbCardRepository @@ -15,13 +16,14 @@ import com.gitlab.sszuev.flashcards.model.common.AppUserEntity import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.DictionaryOperation +import com.gitlab.sszuev.flashcards.model.domain.LangEntity +import com.gitlab.sszuev.flashcards.model.domain.LangId import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository import com.gitlab.sszuev.flashcards.repositories.DbUserRepository -import com.gitlab.sszuev.flashcards.repositories.DictionaryDbResponse -import com.gitlab.sszuev.flashcards.repositories.ImportDictionaryDbResponse import com.gitlab.sszuev.flashcards.repositories.UserEntityDbResponse +import com.gitlab.sszuev.flashcards.stubs.stubCard import com.gitlab.sszuev.flashcards.stubs.stubDictionaries import com.gitlab.sszuev.flashcards.stubs.stubDictionary import kotlinx.coroutines.test.runTest @@ -140,11 +142,11 @@ internal class DictionaryCorProcessorRunTest { var isDeleteDictionaryCalled = false val repository = MockDbDictionaryRepository( - invokeDeleteDictionary = { - isDeleteDictionaryCalled = true + invokeFindDictionaryById = { if (it == testId.asString()) response.toDbDictionary() else Assertions.fail() }, - invokeFindDictionaryById = { + invokeDeleteDictionary = { + isDeleteDictionaryCalled = true if (it == testId.asString()) response.toDbDictionary() else Assertions.fail() } ) @@ -163,50 +165,117 @@ internal class DictionaryCorProcessorRunTest { @Test fun `test download-dictionary success`() = runTest { val testId = DictionaryId("42") - val testData = ResourceEntity(testId, ByteArray(42) { 42 }) - val response = ImportDictionaryDbResponse(resource = testData) + val testDictionary = stubDictionary.copy( + dictionaryId = testId, + sourceLang = LangEntity(LangId("en")), + targetLang = LangEntity(LangId("fr")), + ) + val testCard = stubCard.copy(dictionaryId = testId) - var wasCalled = false - val repository = MockDbDictionaryRepository( - invokeDownloadDictionary = { _, it -> - wasCalled = true - if (it == testId) response else throw AssertionError() + var isFindDictionaryByIdCalled = false + var isFindCardsByDictionaryIdCalled = false + val dictionaryRepository = MockDbDictionaryRepository( + invokeFindDictionaryById = { + isFindDictionaryByIdCalled = true + if (it == testId.asString()) testDictionary.toDbDictionary() else Assertions.fail() + } + ) + val cardsRepository = MockDbCardRepository( + invokeFindCardsByDictionaryId = { + isFindCardsByDictionaryIdCalled = true + if (it == testId.asString()) sequenceOf(testCard.toDbCard()) else Assertions.fail() } ) - val context = testContext(DictionaryOperation.DOWNLOAD_DICTIONARY, repository) + val context = testContext( + op = DictionaryOperation.DOWNLOAD_DICTIONARY, + dictionaryRepository = dictionaryRepository, + cardsRepository = cardsRepository, + ) context.requestDictionaryId = testId DictionaryCorProcessor().execute(context) - Assertions.assertTrue(wasCalled) + Assertions.assertTrue(isFindDictionaryByIdCalled) + Assertions.assertTrue(isFindCardsByDictionaryIdCalled) Assertions.assertEquals(requestId(DictionaryOperation.DOWNLOAD_DICTIONARY), context.requestId) Assertions.assertEquals(AppStatus.OK, context.status) Assertions.assertTrue(context.errors.isEmpty()) + + val document = context.responseDictionaryResourceEntity.data.toString(Charsets.UTF_16) + Assertions.assertTrue(document.contains("")) + Assertions.assertTrue(document.contains("title=\"Stub-dictionary\"")) } @Test fun `test upload-dictionary success`() = runTest { - val testData = ResourceEntity(DictionaryId.NONE, ByteArray(4200) { 42 }) - val response = DictionaryDbResponse(dictionary = stubDictionary) + val testDocument = ResourceEntity.DUMMY.copy( + data = """ + + + + + test + + + + + тест + + + + + + """.trimIndent().toByteArray(Charsets.UTF_16) + ) + val testDictionary = stubDictionary + val testCard = stubCard - var wasCalled = false - val repository = MockDbDictionaryRepository( - invokeUploadDictionary = { id, bytes -> - wasCalled = true - if (id != testUser.id) throw AssertionError() - if (bytes != testData) throw AssertionError() - response + var isCreateDictionaryCalled = false + var isCreateCardsCalled = false + val dictionaryRepository = MockDbDictionaryRepository( + invokeCreateDictionary = { + isCreateDictionaryCalled = true + if (it.name == "test") { + testDictionary.toDbDictionary() + } else { + Assertions.fail() + } + } + ) + val cardsRepository = MockDbCardRepository( + invokeCreateCards = { + isCreateCardsCalled = true + val cards = it.toList() + if (cards.size == 1 && + cards[0].words.single().word == "test" && + cards[0].dictionaryId == testDictionary.dictionaryId.asString() + ) { + listOf(testCard.toDbCard()) + } else { + Assertions.fail() + } } ) - val context = testContext(DictionaryOperation.UPLOAD_DICTIONARY, repository) - context.requestDictionaryResourceEntity = testData + val context = testContext( + op = DictionaryOperation.UPLOAD_DICTIONARY, + dictionaryRepository = dictionaryRepository, + cardsRepository = cardsRepository, + ) + context.requestDictionaryResourceEntity = testDocument DictionaryCorProcessor().execute(context) Assertions.assertTrue(context.errors.isEmpty()) { "errors: ${context.errors}" } - Assertions.assertTrue(wasCalled) + Assertions.assertTrue(isCreateDictionaryCalled) + Assertions.assertTrue(isCreateCardsCalled) Assertions.assertEquals(requestId(DictionaryOperation.UPLOAD_DICTIONARY), context.requestId) Assertions.assertEquals(AppStatus.OK, context.status) } diff --git a/core/src/test/kotlin/mappers/DocMappersTest.kt b/core/src/test/kotlin/mappers/DocMappersTest.kt new file mode 100644 index 00000000..ee2fb337 --- /dev/null +++ b/core/src/test/kotlin/mappers/DocMappersTest.kt @@ -0,0 +1,100 @@ +package com.gitlab.sszuev.flashcards.core.mappers + +import com.gitlab.sszuev.flashcards.AppConfig +import com.gitlab.sszuev.flashcards.common.documents.DocumentCard +import com.gitlab.sszuev.flashcards.common.documents.DocumentCardStatus +import com.gitlab.sszuev.flashcards.model.domain.CardEntity +import com.gitlab.sszuev.flashcards.model.domain.CardId +import com.gitlab.sszuev.flashcards.model.domain.CardWordEntity +import com.gitlab.sszuev.flashcards.model.domain.CardWordExampleEntity +import com.gitlab.sszuev.flashcards.model.domain.DictionaryId +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +internal class DocMappersTest { + companion object { + private val testDocumentCard = DocumentCard( + text = "snowfall", + transcription = "ˈsnəʊfɔːl", + partOfSpeech = "noun", + translations = listOf("снегопад"), + examples = listOf( + "Due to the heavy snowfall, all flights have been cancelled... -- Из-за сильного снегопада все рейсы отменены...", + "It's the first snowfall of Christmas.", + ), + status = DocumentCardStatus.LEARNED, + ) + + private val testCardEntity = CardEntity( + details = emptyMap(), + words = listOf( + CardWordEntity( + word = "snowfall", + transcription = "ˈsnəʊfɔːl", + partOfSpeech = "noun", + translations = listOf(listOf("снегопад")), + examples = listOf( + CardWordExampleEntity( + translation = "Из-за сильного снегопада все рейсы отменены...", + text = "Due to the heavy snowfall, all flights have been cancelled...", + ), + CardWordExampleEntity(text = "It's the first snowfall of Christmas.") + ) + ) + ), + answered = 10, + ) + + private fun assertSplitWords(expectedSize: Int, givenString: String) { + val actual1: List = + fromDocumentCardTranslationToCommonWordDtoTranslation(givenString) + Assertions.assertEquals(expectedSize, actual1.size) + actual1.forEach { assertPhrasePart(it) } + Assertions.assertEquals( + expectedSize, fromDocumentCardTranslationToCommonWordDtoTranslation( + givenString + ).size + ) + val actual2: List = + fromDocumentCardTranslationToCommonWordDtoTranslation(givenString) + Assertions.assertEquals(actual1, actual2) + } + + private fun assertPhrasePart(s: String) { + Assertions.assertFalse(s.isEmpty()) + Assertions.assertFalse(s.startsWith(" ")) + Assertions.assertFalse(s.endsWith(" ")) + if (!s.contains("(") || !s.contains(")")) { + Assertions.assertFalse(s.contains(","), "For string '$s'") + } + } + } + + @Test + fun testSplitIntoWords() { + assertSplitWords(0, " ") + assertSplitWords(1, "a. bb.xxx;yyy") + assertSplitWords(6, "a, ew,ewere;errt,&oipuoirwe,ор43ыфю,,,q,,") + assertSplitWords(10, "mmmmmmmm, uuuuuu, uuu (sss, xzxx, aaa), ddd, sss, q, www,ooo , ppp, sss. in zzzzz") + assertSplitWords(3, "s s s s (smth l.), d (smth=d., smth=g) x, (&,?)x y") + } + + @Test + fun `test map document-card to mem-db-card`() { + val givenCard = testDocumentCard.copy(status = DocumentCardStatus.LEARNED) + val actualCard = givenCard.toCardEntity(AppConfig.DEFAULT) + Assertions.assertEquals(testCardEntity, actualCard) + } + + @Test + fun `test map mem-db-card to document-card`() { + val givenCard = testCardEntity.copy( + cardId = CardId("1"), + dictionaryId = DictionaryId("2"), + answered = 42, + details = mapOf("a" to "b"), + ) + val actualCard = givenCard.toDocumentCard(AppConfig.DEFAULT) + Assertions.assertEquals(testDocumentCard, actualCard) + } +} \ No newline at end of file diff --git a/db-common/src/main/kotlin/DocumentMappers.kt b/db-common/src/main/kotlin/DocumentMappers.kt deleted file mode 100644 index 438fe0f5..00000000 --- a/db-common/src/main/kotlin/DocumentMappers.kt +++ /dev/null @@ -1,107 +0,0 @@ -package com.gitlab.sszuev.flashcards.common - -import com.gitlab.sszuev.flashcards.common.documents.DocumentCard -import com.gitlab.sszuev.flashcards.common.documents.DocumentCardStatus - -fun CommonWordDto.toDocumentTranslations(): List { - return translations.map { it.joinToString(",") } -} - -fun CommonWordDto.toDocumentExamples(): List { - return examples.map { if (it.translation != null) "${it.text} -- ${it.translation}" else it.text } -} - -fun DocumentCard.toCommonWordDtoList(): List { - val forms = this.text.split(",").map { it.trim() }.filter { it.isNotEmpty() } - val primaryTranslations = this.translations.map { fromDocumentCardTranslationToCommonWordDtoTranslation(it) } - val primaryExamples = this.examples.map { example -> - val parts = example.split(" -- ").filter { it.isNotEmpty() } - val (e, t) = if (parts.size == 2) { - parts[0] to parts[1] - } else { - example to null - } - CommonExampleDto(text = e, translation = t) - } - return forms.mapIndexed { i, word -> - val examples = if (i == 0) primaryExamples else emptyList() - val translations = if (i == 0) primaryTranslations else emptyList() - val transcription = if (i == 0) this.transcription ?: "" else null - val pos = if (i == 0) this.partOfSpeech ?: "" else null - CommonWordDto( - word = word, - transcription = transcription, - partOfSpeech = pos, - translations = translations, - examples = examples, - ) - } -} - -/** - * Splits the given `phrase` using comma (i.e. '`,`') as separator. - * Commas inside the parentheses (e.g. "`(x,y)`") are not considered. - * - * @param [phrase] - * @return [List] - */ -internal fun fromDocumentCardTranslationToCommonWordDtoTranslation(phrase: String): List { - val parts = phrase.split(",") - val res = mutableListOf() - var i = 0 - while (i < parts.size) { - val pi = parts[i].trim() - if (pi.isEmpty()) { - i++ - continue - } - if (!pi.contains("(") || pi.contains(")")) { - res.add(pi) - i++ - continue - } - val sb = StringBuilder(pi) - var j = i + 1 - while (j < parts.size) { - val pj = parts[j].trim { it <= ' ' } - if (pj.isEmpty()) { - j++ - continue - } - sb.append(", ").append(pj) - if (pj.contains(")")) { - break - } - j++ - } - if (sb.lastIndexOf(")") == -1) { - res.add(pi) - i++ - continue - } - res.add(sb.toString()) - i = j - i++ - } - return res -} - -fun SysConfig.status(answered: Int?): DocumentCardStatus { - return if (answered == null) { - DocumentCardStatus.UNKNOWN - } else { - if (answered >= this.numberOfRightAnswers) { - DocumentCardStatus.LEARNED - } else { - DocumentCardStatus.IN_PROCESS - } - } -} - -fun SysConfig.answered(status: DocumentCardStatus): Int { - return when (status) { - DocumentCardStatus.UNKNOWN -> 0 - DocumentCardStatus.IN_PROCESS -> 1 - DocumentCardStatus.LEARNED -> this.numberOfRightAnswers - } -} \ No newline at end of file diff --git a/db-common/src/main/kotlin/documents/IdGenerator.kt b/db-common/src/main/kotlin/IdGenerator.kt similarity index 61% rename from db-common/src/main/kotlin/documents/IdGenerator.kt rename to db-common/src/main/kotlin/IdGenerator.kt index 8b3e4221..bf21faee 100644 --- a/db-common/src/main/kotlin/documents/IdGenerator.kt +++ b/db-common/src/main/kotlin/IdGenerator.kt @@ -1,4 +1,4 @@ -package com.gitlab.sszuev.flashcards.common.documents +package com.gitlab.sszuev.flashcards.common interface IdGenerator { fun nextDictionaryId(): Long diff --git a/db-common/src/test/kotlin/EntityMappersTest.kt b/db-common/src/test/kotlin/EntityMappersTest.kt deleted file mode 100644 index ee0fe9bd..00000000 --- a/db-common/src/test/kotlin/EntityMappersTest.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.gitlab.sszuev.flashcards.common - -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Test - -internal class EntityMappersTest { - - companion object { - private fun assertSplitWords(expectedSize: Int, givenString: String) { - val actual1: List = fromDocumentCardTranslationToCommonWordDtoTranslation(givenString) - Assertions.assertEquals(expectedSize, actual1.size) - actual1.forEach { assertPhrasePart(it) } - Assertions.assertEquals(expectedSize, fromDocumentCardTranslationToCommonWordDtoTranslation(givenString).size) - val actual2: List = fromDocumentCardTranslationToCommonWordDtoTranslation(givenString) - Assertions.assertEquals(actual1, actual2) - } - - private fun assertPhrasePart(s: String) { - Assertions.assertFalse(s.isEmpty()) - Assertions.assertFalse(s.startsWith(" ")) - Assertions.assertFalse(s.endsWith(" ")) - if (!s.contains("(") || !s.contains(")")) { - Assertions.assertFalse(s.contains(","), "For string '$s'") - } - } - } - - @Test - fun testSplitIntoWords() { - assertSplitWords(0, " ") - assertSplitWords(1, "a. bb.xxx;yyy") - assertSplitWords(6, "a, ew,ewere;errt,&oipuoirwe,ор43ыфю,,,q,,") - assertSplitWords(10, "mmmmmmmm, uuuuuu, uuu (sss, xzxx, aaa), ddd, sss, q, www,ooo , ppp, sss. in zzzzz") - assertSplitWords(3, "s s s s (smth l.), d (smth=d., smth=g) x, (&,?)x y") - } -} \ No newline at end of file diff --git a/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt b/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt index c4977707..3bedaaa1 100644 --- a/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt +++ b/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt @@ -1,10 +1,5 @@ package com.gitlab.sszuev.flashcards.dbcommon -import com.gitlab.sszuev.flashcards.model.common.AppUserId -import com.gitlab.sszuev.flashcards.model.domain.DictionaryId -import com.gitlab.sszuev.flashcards.model.domain.LangEntity -import com.gitlab.sszuev.flashcards.model.domain.LangId -import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity import com.gitlab.sszuev.flashcards.repositories.DbDataException import com.gitlab.sszuev.flashcards.repositories.DbDictionary import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository @@ -20,39 +15,8 @@ abstract class DbDictionaryRepositoryTest { abstract val repository: DbDictionaryRepository companion object { - private val userId = AppUserId("42") - private val EN = LangEntity( - LangId("en"), listOf( - "noun", - "verb", - "adjective", - "adverb", - "pronoun", - "preposition", - "conjunction", - "interjection", - "article" - ) - ) - private val RU = LangEntity( - LangId("ru"), - listOf( - "существительное", - "прилагательное", - "числительное", - "местоимение", - "глагол", - "наречие", - "причастие", - "предлог", - "союз", - "частица", - "междометие" - ) - ) - - private val _EN = DbLang( + private val EN = DbLang( langId = "en", partsOfSpeech = listOf( "noun", @@ -66,7 +30,7 @@ abstract class DbDictionaryRepositoryTest { "article" ) ) - private val _RU = DbLang( + private val RU = DbLang( langId = "ru", partsOfSpeech = listOf( "существительное", @@ -107,13 +71,13 @@ abstract class DbDictionaryRepositoryTest { val businessDictionary = res[0] Assertions.assertEquals("1", businessDictionary.dictionaryId) Assertions.assertEquals("Irregular Verbs", businessDictionary.name) - Assertions.assertEquals(_EN, businessDictionary.sourceLang) - Assertions.assertEquals(_RU, businessDictionary.targetLang) + Assertions.assertEquals(EN, businessDictionary.sourceLang) + Assertions.assertEquals(RU, businessDictionary.targetLang) val weatherDictionary = res[1] Assertions.assertEquals("2", weatherDictionary.dictionaryId) Assertions.assertEquals("Weather", weatherDictionary.name) - Assertions.assertEquals(_EN, weatherDictionary.sourceLang) - Assertions.assertEquals(_RU, weatherDictionary.targetLang) + Assertions.assertEquals(EN, weatherDictionary.sourceLang) + Assertions.assertEquals(RU, weatherDictionary.targetLang) } @Order(2) @@ -123,18 +87,6 @@ abstract class DbDictionaryRepositoryTest { Assertions.assertEquals(0, res.size) } - @Order(3) - @Test - fun `test download dictionary`() { - // Weather - val res = repository.importDictionary(userId, DictionaryId("2")) - Assertions.assertEquals(0, res.errors.size) { "Errors: ${res.errors}" } - val xml = res.resource.data.toString(Charsets.UTF_16) - Assertions.assertTrue(xml.startsWith("""""")) - Assertions.assertEquals(66, xml.split("").size) - Assertions.assertTrue(xml.endsWith("" + System.lineSeparator())) - } - @Order(4) @Test fun `test delete dictionary success`() { @@ -154,53 +106,15 @@ abstract class DbDictionaryRepositoryTest { } } - @Order(5) - @Test - fun `test upload dictionary`() { - val txt = """ - - - - test - - - - - тестировать - - - - - - - """.trimIndent() - val bytes = txt.toByteArray(Charsets.UTF_16) - val res = repository.exportDictionary(AppUserId("42"), ResourceEntity(DictionaryId.NONE, bytes)) - Assertions.assertEquals(0, res.errors.size) { "Errors: ${res.errors}" } - - Assertions.assertEquals("Test Dictionary", res.dictionary.name) - Assertions.assertTrue(res.dictionary.dictionaryId.asString().isNotBlank()) - Assertions.assertEquals(EN, res.dictionary.sourceLang) - Assertions.assertEquals(RU, res.dictionary.targetLang) - Assertions.assertEquals(0, res.dictionary.totalCardsCount) - Assertions.assertEquals(0, res.dictionary.learnedCardsCount) - } - @Order(6) @Test fun `test create dictionary success`() { val given = - DbDictionary(name = "test-dictionary", sourceLang = _RU, targetLang = _EN, userId = "42", dictionaryId = "") + DbDictionary(name = "test-dictionary", sourceLang = RU, targetLang = EN, userId = "42", dictionaryId = "") val res = repository.createDictionary(given) Assertions.assertEquals(given.name, res.name) - Assertions.assertEquals(_RU, res.sourceLang) - Assertions.assertEquals(_EN, res.targetLang) + Assertions.assertEquals(RU, res.sourceLang) + Assertions.assertEquals(EN, res.targetLang) Assertions.assertFalse(res.dictionaryId.isBlank()) Assertions.assertTrue(res.dictionaryId.matches("\\d+".toRegex())) } diff --git a/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt b/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt index a305872c..4aa18d01 100644 --- a/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt +++ b/db-common/src/testFixtures/kotlin/mocks/MockDbCardRepository.kt @@ -13,8 +13,9 @@ class MockDbCardRepository( private val invokeFindCardsByDictionaryIdIn: (Iterable) -> Sequence = { emptySequence() }, private val invokeFindCardsByIdIn: (Iterable) -> Sequence = { emptySequence() }, private val invokeCreateCard: (DbCard) -> DbCard = { DbCard.NULL }, + private val invokeCreateCards: (Iterable) -> List = { emptyList() }, private val invokeUpdateCard: (DbCard) -> DbCard = { DbCard.NULL }, - private val invokeUpdateCards: (Iterable) -> List = { _ -> emptyList() }, + private val invokeUpdateCards: (Iterable) -> List = { emptyList() }, private val invokeDeleteCard: (String) -> DbCard = { _ -> DbCard.NULL }, ) : DbCardRepository { @@ -30,6 +31,8 @@ class MockDbCardRepository( override fun createCard(cardEntity: DbCard): DbCard = invokeCreateCard(cardEntity) + override fun createCards(cardEntities: Iterable): List = invokeCreateCards(cardEntities) + override fun updateCard(cardEntity: DbCard): DbCard = invokeUpdateCard(cardEntity) override fun updateCards(cardEntities: Iterable): List = invokeUpdateCards(cardEntities) diff --git a/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt b/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt index 5db67492..32e2a5b0 100644 --- a/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt +++ b/db-common/src/testFixtures/kotlin/mocks/MockDbDictionaryRepository.kt @@ -1,12 +1,7 @@ package com.gitlab.sszuev.flashcards.dbcommon.mocks -import com.gitlab.sszuev.flashcards.model.common.AppUserId -import com.gitlab.sszuev.flashcards.model.domain.DictionaryId -import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity import com.gitlab.sszuev.flashcards.repositories.DbDictionary import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository -import com.gitlab.sszuev.flashcards.repositories.DictionaryDbResponse -import com.gitlab.sszuev.flashcards.repositories.ImportDictionaryDbResponse class MockDbDictionaryRepository( private val invokeFindDictionaryById: (String) -> DbDictionary? = { null }, @@ -14,8 +9,6 @@ class MockDbDictionaryRepository( private val invokeGetAllDictionaries: (String) -> Sequence = { emptySequence() }, private val invokeCreateDictionary: (DbDictionary) -> DbDictionary = { DbDictionary.NULL }, private val invokeDeleteDictionary: (String) -> DbDictionary = { DbDictionary.NULL }, - private val invokeDownloadDictionary: (AppUserId, DictionaryId) -> ImportDictionaryDbResponse = { _, _ -> ImportDictionaryDbResponse.EMPTY }, - private val invokeUploadDictionary: (AppUserId, ResourceEntity) -> DictionaryDbResponse = { _, _ -> DictionaryDbResponse.EMPTY }, ) : DbDictionaryRepository { override fun findDictionaryById(dictionaryId: String): DbDictionary? = invokeFindDictionaryById(dictionaryId) @@ -29,10 +22,4 @@ class MockDbDictionaryRepository( override fun deleteDictionary(dictionaryId: String): DbDictionary = invokeDeleteDictionary(dictionaryId) - override fun importDictionary(userId: AppUserId, dictionaryId: DictionaryId): ImportDictionaryDbResponse = - invokeDownloadDictionary(userId, dictionaryId) - - override fun exportDictionary(userId: AppUserId, resource: ResourceEntity): DictionaryDbResponse = - invokeUploadDictionary(userId, resource) - } \ No newline at end of file diff --git a/db-mem/src/main/kotlin/IdSequences.kt b/db-mem/src/main/kotlin/IdSequences.kt index 6c9d1e67..36561e61 100644 --- a/db-mem/src/main/kotlin/IdSequences.kt +++ b/db-mem/src/main/kotlin/IdSequences.kt @@ -1,6 +1,6 @@ package com.gitlab.sszuev.flashcards.dbmem -import com.gitlab.sszuev.flashcards.common.documents.IdGenerator +import com.gitlab.sszuev.flashcards.common.IdGenerator import java.util.concurrent.atomic.AtomicLong internal class IdSequences( diff --git a/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt b/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt index bc3916f7..de555138 100644 --- a/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt +++ b/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt @@ -1,29 +1,12 @@ package com.gitlab.sszuev.flashcards.dbmem -import com.gitlab.sszuev.flashcards.common.SysConfig -import com.gitlab.sszuev.flashcards.common.answered -import com.gitlab.sszuev.flashcards.common.asLong -import com.gitlab.sszuev.flashcards.common.documents.createReader -import com.gitlab.sszuev.flashcards.common.documents.createWriter -import com.gitlab.sszuev.flashcards.common.forbiddenEntityDbError -import com.gitlab.sszuev.flashcards.common.noDictionaryFoundDbError -import com.gitlab.sszuev.flashcards.common.status import com.gitlab.sszuev.flashcards.common.systemNow -import com.gitlab.sszuev.flashcards.common.wrongResourceDbError -import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbDictionary -import com.gitlab.sszuev.flashcards.model.common.AppError -import com.gitlab.sszuev.flashcards.model.common.AppUserId -import com.gitlab.sszuev.flashcards.model.domain.DictionaryId -import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity import com.gitlab.sszuev.flashcards.repositories.DbDataException import com.gitlab.sszuev.flashcards.repositories.DbDictionary import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository -import com.gitlab.sszuev.flashcards.repositories.DictionaryDbResponse -import com.gitlab.sszuev.flashcards.repositories.ImportDictionaryDbResponse class MemDbDictionaryRepository( dbConfig: MemDbConfig = MemDbConfig(), - private val sysConfig: SysConfig = SysConfig(), ) : DbDictionaryRepository { private val database = MemDatabase.get(databaseLocation = dbConfig.dataLocation) @@ -48,58 +31,4 @@ class MemDbDictionaryRepository( return found } - override fun importDictionary( - userId: AppUserId, - dictionaryId: DictionaryId - ): ImportDictionaryDbResponse { - val errors = mutableListOf() - val found = checkDictionaryUser("importDictionary", userId, dictionaryId, errors) - if (errors.isNotEmpty()) { - return ImportDictionaryDbResponse(errors = errors) - } - checkNotNull(found) - val cards = database.findCardsByDictionaryId(checkNotNull(found.id)).toList() - val document = fromDatabaseToDocumentDictionary(found, cards) { sysConfig.status(it) } - val res = try { - createWriter().write(document) - } catch (ex: Exception) { - return ImportDictionaryDbResponse(wrongResourceDbError(ex)) - } - return ImportDictionaryDbResponse(resource = ResourceEntity(resourceId = dictionaryId, data = res)) - } - - override fun exportDictionary(userId: AppUserId, resource: ResourceEntity): DictionaryDbResponse { - val timestamp = systemNow() - val dictionaryDocument = try { - createReader().parse(resource.data) - } catch (ex: Exception) { - return DictionaryDbResponse(wrongResourceDbError(ex)) - } - val dictionary = database.saveDictionary( - dictionaryDocument.toMemDbDictionary().copy(userId = userId.asLong(), changedAt = timestamp) - ) - dictionaryDocument.toMemDbCards { sysConfig.answered(it) }.forEach { - database.saveCard(it.copy(dictionaryId = dictionary.id, changedAt = timestamp)) - } - return DictionaryDbResponse(dictionary = dictionary.toDictionaryEntity()) - } - - @Suppress("DuplicatedCode", "SameParameterValue") - private fun checkDictionaryUser( - operation: String, - userId: AppUserId, - dictionaryId: DictionaryId, - errors: MutableList - ): MemDbDictionary? { - val dictionary = database.findDictionaryById(dictionaryId.asLong()) - if (dictionary == null) { - errors.add(noDictionaryFoundDbError(operation, dictionaryId)) - return null - } - if (dictionary.userId == userId.asLong()) { - return dictionary - } - errors.add(forbiddenEntityDbError(operation, dictionaryId, userId)) - return null - } } \ No newline at end of file diff --git a/db-mem/src/main/kotlin/MemDbEntityMapper.kt b/db-mem/src/main/kotlin/MemDbEntityMapper.kt index 3353f9a5..0a850e86 100644 --- a/db-mem/src/main/kotlin/MemDbEntityMapper.kt +++ b/db-mem/src/main/kotlin/MemDbEntityMapper.kt @@ -9,9 +9,6 @@ import com.gitlab.sszuev.flashcards.common.LanguageRepository import com.gitlab.sszuev.flashcards.common.asJava import com.gitlab.sszuev.flashcards.common.asKotlin import com.gitlab.sszuev.flashcards.common.detailsAsCommonCardDetailsDto -import com.gitlab.sszuev.flashcards.common.documents.DocumentCard -import com.gitlab.sszuev.flashcards.common.documents.DocumentCardStatus -import com.gitlab.sszuev.flashcards.common.documents.DocumentDictionary import com.gitlab.sszuev.flashcards.common.parseCardDetailsJson import com.gitlab.sszuev.flashcards.common.parseCardWordsJson import com.gitlab.sszuev.flashcards.common.parseDictionaryDetailsJson @@ -19,9 +16,6 @@ import com.gitlab.sszuev.flashcards.common.parseUserDetailsJson import com.gitlab.sszuev.flashcards.common.toCardEntityDetails import com.gitlab.sszuev.flashcards.common.toCardEntityStats import com.gitlab.sszuev.flashcards.common.toCardWordEntity -import com.gitlab.sszuev.flashcards.common.toCommonWordDtoList -import com.gitlab.sszuev.flashcards.common.toDocumentExamples -import com.gitlab.sszuev.flashcards.common.toDocumentTranslations import com.gitlab.sszuev.flashcards.common.toJsonString import com.gitlab.sszuev.flashcards.common.wordsAsCommonWordDtoList import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbCard @@ -33,10 +27,6 @@ import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbWord import com.gitlab.sszuev.flashcards.model.common.AppAuthId import com.gitlab.sszuev.flashcards.model.common.AppUserEntity import com.gitlab.sszuev.flashcards.model.common.AppUserId -import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity -import com.gitlab.sszuev.flashcards.model.domain.DictionaryId -import com.gitlab.sszuev.flashcards.model.domain.LangEntity -import com.gitlab.sszuev.flashcards.model.domain.LangId import com.gitlab.sszuev.flashcards.repositories.DbCard import com.gitlab.sszuev.flashcards.repositories.DbDictionary import com.gitlab.sszuev.flashcards.repositories.DbLang @@ -79,64 +69,6 @@ internal fun MemDbUser.toAppUserEntity(): AppUserEntity = AppUserEntity( authId = uuid.asAppAuthId(), ) -internal fun DocumentDictionary.toMemDbCards(mapAnswered: (DocumentCardStatus) -> Int): List { - return this.cards.map { it.toMemDbCard(mapAnswered) } -} - -internal fun DocumentDictionary.toMemDbDictionary(): MemDbDictionary { - return MemDbDictionary( - name = this.name, - sourceLanguage = createMemDbLanguage(this.sourceLang), - targetLanguage = createMemDbLanguage(this.targetLang), - details = emptyMap(), - ) -} - -internal fun fromDatabaseToDocumentDictionary( - dictionary: MemDbDictionary, - cards: List, - mapStatus: (Int?) -> DocumentCardStatus -): DocumentDictionary { - return DocumentDictionary( - name = dictionary.name, - sourceLang = dictionary.sourceLanguage.id, - targetLang = dictionary.targetLanguage.id, - cards = cards.map { it.toDocumentCard(mapStatus) }, - ) -} - -internal fun DocumentCard.toMemDbCard(mapAnswered: (DocumentCardStatus) -> Int): MemDbCard { - return MemDbCard( - words = this.toMemDbWords(), - details = emptyMap(), - answered = mapAnswered(this.status), - ) -} - -private fun DocumentCard.toMemDbWords(): List { - return toCommonWordDtoList().map { it.toMemDbWord() } -} - -internal fun MemDbCard.toDocumentCard(mapStatus: (Int?) -> DocumentCardStatus): DocumentCard { - val word = this.words.first().toCommonWordDto() - return DocumentCard( - text = word.word, - transcription = word.transcription, - partOfSpeech = word.partOfSpeech, - translations = word.toDocumentTranslations(), - examples = word.toDocumentExamples(), - status = mapStatus(this.answered), - ) -} - -internal fun MemDbDictionary.toDictionaryEntity(): DictionaryEntity = DictionaryEntity( - dictionaryId = this.id?.asDictionaryId() ?: DictionaryId.NONE, - userId = this.userId?.asUserId() ?: AppUserId.NONE, - name = this.name, - sourceLang = this.sourceLanguage.toLangEntity(), - targetLang = this.targetLanguage.toLangEntity(), -) - internal fun MemDbDictionary.toDbDictionary() = DbDictionary( dictionaryId = this.id?.toString() ?: "", userId = this.userId?.toString() ?: "", @@ -179,11 +111,6 @@ internal fun DbCard.toMemDbCard(): MemDbCard { ) } -internal fun MemDbLanguage.toLangEntity(): LangEntity = LangEntity( - langId = this.id.asLangId(), - partsOfSpeech = this.partsOfSpeech, -) - internal fun createMemDbLanguage(tag: String): MemDbLanguage = MemDbLanguage( id = tag, partsOfSpeech = LanguageRepository.partsOfSpeech(tag) @@ -231,8 +158,4 @@ private fun CommonCardDetailsDto.toMemDbCardDetails(): Map = thi private fun Long.asUserId(): AppUserId = AppUserId(toString()) -private fun String.asLangId(): LangId = LangId(this) - -internal fun Long.asDictionaryId(): DictionaryId = DictionaryId(toString()) - private fun UUID.asAppAuthId(): AppAuthId = AppAuthId(toString()) \ No newline at end of file diff --git a/db-mem/src/test/kotlin/EntityMapperTest.kt b/db-mem/src/test/kotlin/EntityMapperTest.kt deleted file mode 100644 index 44d16ec4..00000000 --- a/db-mem/src/test/kotlin/EntityMapperTest.kt +++ /dev/null @@ -1,65 +0,0 @@ -package com.gitlab.sszuev.flashcards.dbmem - -import com.gitlab.sszuev.flashcards.common.documents.DocumentCard -import com.gitlab.sszuev.flashcards.common.documents.DocumentCardStatus -import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbCard -import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbExample -import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbWord -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Test - -internal class EntityMapperTest { - - companion object { - private val testDocumentCard = DocumentCard( - text = "snowfall", - transcription = "ˈsnəʊfɔːl", - partOfSpeech = "noun", - translations = listOf("снегопад"), - examples = listOf( - "Due to the heavy snowfall, all flights have been cancelled... -- Из-за сильного снегопада все рейсы отменены...", - "It's the first snowfall of Christmas.", - ), - status = DocumentCardStatus.LEARNED, - ) - - private val testMemDbCard = MemDbCard( - details = emptyMap(), - words = listOf( - MemDbWord( - word = "snowfall", - transcription = "ˈsnəʊfɔːl", - partOfSpeech = "noun", - translations = listOf(listOf("снегопад")), - examples = listOf( - MemDbExample( - translation = "Из-за сильного снегопада все рейсы отменены...", - text = "Due to the heavy snowfall, all flights have been cancelled...", - ), - MemDbExample(text = "It's the first snowfall of Christmas.") - ) - ) - ), - answered = 42, - ) - } - - @Test - fun `test map document-card to mem-db-card`() { - val givenCard = testDocumentCard.copy(status = DocumentCardStatus.LEARNED) - val actualCard = givenCard.toMemDbCard { - if (it == givenCard.status) testMemDbCard.answered!! else throw AssertionError() - } - Assertions.assertEquals(testMemDbCard, actualCard) - } - - @Test - fun `test map mem-db-card to document-card`() { - val givenCard = testMemDbCard.copy(id = 1, dictionaryId = 2, answered = 42, details = mapOf("a" to "b")) - val actualCard = givenCard.toDocumentCard { - if (it == givenCard.answered) testDocumentCard.status else throw AssertionError() - } - Assertions.assertEquals(testDocumentCard, actualCard) - } - -} \ No newline at end of file diff --git a/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt b/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt index 29576bf6..7077dbe3 100644 --- a/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt +++ b/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt @@ -1,33 +1,12 @@ package com.gitlab.sszuev.flashcards.dbpg -import com.gitlab.sszuev.flashcards.common.SysConfig -import com.gitlab.sszuev.flashcards.common.asLong -import com.gitlab.sszuev.flashcards.common.documents.DocumentCard -import com.gitlab.sszuev.flashcards.common.documents.DocumentDictionary -import com.gitlab.sszuev.flashcards.common.documents.createReader -import com.gitlab.sszuev.flashcards.common.documents.createWriter -import com.gitlab.sszuev.flashcards.common.forbiddenEntityDbError -import com.gitlab.sszuev.flashcards.common.noDictionaryFoundDbError -import com.gitlab.sszuev.flashcards.common.parseCardWordsJson -import com.gitlab.sszuev.flashcards.common.status import com.gitlab.sszuev.flashcards.common.systemNow -import com.gitlab.sszuev.flashcards.common.toDocumentExamples -import com.gitlab.sszuev.flashcards.common.toDocumentTranslations -import com.gitlab.sszuev.flashcards.common.wrongResourceDbError import com.gitlab.sszuev.flashcards.dbpg.dao.Cards import com.gitlab.sszuev.flashcards.dbpg.dao.Dictionaries -import com.gitlab.sszuev.flashcards.dbpg.dao.PgDbCard import com.gitlab.sszuev.flashcards.dbpg.dao.PgDbDictionary -import com.gitlab.sszuev.flashcards.model.common.AppError -import com.gitlab.sszuev.flashcards.model.common.AppUserId -import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity -import com.gitlab.sszuev.flashcards.model.domain.DictionaryId -import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity import com.gitlab.sszuev.flashcards.repositories.DbDataException import com.gitlab.sszuev.flashcards.repositories.DbDictionary import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository -import com.gitlab.sszuev.flashcards.repositories.DictionaryDbResponse -import com.gitlab.sszuev.flashcards.repositories.ImportDictionaryDbResponse import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList import org.jetbrains.exposed.sql.deleteWhere @@ -36,7 +15,6 @@ import org.jetbrains.exposed.sql.selectAll class PgDbDictionaryRepository( dbConfig: PgDbConfig = PgDbConfig(), - private val sysConfig: SysConfig = SysConfig(), ) : DbDictionaryRepository { private val connection by lazy { // lazy, to avoid initialization error when there is no real pg-database @@ -85,93 +63,4 @@ class PgDbDictionaryRepository( found } - override fun importDictionary(userId: AppUserId, dictionaryId: DictionaryId): ImportDictionaryDbResponse { - return connection.execute { - val errors = mutableListOf() - val found = checkDictionaryUser("importDictionary", userId, dictionaryId, errors) - if (errors.isNotEmpty()) { - return@execute ImportDictionaryDbResponse(errors = errors) - } - checkNotNull(found) - val cards = PgDbCard.find { - Cards.dictionaryId eq found.id - } - val res = DocumentDictionary( - name = found.name, - sourceLang = found.sourceLang, - targetLang = found.targetLang, - cards = cards.map { card -> - val word = parseCardWordsJson(card.words).first() - DocumentCard( - text = word.word, - transcription = word.transcription, - partOfSpeech = word.partOfSpeech, - translations = word.toDocumentTranslations(), - examples = word.toDocumentExamples(), - status = sysConfig.status(card.answered), - ) - } - ) - val data = try { - createWriter().write(res) - } catch (ex: Exception) { - return@execute ImportDictionaryDbResponse(wrongResourceDbError(ex)) - } - ImportDictionaryDbResponse(resource = ResourceEntity(dictionaryId, data)) - } - } - - override fun exportDictionary(userId: AppUserId, resource: ResourceEntity): DictionaryDbResponse { - val timestamp = systemNow() - val document = try { - createReader().parse(resource.data) - } catch (ex: Exception) { - return DictionaryDbResponse(wrongResourceDbError(ex)) - } - return connection.execute { - val sourceLang = document.sourceLang - val targetLang = document.targetLang - val dictionaryId = Dictionaries.insertAndGetId { - it[sourceLanguage] = sourceLang - it[targetLanguage] = targetLang - it[name] = document.name - it[Dictionaries.userId] = userId.asLong() - it[changedAt] = timestamp - } - document.cards.forEach { - PgDbCard.new { - this.dictionaryId = dictionaryId - this.words = it.toPgDbCardWordsJson() - this.details = "{}" - this.changedAt = timestamp - } - } - val res = DictionaryEntity( - dictionaryId = dictionaryId.asDictionaryId(), - name = document.name, - sourceLang = createLangEntity(sourceLang), - targetLang = createLangEntity(targetLang), - ) - DictionaryDbResponse(dictionary = res) - } - } - - @Suppress("DuplicatedCode", "SameParameterValue") - private fun checkDictionaryUser( - operation: String, - userId: AppUserId, - dictionaryId: DictionaryId, - errors: MutableList - ): PgDbDictionary? { - val dictionary = PgDbDictionary.findById(dictionaryId.asLong()) - if (dictionary == null) { - errors.add(noDictionaryFoundDbError(operation, dictionaryId)) - return null - } - if (dictionary.userId.value == userId.asLong()) { - return dictionary - } - errors.add(forbiddenEntityDbError(operation, dictionaryId, userId)) - return null - } } \ No newline at end of file diff --git a/db-pg/src/main/kotlin/PgDbEntityMapper.kt b/db-pg/src/main/kotlin/PgDbEntityMapper.kt index 7cfb8c75..f8cea878 100644 --- a/db-pg/src/main/kotlin/PgDbEntityMapper.kt +++ b/db-pg/src/main/kotlin/PgDbEntityMapper.kt @@ -3,13 +3,11 @@ package com.gitlab.sszuev.flashcards.dbpg import com.gitlab.sszuev.flashcards.common.LanguageRepository import com.gitlab.sszuev.flashcards.common.asKotlin import com.gitlab.sszuev.flashcards.common.detailsAsCommonCardDetailsDto -import com.gitlab.sszuev.flashcards.common.documents.DocumentCard import com.gitlab.sszuev.flashcards.common.parseCardDetailsJson import com.gitlab.sszuev.flashcards.common.parseCardWordsJson import com.gitlab.sszuev.flashcards.common.toCardEntityDetails import com.gitlab.sszuev.flashcards.common.toCardEntityStats import com.gitlab.sszuev.flashcards.common.toCardWordEntity -import com.gitlab.sszuev.flashcards.common.toCommonWordDtoList import com.gitlab.sszuev.flashcards.common.toJsonString import com.gitlab.sszuev.flashcards.common.wordsAsCommonWordDtoList import com.gitlab.sszuev.flashcards.dbpg.dao.Cards @@ -21,9 +19,6 @@ import com.gitlab.sszuev.flashcards.dbpg.dao.Users import com.gitlab.sszuev.flashcards.model.common.AppAuthId import com.gitlab.sszuev.flashcards.model.common.AppUserEntity import com.gitlab.sszuev.flashcards.model.common.AppUserId -import com.gitlab.sszuev.flashcards.model.domain.DictionaryId -import com.gitlab.sszuev.flashcards.model.domain.LangEntity -import com.gitlab.sszuev.flashcards.model.domain.LangId import com.gitlab.sszuev.flashcards.repositories.DbCard import com.gitlab.sszuev.flashcards.repositories.DbDictionary import com.gitlab.sszuev.flashcards.repositories.DbLang @@ -68,28 +63,17 @@ internal fun writeCardEntityToPgDbCard(from: DbCard, to: PgDbCard, timestamp: Lo internal fun DbCard.toPgDbCardWordsJson(): String = wordsAsCommonWordDtoList().toJsonString() -internal fun DocumentCard.toPgDbCardWordsJson(): String = toCommonWordDtoList().toJsonString() - internal fun EntityID.asUserId(): AppUserId = AppUserId(value.toString()) -internal fun EntityID.asDictionaryId(): DictionaryId = value.asDictionaryId() - internal fun String.toDictionariesId(): EntityID = EntityID(toLong(), Dictionaries) internal fun String.toCardsId(): EntityID = EntityID(toLong(), Cards) internal fun String.toUserId(): EntityID = EntityID(toLong(), Users) -internal fun createLangEntity(tag: String) = LangEntity( - langId = LangId(tag), - partsOfSpeech = LanguageRepository.partsOfSpeech(tag) -) - internal fun createDbLang(tag: String) = DbLang( langId = tag, partsOfSpeech = LanguageRepository.partsOfSpeech(tag) ) private fun UUID.asAppAuthId(): AppAuthId = AppAuthId(toString()) - -internal fun Long.asDictionaryId(): DictionaryId = DictionaryId(toString()) \ No newline at end of file From cd598ca7ab465e6cbc71eea8ed5822d70976ac5f Mon Sep 17 00:00:00 2001 From: sszuev Date: Fri, 5 Apr 2024 12:31:10 +0300 Subject: [PATCH 15/18] common & core & db: [#28] move lingvo document parsing from :db-common to :core --- .../src/main/kotlin/documents/DocumentCard.kt | 2 +- .../kotlin/documents/DocumentCardStatus.kt | 2 +- .../kotlin/documents/DocumentDictionary.kt | 2 +- .../main/kotlin/documents/DocumentReader.kt | 2 +- .../main/kotlin/documents/DocumentWriter.kt | 2 +- core/src/main/kotlin/documents/Documents.kt | 8 ++++ .../src/main/kotlin/documents/xml/DOMUtils.kt | 2 +- .../documents/xml/LingvoDocumentReader.kt | 14 +++--- .../documents/xml/LingvoDocumentWriter.kt | 8 ++-- .../kotlin/documents/xml/LingvoMappings.kt | 4 +- core/src/main/kotlin/mappers/DocMappers.kt | 6 +-- .../processes/DictionaryProcessWokers.kt | 4 +- .../kotlin/documents/LingvoDocumentTest.kt | 12 +++--- .../src/test/kotlin/mappers/DocMappersTest.kt | 4 +- .../documents/IrregularVerbsEnRu.xml | Bin .../documents/TestDictionaryEnRu.xml | Bin .../test/resources/documents/WeatherEnRu.xml | Bin db-common/build.gradle.kts | 1 - db-common/src/main/kotlin/CommonErrors.kt | 40 ------------------ .../src/main/kotlin/DomainModelMappers.kt | 7 --- db-common/src/main/kotlin/SysConfig.kt | 5 --- db-common/src/main/kotlin/TutorSettings.kt | 29 ------------- .../src/main/kotlin/documents/DocumentLang.kt | 6 --- .../src/main/kotlin/documents/Documents.kt | 8 ---- .../src/main/resources/application.properties | 1 - 25 files changed, 41 insertions(+), 128 deletions(-) rename {db-common => core}/src/main/kotlin/documents/DocumentCard.kt (81%) rename {db-common => core}/src/main/kotlin/documents/DocumentCardStatus.kt (55%) rename {db-common => core}/src/main/kotlin/documents/DocumentDictionary.kt (73%) rename {db-common => core}/src/main/kotlin/documents/DocumentReader.kt (91%) rename {db-common => core}/src/main/kotlin/documents/DocumentWriter.kt (93%) create mode 100644 core/src/main/kotlin/documents/Documents.kt rename {db-common => core}/src/main/kotlin/documents/xml/DOMUtils.kt (95%) rename {db-common => core}/src/main/kotlin/documents/xml/LingvoDocumentReader.kt (85%) rename {db-common => core}/src/main/kotlin/documents/xml/LingvoDocumentWriter.kt (93%) rename {db-common => core}/src/main/kotlin/documents/xml/LingvoMappings.kt (96%) rename {db-common => core}/src/test/kotlin/documents/LingvoDocumentTest.kt (92%) rename {db-common => core}/src/test/resources/documents/IrregularVerbsEnRu.xml (100%) rename {db-common => core}/src/test/resources/documents/TestDictionaryEnRu.xml (100%) rename {db-common => core}/src/test/resources/documents/WeatherEnRu.xml (100%) delete mode 100644 db-common/src/main/kotlin/SysConfig.kt delete mode 100644 db-common/src/main/kotlin/TutorSettings.kt delete mode 100644 db-common/src/main/kotlin/documents/DocumentLang.kt delete mode 100644 db-common/src/main/kotlin/documents/Documents.kt delete mode 100644 db-common/src/main/resources/application.properties diff --git a/db-common/src/main/kotlin/documents/DocumentCard.kt b/core/src/main/kotlin/documents/DocumentCard.kt similarity index 81% rename from db-common/src/main/kotlin/documents/DocumentCard.kt rename to core/src/main/kotlin/documents/DocumentCard.kt index 7280686e..4c55fd80 100644 --- a/db-common/src/main/kotlin/documents/DocumentCard.kt +++ b/core/src/main/kotlin/documents/DocumentCard.kt @@ -1,4 +1,4 @@ -package com.gitlab.sszuev.flashcards.common.documents +package com.gitlab.sszuev.flashcards.core.documents data class DocumentCard( val text: String, diff --git a/db-common/src/main/kotlin/documents/DocumentCardStatus.kt b/core/src/main/kotlin/documents/DocumentCardStatus.kt similarity index 55% rename from db-common/src/main/kotlin/documents/DocumentCardStatus.kt rename to core/src/main/kotlin/documents/DocumentCardStatus.kt index 7ed403a1..c90b25e1 100644 --- a/db-common/src/main/kotlin/documents/DocumentCardStatus.kt +++ b/core/src/main/kotlin/documents/DocumentCardStatus.kt @@ -1,4 +1,4 @@ -package com.gitlab.sszuev.flashcards.common.documents +package com.gitlab.sszuev.flashcards.core.documents enum class DocumentCardStatus { UNKNOWN, IN_PROCESS, LEARNED diff --git a/db-common/src/main/kotlin/documents/DocumentDictionary.kt b/core/src/main/kotlin/documents/DocumentDictionary.kt similarity index 73% rename from db-common/src/main/kotlin/documents/DocumentDictionary.kt rename to core/src/main/kotlin/documents/DocumentDictionary.kt index 969cbea7..6495249d 100644 --- a/db-common/src/main/kotlin/documents/DocumentDictionary.kt +++ b/core/src/main/kotlin/documents/DocumentDictionary.kt @@ -1,4 +1,4 @@ -package com.gitlab.sszuev.flashcards.common.documents +package com.gitlab.sszuev.flashcards.core.documents data class DocumentDictionary( val name: String, diff --git a/db-common/src/main/kotlin/documents/DocumentReader.kt b/core/src/main/kotlin/documents/DocumentReader.kt similarity index 91% rename from db-common/src/main/kotlin/documents/DocumentReader.kt rename to core/src/main/kotlin/documents/DocumentReader.kt index 28ae28a0..746e7d70 100644 --- a/db-common/src/main/kotlin/documents/DocumentReader.kt +++ b/core/src/main/kotlin/documents/DocumentReader.kt @@ -1,4 +1,4 @@ -package com.gitlab.sszuev.flashcards.common.documents +package com.gitlab.sszuev.flashcards.core.documents import java.io.InputStream diff --git a/db-common/src/main/kotlin/documents/DocumentWriter.kt b/core/src/main/kotlin/documents/DocumentWriter.kt similarity index 93% rename from db-common/src/main/kotlin/documents/DocumentWriter.kt rename to core/src/main/kotlin/documents/DocumentWriter.kt index facbc806..01c18319 100644 --- a/db-common/src/main/kotlin/documents/DocumentWriter.kt +++ b/core/src/main/kotlin/documents/DocumentWriter.kt @@ -1,4 +1,4 @@ -package com.gitlab.sszuev.flashcards.common.documents +package com.gitlab.sszuev.flashcards.core.documents import java.io.ByteArrayOutputStream import java.io.OutputStream diff --git a/core/src/main/kotlin/documents/Documents.kt b/core/src/main/kotlin/documents/Documents.kt new file mode 100644 index 00000000..1127d465 --- /dev/null +++ b/core/src/main/kotlin/documents/Documents.kt @@ -0,0 +1,8 @@ +package com.gitlab.sszuev.flashcards.core.documents + +import com.gitlab.sszuev.flashcards.core.documents.xml.LingvoDocumentReader +import com.gitlab.sszuev.flashcards.core.documents.xml.LingvoDocumentWriter + +fun createReader(): DocumentReader = LingvoDocumentReader() + +fun createWriter(): DocumentWriter = LingvoDocumentWriter() \ No newline at end of file diff --git a/db-common/src/main/kotlin/documents/xml/DOMUtils.kt b/core/src/main/kotlin/documents/xml/DOMUtils.kt similarity index 95% rename from db-common/src/main/kotlin/documents/xml/DOMUtils.kt rename to core/src/main/kotlin/documents/xml/DOMUtils.kt index 26d625f7..36a7f085 100644 --- a/db-common/src/main/kotlin/documents/xml/DOMUtils.kt +++ b/core/src/main/kotlin/documents/xml/DOMUtils.kt @@ -1,6 +1,6 @@ @file:Suppress("MemberVisibilityCanBePrivate") -package com.gitlab.sszuev.flashcards.common.documents.xml +package com.gitlab.sszuev.flashcards.core.documents.xml import org.w3c.dom.Element import org.w3c.dom.Node diff --git a/db-common/src/main/kotlin/documents/xml/LingvoDocumentReader.kt b/core/src/main/kotlin/documents/xml/LingvoDocumentReader.kt similarity index 85% rename from db-common/src/main/kotlin/documents/xml/LingvoDocumentReader.kt rename to core/src/main/kotlin/documents/xml/LingvoDocumentReader.kt index 9128d2ab..ce145459 100644 --- a/db-common/src/main/kotlin/documents/xml/LingvoDocumentReader.kt +++ b/core/src/main/kotlin/documents/xml/LingvoDocumentReader.kt @@ -1,11 +1,11 @@ -package com.gitlab.sszuev.flashcards.common.documents.xml +package com.gitlab.sszuev.flashcards.core.documents.xml -import com.gitlab.sszuev.flashcards.common.documents.DocumentCard -import com.gitlab.sszuev.flashcards.common.documents.DocumentCardStatus -import com.gitlab.sszuev.flashcards.common.documents.DocumentDictionary -import com.gitlab.sszuev.flashcards.common.documents.DocumentReader -import com.gitlab.sszuev.flashcards.common.documents.xml.DOMUtils.element -import com.gitlab.sszuev.flashcards.common.documents.xml.DOMUtils.elements +import com.gitlab.sszuev.flashcards.core.documents.DocumentCard +import com.gitlab.sszuev.flashcards.core.documents.DocumentCardStatus +import com.gitlab.sszuev.flashcards.core.documents.DocumentDictionary +import com.gitlab.sszuev.flashcards.core.documents.DocumentReader +import com.gitlab.sszuev.flashcards.core.documents.xml.DOMUtils.element +import com.gitlab.sszuev.flashcards.core.documents.xml.DOMUtils.elements import org.w3c.dom.Element import org.xml.sax.InputSource import org.xml.sax.SAXException diff --git a/db-common/src/main/kotlin/documents/xml/LingvoDocumentWriter.kt b/core/src/main/kotlin/documents/xml/LingvoDocumentWriter.kt similarity index 93% rename from db-common/src/main/kotlin/documents/xml/LingvoDocumentWriter.kt rename to core/src/main/kotlin/documents/xml/LingvoDocumentWriter.kt index bf76cc18..7a0d82dc 100644 --- a/db-common/src/main/kotlin/documents/xml/LingvoDocumentWriter.kt +++ b/core/src/main/kotlin/documents/xml/LingvoDocumentWriter.kt @@ -1,8 +1,8 @@ -package com.gitlab.sszuev.flashcards.common.documents.xml +package com.gitlab.sszuev.flashcards.core.documents.xml -import com.gitlab.sszuev.flashcards.common.documents.DocumentCard -import com.gitlab.sszuev.flashcards.common.documents.DocumentDictionary -import com.gitlab.sszuev.flashcards.common.documents.DocumentWriter +import com.gitlab.sszuev.flashcards.core.documents.DocumentCard +import com.gitlab.sszuev.flashcards.core.documents.DocumentDictionary +import com.gitlab.sszuev.flashcards.core.documents.DocumentWriter import org.w3c.dom.Document import org.w3c.dom.Element import java.io.OutputStream diff --git a/db-common/src/main/kotlin/documents/xml/LingvoMappings.kt b/core/src/main/kotlin/documents/xml/LingvoMappings.kt similarity index 96% rename from db-common/src/main/kotlin/documents/xml/LingvoMappings.kt rename to core/src/main/kotlin/documents/xml/LingvoMappings.kt index 45428d6c..9e1e5794 100644 --- a/db-common/src/main/kotlin/documents/xml/LingvoMappings.kt +++ b/core/src/main/kotlin/documents/xml/LingvoMappings.kt @@ -1,6 +1,6 @@ -package com.gitlab.sszuev.flashcards.common.documents.xml +package com.gitlab.sszuev.flashcards.core.documents.xml -import com.gitlab.sszuev.flashcards.common.documents.DocumentCardStatus +import com.gitlab.sszuev.flashcards.core.documents.DocumentCardStatus import java.nio.charset.Charset import java.nio.charset.StandardCharsets diff --git a/core/src/main/kotlin/mappers/DocMappers.kt b/core/src/main/kotlin/mappers/DocMappers.kt index b7f8ebf6..2c02cdfa 100644 --- a/core/src/main/kotlin/mappers/DocMappers.kt +++ b/core/src/main/kotlin/mappers/DocMappers.kt @@ -2,9 +2,9 @@ package com.gitlab.sszuev.flashcards.core.mappers import com.gitlab.sszuev.flashcards.AppConfig import com.gitlab.sszuev.flashcards.common.LanguageRepository -import com.gitlab.sszuev.flashcards.common.documents.DocumentCard -import com.gitlab.sszuev.flashcards.common.documents.DocumentCardStatus -import com.gitlab.sszuev.flashcards.common.documents.DocumentDictionary +import com.gitlab.sszuev.flashcards.core.documents.DocumentCard +import com.gitlab.sszuev.flashcards.core.documents.DocumentCardStatus +import com.gitlab.sszuev.flashcards.core.documents.DocumentDictionary import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardWordEntity import com.gitlab.sszuev.flashcards.model.domain.CardWordExampleEntity diff --git a/core/src/main/kotlin/processes/DictionaryProcessWokers.kt b/core/src/main/kotlin/processes/DictionaryProcessWokers.kt index 6940c7ab..a259bd50 100644 --- a/core/src/main/kotlin/processes/DictionaryProcessWokers.kt +++ b/core/src/main/kotlin/processes/DictionaryProcessWokers.kt @@ -1,8 +1,8 @@ package com.gitlab.sszuev.flashcards.core.processes import com.gitlab.sszuev.flashcards.DictionaryContext -import com.gitlab.sszuev.flashcards.common.documents.createReader -import com.gitlab.sszuev.flashcards.common.documents.createWriter +import com.gitlab.sszuev.flashcards.core.documents.createReader +import com.gitlab.sszuev.flashcards.core.documents.createWriter import com.gitlab.sszuev.flashcards.core.mappers.toCardEntity import com.gitlab.sszuev.flashcards.core.mappers.toDbCard import com.gitlab.sszuev.flashcards.core.mappers.toDbDictionary diff --git a/db-common/src/test/kotlin/documents/LingvoDocumentTest.kt b/core/src/test/kotlin/documents/LingvoDocumentTest.kt similarity index 92% rename from db-common/src/test/kotlin/documents/LingvoDocumentTest.kt rename to core/src/test/kotlin/documents/LingvoDocumentTest.kt index b3c37607..f732023e 100644 --- a/db-common/src/test/kotlin/documents/LingvoDocumentTest.kt +++ b/core/src/test/kotlin/documents/LingvoDocumentTest.kt @@ -1,11 +1,15 @@ package com.gitlab.sszuev.flashcards.common.documents -import com.gitlab.sszuev.flashcards.common.documents.xml.LingvoDocumentReader -import com.gitlab.sszuev.flashcards.common.documents.xml.LingvoDocumentWriter +import com.gitlab.sszuev.flashcards.core.documents.DocumentCard +import com.gitlab.sszuev.flashcards.core.documents.DocumentCardStatus +import com.gitlab.sszuev.flashcards.core.documents.DocumentDictionary +import com.gitlab.sszuev.flashcards.core.documents.DocumentReader +import com.gitlab.sszuev.flashcards.core.documents.DocumentWriter +import com.gitlab.sszuev.flashcards.core.documents.xml.LingvoDocumentReader +import com.gitlab.sszuev.flashcards.core.documents.xml.LingvoDocumentWriter import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir -import org.slf4j.LoggerFactory import java.io.ByteArrayOutputStream import java.nio.charset.StandardCharsets import java.nio.file.Files @@ -13,7 +17,6 @@ import java.nio.file.Path internal class LingvoDocumentTest { companion object { - private val LOGGER = LoggerFactory.getLogger(LingvoDocumentTest::class.java) private fun normalize(s: String): String { return s.replace("[\n\r\t]".toRegex(), "") @@ -97,7 +100,6 @@ internal class LingvoDocumentTest { val out = ByteArrayOutputStream() createWriter().write(dictionary, out) val txt = out.toString(StandardCharsets.UTF_16) - LOGGER.info("\n{}", txt) val actual = normalize(txt) Assertions.assertEquals(expected, actual) } diff --git a/core/src/test/kotlin/mappers/DocMappersTest.kt b/core/src/test/kotlin/mappers/DocMappersTest.kt index ee2fb337..87916a6f 100644 --- a/core/src/test/kotlin/mappers/DocMappersTest.kt +++ b/core/src/test/kotlin/mappers/DocMappersTest.kt @@ -1,8 +1,8 @@ package com.gitlab.sszuev.flashcards.core.mappers import com.gitlab.sszuev.flashcards.AppConfig -import com.gitlab.sszuev.flashcards.common.documents.DocumentCard -import com.gitlab.sszuev.flashcards.common.documents.DocumentCardStatus +import com.gitlab.sszuev.flashcards.core.documents.DocumentCard +import com.gitlab.sszuev.flashcards.core.documents.DocumentCardStatus import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.CardWordEntity diff --git a/db-common/src/test/resources/documents/IrregularVerbsEnRu.xml b/core/src/test/resources/documents/IrregularVerbsEnRu.xml similarity index 100% rename from db-common/src/test/resources/documents/IrregularVerbsEnRu.xml rename to core/src/test/resources/documents/IrregularVerbsEnRu.xml diff --git a/db-common/src/test/resources/documents/TestDictionaryEnRu.xml b/core/src/test/resources/documents/TestDictionaryEnRu.xml similarity index 100% rename from db-common/src/test/resources/documents/TestDictionaryEnRu.xml rename to core/src/test/resources/documents/TestDictionaryEnRu.xml diff --git a/db-common/src/test/resources/documents/WeatherEnRu.xml b/core/src/test/resources/documents/WeatherEnRu.xml similarity index 100% rename from db-common/src/test/resources/documents/WeatherEnRu.xml rename to core/src/test/resources/documents/WeatherEnRu.xml diff --git a/db-common/build.gradle.kts b/db-common/build.gradle.kts index 0099aa70..91b6dec3 100644 --- a/db-common/build.gradle.kts +++ b/db-common/build.gradle.kts @@ -13,7 +13,6 @@ dependencies { val jacksonVersion: String by project implementation(project(":common")) - implementation("com.typesafe:config:$typesafeConfigVersion") implementation("org.slf4j:slf4j-api:$slf4jVersion") implementation("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion") implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion") diff --git a/db-common/src/main/kotlin/CommonErrors.kt b/db-common/src/main/kotlin/CommonErrors.kt index 0bdd669f..ad198dc0 100644 --- a/db-common/src/main/kotlin/CommonErrors.kt +++ b/db-common/src/main/kotlin/CommonErrors.kt @@ -1,41 +1,7 @@ package com.gitlab.sszuev.flashcards.common -import com.gitlab.sszuev.flashcards.model.Id import com.gitlab.sszuev.flashcards.model.common.AppAuthId import com.gitlab.sszuev.flashcards.model.common.AppError -import com.gitlab.sszuev.flashcards.model.common.AppUserId -import com.gitlab.sszuev.flashcards.model.domain.CardId -import com.gitlab.sszuev.flashcards.model.domain.DictionaryId - -fun forbiddenEntityDbError( - operation: String, - entityId: Id, - userId: AppUserId, -): AppError { - return dbError( - operation = operation, - fieldName = entityId.asString(), - details = when (entityId) { - is DictionaryId -> "access denied: the dictionary (id=${entityId.asString()}) is not owned by the used (id=${userId.asString()})" - is CardId -> "access denied: the card (id=${entityId.asString()}) is not owned by the the used (id=${userId.asString()})" - else -> throw IllegalArgumentException() - }, - ) -} - -fun noDictionaryFoundDbError( - operation: String, - id: DictionaryId, -) = dbError( - operation = operation, - fieldName = id.asString(), - details = """dictionary with id="${id.asString()}" not found""" -) - -fun noCardFoundDbError( - operation: String, - id: CardId, -) = dbError(operation = operation, fieldName = id.asString(), details = """card with id="${id.asString()}" not found""") fun noUserFoundDbError( operation: String, @@ -55,12 +21,6 @@ fun wrongUserUuidDbError( details = """wrong uuid="${uid.asString()}"""", ) -fun wrongResourceDbError(exception: Throwable) = dbError( - operation = "uploadDictionary", - details = """can't parse dictionary from byte-array""", - exception = exception, -) - fun dbError( operation: String, fieldName: String = "", diff --git a/db-common/src/main/kotlin/DomainModelMappers.kt b/db-common/src/main/kotlin/DomainModelMappers.kt index 514981c7..9f36836f 100644 --- a/db-common/src/main/kotlin/DomainModelMappers.kt +++ b/db-common/src/main/kotlin/DomainModelMappers.kt @@ -1,6 +1,5 @@ package com.gitlab.sszuev.flashcards.common -import com.gitlab.sszuev.flashcards.model.Id import com.gitlab.sszuev.flashcards.model.domain.Stage import com.gitlab.sszuev.flashcards.repositories.DbCard @@ -85,9 +84,3 @@ fun CommonCardDetailsDto.toCardEntityStats(): Map = fun CommonCardDetailsDto.toCardEntityDetails(): Map = this.filterKeys { !Stage.entries.map { s -> s.name }.contains(it) } - -fun Id.asLong(): Long = if (this.asString().matches("\\d+".toRegex())) { - this.asString().toLong() -} else { - throw IllegalArgumentException("Wrong id specified: $this") -} \ No newline at end of file diff --git a/db-common/src/main/kotlin/SysConfig.kt b/db-common/src/main/kotlin/SysConfig.kt deleted file mode 100644 index 5c16b5aa..00000000 --- a/db-common/src/main/kotlin/SysConfig.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.gitlab.sszuev.flashcards.common - -data class SysConfig( - val numberOfRightAnswers: Int = TutorSettings.numberOfRightAnswers, -) \ No newline at end of file diff --git a/db-common/src/main/kotlin/TutorSettings.kt b/db-common/src/main/kotlin/TutorSettings.kt deleted file mode 100644 index 19291426..00000000 --- a/db-common/src/main/kotlin/TutorSettings.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.gitlab.sszuev.flashcards.common - -import com.typesafe.config.Config -import com.typesafe.config.ConfigFactory -import org.slf4j.LoggerFactory - -object TutorSettings { - private val logger = LoggerFactory.getLogger(TutorSettings::class.java) - - private val conf: Config = ConfigFactory.load() - - val numberOfRightAnswers = conf.get(key = "app.tutor.run.answers", default = 10) - - init { - logger.info(printDetails()) - } - - private fun printDetails(): String { - return """ - | - |number-of-right-answers = $numberOfRightAnswers - """.replaceIndentByMargin("\t") - } - - private fun Config.get(key: String, default: Int): Int { - return if (hasPath(key)) getInt(key) else default - } - -} \ No newline at end of file diff --git a/db-common/src/main/kotlin/documents/DocumentLang.kt b/db-common/src/main/kotlin/documents/DocumentLang.kt deleted file mode 100644 index ce91b4d0..00000000 --- a/db-common/src/main/kotlin/documents/DocumentLang.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.gitlab.sszuev.flashcards.common.documents - -data class DocumentLang( - val tag: String, - val partsOfSpeech: List, -) \ No newline at end of file diff --git a/db-common/src/main/kotlin/documents/Documents.kt b/db-common/src/main/kotlin/documents/Documents.kt deleted file mode 100644 index 9770ba51..00000000 --- a/db-common/src/main/kotlin/documents/Documents.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.gitlab.sszuev.flashcards.common.documents - -import com.gitlab.sszuev.flashcards.common.documents.xml.LingvoDocumentReader -import com.gitlab.sszuev.flashcards.common.documents.xml.LingvoDocumentWriter - -fun createReader(): DocumentReader = LingvoDocumentReader() - -fun createWriter(): DocumentWriter = LingvoDocumentWriter() \ No newline at end of file diff --git a/db-common/src/main/resources/application.properties b/db-common/src/main/resources/application.properties deleted file mode 100644 index 16e5f1d5..00000000 --- a/db-common/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -app.tutor.run.answers=10 \ No newline at end of file From fa050809a3048e12c6dc6cba1d4bd1c68a27d2b1 Mon Sep 17 00:00:00 2001 From: sszuev Date: Fri, 5 Apr 2024 13:48:38 +0300 Subject: [PATCH 16/18] common & core & db: [#28] move DbCardRepository & DbDictionaryRepository from :common to :db-common; related moving and refactoring --- common/build.gradle.kts | 3 +-- .../kotlin/repositories}/CommonErrors.kt | 2 +- core/src/main/kotlin/mappers/DocMappers.kt | 2 +- db-common/build.gradle.kts | 3 ++- db-common/src/main/kotlin/Timestamps.kt | 21 +++++++++++++++++++ .../main/kotlin/{ => common}/CommonMappers.kt | 12 ----------- .../kotlin/{ => common}/DomainModelMappers.kt | 6 +++--- .../main/kotlin/repositories/DbCadStage.kt | 8 +++++++ .../src/main}/kotlin/repositories/DbCard.kt | 5 +---- .../kotlin/repositories/DbCardRepository.kt | 0 .../kotlin/repositories/DbDataException.kt | 0 .../main}/kotlin/repositories/DbDictionary.kt | 0 .../repositories/DbDictionaryRepository.kt | 0 .../src/main}/kotlin/repositories/DbLang.kt | 0 .../{ => repositories}/LanguageRepository.kt | 2 +- .../repositories/NoOpDbCardRepository.kt | 0 .../NoOpDbDictionaryRepository.kt | 0 .../kotlin/DbCardRepositoryTest.kt | 8 +++---- .../src/main/kotlin/IdGenerator.kt | 2 +- db-mem/src/main/kotlin/IdSequences.kt | 1 - db-mem/src/main/kotlin/MemDatabase.kt | 2 +- db-mem/src/main/kotlin/MemDbCardRepository.kt | 2 +- .../main/kotlin/MemDbDictionaryRepository.kt | 2 +- db-mem/src/main/kotlin/MemDbEntityMapper.kt | 6 +++--- db-mem/src/main/kotlin/MemDbUserRepository.kt | 4 ++-- db-mem/src/test/kotlin/MemDatabaseTest.kt | 2 +- db-pg/src/main/kotlin/PgDbCardRepository.kt | 4 ++-- .../main/kotlin/PgDbDictionaryRepository.kt | 2 +- db-pg/src/main/kotlin/PgDbEntityMapper.kt | 4 ++-- db-pg/src/main/kotlin/PgDbUserRepository.kt | 4 ++-- 30 files changed, 60 insertions(+), 47 deletions(-) rename {db-common/src/main/kotlin => common/src/commonMain/kotlin/repositories}/CommonErrors.kt (94%) create mode 100644 db-common/src/main/kotlin/Timestamps.kt rename db-common/src/main/kotlin/{ => common}/CommonMappers.kt (76%) rename db-common/src/main/kotlin/{ => common}/DomainModelMappers.kt (92%) create mode 100644 db-common/src/main/kotlin/repositories/DbCadStage.kt rename {common/src/commonMain => db-common/src/main}/kotlin/repositories/DbCard.kt (92%) rename {common/src/commonMain => db-common/src/main}/kotlin/repositories/DbCardRepository.kt (100%) rename {common/src/commonMain => db-common/src/main}/kotlin/repositories/DbDataException.kt (100%) rename {common/src/commonMain => db-common/src/main}/kotlin/repositories/DbDictionary.kt (100%) rename {common/src/commonMain => db-common/src/main}/kotlin/repositories/DbDictionaryRepository.kt (100%) rename {common/src/commonMain => db-common/src/main}/kotlin/repositories/DbLang.kt (100%) rename db-common/src/main/kotlin/{ => repositories}/LanguageRepository.kt (93%) rename {common/src/commonMain => db-common/src/main}/kotlin/repositories/NoOpDbCardRepository.kt (100%) rename {common/src/commonMain => db-common/src/main}/kotlin/repositories/NoOpDbDictionaryRepository.kt (100%) rename {db-common => db-mem}/src/main/kotlin/IdGenerator.kt (66%) diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 868ce87a..2a4181c1 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -1,5 +1,3 @@ -@file:Suppress("UNUSED_VARIABLE") - plugins { kotlin("multiplatform") } @@ -14,6 +12,7 @@ kotlin { val kotlinDatetimeVersion: String by project val commonMain by getting { dependencies { + implementation(project(":db-common")) implementation(kotlin("stdlib-common")) api("org.jetbrains.kotlinx:kotlinx-datetime:$kotlinDatetimeVersion") } diff --git a/db-common/src/main/kotlin/CommonErrors.kt b/common/src/commonMain/kotlin/repositories/CommonErrors.kt similarity index 94% rename from db-common/src/main/kotlin/CommonErrors.kt rename to common/src/commonMain/kotlin/repositories/CommonErrors.kt index ad198dc0..23db4fe3 100644 --- a/db-common/src/main/kotlin/CommonErrors.kt +++ b/common/src/commonMain/kotlin/repositories/CommonErrors.kt @@ -1,4 +1,4 @@ -package com.gitlab.sszuev.flashcards.common +package com.gitlab.sszuev.flashcards.repositories import com.gitlab.sszuev.flashcards.model.common.AppAuthId import com.gitlab.sszuev.flashcards.model.common.AppError diff --git a/core/src/main/kotlin/mappers/DocMappers.kt b/core/src/main/kotlin/mappers/DocMappers.kt index 2c02cdfa..a2439ba4 100644 --- a/core/src/main/kotlin/mappers/DocMappers.kt +++ b/core/src/main/kotlin/mappers/DocMappers.kt @@ -1,7 +1,6 @@ package com.gitlab.sszuev.flashcards.core.mappers import com.gitlab.sszuev.flashcards.AppConfig -import com.gitlab.sszuev.flashcards.common.LanguageRepository import com.gitlab.sszuev.flashcards.core.documents.DocumentCard import com.gitlab.sszuev.flashcards.core.documents.DocumentCardStatus import com.gitlab.sszuev.flashcards.core.documents.DocumentDictionary @@ -11,6 +10,7 @@ import com.gitlab.sszuev.flashcards.model.domain.CardWordExampleEntity import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity import com.gitlab.sszuev.flashcards.model.domain.LangEntity import com.gitlab.sszuev.flashcards.model.domain.LangId +import com.gitlab.sszuev.flashcards.repositories.LanguageRepository fun DocumentDictionary.toDictionaryEntity(): DictionaryEntity = DictionaryEntity( name = this.name, diff --git a/db-common/build.gradle.kts b/db-common/build.gradle.kts index 91b6dec3..c835a396 100644 --- a/db-common/build.gradle.kts +++ b/db-common/build.gradle.kts @@ -11,11 +11,12 @@ dependencies { val slf4jVersion: String by project val typesafeConfigVersion: String by project val jacksonVersion: String by project + val kotlinDatetimeVersion: String by project - implementation(project(":common")) implementation("org.slf4j:slf4j-api:$slf4jVersion") implementation("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion") implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion") + implementation("org.jetbrains.kotlinx:kotlinx-datetime:$kotlinDatetimeVersion") testImplementation("org.junit.jupiter:junit-jupiter-api:$junitVersion") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitVersion") diff --git a/db-common/src/main/kotlin/Timestamps.kt b/db-common/src/main/kotlin/Timestamps.kt new file mode 100644 index 00000000..7c993c56 --- /dev/null +++ b/db-common/src/main/kotlin/Timestamps.kt @@ -0,0 +1,21 @@ +package com.gitlab.sszuev.flashcards + +import kotlinx.datetime.Instant +import kotlinx.datetime.toJavaInstant +import kotlinx.datetime.toKotlinInstant +import java.time.temporal.ChronoUnit + +private val none = Instant.fromEpochMilliseconds(Long.MIN_VALUE) +val Instant.Companion.NONE + get() = none + +fun systemNow(): java.time.LocalDateTime = + java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC).truncatedTo(ChronoUnit.MILLIS).toLocalDateTime() + +fun Instant?.asJava(): java.time.LocalDateTime = + (this ?: Instant.NONE).toJavaInstant().atOffset(java.time.ZoneOffset.UTC).toLocalDateTime() + +fun java.time.LocalDateTime?.asKotlin(): Instant = + this?.toInstant(java.time.ZoneOffset.UTC)?.toKotlinInstant() ?: Instant.NONE + + diff --git a/db-common/src/main/kotlin/CommonMappers.kt b/db-common/src/main/kotlin/common/CommonMappers.kt similarity index 76% rename from db-common/src/main/kotlin/CommonMappers.kt rename to db-common/src/main/kotlin/common/CommonMappers.kt index f527fe89..a9244a19 100644 --- a/db-common/src/main/kotlin/CommonMappers.kt +++ b/db-common/src/main/kotlin/common/CommonMappers.kt @@ -4,19 +4,7 @@ import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.KotlinModule -import com.gitlab.sszuev.flashcards.model.common.NONE -import kotlinx.datetime.toJavaInstant -import kotlinx.datetime.toKotlinInstant -import java.time.temporal.ChronoUnit -fun systemNow(): java.time.LocalDateTime = - java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC).truncatedTo(ChronoUnit.MILLIS).toLocalDateTime() - -fun kotlinx.datetime.Instant?.asJava(): java.time.LocalDateTime = - (this?:kotlinx.datetime.Instant.NONE).toJavaInstant().atOffset(java.time.ZoneOffset.UTC).toLocalDateTime() - -fun java.time.LocalDateTime?.asKotlin(): kotlinx.datetime.Instant = - this?.toInstant(java.time.ZoneOffset.UTC)?.toKotlinInstant() ?: kotlinx.datetime.Instant.NONE private val mapper = ObjectMapper() .setSerializationInclusion(JsonInclude.Include.NON_EMPTY) diff --git a/db-common/src/main/kotlin/DomainModelMappers.kt b/db-common/src/main/kotlin/common/DomainModelMappers.kt similarity index 92% rename from db-common/src/main/kotlin/DomainModelMappers.kt rename to db-common/src/main/kotlin/common/DomainModelMappers.kt index 9f36836f..c9e18da9 100644 --- a/db-common/src/main/kotlin/DomainModelMappers.kt +++ b/db-common/src/main/kotlin/common/DomainModelMappers.kt @@ -1,6 +1,6 @@ package com.gitlab.sszuev.flashcards.common -import com.gitlab.sszuev.flashcards.model.domain.Stage +import com.gitlab.sszuev.flashcards.repositories.DbCadStage import com.gitlab.sszuev.flashcards.repositories.DbCard fun validateCardEntityForCreate(entity: DbCard) { @@ -79,8 +79,8 @@ fun DbCard.Word.Example.toCommonExampleDto(): CommonExampleDto = CommonExampleDt ) fun CommonCardDetailsDto.toCardEntityStats(): Map = - this.filterKeys { Stage.entries.map { s -> s.name }.contains(it) } + this.filterKeys { DbCadStage.entries.map { s -> s.name }.contains(it) } .mapValues { it.value.toString().toLong() } fun CommonCardDetailsDto.toCardEntityDetails(): Map = - this.filterKeys { !Stage.entries.map { s -> s.name }.contains(it) } + this.filterKeys { !DbCadStage.entries.map { s -> s.name }.contains(it) } diff --git a/db-common/src/main/kotlin/repositories/DbCadStage.kt b/db-common/src/main/kotlin/repositories/DbCadStage.kt new file mode 100644 index 00000000..33c4f403 --- /dev/null +++ b/db-common/src/main/kotlin/repositories/DbCadStage.kt @@ -0,0 +1,8 @@ +package com.gitlab.sszuev.flashcards.repositories + +enum class DbCadStage { + MOSAIC, + OPTIONS, + WRITING, + SELF_TEST, +} \ No newline at end of file diff --git a/common/src/commonMain/kotlin/repositories/DbCard.kt b/db-common/src/main/kotlin/repositories/DbCard.kt similarity index 92% rename from common/src/commonMain/kotlin/repositories/DbCard.kt rename to db-common/src/main/kotlin/repositories/DbCard.kt index f0760863..36e666c7 100644 --- a/common/src/commonMain/kotlin/repositories/DbCard.kt +++ b/db-common/src/main/kotlin/repositories/DbCard.kt @@ -1,11 +1,8 @@ package com.gitlab.sszuev.flashcards.repositories +import com.gitlab.sszuev.flashcards.NONE import kotlinx.datetime.Instant -private val none = Instant.fromEpochMilliseconds(Long.MIN_VALUE) -val Instant.Companion.NONE - get() = none - data class DbCard( val cardId: String, val dictionaryId: String, diff --git a/common/src/commonMain/kotlin/repositories/DbCardRepository.kt b/db-common/src/main/kotlin/repositories/DbCardRepository.kt similarity index 100% rename from common/src/commonMain/kotlin/repositories/DbCardRepository.kt rename to db-common/src/main/kotlin/repositories/DbCardRepository.kt diff --git a/common/src/commonMain/kotlin/repositories/DbDataException.kt b/db-common/src/main/kotlin/repositories/DbDataException.kt similarity index 100% rename from common/src/commonMain/kotlin/repositories/DbDataException.kt rename to db-common/src/main/kotlin/repositories/DbDataException.kt diff --git a/common/src/commonMain/kotlin/repositories/DbDictionary.kt b/db-common/src/main/kotlin/repositories/DbDictionary.kt similarity index 100% rename from common/src/commonMain/kotlin/repositories/DbDictionary.kt rename to db-common/src/main/kotlin/repositories/DbDictionary.kt diff --git a/common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt b/db-common/src/main/kotlin/repositories/DbDictionaryRepository.kt similarity index 100% rename from common/src/commonMain/kotlin/repositories/DbDictionaryRepository.kt rename to db-common/src/main/kotlin/repositories/DbDictionaryRepository.kt diff --git a/common/src/commonMain/kotlin/repositories/DbLang.kt b/db-common/src/main/kotlin/repositories/DbLang.kt similarity index 100% rename from common/src/commonMain/kotlin/repositories/DbLang.kt rename to db-common/src/main/kotlin/repositories/DbLang.kt diff --git a/db-common/src/main/kotlin/LanguageRepository.kt b/db-common/src/main/kotlin/repositories/LanguageRepository.kt similarity index 93% rename from db-common/src/main/kotlin/LanguageRepository.kt rename to db-common/src/main/kotlin/repositories/LanguageRepository.kt index 99293e4b..136b0f7c 100644 --- a/db-common/src/main/kotlin/LanguageRepository.kt +++ b/db-common/src/main/kotlin/repositories/LanguageRepository.kt @@ -1,4 +1,4 @@ -package com.gitlab.sszuev.flashcards.common +package com.gitlab.sszuev.flashcards.repositories import java.util.Locale import java.util.Objects diff --git a/common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt b/db-common/src/main/kotlin/repositories/NoOpDbCardRepository.kt similarity index 100% rename from common/src/commonMain/kotlin/repositories/NoOpDbCardRepository.kt rename to db-common/src/main/kotlin/repositories/NoOpDbCardRepository.kt diff --git a/common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt b/db-common/src/main/kotlin/repositories/NoOpDbDictionaryRepository.kt similarity index 100% rename from common/src/commonMain/kotlin/repositories/NoOpDbDictionaryRepository.kt rename to db-common/src/main/kotlin/repositories/NoOpDbDictionaryRepository.kt diff --git a/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt b/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt index be63a570..a60a11cf 100644 --- a/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt +++ b/db-common/src/testFixtures/kotlin/DbCardRepositoryTest.kt @@ -1,11 +1,12 @@ package com.gitlab.sszuev.flashcards.dbcommon +import com.gitlab.sszuev.flashcards.asKotlin import com.gitlab.sszuev.flashcards.model.common.NONE import com.gitlab.sszuev.flashcards.model.domain.Stage import com.gitlab.sszuev.flashcards.repositories.DbCard import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDataException -import kotlinx.datetime.Clock +import com.gitlab.sszuev.flashcards.systemNow import kotlinx.datetime.Instant import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals @@ -22,7 +23,6 @@ import org.junit.jupiter.api.TestMethodOrder /** * Note: all implementations must have the same ids in tests for the same entities to have deterministic behavior. */ -@Suppress("FunctionName") @TestMethodOrder(MethodOrderer.OrderAnnotation::class) abstract class DbCardRepositoryTest { @@ -290,7 +290,7 @@ abstract class DbCardRepositoryTest { @Order(11) @Test fun `test bulk update & find by card ids - success`() { - val now = Clock.System.now() + val now = systemNow().asKotlin() val toUpdate = sequenceOf(forgiveCardEntity, snowCardEntity, drawCardEntity).map { it.copy(answered = 42) }.toSet() @@ -303,7 +303,7 @@ abstract class DbCardRepositoryTest { assertCard(expected = forgiveCardEntity.copy(answered = 42), actual = res1[1], ignoreChangeAt = true) assertCard(expected = snowCardEntity.copy(answered = 42), actual = res1[2], ignoreChangeAt = true) res1.forEach { - assertTrue(it.changedAt >= now) + assertTrue(it.changedAt >= now) { "expected ${it.changedAt} >= $now" } } val res2 = diff --git a/db-common/src/main/kotlin/IdGenerator.kt b/db-mem/src/main/kotlin/IdGenerator.kt similarity index 66% rename from db-common/src/main/kotlin/IdGenerator.kt rename to db-mem/src/main/kotlin/IdGenerator.kt index bf21faee..af741265 100644 --- a/db-common/src/main/kotlin/IdGenerator.kt +++ b/db-mem/src/main/kotlin/IdGenerator.kt @@ -1,4 +1,4 @@ -package com.gitlab.sszuev.flashcards.common +package com.gitlab.sszuev.flashcards.dbmem interface IdGenerator { fun nextDictionaryId(): Long diff --git a/db-mem/src/main/kotlin/IdSequences.kt b/db-mem/src/main/kotlin/IdSequences.kt index 36561e61..a9925ccf 100644 --- a/db-mem/src/main/kotlin/IdSequences.kt +++ b/db-mem/src/main/kotlin/IdSequences.kt @@ -1,6 +1,5 @@ package com.gitlab.sszuev.flashcards.dbmem -import com.gitlab.sszuev.flashcards.common.IdGenerator import java.util.concurrent.atomic.AtomicLong internal class IdSequences( diff --git a/db-mem/src/main/kotlin/MemDatabase.kt b/db-mem/src/main/kotlin/MemDatabase.kt index 041c9883..57f2888f 100644 --- a/db-mem/src/main/kotlin/MemDatabase.kt +++ b/db-mem/src/main/kotlin/MemDatabase.kt @@ -1,9 +1,9 @@ package com.gitlab.sszuev.flashcards.dbmem -import com.gitlab.sszuev.flashcards.common.systemNow import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbCard import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbDictionary import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbUser +import com.gitlab.sszuev.flashcards.systemNow import org.apache.commons.csv.CSVFormat import org.apache.commons.csv.CSVParser import org.apache.commons.csv.CSVPrinter diff --git a/db-mem/src/main/kotlin/MemDbCardRepository.kt b/db-mem/src/main/kotlin/MemDbCardRepository.kt index ce5b9fab..40aa14bb 100644 --- a/db-mem/src/main/kotlin/MemDbCardRepository.kt +++ b/db-mem/src/main/kotlin/MemDbCardRepository.kt @@ -1,11 +1,11 @@ package com.gitlab.sszuev.flashcards.dbmem -import com.gitlab.sszuev.flashcards.common.systemNow import com.gitlab.sszuev.flashcards.common.validateCardEntityForCreate import com.gitlab.sszuev.flashcards.common.validateCardEntityForUpdate import com.gitlab.sszuev.flashcards.repositories.DbCard import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDataException +import com.gitlab.sszuev.flashcards.systemNow class MemDbCardRepository( dbConfig: MemDbConfig = MemDbConfig(), diff --git a/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt b/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt index de555138..9c415b9e 100644 --- a/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt +++ b/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt @@ -1,9 +1,9 @@ package com.gitlab.sszuev.flashcards.dbmem -import com.gitlab.sszuev.flashcards.common.systemNow import com.gitlab.sszuev.flashcards.repositories.DbDataException import com.gitlab.sszuev.flashcards.repositories.DbDictionary import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository +import com.gitlab.sszuev.flashcards.systemNow class MemDbDictionaryRepository( dbConfig: MemDbConfig = MemDbConfig(), diff --git a/db-mem/src/main/kotlin/MemDbEntityMapper.kt b/db-mem/src/main/kotlin/MemDbEntityMapper.kt index 0a850e86..e0db036d 100644 --- a/db-mem/src/main/kotlin/MemDbEntityMapper.kt +++ b/db-mem/src/main/kotlin/MemDbEntityMapper.kt @@ -1,13 +1,12 @@ package com.gitlab.sszuev.flashcards.dbmem +import com.gitlab.sszuev.flashcards.asJava +import com.gitlab.sszuev.flashcards.asKotlin import com.gitlab.sszuev.flashcards.common.CommonCardDetailsDto import com.gitlab.sszuev.flashcards.common.CommonDictionaryDetailsDto import com.gitlab.sszuev.flashcards.common.CommonExampleDto import com.gitlab.sszuev.flashcards.common.CommonUserDetailsDto import com.gitlab.sszuev.flashcards.common.CommonWordDto -import com.gitlab.sszuev.flashcards.common.LanguageRepository -import com.gitlab.sszuev.flashcards.common.asJava -import com.gitlab.sszuev.flashcards.common.asKotlin import com.gitlab.sszuev.flashcards.common.detailsAsCommonCardDetailsDto import com.gitlab.sszuev.flashcards.common.parseCardDetailsJson import com.gitlab.sszuev.flashcards.common.parseCardWordsJson @@ -30,6 +29,7 @@ import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.repositories.DbCard import com.gitlab.sszuev.flashcards.repositories.DbDictionary import com.gitlab.sszuev.flashcards.repositories.DbLang +import com.gitlab.sszuev.flashcards.repositories.LanguageRepository import java.util.UUID internal fun MemDbUser.detailsAsJsonString(): String { diff --git a/db-mem/src/main/kotlin/MemDbUserRepository.kt b/db-mem/src/main/kotlin/MemDbUserRepository.kt index 16954c27..2a914597 100644 --- a/db-mem/src/main/kotlin/MemDbUserRepository.kt +++ b/db-mem/src/main/kotlin/MemDbUserRepository.kt @@ -1,11 +1,11 @@ package com.gitlab.sszuev.flashcards.dbmem -import com.gitlab.sszuev.flashcards.common.noUserFoundDbError -import com.gitlab.sszuev.flashcards.common.wrongUserUuidDbError import com.gitlab.sszuev.flashcards.model.common.AppAuthId import com.gitlab.sszuev.flashcards.model.common.AppUserEntity import com.gitlab.sszuev.flashcards.repositories.DbUserRepository import com.gitlab.sszuev.flashcards.repositories.UserEntityDbResponse +import com.gitlab.sszuev.flashcards.repositories.noUserFoundDbError +import com.gitlab.sszuev.flashcards.repositories.wrongUserUuidDbError import java.util.UUID class MemDbUserRepository( diff --git a/db-mem/src/test/kotlin/MemDatabaseTest.kt b/db-mem/src/test/kotlin/MemDatabaseTest.kt index cd61b870..afd0922b 100644 --- a/db-mem/src/test/kotlin/MemDatabaseTest.kt +++ b/db-mem/src/test/kotlin/MemDatabaseTest.kt @@ -1,11 +1,11 @@ package com.gitlab.sszuev.flashcards.dbmem -import com.gitlab.sszuev.flashcards.common.systemNow import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbCard import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbUser import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbWord import com.gitlab.sszuev.flashcards.dbmem.testutils.classPathResourceDir import com.gitlab.sszuev.flashcards.dbmem.testutils.copyClassPathDataToDir +import com.gitlab.sszuev.flashcards.systemNow import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Order import org.junit.jupiter.api.Test diff --git a/db-pg/src/main/kotlin/PgDbCardRepository.kt b/db-pg/src/main/kotlin/PgDbCardRepository.kt index 9427881f..be0eb4d5 100644 --- a/db-pg/src/main/kotlin/PgDbCardRepository.kt +++ b/db-pg/src/main/kotlin/PgDbCardRepository.kt @@ -1,8 +1,7 @@ package com.gitlab.sszuev.flashcards.dbpg -import com.gitlab.sszuev.flashcards.common.asKotlin +import com.gitlab.sszuev.flashcards.asKotlin import com.gitlab.sszuev.flashcards.common.detailsAsCommonCardDetailsDto -import com.gitlab.sszuev.flashcards.common.systemNow import com.gitlab.sszuev.flashcards.common.toJsonString import com.gitlab.sszuev.flashcards.common.validateCardEntityForCreate import com.gitlab.sszuev.flashcards.common.validateCardEntityForUpdate @@ -11,6 +10,7 @@ import com.gitlab.sszuev.flashcards.dbpg.dao.PgDbCard import com.gitlab.sszuev.flashcards.repositories.DbCard import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDataException +import com.gitlab.sszuev.flashcards.systemNow import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.deleteWhere import org.jetbrains.exposed.sql.statements.BatchUpdateStatement diff --git a/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt b/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt index 7077dbe3..899108f6 100644 --- a/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt +++ b/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt @@ -1,12 +1,12 @@ package com.gitlab.sszuev.flashcards.dbpg -import com.gitlab.sszuev.flashcards.common.systemNow import com.gitlab.sszuev.flashcards.dbpg.dao.Cards import com.gitlab.sszuev.flashcards.dbpg.dao.Dictionaries import com.gitlab.sszuev.flashcards.dbpg.dao.PgDbDictionary import com.gitlab.sszuev.flashcards.repositories.DbDataException import com.gitlab.sszuev.flashcards.repositories.DbDictionary import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository +import com.gitlab.sszuev.flashcards.systemNow import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList import org.jetbrains.exposed.sql.deleteWhere diff --git a/db-pg/src/main/kotlin/PgDbEntityMapper.kt b/db-pg/src/main/kotlin/PgDbEntityMapper.kt index f8cea878..6e7c8fd5 100644 --- a/db-pg/src/main/kotlin/PgDbEntityMapper.kt +++ b/db-pg/src/main/kotlin/PgDbEntityMapper.kt @@ -1,7 +1,6 @@ package com.gitlab.sszuev.flashcards.dbpg -import com.gitlab.sszuev.flashcards.common.LanguageRepository -import com.gitlab.sszuev.flashcards.common.asKotlin +import com.gitlab.sszuev.flashcards.asKotlin import com.gitlab.sszuev.flashcards.common.detailsAsCommonCardDetailsDto import com.gitlab.sszuev.flashcards.common.parseCardDetailsJson import com.gitlab.sszuev.flashcards.common.parseCardWordsJson @@ -22,6 +21,7 @@ import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.repositories.DbCard import com.gitlab.sszuev.flashcards.repositories.DbDictionary import com.gitlab.sszuev.flashcards.repositories.DbLang +import com.gitlab.sszuev.flashcards.repositories.LanguageRepository import org.jetbrains.exposed.dao.id.EntityID import java.time.LocalDateTime import java.util.UUID diff --git a/db-pg/src/main/kotlin/PgDbUserRepository.kt b/db-pg/src/main/kotlin/PgDbUserRepository.kt index 97bd407a..f0191667 100644 --- a/db-pg/src/main/kotlin/PgDbUserRepository.kt +++ b/db-pg/src/main/kotlin/PgDbUserRepository.kt @@ -2,14 +2,14 @@ package com.gitlab.sszuev.flashcards.dbpg import com.github.benmanes.caffeine.cache.Cache import com.github.benmanes.caffeine.cache.Caffeine -import com.gitlab.sszuev.flashcards.common.noUserFoundDbError -import com.gitlab.sszuev.flashcards.common.wrongUserUuidDbError import com.gitlab.sszuev.flashcards.dbpg.dao.PgDbUser import com.gitlab.sszuev.flashcards.dbpg.dao.Users import com.gitlab.sszuev.flashcards.model.common.AppAuthId import com.gitlab.sszuev.flashcards.model.common.AppUserEntity import com.gitlab.sszuev.flashcards.repositories.DbUserRepository import com.gitlab.sszuev.flashcards.repositories.UserEntityDbResponse +import com.gitlab.sszuev.flashcards.repositories.noUserFoundDbError +import com.gitlab.sszuev.flashcards.repositories.wrongUserUuidDbError import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import java.util.UUID From f439007ce1bfc92d88bcdb7257201a2f34e402ed Mon Sep 17 00:00:00 2001 From: sszuev Date: Sat, 6 Apr 2024 14:08:49 +0300 Subject: [PATCH 17/18] common & core & db: [#28] remove Users table; replace user_id with auth id; related changes --- .../src/main/resources/data/dictionaries.csv | 4 +- app-ktor/src/main/resources/data/users.csv | 2 - .../kotlin/model/domain/DictionaryEntity.kt | 4 +- core/src/main/kotlin/CardCorProcessor.kt | 10 - .../src/main/kotlin/DictionaryCorProcessor.kt | 6 - core/src/main/kotlin/mappers/DbMappers.kt | 4 +- .../kotlin/processes/CardProcessWorkers.kt | 96 +++++-- .../processes/DictionaryProcessWokers.kt | 31 ++- core/src/main/kotlin/processes/Processes.kt | 21 +- .../kotlin/processes/UserProcessWorkers.kt | 31 --- .../kotlin/CardCorProcessorRunCardsTest.kt | 80 +++--- .../kotlin/DictionaryCorProcessorRunTest.kt | 7 +- .../kotlin/DbDictionaryRepositoryTest.kt | 8 +- .../kotlin/DbUserRepositoryTest.kt | 14 - db-mem/src/main/kotlin/IdSequences.kt | 6 - db-mem/src/main/kotlin/MemDatabase.kt | 260 ++++++------------ .../main/kotlin/MemDbDictionaryRepository.kt | 2 +- db-mem/src/main/kotlin/MemDbEntityMapper.kt | 28 +- db-mem/src/main/kotlin/MemDbUserRepository.kt | 14 +- db-mem/src/main/kotlin/dao/MemDbDictionary.kt | 2 +- db-mem/src/main/kotlin/dao/MemDbUser.kt | 11 - db-mem/src/test/kotlin/MemDatabaseTest.kt | 55 +--- .../db-mem-test-data/dictionaries.csv | 4 +- .../test/resources/db-mem-test-data/users.csv | 2 - .../main/kotlin/PgDbDictionaryRepository.kt | 4 +- db-pg/src/main/kotlin/PgDbEntityMapper.kt | 21 +- db-pg/src/main/kotlin/PgDbUserRepository.kt | 28 +- db-pg/src/main/kotlin/dao/PgDbDictionary.kt | 13 +- db-pg/src/main/kotlin/dao/PgDbUser.kt | 13 - db-pg/src/main/kotlin/dao/Tables.kt | 22 +- .../migrations/schema/001-init-schema.sql | 24 +- .../data/001-01-before-load-test-data.sql | 1 - .../data/001-02-test-dictionaries.csv | 4 +- .../migrations/data/001-02-test-users.csv | 2 - .../data/001-03-test-data-sequences.sql | 4 +- .../data/001-04-after-load-test-data.sql | 1 - .../migrations/data/test-data-changelog.xml | 8 - specs/src/main/kotlin/Stubs.kt | 3 +- 38 files changed, 284 insertions(+), 566 deletions(-) delete mode 100644 app-ktor/src/main/resources/data/users.csv delete mode 100644 core/src/main/kotlin/processes/UserProcessWorkers.kt delete mode 100644 db-mem/src/main/kotlin/dao/MemDbUser.kt delete mode 100644 db-mem/src/test/resources/db-mem-test-data/users.csv delete mode 100644 db-pg/src/main/kotlin/dao/PgDbUser.kt delete mode 100644 db-pg/src/test/resources/migrations/data/001-02-test-users.csv diff --git a/app-ktor/src/main/resources/data/dictionaries.csv b/app-ktor/src/main/resources/data/dictionaries.csv index 3b73692b..466e7a37 100644 --- a/app-ktor/src/main/resources/data/dictionaries.csv +++ b/app-ktor/src/main/resources/data/dictionaries.csv @@ -1,3 +1,3 @@ id,name,user_id,source_lang,target_lang,details,changed_at -1,Irregular Verbs,42,en,ru,{},2022-12-26T16:04:14 -2,Weather,42,en,ru,{},2022-12-26T16:04:14 \ No newline at end of file +1,Irregular Verbs,c9a414f5-3f75-4494-b664-f4c8b33ff4e6,en,ru,{},2022-12-26T16:04:14 +2,Weather,c9a414f5-3f75-4494-b664-f4c8b33ff4e6,en,ru,{},2022-12-26T16:04:14 \ No newline at end of file diff --git a/app-ktor/src/main/resources/data/users.csv b/app-ktor/src/main/resources/data/users.csv deleted file mode 100644 index 21c35afe..00000000 --- a/app-ktor/src/main/resources/data/users.csv +++ /dev/null @@ -1,2 +0,0 @@ -id,uuid,details,changed_at -42,c9a414f5-3f75-4494-b664-f4c8b33ff4e6,{},2022-12-26T16:04:14 \ No newline at end of file diff --git a/common/src/commonMain/kotlin/model/domain/DictionaryEntity.kt b/common/src/commonMain/kotlin/model/domain/DictionaryEntity.kt index 689b790b..5164ae43 100644 --- a/common/src/commonMain/kotlin/model/domain/DictionaryEntity.kt +++ b/common/src/commonMain/kotlin/model/domain/DictionaryEntity.kt @@ -1,10 +1,10 @@ package com.gitlab.sszuev.flashcards.model.domain -import com.gitlab.sszuev.flashcards.model.common.AppUserId +import com.gitlab.sszuev.flashcards.model.common.AppAuthId data class DictionaryEntity( val dictionaryId: DictionaryId = DictionaryId.NONE, - val userId: AppUserId = AppUserId.NONE, + val userId: AppAuthId = AppAuthId.NONE, val name: String = "", val sourceLang: LangEntity = LangEntity.EMPTY, val targetLang: LangEntity = LangEntity.EMPTY, diff --git a/core/src/main/kotlin/CardCorProcessor.kt b/core/src/main/kotlin/CardCorProcessor.kt index c50531c1..fba6fe4c 100644 --- a/core/src/main/kotlin/CardCorProcessor.kt +++ b/core/src/main/kotlin/CardCorProcessor.kt @@ -5,7 +5,6 @@ import com.gitlab.sszuev.flashcards.core.normalizers.normalizers import com.gitlab.sszuev.flashcards.core.processes.processCardSearch import com.gitlab.sszuev.flashcards.core.processes.processCreateCard import com.gitlab.sszuev.flashcards.core.processes.processDeleteCard -import com.gitlab.sszuev.flashcards.core.processes.processFindUser import com.gitlab.sszuev.flashcards.core.processes.processGetAllCards import com.gitlab.sszuev.flashcards.core.processes.processGetCard import com.gitlab.sszuev.flashcards.core.processes.processLearnCards @@ -65,7 +64,6 @@ class CardCorProcessor { validateResourceGetWord() } runs(CardOperation.GET_RESOURCE) { - processFindUser(CardOperation.GET_RESOURCE) processResource() } } @@ -86,7 +84,6 @@ class CardCorProcessor { validateCardFilterDictionaryIds { it.normalizedRequestCardFilter } } runs(CardOperation.SEARCH_CARDS) { - processFindUser(CardOperation.SEARCH_CARDS) processCardSearch() } } @@ -105,7 +102,6 @@ class CardCorProcessor { validateDictionaryId { (it as CardContext).normalizedRequestDictionaryId } } runs(CardOperation.GET_ALL_CARDS) { - processFindUser(CardOperation.GET_ALL_CARDS) processGetAllCards() } } @@ -133,7 +129,6 @@ class CardCorProcessor { validateCardEntityWords { it.normalizedRequestCardEntity } } runs(CardOperation.CREATE_CARD) { - processFindUser(CardOperation.CREATE_CARD) processCreateCard() } } @@ -161,7 +156,6 @@ class CardCorProcessor { validateCardEntityWords { it.normalizedRequestCardEntity } } runs(CardOperation.UPDATE_CARD) { - processFindUser(CardOperation.UPDATE_CARD) processUpdateCard() } } @@ -182,7 +176,6 @@ class CardCorProcessor { validateCardLearnListDetails { it.normalizedRequestCardLearnList } } runs(CardOperation.LEARN_CARDS) { - processFindUser(CardOperation.LEARN_CARDS) processLearnCards() } } @@ -201,7 +194,6 @@ class CardCorProcessor { validateCardId { it.normalizedRequestCardEntityId } } runs(CardOperation.GET_CARD) { - processFindUser(CardOperation.GET_CARD) processGetCard() } } @@ -218,7 +210,6 @@ class CardCorProcessor { validateCardId { it.normalizedRequestCardEntityId } } runs(CardOperation.RESET_CARD) { - processFindUser(CardOperation.RESET_CARD) processResetCard() } } @@ -235,7 +226,6 @@ class CardCorProcessor { validateCardId { it.normalizedRequestCardEntityId } } runs(CardOperation.DELETE_CARD) { - processFindUser(CardOperation.DELETE_CARD) processDeleteCard() } } diff --git a/core/src/main/kotlin/DictionaryCorProcessor.kt b/core/src/main/kotlin/DictionaryCorProcessor.kt index 614a4ea1..14baa365 100644 --- a/core/src/main/kotlin/DictionaryCorProcessor.kt +++ b/core/src/main/kotlin/DictionaryCorProcessor.kt @@ -5,7 +5,6 @@ import com.gitlab.sszuev.flashcards.core.normalizers.normalizers import com.gitlab.sszuev.flashcards.core.processes.processCreateDictionary import com.gitlab.sszuev.flashcards.core.processes.processDeleteDictionary import com.gitlab.sszuev.flashcards.core.processes.processDownloadDictionary -import com.gitlab.sszuev.flashcards.core.processes.processFindUser import com.gitlab.sszuev.flashcards.core.processes.processGetAllDictionary import com.gitlab.sszuev.flashcards.core.processes.processUploadDictionary import com.gitlab.sszuev.flashcards.core.stubs.dictionaryStubSuccess @@ -38,7 +37,6 @@ class DictionaryCorProcessor { validators(DictionaryOperation.GET_ALL_DICTIONARIES) { } runs(DictionaryOperation.GET_ALL_DICTIONARIES) { - processFindUser(DictionaryOperation.GET_ALL_DICTIONARIES) processGetAllDictionary() } } @@ -52,7 +50,6 @@ class DictionaryCorProcessor { validateDictionaryLangId("target-lang") { it.normalizedRequestDictionaryEntity.targetLang.langId } } runs(DictionaryOperation.CREATE_DICTIONARY) { - processFindUser(DictionaryOperation.CREATE_DICTIONARY) processCreateDictionary() } } @@ -64,7 +61,6 @@ class DictionaryCorProcessor { validateDictionaryId { (it as DictionaryContext).normalizedRequestDictionaryId } } runs(DictionaryOperation.DELETE_DICTIONARY) { - processFindUser(DictionaryOperation.DELETE_DICTIONARY) processDeleteDictionary() } } @@ -76,7 +72,6 @@ class DictionaryCorProcessor { validateDictionaryId { (it as DictionaryContext).normalizedRequestDictionaryId } } runs(DictionaryOperation.DOWNLOAD_DICTIONARY) { - processFindUser(DictionaryOperation.DOWNLOAD_DICTIONARY) processDownloadDictionary() } } @@ -88,7 +83,6 @@ class DictionaryCorProcessor { validateDictionaryResource() } runs(DictionaryOperation.UPLOAD_DICTIONARY) { - processFindUser(DictionaryOperation.UPLOAD_DICTIONARY) processUploadDictionary() } } diff --git a/core/src/main/kotlin/mappers/DbMappers.kt b/core/src/main/kotlin/mappers/DbMappers.kt index 85e7ba81..f666c090 100644 --- a/core/src/main/kotlin/mappers/DbMappers.kt +++ b/core/src/main/kotlin/mappers/DbMappers.kt @@ -1,6 +1,6 @@ package com.gitlab.sszuev.flashcards.core.mappers -import com.gitlab.sszuev.flashcards.model.common.AppUserId +import com.gitlab.sszuev.flashcards.model.common.AppAuthId import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.CardWordEntity @@ -45,7 +45,7 @@ fun DictionaryEntity.toDbDictionary() = DbDictionary( fun DbDictionary.toDictionaryEntity() = DictionaryEntity( dictionaryId = DictionaryId(dictionaryId), name = name, - userId = AppUserId(userId), + userId = AppAuthId(userId), sourceLang = sourceLang.toLangEntity(), targetLang = targetLang.toLangEntity(), ) diff --git a/core/src/main/kotlin/processes/CardProcessWorkers.kt b/core/src/main/kotlin/processes/CardProcessWorkers.kt index d4148370..fca46ce4 100644 --- a/core/src/main/kotlin/processes/CardProcessWorkers.kt +++ b/core/src/main/kotlin/processes/CardProcessWorkers.kt @@ -21,18 +21,24 @@ fun ChainDSL.processGetCard() = worker { this.status == AppStatus.RUN } process { - val userId = this.contextUserEntity.id + val userId = this.normalizedRequestAppAuthId val cardId = this.normalizedRequestCardEntityId val card = this.repositories.cardRepository(this.workMode).findCardById(cardId.asString())?.toCardEntity() if (card == null) { - this.errors.add(noCardFoundDataError("getCard", cardId)) + this.errors.add(noCardFoundDataError(CardOperation.GET_CARD, cardId)) } else { val dictionary = this.repositories.dictionaryRepository(this.workMode) .findDictionaryById(card.dictionaryId.asString())?.toDictionaryEntity() if (dictionary == null) { - this.errors.add(noDictionaryFoundDataError("getCard", card.dictionaryId)) + this.errors.add( + noDictionaryFoundDataError( + CardOperation.GET_CARD, + card.dictionaryId, + normalizedRequestAppAuthId + ) + ) } else if (dictionary.userId != userId) { - this.errors.add(forbiddenEntityDataError("getCard", card.cardId, userId)) + this.errors.add(forbiddenEntityDataError(CardOperation.GET_CARD, card.cardId, userId)) } else { this.responseCardEntity = postProcess(card) { dictionary } } @@ -57,15 +63,21 @@ fun ChainDSL.processGetAllCards() = worker { this.status == AppStatus.RUN } process { - val userId = this.contextUserEntity.id + val userId = this.normalizedRequestAppAuthId val dictionaryId = this.normalizedRequestDictionaryId val dictionary = this.repositories.dictionaryRepository(this.workMode).findDictionaryById(dictionaryId.asString()) ?.toDictionaryEntity() if (dictionary == null) { - this.errors.add(noDictionaryFoundDataError("getAllCards", dictionaryId)) + this.errors.add( + noDictionaryFoundDataError( + CardOperation.GET_ALL_CARDS, + dictionaryId, + normalizedRequestAppAuthId + ) + ) } else if (dictionary.userId != userId) { - this.errors.add(forbiddenEntityDataError("getAllCards", dictionaryId, userId)) + this.errors.add(forbiddenEntityDataError(CardOperation.GET_ALL_CARDS, dictionaryId, userId)) } else { val cards = postProcess( this.repositories.cardRepository(this.workMode) @@ -93,17 +105,17 @@ fun ChainDSL.processCardSearch() = worker { this.status == AppStatus.RUN } process { - val userId = this.contextUserEntity.id + val userId = this.normalizedRequestAppAuthId val found = this.repositories.dictionaryRepository(this.workMode) .findDictionariesByIdIn(this.normalizedRequestCardFilter.dictionaryIds.map { it.asString() }) .map { it.toDictionaryEntity() } .associateBy { it.dictionaryId } this.normalizedRequestCardFilter.dictionaryIds.filterNot { found.containsKey(it) }.forEach { - this.errors.add(noDictionaryFoundDataError("searchCards", it)) + this.errors.add(noDictionaryFoundDataError(CardOperation.SEARCH_CARDS, it, normalizedRequestAppAuthId)) } found.values.forEach { dictionary -> if (dictionary.userId != userId) { - this.errors.add(forbiddenEntityDataError("searchCards", dictionary.dictionaryId, userId)) + this.errors.add(forbiddenEntityDataError(CardOperation.SEARCH_CARDS, dictionary.dictionaryId, userId)) } } if (errors.isEmpty()) { @@ -123,14 +135,20 @@ fun ChainDSL.processCreateCard() = worker { this.status == AppStatus.RUN } process { - val userId = this.contextUserEntity.id + val userId = this.normalizedRequestAppAuthId val dictionaryId = this.normalizedRequestCardEntity.dictionaryId val dictionary = this.repositories.dictionaryRepository(this.workMode) .findDictionaryById(dictionaryId.asString())?.toDictionaryEntity() if (dictionary == null) { - this.errors.add(noDictionaryFoundDataError("createCard", dictionaryId)) + this.errors.add( + noDictionaryFoundDataError( + operation = CardOperation.CREATE_CARD, + id = dictionaryId, + userId = normalizedRequestAppAuthId + ) + ) } else if (dictionary.userId != userId) { - this.errors.add(forbiddenEntityDataError("createCard", dictionaryId, userId)) + this.errors.add(forbiddenEntityDataError(CardOperation.CREATE_CARD, dictionaryId, userId)) } else { val res = this.repositories.cardRepository(this.workMode) .createCard(this.normalizedRequestCardEntity.toDbCard()).toCardEntity() @@ -149,14 +167,20 @@ fun ChainDSL.processUpdateCard() = worker { this.status == AppStatus.RUN } process { - val userId = this.contextUserEntity.id + val userId = this.normalizedRequestAppAuthId val dictionaryId = this.normalizedRequestCardEntity.dictionaryId val dictionary = this.repositories.dictionaryRepository(this.workMode) .findDictionaryById(dictionaryId.asString())?.toDictionaryEntity() if (dictionary == null) { - this.errors.add(noDictionaryFoundDataError("updateCard", dictionaryId)) + this.errors.add( + noDictionaryFoundDataError( + operation = CardOperation.UPDATE_CARD, + id = dictionaryId, + userId = normalizedRequestAppAuthId + ) + ) } else if (dictionary.userId != userId) { - this.errors.add(forbiddenEntityDataError("updateCard", dictionaryId, userId)) + this.errors.add(forbiddenEntityDataError(CardOperation.UPDATE_CARD, dictionaryId, userId)) } else { val res = this.repositories.cardRepository(this.workMode) .updateCard(this.normalizedRequestCardEntity.toDbCard()).toCardEntity() @@ -175,14 +199,14 @@ fun ChainDSL.processLearnCards() = worker { this.status == AppStatus.RUN } process { - val userId = this.contextUserEntity.id + val userId = this.normalizedRequestAppAuthId val cardLearns = this.normalizedRequestCardLearnList.associateBy { it.cardId } val foundCards = this.repositories.cardRepository(this.workMode) .findCardsByIdIn(cardLearns.keys.map { it.asString() }).map { it.toCardEntity() }.toSet() val foundCardIds = foundCards.map { it.cardId }.toSet() val missedCardIds = cardLearns.keys - foundCardIds missedCardIds.forEach { - errors.add(noCardFoundDataError("learnCards", it)) + errors.add(noCardFoundDataError(CardOperation.LEARN_CARDS, it)) } val dictionaryIds = foundCards.map { it.dictionaryId }.toSet() val foundDictionaries = @@ -190,9 +214,13 @@ fun ChainDSL.processLearnCards() = worker { .findDictionariesByIdIn(dictionaryIds.map { it.asString() }) .map { it.toDictionaryEntity() } .associateBy { it.dictionaryId } + val missedDictionaries = dictionaryIds - foundDictionaries.keys + missedDictionaries.forEach { + errors.add(noDictionaryFoundDataError(CardOperation.LEARN_CARDS, it, userId)) + } foundDictionaries.onEach { if (it.value.userId != userId) { - errors.add(forbiddenEntityDataError("learnCards", it.key, userId)) + errors.add(forbiddenEntityDataError(CardOperation.LEARN_CARDS, it.key, userId)) } } if (errors.isEmpty()) { @@ -213,19 +241,25 @@ fun ChainDSL.processResetCard() = worker { this.status == AppStatus.RUN } process { - val userId = this.contextUserEntity.id + val userId = this.normalizedRequestAppAuthId val cardId = this.normalizedRequestCardEntityId val card = this.repositories.cardRepository(this.workMode).findCardById(cardId.asString())?.toCardEntity() if (card == null) { - this.errors.add(noCardFoundDataError("resetCard", cardId)) + this.errors.add(noCardFoundDataError(CardOperation.RESET_CARD, cardId)) } else { val dictionaryId = card.dictionaryId val dictionary = this.repositories.dictionaryRepository(this.workMode) .findDictionaryById(dictionaryId.asString())?.toDictionaryEntity() if (dictionary == null) { - this.errors.add(noDictionaryFoundDataError("resetCard", dictionaryId)) + this.errors.add( + noDictionaryFoundDataError( + operation = CardOperation.RESET_CARD, + id = dictionaryId, + userId = normalizedRequestAppAuthId + ) + ) } else if (dictionary.userId != userId) { - this.errors.add(forbiddenEntityDataError("resetCard", dictionaryId, userId)) + this.errors.add(forbiddenEntityDataError(CardOperation.RESET_CARD, dictionaryId, userId)) } else { val res = this.repositories.cardRepository(this.workMode).updateCard(card.copy(answered = 0).toDbCard()) this.responseCardEntity = postProcess(res.toCardEntity()) { dictionary } @@ -244,24 +278,30 @@ fun ChainDSL.processDeleteCard() = worker { this.status == AppStatus.RUN } process { - val userId = this.contextUserEntity.id - + val userId = this.normalizedRequestAppAuthId val cardId = this.normalizedRequestCardEntityId val card = this.repositories.cardRepository(this.workMode).findCardById(cardId.asString())?.toCardEntity() if (card == null) { - this.errors.add(noCardFoundDataError("deleteCard", cardId)) + this.errors.add(noCardFoundDataError(CardOperation.DELETE_CARD, cardId)) } else { val dictionary = this.repositories.dictionaryRepository(this.workMode) .findDictionaryById(card.dictionaryId.asString())?.toDictionaryEntity() if (dictionary == null) { - this.errors.add(noDictionaryFoundDataError("deleteCard", card.dictionaryId)) + this.errors.add( + noDictionaryFoundDataError( + operation = CardOperation.DELETE_CARD, + id = card.dictionaryId, + userId = normalizedRequestAppAuthId + ) + ) } else if (dictionary.userId != userId) { - this.errors.add(forbiddenEntityDataError("deleteCard", card.cardId, userId)) + this.errors.add(forbiddenEntityDataError(CardOperation.DELETE_CARD, card.cardId, userId)) } else { this.repositories.cardRepository(this.workMode) .deleteCard(this.normalizedRequestCardEntityId.asString()) } } + this.status = if (this.errors.isNotEmpty()) AppStatus.FAIL else AppStatus.RUN } onException { this.handleThrowable(CardOperation.DELETE_CARD, it) diff --git a/core/src/main/kotlin/processes/DictionaryProcessWokers.kt b/core/src/main/kotlin/processes/DictionaryProcessWokers.kt index a259bd50..8c3d62cf 100644 --- a/core/src/main/kotlin/processes/DictionaryProcessWokers.kt +++ b/core/src/main/kotlin/processes/DictionaryProcessWokers.kt @@ -22,7 +22,7 @@ fun ChainDSL.processGetAllDictionary() = worker { this.status == AppStatus.RUN } process { - val userId = this.contextUserEntity.id + val userId = this.normalizedRequestAppAuthId val res = this.repositories.dictionaryRepository(this.workMode) .findDictionariesByUserId(userId.asString()) .map { it.toDictionaryEntity() }.toList() @@ -56,7 +56,7 @@ fun ChainDSL.processCreateDictionary() = worker { this.status == AppStatus.RUN } process { - val userId = this.contextUserEntity.id + val userId = this.normalizedRequestAppAuthId val res = this.repositories.dictionaryRepository(this.workMode) .createDictionary(this.normalizedRequestDictionaryEntity.copy(userId = userId).toDbDictionary()) .toDictionaryEntity() @@ -74,14 +74,20 @@ fun ChainDSL.processDeleteDictionary() = worker { this.status == AppStatus.RUN } process { - val userId = this.contextUserEntity.id + val userId = this.normalizedRequestAppAuthId val dictionaryId = this.normalizedRequestDictionaryId val dictionary = this.repositories.dictionaryRepository(this.workMode) .findDictionaryById(dictionaryId.asString())?.toDictionaryEntity() if (dictionary == null) { - this.errors.add(noDictionaryFoundDataError("deleteDictionary", dictionaryId)) + this.errors.add( + noDictionaryFoundDataError( + operation = DictionaryOperation.DELETE_DICTIONARY, + id = dictionaryId, + userId = normalizedRequestAppAuthId + ) + ) } else if (dictionary.userId != userId) { - this.errors.add(forbiddenEntityDataError("deleteDictionary", dictionaryId, userId)) + this.errors.add(forbiddenEntityDataError(DictionaryOperation.DELETE_DICTIONARY, dictionaryId, userId)) } else { this.repositories.dictionaryRepository(this.workMode) .deleteDictionary(this.normalizedRequestDictionaryId.asString()) @@ -99,14 +105,20 @@ fun ChainDSL.processDownloadDictionary() = worker { this.status == AppStatus.RUN } process { - val userId = this.contextUserEntity.id + val userId = this.normalizedRequestAppAuthId val dictionaryId = this.normalizedRequestDictionaryId val dictionary = this.repositories.dictionaryRepository(this.workMode) .findDictionaryById(dictionaryId.asString())?.toDictionaryEntity() if (dictionary == null) { - this.errors.add(noDictionaryFoundDataError("downloadDictionary", dictionaryId)) + this.errors.add( + noDictionaryFoundDataError( + operation = DictionaryOperation.DOWNLOAD_DICTIONARY, + id = dictionaryId, + userId = normalizedRequestAppAuthId + ) + ) } else if (dictionary.userId != userId) { - this.errors.add(forbiddenEntityDataError("downloadDictionary", dictionaryId, userId)) + this.errors.add(forbiddenEntityDataError(DictionaryOperation.DOWNLOAD_DICTIONARY, dictionaryId, userId)) } else { val cards = this.repositories.cardRepository(this.workMode) .findCardsByDictionaryId(dictionaryId.asString()) @@ -135,10 +147,11 @@ fun ChainDSL.processUploadDictionary() = worker { } process { try { + val userId = this.normalizedRequestAppAuthId val document = createReader().parse(this.requestDictionaryResourceEntity.data) val dictionary = this.repositories.dictionaryRepository(this.workMode) .createDictionary( - document.toDictionaryEntity().copy(userId = this.contextUserEntity.id).toDbDictionary() + document.toDictionaryEntity().copy(userId = userId).toDbDictionary() ) .toDictionaryEntity() val cards = document.cards.asSequence() diff --git a/core/src/main/kotlin/processes/Processes.kt b/core/src/main/kotlin/processes/Processes.kt index 777fcf69..3bad72f1 100644 --- a/core/src/main/kotlin/processes/Processes.kt +++ b/core/src/main/kotlin/processes/Processes.kt @@ -1,11 +1,11 @@ package com.gitlab.sszuev.flashcards.core.processes import com.gitlab.sszuev.flashcards.model.Id +import com.gitlab.sszuev.flashcards.model.common.AppAuthId import com.gitlab.sszuev.flashcards.model.common.AppContext import com.gitlab.sszuev.flashcards.model.common.AppError import com.gitlab.sszuev.flashcards.model.common.AppOperation import com.gitlab.sszuev.flashcards.model.common.AppStatus -import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.TTSResourceGet @@ -19,9 +19,9 @@ internal fun Id.toFieldName(): String { } fun forbiddenEntityDataError( - operation: String, + operation: AppOperation, entityId: Id, - userId: AppUserId, + userId: AppAuthId, ) = dataError( operation = operation, fieldName = entityId.asString(), @@ -33,16 +33,17 @@ fun forbiddenEntityDataError( ) fun noDictionaryFoundDataError( - operation: String, + operation: AppOperation, id: DictionaryId, + userId: AppAuthId, ) = dataError( operation = operation, fieldName = id.asString(), - details = """dictionary with id="${id.toFieldName()}" not found""" + details = """dictionary with id="${id.toFieldName()}" not found for user ${userId.toFieldName()}""" ) fun noCardFoundDataError( - operation: String, + operation: AppOperation, id: CardId, ) = dataError( operation = operation, @@ -51,15 +52,15 @@ fun noCardFoundDataError( ) fun dataError( - operation: String, + operation: AppOperation, fieldName: String = "", details: String = "", exception: Throwable? = null, ) = AppError( - code = "database::$operation", + code = operation.name, field = fieldName, - group = "database", - message = if (details.isBlank()) "Error while $operation" else "Error while $operation: $details", + group = "core", + message = if (details.isBlank()) "Error while ${operation.name}" else "Error while ${operation.name}: $details", exception = exception ) diff --git a/core/src/main/kotlin/processes/UserProcessWorkers.kt b/core/src/main/kotlin/processes/UserProcessWorkers.kt deleted file mode 100644 index be6f9bda..00000000 --- a/core/src/main/kotlin/processes/UserProcessWorkers.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.gitlab.sszuev.flashcards.core.processes - -import com.gitlab.sszuev.flashcards.core.validators.fail -import com.gitlab.sszuev.flashcards.corlib.ChainDSL -import com.gitlab.sszuev.flashcards.corlib.worker -import com.gitlab.sszuev.flashcards.model.common.AppContext -import com.gitlab.sszuev.flashcards.model.common.AppOperation -import com.gitlab.sszuev.flashcards.model.common.AppStatus - -internal inline fun ChainDSL.processFindUser(operation: AppOperation) = worker { - this.name = "${Context::class.java.simpleName} :: process get-user" - process { - val uid = this.normalizedRequestAppAuthId - val res = this.repositories.userRepository(this.workMode).getUser(uid) - this.contextUserEntity = res.user - if (res.errors.isNotEmpty()) { - this.errors.addAll(res.errors) - } - this.status = if (this.errors.isNotEmpty()) AppStatus.FAIL else AppStatus.RUN - } - onException { - fail( - runError( - operation = operation, - fieldName = this.normalizedRequestAppAuthId.toFieldName(), - description = "exception while get user", - exception = it, - ) - ) - } -} \ No newline at end of file diff --git a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt index fedb7965..dac47cb5 100644 --- a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt +++ b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt @@ -42,16 +42,11 @@ import org.junit.jupiter.params.provider.EnumSource internal class CardCorProcessorRunCardsTest { companion object { - private val testUser = AppUserEntity(AppUserId("42"), AppAuthId("00000000-0000-0000-0000-000000000000")) + private val testUserId = stubDictionary.userId private fun testContext( op: CardOperation, cardRepository: DbCardRepository, - userRepository: DbUserRepository = MockDbUserRepository( - invokeGetUser = { - if (it == testUser.authId) UserEntityDbResponse(user = testUser) else Assertions.fail() - } - ), dictionaryRepository: DbDictionaryRepository = MockDbDictionaryRepository(), ttsResourceRepository: TTSResourceRepository = MockTTSResourceRepository(invokeFindResourceId = { TTSResourceIdResponse.EMPTY.copy(TTSResourceId(it.lang.asString() + ":" + it.word)) @@ -60,13 +55,12 @@ internal class CardCorProcessorRunCardsTest { val context = CardContext( operation = op, repositories = AppRepositories().copy( - testUserRepository = userRepository, testCardRepository = cardRepository, testDictionaryRepository = dictionaryRepository, testTTSClientRepository = ttsResourceRepository, ), ) - context.requestAppAuthId = testUser.authId + context.requestAppAuthId = testUserId context.workMode = AppMode.TEST context.requestId = requestId(op) return context @@ -140,7 +134,6 @@ internal class CardCorProcessorRunCardsTest { fun `test get-card error - unexpected fail`() = runTest { val testCardId = CardId("42") - var getUserWasCalled = false var getIsWasCalled = false val cardRepository = MockDbCardRepository( invokeFindCardById = { _ -> @@ -148,21 +141,13 @@ internal class CardCorProcessorRunCardsTest { throw TestException() } ) - val userRepository = MockDbUserRepository( - invokeGetUser = { - getUserWasCalled = true - if (it == testUser.authId) UserEntityDbResponse(user = testUser) else throw TestException() - } - ) val context = - testContext(op = CardOperation.GET_CARD, cardRepository = cardRepository, userRepository = userRepository) - context.requestAppAuthId = testUser.authId + testContext(op = CardOperation.GET_CARD, cardRepository = cardRepository) context.requestCardEntityId = testCardId CardCorProcessor().execute(context) - Assertions.assertTrue(getUserWasCalled) Assertions.assertTrue(getIsWasCalled) Assertions.assertEquals(requestId(CardOperation.GET_CARD), context.requestId) assertUnknownError(context, CardOperation.GET_CARD) @@ -738,10 +723,7 @@ internal class CardCorProcessorRunCardsTest { @ParameterizedTest @EnumSource(value = CardOperation::class, names = ["NONE", "GET_RESOURCE"], mode = EnumSource.Mode.EXCLUDE) - fun `test no user found`(op: CardOperation) = runTest { - val testUid = AppAuthId("21") - val testError = AppError(group = "test-error", code = "test-error") - + fun `test no resource found`(op: CardOperation) = runTest { val testCardId = CardId("42") val testDictionaryId = DictionaryId("42") val testLearn = CardLearn(testCardId, mapOf(Stage.SELF_TEST to 42)) @@ -760,19 +742,28 @@ internal class CardCorProcessorRunCardsTest { length = 42, ) - var getUserIsCalled = false - var getCardIsCalled = false - val cardRepository = MockDbCardRepository(invokeFindCardById = { _ -> - getCardIsCalled = true - throw TestException() - }) - val userRepository = MockDbUserRepository(invokeGetUser = { - getUserIsCalled = true - UserEntityDbResponse(user = AppUserEntity.EMPTY, errors = listOf(testError)) - }) + val expectedError = AppError( + group = "core", + code = op.name, + field = testCardId.asString(), + message = "Error while ${op.name}: dictionary with id=\"${testDictionaryId.asString()}\" " + + "not found for user ${testUserId.asString()}" + ) - val context = testContext(op, cardRepository = cardRepository, userRepository = userRepository) - context.requestAppAuthId = testUid + var findCardByIdIsCalled = false + var findCardsByIdInIsCalled = false + val cardRepository = MockDbCardRepository( + invokeFindCardById = { _ -> + findCardByIdIsCalled = true + stubCard.toDbCard() + }, + invokeFindCardsByIdIn = { + findCardsByIdInIsCalled = true + sequenceOf(stubCard.toDbCard()) + } + ) + + val context = testContext(op, cardRepository = cardRepository) context.requestCardEntityId = testCardId context.requestCardLearnList = listOf(testLearn) context.requestCardEntity = testCardEntity @@ -781,10 +772,25 @@ internal class CardCorProcessorRunCardsTest { CardCorProcessor().execute(context) - Assertions.assertTrue(getUserIsCalled) - Assertions.assertFalse(getCardIsCalled) + when (op) { + CardOperation.NONE, CardOperation.GET_RESOURCE -> Assertions.fail() + CardOperation.SEARCH_CARDS, CardOperation.GET_ALL_CARDS, CardOperation.CREATE_CARD, CardOperation.UPDATE_CARD -> { + Assertions.assertFalse(findCardByIdIsCalled) + Assertions.assertFalse(findCardsByIdInIsCalled) + } + + CardOperation.GET_CARD, CardOperation.DELETE_CARD, CardOperation.RESET_CARD -> { + Assertions.assertTrue(findCardByIdIsCalled) + Assertions.assertFalse(findCardsByIdInIsCalled) + } + + CardOperation.LEARN_CARDS -> { + Assertions.assertFalse(findCardByIdIsCalled) + Assertions.assertTrue(findCardsByIdInIsCalled) + } + } Assertions.assertEquals(requestId(op), context.requestId) val actual = assertSingleError(context, op) - Assertions.assertSame(testError, actual) + Assertions.assertEquals(expectedError, actual) } } \ No newline at end of file diff --git a/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt b/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt index 11787edc..e3a8e0d8 100644 --- a/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt +++ b/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt @@ -32,7 +32,8 @@ import org.junit.jupiter.api.Test internal class DictionaryCorProcessorRunTest { companion object { - private val testUser = AppUserEntity(AppUserId("42"), AppAuthId("00000000-0000-0000-0000-000000000000")) + private val testUserId = stubDictionary.userId + private val testUser = AppUserEntity(AppUserId("42"), testUserId) @Suppress("SameParameterValue") private fun testContext( @@ -51,7 +52,7 @@ internal class DictionaryCorProcessorRunTest { testCardRepository = cardsRepository, ) ) - context.requestAppAuthId = testUser.authId + context.requestAppAuthId = testUserId context.workMode = AppMode.TEST context.requestId = requestId(op) return context @@ -71,7 +72,7 @@ internal class DictionaryCorProcessorRunTest { val dictionaryRepository = MockDbDictionaryRepository( invokeGetAllDictionaries = { userId -> getAllDictionariesWasCalled = true - if (userId == testUser.id.asString()) { + if (userId == testUserId.asString()) { testResponseEntities.asSequence().map { it.toDbDictionary() } } else { emptySequence() diff --git a/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt b/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt index 3bedaaa1..8846c3a5 100644 --- a/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt +++ b/db-common/src/testFixtures/kotlin/DbDictionaryRepositoryTest.kt @@ -16,6 +16,8 @@ abstract class DbDictionaryRepositoryTest { companion object { + private const val USER_ID = "c9a414f5-3f75-4494-b664-f4c8b33ff4e6" + private val EN = DbLang( langId = "en", partsOfSpeech = listOf( @@ -55,17 +57,17 @@ abstract class DbDictionaryRepositoryTest { val res1 = repository.findDictionaryById("2") Assertions.assertNotNull(res1) Assertions.assertEquals("Weather", res1!!.name) - Assertions.assertEquals("42", res1.userId) + Assertions.assertEquals(USER_ID, res1.userId) val res2 = repository.findDictionaryById("1") Assertions.assertNotNull(res2) Assertions.assertEquals("Irregular Verbs", res2!!.name) - Assertions.assertEquals("42", res2.userId) + Assertions.assertEquals(USER_ID, res2.userId) } @Order(1) @Test fun `test get all dictionaries by user-id success`() { - val res = repository.findDictionariesByUserId("42").toList() + val res = repository.findDictionariesByUserId(USER_ID).toList() Assertions.assertEquals(2, res.size) val businessDictionary = res[0] diff --git a/db-common/src/testFixtures/kotlin/DbUserRepositoryTest.kt b/db-common/src/testFixtures/kotlin/DbUserRepositoryTest.kt index 7c736b8f..7e7820c5 100644 --- a/db-common/src/testFixtures/kotlin/DbUserRepositoryTest.kt +++ b/db-common/src/testFixtures/kotlin/DbUserRepositoryTest.kt @@ -44,20 +44,6 @@ abstract class DbUserRepositoryTest { Assertions.assertNull(error.exception) } - @Test - fun `test get user error wrong uuid`() { - val uuid = "xxx" - val res = repository.getUser(AppAuthId(uuid)) - Assertions.assertEquals(AppUserEntity.EMPTY, res.user) - - val error = assertAppError(res, uuid, "getUser") - Assertions.assertEquals( - """Error while getUser: wrong uuid="$uuid"""", - error.message - ) - Assertions.assertNull(error.exception) - } - @Test fun `test get user success`() { val res = repository.getUser(demo.authId) diff --git a/db-mem/src/main/kotlin/IdSequences.kt b/db-mem/src/main/kotlin/IdSequences.kt index a9925ccf..8a2fa6d4 100644 --- a/db-mem/src/main/kotlin/IdSequences.kt +++ b/db-mem/src/main/kotlin/IdSequences.kt @@ -3,17 +3,11 @@ package com.gitlab.sszuev.flashcards.dbmem import java.util.concurrent.atomic.AtomicLong internal class IdSequences( - initUserId: Long = 0, initDictionaryId: Long = 0, initCardId: Long = 0, ) : IdGenerator { private val dictionarySequence = AtomicLong(initDictionaryId) private val cardSequence = AtomicLong(initCardId) - private val userSequence = AtomicLong(initUserId) - - fun nextUserId(): Long { - return userSequence.incrementAndGet() - } override fun nextDictionaryId(): Long { return dictionarySequence.incrementAndGet() diff --git a/db-mem/src/main/kotlin/MemDatabase.kt b/db-mem/src/main/kotlin/MemDatabase.kt index 57f2888f..9165cabe 100644 --- a/db-mem/src/main/kotlin/MemDatabase.kt +++ b/db-mem/src/main/kotlin/MemDatabase.kt @@ -2,7 +2,6 @@ package com.gitlab.sszuev.flashcards.dbmem import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbCard import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbDictionary -import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbUser import com.gitlab.sszuev.flashcards.systemNow import org.apache.commons.csv.CSVFormat import org.apache.commons.csv.CSVParser @@ -15,19 +14,18 @@ import java.nio.file.Paths import java.time.LocalDateTime import java.time.OffsetDateTime import java.time.ZoneOffset -import java.util.UUID import java.util.concurrent.ConcurrentHashMap import kotlin.concurrent.timer import kotlin.io.path.inputStream import kotlin.io.path.outputStream /** - * A dictionary store, attached to file system or classpath. - * In the first case it is persistent. + * A dictionary store, attached to a file system or classpath. + * In the first case, it is persistent. */ class MemDatabase private constructor( private val idGenerator: IdSequences, - private val resources: MutableMap, + private val resources: MutableMap>, private val databaseHomeDirectory: String?, ) { @@ -37,36 +35,24 @@ class MemDatabase private constructor( @Volatile private var cardsChanged = false - @Volatile - private var usersChanged = false - fun countUsers(): Long { return resources.size.toLong() } - fun findUsers(): Sequence { - return resources.asSequence().map { it.value.user } - } - - fun findUserByUuid(userUuid: UUID): MemDbUser? { - return resources.asSequence().map { it.value.user }.singleOrNull { it.uuid == userUuid } + fun findUserIds(): Sequence { + return resources.keys.asSequence() } - fun saveUser(user: MemDbUser): MemDbUser { - require(user.id != null || user.changedAt == null) - val id = user.id ?: idGenerator.nextUserId() - val res = user.copy(id = id, changedAt = systemNow()) - resources[id] = UserResource(res) - usersChanged = true - return res + fun containsUser(id: String): Boolean { + return resources.contains(id) } fun countDictionaries(): Long { - return resources.asSequence().map { it.value.dictionaries.size.toLong() }.sum() + return resources.asSequence().sumOf { it.value.size.toLong() } } - fun findDictionariesByUserId(userId: Long): Sequence { - return resources[userId]?.dictionaries?.asSequence()?.map { it.value.dictionary } ?: emptySequence() + fun findDictionariesByUserId(userId: String): Sequence { + return resources[userId]?.values?.asSequence()?.map { it.dictionary } ?: emptySequence() } fun findDictionariesByIds(dictionaryIds: Collection): Sequence { @@ -80,20 +66,19 @@ class MemDatabase private constructor( fun saveDictionary(dictionary: MemDbDictionary): MemDbDictionary { val userId = requireNotNull(dictionary.userId) { "User id is required" } - val resource = - requireNotNull(resources[userId]) { "Unknown user ${dictionary.userId}" } + val resource = resources.computeIfAbsent(userId) { ConcurrentHashMap() } val id = dictionary.id ?: idGenerator.nextDictionaryId() val res = dictionary.copy( id = id, changedAt = dictionary.changedAt ?: OffsetDateTime.now(ZoneOffset.UTC).toLocalDateTime() ) - resource.dictionaries[id] = DictionaryResource(res) + resource[id] = DictionaryResource(res) dictionariesChanged = true return res } fun deleteDictionaryById(dictionaryId: Long): Boolean { - val resource = resources.map { it.value.dictionaries }.singleOrNull { it[dictionaryId] != null } + val resource = resources.map { it.value }.singleOrNull { it[dictionaryId] != null } return if (resource?.remove(dictionaryId) != null) { dictionariesChanged = true true @@ -149,33 +134,22 @@ class MemDatabase private constructor( } private fun dictionaryResourceById(dictionaryId: Long): DictionaryResource? { - return resources.values.mapNotNull { it.dictionaries[dictionaryId] }.singleOrNull() + return resources.values.mapNotNull { it[dictionaryId] }.singleOrNull() } private fun dictionaryResources(): Sequence { - return resources.values.asSequence().flatMap { it.dictionaries.values.asSequence() } + return resources.values.asSequence().flatMap { it.values.asSequence() } } private fun cards(): Sequence { - return resources.values.asSequence().flatMap { it.dictionaries.values.asSequence() } + return resources.values.asSequence().flatMap { it.values.asSequence() } .flatMap { it.cards.values.asSequence() } } - private fun users(): Sequence { - return resources.values.asSequence().map { it.user } - } - private fun saveData() { if (databaseHomeDirectory == null) { return } - if (usersChanged) { - val users = users().sortedBy { it.id }.toList() - Paths.get(databaseHomeDirectory).resolve(USERS_DB_FILE).outputStream().use { - writeUsers(users, it) - } - usersChanged = false - } if (cardsChanged) { val cards = cards().sortedBy { it.id }.toList() Paths.get(databaseHomeDirectory).resolve(CARDS_DB_FILE).outputStream().use { @@ -192,18 +166,12 @@ class MemDatabase private constructor( } } - private data class UserResource( - val user: MemDbUser, - val dictionaries: MutableMap = ConcurrentHashMap(), - ) - private data class DictionaryResource( val dictionary: MemDbDictionary, val cards: MutableMap = ConcurrentHashMap(), ) companion object { - private const val USERS_DB_FILE = "users.csv" private const val DICTIONARY_DB_FILE = "dictionaries.csv" private const val CARDS_DB_FILE = "cards.csv" private const val CLASSPATH_PREFIX = "classpath:" @@ -250,14 +218,12 @@ class MemDatabase private constructor( } else { loadDatabaseResourcesFromDirectory(databaseLocation) } - val maxUserId = res.keys.max() - val maxDictionaryId = res.values.asSequence().flatMap { it.dictionaries.keys.asSequence() }.max() + val maxDictionaryId = res.values.asSequence().flatMap { it.keys }.max() val maxCardId = res.values.asSequence() - .flatMap { it.dictionaries.asSequence() } - .flatMap { it.value.cards.keys.asSequence() } + .flatMap { it.values.asSequence() } + .flatMap { it.cards.map { card -> card.key } } .max() val ids = IdSequences( - initUserId = maxUserId, initDictionaryId = maxDictionaryId, initCardId = maxCardId, ) @@ -270,35 +236,25 @@ class MemDatabase private constructor( private fun loadDatabaseResourcesFromDirectory( directoryDbLocation: String, - ): MutableMap { - val usersFile = Paths.get(directoryDbLocation).resolve(USERS_DB_FILE).toRealPath() - val cardsFile = Paths.get(directoryDbLocation).resolve(CARDS_DB_FILE).toRealPath() - val dictionariesFile = Paths.get(directoryDbLocation).resolve(DICTIONARY_DB_FILE).toRealPath() - logger.info("Load users data from file: <$usersFile>.") - val users = usersFile.inputStream().use { - readUsers(it) - } - logger.info("Load cards data from file: <$cardsFile>.") - val cards = cardsFile.inputStream().use { + ): MutableMap> { + val cardFile = Paths.get(directoryDbLocation).resolve(CARDS_DB_FILE).toRealPath() + val dictionaryFile = Paths.get(directoryDbLocation).resolve(DICTIONARY_DB_FILE).toRealPath() + logger.info("Load cards data from file: <$cardFile>.") + val cards = cardFile.inputStream().use { readCards(it) } - logger.info("Load dictionaries data from file: <$dictionariesFile>.") - val dictionaries = dictionariesFile.inputStream().use { + logger.info("Load dictionaries data from file: <$dictionaryFile>.") + val dictionaries = dictionaryFile.inputStream().use { readDictionaries(it) } - return composeDatabaseData(directoryDbLocation, users, dictionaries, cards) + return composeDatabaseData(directoryDbLocation, dictionaries, cards) } private fun loadDatabaseResourcesFromClassPath( classpathDbLocation: String, - ): MutableMap { - val usersFile = resolveClasspathResource(classpathDbLocation, USERS_DB_FILE) + ): MutableMap> { val cardsFile = resolveClasspathResource(classpathDbLocation, CARDS_DB_FILE) val dictionariesFile = resolveClasspathResource(classpathDbLocation, DICTIONARY_DB_FILE) - logger.info("Load users data from classpath: <$usersFile>.") - val users = checkNotNull(MemDatabase::class.java.getResourceAsStream(usersFile)).use { - readUsers(it) - } logger.info("Load cards data from classpath: <$cardsFile>.") val cards = checkNotNull(MemDatabase::class.java.getResourceAsStream(cardsFile)).use { readCards(it) @@ -307,39 +263,33 @@ class MemDatabase private constructor( val dictionaries = checkNotNull(MemDatabase::class.java.getResourceAsStream(dictionariesFile)).use { readDictionaries(it) } - return composeDatabaseData(classpathDbLocation, users, dictionaries, cards) + return composeDatabaseData(classpathDbLocation, dictionaries, cards) } private fun composeDatabaseData( dbLocation: String, - users: List, dictionaries: List, cards: List - ): MutableMap { - val res = users.map { user -> - val userDictionaries = dictionaries.asSequence() - .filter { it.userId == user.id } - .map { dictionary -> + ): MutableMap> { + val dictionaryIds = mutableSetOf() + val res = dictionaries + .filter { it.userId != null } + .groupBy { checkNotNull(it.userId) } + .mapValues { (_, userDictionaries) -> + userDictionaries.map { dictionary -> + dictionaryIds.add(checkNotNull(dictionary.id)) val dictionaryCards = cards.asSequence() .filter { it.dictionaryId == dictionary.id } .associateByTo(ConcurrentHashMap()) { checkNotNull(it.id) } DictionaryResource(dictionary, dictionaryCards) - } - .associateByTo(ConcurrentHashMap()) { checkNotNull(it.dictionary.id) } - UserResource(user, userDictionaries) - }.associateByTo(ConcurrentHashMap()) { checkNotNull(it.user.id) } - - val unattachedDictionaryIds = dictionaries.asSequence().map { it.id }.toMutableSet() - val unattachedCardIds = cards.asSequence().map { it.id }.toMutableSet() - val dictionariesCount = res.values.asSequence() - .flatMap { it.dictionaries.keys.asSequence() } - .onEach { unattachedDictionaryIds.remove(it) } - .count() - val cardsCount = res.values.asSequence() - .flatMap { it.dictionaries.values.asSequence() } - .flatMap { it.cards.keys.asSequence() } - .onEach { unattachedCardIds.remove(it) } - .count() + }.associateByTo(ConcurrentHashMap()) { checkNotNull(it.dictionary.id) } + }.toMap(ConcurrentHashMap()) + + val unattachedDictionaryIds = dictionaries.asSequence().filter { it.userId == null }.map { it.id }.toList() + val unattachedCardIds = + cards.asSequence().filterNot { dictionaryIds.contains(it.dictionaryId) }.map { it.id }.toMutableSet() + val dictionariesCount = res.values.sumOf { it.size } + val cardsCount = res.values.flatMap { it.values }.sumOf { it.cards.size } logger.info("In the store=<$dbLocation> there are ${res.size} users, $dictionariesCount dictionaries and $cardsCount cards.") if (unattachedDictionaryIds.isNotEmpty()) { @@ -348,18 +298,8 @@ class MemDatabase private constructor( if (unattachedCardIds.isNotEmpty()) { logger.warn("The ${unattachedCardIds.size} cards assigned to unknown dictionaries. ids = $unattachedCardIds") } - return res - } - - private fun readUsers(inputStream: InputStream): List = userCsvFormat(false).read(inputStream).use { - it.records.map { record -> - MemDbUser( - id = record.value("id").toLong(), - uuid = UUID.fromString(record.value("uuid")), - details = fromJsonStringToMemDbUserDetails(record.value("details")), - changedAt = LocalDateTime.parse(record.value("changed_at")), - ) - } + @Suppress("UNCHECKED_CAST") + return res as MutableMap> } private fun readDictionaries(inputStream: InputStream): List = @@ -368,7 +308,7 @@ class MemDatabase private constructor( MemDbDictionary( id = record.value("id").toLong(), name = record.value("name"), - userId = record.value("user_id").toLong(), + userId = record.value("user_id"), sourceLanguage = createMemDbLanguage(record.get("source_lang")), targetLanguage = createMemDbLanguage(record.get("target_lang")), details = fromJsonStringToMemDbDictionaryDetails(record.value("details")), @@ -390,18 +330,6 @@ class MemDatabase private constructor( } } - private fun writeUsers(users: Collection, outputStream: OutputStream) = - userCsvFormat(true).write(outputStream).use { - users.forEach { user -> - it.printRecord( - user.id, - user.uuid, - user.detailsAsJsonString(), - user.changedAt, - ) - } - } - private fun writeDictionaries(dictionaries: Collection, outputStream: OutputStream) = dictionaryCsvFormat(true).write(outputStream).use { dictionaries.forEach { dictionary -> @@ -431,70 +359,46 @@ class MemDatabase private constructor( } } - private fun userCsvFormat(withHeader: Boolean): CSVFormat { - return CSVFormat.DEFAULT.builder() - .setHeader( - "id", - "uuid", - "details", - "changed_at", - ) - .setSkipHeaderRecord(!withHeader) - .build() - } - - private fun dictionaryCsvFormat(withHeader: Boolean): CSVFormat { - return CSVFormat.DEFAULT.builder() - .setHeader( - "id", - "name", - "user_id", - "source_lang", - "target_lang", - "details", - "changed_at", - ) - .setSkipHeaderRecord(!withHeader) - .build() - } - - private fun cardCsvFormat(withHeader: Boolean): CSVFormat { - return CSVFormat.DEFAULT.builder() - .setHeader( - "id", - "dictionary_id", - "words", - "details", - "answered", - "changed_at", - ) - .setSkipHeaderRecord(!withHeader) - .build() - } + private fun dictionaryCsvFormat(withHeader: Boolean): CSVFormat = CSVFormat.DEFAULT.builder() + .setHeader( + "id", + "name", + "user_id", + "source_lang", + "target_lang", + "details", + "changed_at", + ) + .setSkipHeaderRecord(!withHeader) + .build() + + private fun cardCsvFormat(withHeader: Boolean): CSVFormat = CSVFormat.DEFAULT.builder() + .setHeader( + "id", + "dictionary_id", + "words", + "details", + "answered", + "changed_at", + ) + .setSkipHeaderRecord(!withHeader) + .build() - private fun CSVRecord.value(key: String): String { - return requireNotNull(get(key)) { "null value for '$key'. record = $this" } - } + private fun CSVRecord.value(key: String): String = + requireNotNull(get(key)) { "null value for '$key'. record = $this" } - private fun CSVRecord.valueOrNull(key: String): String? { - return get(key)?.takeIf { it.isNotBlank() } - } + private fun CSVRecord.valueOrNull(key: String): String? = get(key)?.takeIf { it.isNotBlank() } - private fun CSVFormat.write(outputStream: OutputStream): CSVPrinter { - return print(outputStream.bufferedWriter(charset = Charsets.UTF_8)) - } + private fun CSVFormat.write(outputStream: OutputStream): CSVPrinter = + print(outputStream.bufferedWriter(charset = Charsets.UTF_8)) - private fun CSVFormat.read(inputStream: InputStream): CSVParser { - return parse(inputStream.bufferedReader(charset = Charsets.UTF_8)) - } + private fun CSVFormat.read(inputStream: InputStream): CSVParser = + parse(inputStream.bufferedReader(charset = Charsets.UTF_8)) - private fun Collection.asSet(): Set { - return if (this is Set) this else toSet() - } + private fun Collection.asSet(): Set = if (this is Set) this else toSet() - private fun resolveClasspathResource(classpathDir: String, classpathFilename: String): String { - return "${classpathDir.substringAfter(CLASSPATH_PREFIX)}/$classpathFilename".replace("//", "/") - } + private fun resolveClasspathResource(classpathDir: String, classpathFilename: String): String = + "${classpathDir.substringAfter(CLASSPATH_PREFIX)}/$classpathFilename".replace("//", "/") } } \ No newline at end of file diff --git a/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt b/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt index 9c415b9e..73c0b4d6 100644 --- a/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt +++ b/db-mem/src/main/kotlin/MemDbDictionaryRepository.kt @@ -15,7 +15,7 @@ class MemDbDictionaryRepository( database.findDictionaryById(dictionaryId.toLong())?.toDbDictionary() override fun findDictionariesByUserId(userId: String): Sequence = - this.database.findDictionariesByUserId(userId.toLong()).map { it.toDbDictionary() } + this.database.findDictionariesByUserId(userId).map { it.toDbDictionary() } override fun createDictionary(entity: DbDictionary): DbDictionary = database.saveDictionary(entity.toMemDbDictionary().copy(changedAt = systemNow())).toDbDictionary() diff --git a/db-mem/src/main/kotlin/MemDbEntityMapper.kt b/db-mem/src/main/kotlin/MemDbEntityMapper.kt index e0db036d..59a4e341 100644 --- a/db-mem/src/main/kotlin/MemDbEntityMapper.kt +++ b/db-mem/src/main/kotlin/MemDbEntityMapper.kt @@ -5,13 +5,11 @@ import com.gitlab.sszuev.flashcards.asKotlin import com.gitlab.sszuev.flashcards.common.CommonCardDetailsDto import com.gitlab.sszuev.flashcards.common.CommonDictionaryDetailsDto import com.gitlab.sszuev.flashcards.common.CommonExampleDto -import com.gitlab.sszuev.flashcards.common.CommonUserDetailsDto import com.gitlab.sszuev.flashcards.common.CommonWordDto import com.gitlab.sszuev.flashcards.common.detailsAsCommonCardDetailsDto import com.gitlab.sszuev.flashcards.common.parseCardDetailsJson import com.gitlab.sszuev.flashcards.common.parseCardWordsJson import com.gitlab.sszuev.flashcards.common.parseDictionaryDetailsJson -import com.gitlab.sszuev.flashcards.common.parseUserDetailsJson import com.gitlab.sszuev.flashcards.common.toCardEntityDetails import com.gitlab.sszuev.flashcards.common.toCardEntityStats import com.gitlab.sszuev.flashcards.common.toCardWordEntity @@ -21,24 +19,11 @@ import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbCard import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbDictionary import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbExample import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbLanguage -import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbUser import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbWord -import com.gitlab.sszuev.flashcards.model.common.AppAuthId -import com.gitlab.sszuev.flashcards.model.common.AppUserEntity -import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.repositories.DbCard import com.gitlab.sszuev.flashcards.repositories.DbDictionary import com.gitlab.sszuev.flashcards.repositories.DbLang import com.gitlab.sszuev.flashcards.repositories.LanguageRepository -import java.util.UUID - -internal fun MemDbUser.detailsAsJsonString(): String { - return CommonUserDetailsDto(details).toJsonString() -} - -internal fun fromJsonStringToMemDbUserDetails(json: String): Map { - return parseUserDetailsJson(json).mapValues { it.toString() } -} internal fun MemDbDictionary.detailsAsJsonString(): String { return CommonDictionaryDetailsDto(this.details).toJsonString() @@ -64,14 +49,9 @@ internal fun fromJsonStringToMemDbWords(json: String): List { return parseCardWordsJson(json).map { it.toMemDbWord() } } -internal fun MemDbUser.toAppUserEntity(): AppUserEntity = AppUserEntity( - id = id?.asUserId() ?: AppUserId.NONE, - authId = uuid.asAppAuthId(), -) - internal fun MemDbDictionary.toDbDictionary() = DbDictionary( dictionaryId = this.id?.toString() ?: "", - userId = this.userId?.toString() ?: "", + userId = this.userId ?: "", name = this.name, sourceLang = this.sourceLanguage.toDbLang(), targetLang = this.targetLanguage.toDbLang(), @@ -83,7 +63,7 @@ internal fun DbDictionary.toMemDbDictionary(): MemDbDictionary = MemDbDictionary sourceLanguage = this.sourceLang.toMemDbLanguage(), targetLanguage = this.targetLang.toMemDbLanguage(), details = emptyMap(), - userId = if (this.userId.isBlank()) null else this.userId.toLong() + userId = this.userId.ifBlank { null } ) internal fun MemDbCard.toDbCard(): DbCard { @@ -155,7 +135,3 @@ private fun CommonExampleDto.toMemDbExample(): MemDbExample = MemDbExample( internal fun MemDbCard.detailsAsCommonCardDetailsDto(): CommonCardDetailsDto = CommonCardDetailsDto(this.details) private fun CommonCardDetailsDto.toMemDbCardDetails(): Map = this.mapValues { it.value.toString() } - -private fun Long.asUserId(): AppUserId = AppUserId(toString()) - -private fun UUID.asAppAuthId(): AppAuthId = AppAuthId(toString()) \ No newline at end of file diff --git a/db-mem/src/main/kotlin/MemDbUserRepository.kt b/db-mem/src/main/kotlin/MemDbUserRepository.kt index 2a914597..4d879152 100644 --- a/db-mem/src/main/kotlin/MemDbUserRepository.kt +++ b/db-mem/src/main/kotlin/MemDbUserRepository.kt @@ -2,6 +2,7 @@ package com.gitlab.sszuev.flashcards.dbmem import com.gitlab.sszuev.flashcards.model.common.AppAuthId import com.gitlab.sszuev.flashcards.model.common.AppUserEntity +import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.repositories.DbUserRepository import com.gitlab.sszuev.flashcards.repositories.UserEntityDbResponse import com.gitlab.sszuev.flashcards.repositories.noUserFoundDbError @@ -15,17 +16,12 @@ class MemDbUserRepository( private val database = MemDatabase.get(dbConfig.dataLocation) override fun getUser(authId: AppAuthId): UserEntityDbResponse { - val uuid = try { - UUID.fromString(authId.asString()) - } catch (ex: IllegalArgumentException) { + if (!database.containsUser(authId.asString())) { return UserEntityDbResponse( - user = AppUserEntity.EMPTY, errors = listOf(wrongUserUuidDbError("getUser", authId)) - ) - } - val res = database.findUserByUuid(uuid) - ?: return UserEntityDbResponse( user = AppUserEntity.EMPTY, errors = listOf(noUserFoundDbError("getUser", authId)) ) - return UserEntityDbResponse(user = res.toAppUserEntity()) + } + val res = AppUserEntity(authId = authId, id = AppUserId("42")) // TODO + return UserEntityDbResponse(user = res) } } \ No newline at end of file diff --git a/db-mem/src/main/kotlin/dao/MemDbDictionary.kt b/db-mem/src/main/kotlin/dao/MemDbDictionary.kt index 2ac84a29..04fcd963 100644 --- a/db-mem/src/main/kotlin/dao/MemDbDictionary.kt +++ b/db-mem/src/main/kotlin/dao/MemDbDictionary.kt @@ -10,7 +10,7 @@ data class MemDbDictionary( val sourceLanguage: MemDbLanguage, val targetLanguage: MemDbLanguage, val details: Map = emptyMap(), - val userId: Long? = null, + val userId: String? = null, val id: Long? = null, val changedAt: LocalDateTime? = null, ) \ No newline at end of file diff --git a/db-mem/src/main/kotlin/dao/MemDbUser.kt b/db-mem/src/main/kotlin/dao/MemDbUser.kt deleted file mode 100644 index 1787d01c..00000000 --- a/db-mem/src/main/kotlin/dao/MemDbUser.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.gitlab.sszuev.flashcards.dbmem.dao - -import java.time.LocalDateTime -import java.util.UUID - -data class MemDbUser( - val id: Long?, - val uuid: UUID, - val details: Map = emptyMap(), - val changedAt: LocalDateTime? = null, -) \ No newline at end of file diff --git a/db-mem/src/test/kotlin/MemDatabaseTest.kt b/db-mem/src/test/kotlin/MemDatabaseTest.kt index afd0922b..16dc650e 100644 --- a/db-mem/src/test/kotlin/MemDatabaseTest.kt +++ b/db-mem/src/test/kotlin/MemDatabaseTest.kt @@ -1,17 +1,14 @@ package com.gitlab.sszuev.flashcards.dbmem import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbCard -import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbUser import com.gitlab.sszuev.flashcards.dbmem.dao.MemDbWord import com.gitlab.sszuev.flashcards.dbmem.testutils.classPathResourceDir import com.gitlab.sszuev.flashcards.dbmem.testutils.copyClassPathDataToDir -import com.gitlab.sszuev.flashcards.systemNow import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Order import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir import java.nio.file.Path -import java.time.LocalDateTime import java.util.UUID @Order(1) @@ -19,66 +16,18 @@ internal class MemDatabaseTest { companion object { private val existingUUID: UUID = UUID.fromString("c9a414f5-3f75-4494-b664-f4c8b33ff4e6") - private val newUUID: UUID = UUID.fromString("45a34bd8-5472-491e-8e27-84290314ee38") - private val timestamp: LocalDateTime = LocalDateTime.parse("2022-12-26T16:04:14") - private val existingUser = MemDbUser( - id = 42, - uuid = existingUUID, - changedAt = timestamp, - details = emptyMap(), - ) - private val testUser = MemDbUser( - id = null, - uuid = newUUID, - changedAt = null, - details = emptyMap(), - ) private val testCard = MemDbCard( id = null, words = listOf(MemDbWord(word = "word", translations = listOf(listOf("слово")))), ) - - private fun LocalDateTime.isAfterOrEqual(other: LocalDateTime): Boolean { - return this == other || isAfter(other) - } } @Test fun `test find users`() { val database = MemDatabase.load("classpath:$classPathResourceDir") - val users = database.findUsers().toList() - Assertions.assertEquals(listOf(existingUser), users) - } - - @Test - fun `test load from directory & reload & find-users & find-user-by-uuid & save-user`(@TempDir dir: Path) { - val timestamp = systemNow() - copyClassPathDataToDir(classPathResourceDir, dir) - val database1 = MemDatabase.get(dir.toString()) - Assertions.assertEquals(listOf(existingUser), database1.findUsers().toList()) - val newUser = database1.saveUser(testUser) - Assertions.assertEquals(43, newUser.id) - Assertions.assertTrue(newUser.changedAt!!.isAfterOrEqual(timestamp)) - Assertions.assertEquals(listOf(existingUser, newUser), database1.findUsers().toList()) - - // test cleaned: wait 0.5 second (test period is 200 ms) and reload store - Thread.sleep(500) - MemDatabase.clear() - - val database2 = MemDatabase.get(dir.toString()) - Assertions.assertNotSame(database1, database2) - Assertions.assertEquals(2, database2.countUsers()) - Assertions.assertEquals(existingUser, database2.findUserByUuid(existingUUID)!!) - Assertions.assertEquals(newUser, database2.findUserByUuid(newUUID)!!) - - val database3 = MemDatabase.load(dir.toString()) - Assertions.assertNotSame(database1, database3) - Assertions.assertEquals(listOf(existingUser, newUser), database3.findUsers().toList()) - Assertions.assertEquals(existingUser, database3.findUserByUuid(existingUUID)!!) - Assertions.assertEquals(newUser, database3.findUserByUuid(newUUID)!!) - - MemDatabase.clear() + val users = database.findUserIds().toList() + Assertions.assertEquals(listOf(existingUUID.toString()), users) } @Test diff --git a/db-mem/src/test/resources/db-mem-test-data/dictionaries.csv b/db-mem/src/test/resources/db-mem-test-data/dictionaries.csv index 3b73692b..466e7a37 100644 --- a/db-mem/src/test/resources/db-mem-test-data/dictionaries.csv +++ b/db-mem/src/test/resources/db-mem-test-data/dictionaries.csv @@ -1,3 +1,3 @@ id,name,user_id,source_lang,target_lang,details,changed_at -1,Irregular Verbs,42,en,ru,{},2022-12-26T16:04:14 -2,Weather,42,en,ru,{},2022-12-26T16:04:14 \ No newline at end of file +1,Irregular Verbs,c9a414f5-3f75-4494-b664-f4c8b33ff4e6,en,ru,{},2022-12-26T16:04:14 +2,Weather,c9a414f5-3f75-4494-b664-f4c8b33ff4e6,en,ru,{},2022-12-26T16:04:14 \ No newline at end of file diff --git a/db-mem/src/test/resources/db-mem-test-data/users.csv b/db-mem/src/test/resources/db-mem-test-data/users.csv deleted file mode 100644 index 21c35afe..00000000 --- a/db-mem/src/test/resources/db-mem-test-data/users.csv +++ /dev/null @@ -1,2 +0,0 @@ -id,uuid,details,changed_at -42,c9a414f5-3f75-4494-b664-f4c8b33ff4e6,{},2022-12-26T16:04:14 \ No newline at end of file diff --git a/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt b/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt index 899108f6..af532cd4 100644 --- a/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt +++ b/db-pg/src/main/kotlin/PgDbDictionaryRepository.kt @@ -27,7 +27,7 @@ class PgDbDictionaryRepository( } override fun findDictionariesByUserId(userId: String): Sequence = connection.execute { - PgDbDictionary.find(Dictionaries.userId eq userId.toUserId()).map { it.toDbDictionary() }.asSequence() + PgDbDictionary.find(Dictionaries.userId eq userId).map { it.toDbDictionary() }.asSequence() } override fun createDictionary(entity: DbDictionary): DbDictionary = connection.execute { @@ -36,7 +36,7 @@ class PgDbDictionaryRepository( it[sourceLanguage] = entity.sourceLang.langId it[targetLanguage] = entity.targetLang.langId it[name] = entity.name - it[userId] = entity.userId.toLong() + it[userId] = entity.userId it[changedAt] = timestamp } entity.copy(dictionaryId = dictionaryId.value.toString()) diff --git a/db-pg/src/main/kotlin/PgDbEntityMapper.kt b/db-pg/src/main/kotlin/PgDbEntityMapper.kt index 6e7c8fd5..474074f1 100644 --- a/db-pg/src/main/kotlin/PgDbEntityMapper.kt +++ b/db-pg/src/main/kotlin/PgDbEntityMapper.kt @@ -13,27 +13,16 @@ import com.gitlab.sszuev.flashcards.dbpg.dao.Cards import com.gitlab.sszuev.flashcards.dbpg.dao.Dictionaries import com.gitlab.sszuev.flashcards.dbpg.dao.PgDbCard import com.gitlab.sszuev.flashcards.dbpg.dao.PgDbDictionary -import com.gitlab.sszuev.flashcards.dbpg.dao.PgDbUser -import com.gitlab.sszuev.flashcards.dbpg.dao.Users -import com.gitlab.sszuev.flashcards.model.common.AppAuthId -import com.gitlab.sszuev.flashcards.model.common.AppUserEntity -import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.repositories.DbCard import com.gitlab.sszuev.flashcards.repositories.DbDictionary import com.gitlab.sszuev.flashcards.repositories.DbLang import com.gitlab.sszuev.flashcards.repositories.LanguageRepository import org.jetbrains.exposed.dao.id.EntityID import java.time.LocalDateTime -import java.util.UUID - -internal fun PgDbUser.toAppUserEntity(): AppUserEntity = AppUserEntity( - id = this.id.asUserId(), - authId = this.uuid.asAppAuthId(), -) internal fun PgDbDictionary.toDbDictionary(): DbDictionary = DbDictionary( dictionaryId = this.id.value.toString(), - userId = this.userId.value.toString(), + userId = this.userId, name = this.name, sourceLang = createDbLang(this.sourceLang), targetLang = createDbLang(this.targetLang), @@ -63,17 +52,11 @@ internal fun writeCardEntityToPgDbCard(from: DbCard, to: PgDbCard, timestamp: Lo internal fun DbCard.toPgDbCardWordsJson(): String = wordsAsCommonWordDtoList().toJsonString() -internal fun EntityID.asUserId(): AppUserId = AppUserId(value.toString()) - internal fun String.toDictionariesId(): EntityID = EntityID(toLong(), Dictionaries) internal fun String.toCardsId(): EntityID = EntityID(toLong(), Cards) -internal fun String.toUserId(): EntityID = EntityID(toLong(), Users) - internal fun createDbLang(tag: String) = DbLang( langId = tag, partsOfSpeech = LanguageRepository.partsOfSpeech(tag) -) - -private fun UUID.asAppAuthId(): AppAuthId = AppAuthId(toString()) +) \ No newline at end of file diff --git a/db-pg/src/main/kotlin/PgDbUserRepository.kt b/db-pg/src/main/kotlin/PgDbUserRepository.kt index f0191667..c6639c1f 100644 --- a/db-pg/src/main/kotlin/PgDbUserRepository.kt +++ b/db-pg/src/main/kotlin/PgDbUserRepository.kt @@ -1,17 +1,13 @@ package com.gitlab.sszuev.flashcards.dbpg -import com.github.benmanes.caffeine.cache.Cache -import com.github.benmanes.caffeine.cache.Caffeine -import com.gitlab.sszuev.flashcards.dbpg.dao.PgDbUser -import com.gitlab.sszuev.flashcards.dbpg.dao.Users +import com.gitlab.sszuev.flashcards.dbpg.dao.Dictionaries +import com.gitlab.sszuev.flashcards.dbpg.dao.PgDbDictionary import com.gitlab.sszuev.flashcards.model.common.AppAuthId import com.gitlab.sszuev.flashcards.model.common.AppUserEntity +import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.repositories.DbUserRepository import com.gitlab.sszuev.flashcards.repositories.UserEntityDbResponse import com.gitlab.sszuev.flashcards.repositories.noUserFoundDbError -import com.gitlab.sszuev.flashcards.repositories.wrongUserUuidDbError -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import java.util.UUID class PgDbUserRepository( dbConfig: PgDbConfig = PgDbConfig(), @@ -21,28 +17,16 @@ class PgDbUserRepository( // and memory-storage is used instead PgDbConnector.connection(dbConfig) } - private val cache: Cache = Caffeine.newBuilder().build() override fun getUser(authId: AppAuthId): UserEntityDbResponse { - val uuid = try { - UUID.fromString(authId.asString()) - } catch (ex: IllegalArgumentException) { - return UserEntityDbResponse( - user = AppUserEntity.EMPTY, errors = listOf(wrongUserUuidDbError("getUser", authId)) - ) - } return connection.execute { - // use local cache - // expected that deletion when using different devices is rare - // also, currently user has no details - val entity = cache.getIfPresent(uuid) ?: PgDbUser.find(Users.uuid eq uuid).singleOrNull()?.toAppUserEntity() - if (entity == null) { + if (PgDbDictionary.find { Dictionaries.userId eq authId.asString() }.empty()) { UserEntityDbResponse( user = AppUserEntity.EMPTY, errors = listOf(noUserFoundDbError("getUser", authId)) ) } else { - cache.put(uuid, entity) - UserEntityDbResponse(user = entity) + val res = AppUserEntity(authId = authId, id = AppUserId("42")) // TODO + UserEntityDbResponse(user = res) } } } diff --git a/db-pg/src/main/kotlin/dao/PgDbDictionary.kt b/db-pg/src/main/kotlin/dao/PgDbDictionary.kt index 519430d4..8e1eb857 100644 --- a/db-pg/src/main/kotlin/dao/PgDbDictionary.kt +++ b/db-pg/src/main/kotlin/dao/PgDbDictionary.kt @@ -3,14 +3,15 @@ package com.gitlab.sszuev.flashcards.dbpg.dao import org.jetbrains.exposed.dao.Entity import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.id.EntityID +import java.time.LocalDateTime class PgDbDictionary(id: EntityID) : Entity(id) { companion object : EntityClass(Dictionaries) - var userId by Dictionaries.userId - var name by Dictionaries.name - val sourceLang by Dictionaries.sourceLanguage - val targetLang by Dictionaries.targetLanguage - var details by Dictionaries.details - var changedAt by Dictionaries.changedAt + var userId: String by Dictionaries.userId + var name: String by Dictionaries.name + val sourceLang: String by Dictionaries.sourceLanguage + val targetLang: String by Dictionaries.targetLanguage + var details: String by Dictionaries.details + var changedAt: LocalDateTime by Dictionaries.changedAt } \ No newline at end of file diff --git a/db-pg/src/main/kotlin/dao/PgDbUser.kt b/db-pg/src/main/kotlin/dao/PgDbUser.kt deleted file mode 100644 index bc0355e8..00000000 --- a/db-pg/src/main/kotlin/dao/PgDbUser.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.gitlab.sszuev.flashcards.dbpg.dao - -import org.jetbrains.exposed.dao.Entity -import org.jetbrains.exposed.dao.EntityClass -import org.jetbrains.exposed.dao.id.EntityID - -class PgDbUser(id: EntityID) : Entity(id) { - companion object : EntityClass(Users) - - var uuid by Users.uuid - var details by Users.details - var changedAt by Users.changedAt -} \ No newline at end of file diff --git a/db-pg/src/main/kotlin/dao/Tables.kt b/db-pg/src/main/kotlin/dao/Tables.kt index 17466afd..3f00ad8f 100644 --- a/db-pg/src/main/kotlin/dao/Tables.kt +++ b/db-pg/src/main/kotlin/dao/Tables.kt @@ -8,6 +8,7 @@ import org.jetbrains.exposed.sql.Table import org.jetbrains.exposed.sql.javatime.datetime import org.postgresql.util.PGobject import java.sql.ResultSet +import java.time.LocalDateTime /** @@ -18,12 +19,12 @@ object Dictionaries : LongIdTableWithSequence( idSeqName = "dictionaries_id_seq", pkeyName = "dictionaries_pkey" ) { - val name = varchar("name", 1024) - val userId = reference("user_id", Users.id) - val sourceLanguage = varchar("source_lang", 42) - val targetLanguage = varchar("target_lang", 42) - val details = json("details") - val changedAt = datetime("changed_at") + val name: Column = varchar("name", 1024) + val userId: Column = varchar("user_id", 36) + val sourceLanguage: Column = varchar("source_lang", 42) + val targetLanguage: Column = varchar("target_lang", 42) + val details: Column = json("details") + val changedAt: Column = datetime("changed_at") } /** @@ -37,15 +38,6 @@ object Cards : LongIdTableWithSequence(tableName = "cards", idSeqName = "cards_i val changedAt = datetime("changed_at") } -/** - * id;uuid,role - */ -object Users : LongIdTableWithSequence(tableName = "users", idSeqName = "users_id_seq", pkeyName = "users_pkey") { - val uuid = uuid("uuid").uniqueIndex() - val details = json("details") - val changedAt = datetime("changed_at") -} - open class LongIdTableWithSequence(tableName: String, idSeqName: String, pkeyName: String, columnName: String = "id") : IdTable(tableName) { final override val id: Column> = long(columnName).autoIncrement(idSeqName).entityId() diff --git a/db-pg/src/main/resources/migrations/schema/001-init-schema.sql b/db-pg/src/main/resources/migrations/schema/001-init-schema.sql index a21b9296..b0263d9b 100644 --- a/db-pg/src/main/resources/migrations/schema/001-init-schema.sql +++ b/db-pg/src/main/resources/migrations/schema/001-init-schema.sql @@ -1,14 +1,7 @@ -CREATE TABLE public.users ( - id bigint NOT NULL, - uuid UUID NOT NULL UNIQUE, - details JSON NOT NULL DEFAULT '{}'::JSON, - changed_at TIMESTAMP NOT NULL DEFAULT now()::TIMESTAMP -); - CREATE TABLE public.dictionaries ( id BIGINT NOT NULL, name VARCHAR(1024) NOT NULL, - user_id bigint NOT NULL, + user_id VARCHAR(36) NOT NULL, target_lang VARCHAR(42) NOT NULL, source_lang VARCHAR(42) NOT NULL, details JSON NOT NULL DEFAULT '{}'::JSON, @@ -24,15 +17,6 @@ CREATE TABLE public.cards ( changed_at TIMESTAMP NOT NULL DEFAULT now()::TIMESTAMP ); -ALTER TABLE public.users ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( - SEQUENCE NAME public.users_id_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1 -); - ALTER TABLE public.dictionaries ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( SEQUENCE NAME public.dictionaries_id_seq START WITH 1 @@ -57,11 +41,5 @@ ALTER TABLE ONLY public.cards ALTER TABLE ONLY public.dictionaries ADD CONSTRAINT dictionaries_pkey PRIMARY KEY (id); -ALTER TABLE ONLY public.users - ADD CONSTRAINT users_pkey PRIMARY KEY (id); - -ALTER TABLE ONLY public.dictionaries - ADD CONSTRAINT fk_dictionaries_users_id FOREIGN KEY (user_id) REFERENCES public.users(id); - ALTER TABLE ONLY public.cards ADD CONSTRAINT fk_cards_dictionaries_id FOREIGN KEY (dictionary_id) REFERENCES public.dictionaries(id); \ No newline at end of file diff --git a/db-pg/src/test/resources/migrations/data/001-01-before-load-test-data.sql b/db-pg/src/test/resources/migrations/data/001-01-before-load-test-data.sql index e15aeeee..6067b4bb 100644 --- a/db-pg/src/test/resources/migrations/data/001-01-before-load-test-data.sql +++ b/db-pg/src/test/resources/migrations/data/001-01-before-load-test-data.sql @@ -1,4 +1,3 @@ -ALTER TABLE public.users ALTER COLUMN details TYPE VARCHAR; ALTER TABLE public.dictionaries ALTER COLUMN details TYPE VARCHAR; ALTER TABLE public.cards ALTER COLUMN details TYPE VARCHAR; ALTER TABLE public.cards ALTER COLUMN words TYPE VARCHAR; \ No newline at end of file diff --git a/db-pg/src/test/resources/migrations/data/001-02-test-dictionaries.csv b/db-pg/src/test/resources/migrations/data/001-02-test-dictionaries.csv index 3b73692b..466e7a37 100644 --- a/db-pg/src/test/resources/migrations/data/001-02-test-dictionaries.csv +++ b/db-pg/src/test/resources/migrations/data/001-02-test-dictionaries.csv @@ -1,3 +1,3 @@ id,name,user_id,source_lang,target_lang,details,changed_at -1,Irregular Verbs,42,en,ru,{},2022-12-26T16:04:14 -2,Weather,42,en,ru,{},2022-12-26T16:04:14 \ No newline at end of file +1,Irregular Verbs,c9a414f5-3f75-4494-b664-f4c8b33ff4e6,en,ru,{},2022-12-26T16:04:14 +2,Weather,c9a414f5-3f75-4494-b664-f4c8b33ff4e6,en,ru,{},2022-12-26T16:04:14 \ No newline at end of file diff --git a/db-pg/src/test/resources/migrations/data/001-02-test-users.csv b/db-pg/src/test/resources/migrations/data/001-02-test-users.csv deleted file mode 100644 index 21c35afe..00000000 --- a/db-pg/src/test/resources/migrations/data/001-02-test-users.csv +++ /dev/null @@ -1,2 +0,0 @@ -id,uuid,details,changed_at -42,c9a414f5-3f75-4494-b664-f4c8b33ff4e6,{},2022-12-26T16:04:14 \ No newline at end of file diff --git a/db-pg/src/test/resources/migrations/data/001-03-test-data-sequences.sql b/db-pg/src/test/resources/migrations/data/001-03-test-data-sequences.sql index 8c193d1b..f0e161ab 100644 --- a/db-pg/src/test/resources/migrations/data/001-03-test-data-sequences.sql +++ b/db-pg/src/test/resources/migrations/data/001-03-test-data-sequences.sql @@ -1,4 +1,2 @@ -SELECT setval('public.users_id_seq', 1001, true); SELECT setval('public.cards_id_seq', 10001, true); -SELECT setval('public.dictionaries_id_seq', 101, true); -SELECT setval('public.users_id_seq', 1001, true); \ No newline at end of file +SELECT setval('public.dictionaries_id_seq', 101, true); \ No newline at end of file diff --git a/db-pg/src/test/resources/migrations/data/001-04-after-load-test-data.sql b/db-pg/src/test/resources/migrations/data/001-04-after-load-test-data.sql index b101eabe..992112a6 100644 --- a/db-pg/src/test/resources/migrations/data/001-04-after-load-test-data.sql +++ b/db-pg/src/test/resources/migrations/data/001-04-after-load-test-data.sql @@ -1,4 +1,3 @@ -ALTER TABLE public.users ALTER COLUMN details TYPE JSON USING details::json; ALTER TABLE public.dictionaries ALTER COLUMN details TYPE JSON USING details::json; ALTER TABLE public.cards ALTER COLUMN details TYPE JSON USING details::jsonb; ALTER TABLE public.cards ALTER COLUMN words TYPE JSON USING words::json; \ No newline at end of file diff --git a/db-pg/src/test/resources/migrations/data/test-data-changelog.xml b/db-pg/src/test/resources/migrations/data/test-data-changelog.xml index c84f1dba..22527721 100644 --- a/db-pg/src/test/resources/migrations/data/test-data-changelog.xml +++ b/db-pg/src/test/resources/migrations/data/test-data-changelog.xml @@ -11,14 +11,6 @@ - - Date: Sat, 6 Apr 2024 14:30:20 +0300 Subject: [PATCH 18/18] common & core & db: [#28] remove DbUserRepository, AppUserEntity & AppUserId & related things --- app-ktor/src/main/kotlin/Repositories.kt | 4 -- .../src/commonMain/kotlin/AppRepositories.kt | 12 ----- common/src/commonMain/kotlin/CardContext.kt | 2 - .../commonMain/kotlin/DictionaryContext.kt | 2 - .../kotlin/model/common/AppContext.kt | 1 - .../kotlin/model/common/AppUserEntity.kt | 7 --- .../kotlin/model/common/AppUserId.kt | 12 ----- .../kotlin/repositories/AppUserRepository.kt | 14 ----- .../kotlin/repositories/CommonErrors.kt | 35 ------------ .../kotlin/repositories/DbUserRepository.kt | 16 ------ .../repositories/NoOpDbUserRepository.kt | 13 ----- .../processes/{Processes.kt => AppErrors.kt} | 26 ++++----- .../processes/DictionaryProcessWokers.kt | 10 +--- .../kotlin/CardCorProcessorRunCardsTest.kt | 11 ++-- .../kotlin/CardCorProcessorRunResourceTest.kt | 6 +-- .../kotlin/DictionaryCorProcessorRunTest.kt | 11 ---- .../kotlin/DictionaryCorProcessorStubTest.kt | 13 +++-- .../kotlin/DbUserRepositoryTest.kt | 54 ------------------- .../kotlin/mocks/MockDbUserRepository.kt | 14 ----- db-mem/src/main/kotlin/MemDbUserRepository.kt | 27 ---------- .../test/kotlin/MemDbUserRepositoryTest.kt | 10 ---- db-pg/src/main/kotlin/PgDbUserRepository.kt | 33 ------------ .../src/test/kotlin/PgDbUserRepositoryTest.kt | 9 ---- .../flashcards/logmappers/FromContext.kt | 9 ++-- specs/src/main/kotlin/Stubs.kt | 1 - 25 files changed, 29 insertions(+), 323 deletions(-) delete mode 100644 common/src/commonMain/kotlin/model/common/AppUserEntity.kt delete mode 100644 common/src/commonMain/kotlin/model/common/AppUserId.kt delete mode 100644 common/src/commonMain/kotlin/repositories/AppUserRepository.kt delete mode 100644 common/src/commonMain/kotlin/repositories/CommonErrors.kt delete mode 100644 common/src/commonMain/kotlin/repositories/DbUserRepository.kt delete mode 100644 common/src/commonMain/kotlin/repositories/NoOpDbUserRepository.kt rename core/src/main/kotlin/processes/{Processes.kt => AppErrors.kt} (94%) delete mode 100644 db-common/src/testFixtures/kotlin/DbUserRepositoryTest.kt delete mode 100644 db-common/src/testFixtures/kotlin/mocks/MockDbUserRepository.kt delete mode 100644 db-mem/src/main/kotlin/MemDbUserRepository.kt delete mode 100644 db-mem/src/test/kotlin/MemDbUserRepositoryTest.kt delete mode 100644 db-pg/src/main/kotlin/PgDbUserRepository.kt delete mode 100644 db-pg/src/test/kotlin/PgDbUserRepositoryTest.kt diff --git a/app-ktor/src/main/kotlin/Repositories.kt b/app-ktor/src/main/kotlin/Repositories.kt index 5b12149d..6b9b4cca 100644 --- a/app-ktor/src/main/kotlin/Repositories.kt +++ b/app-ktor/src/main/kotlin/Repositories.kt @@ -2,10 +2,8 @@ package com.gitlab.sszuev.flashcards import com.gitlab.sszuev.flashcards.dbmem.MemDbCardRepository import com.gitlab.sszuev.flashcards.dbmem.MemDbDictionaryRepository -import com.gitlab.sszuev.flashcards.dbmem.MemDbUserRepository import com.gitlab.sszuev.flashcards.dbpg.PgDbCardRepository import com.gitlab.sszuev.flashcards.dbpg.PgDbDictionaryRepository -import com.gitlab.sszuev.flashcards.dbpg.PgDbUserRepository import com.gitlab.sszuev.flashcards.speaker.createDirectTTSResourceRepository import com.gitlab.sszuev.flashcards.speaker.rabbitmq.RMQTTSResourceRepository @@ -16,6 +14,4 @@ fun appRepositories() = AppRepositories( testCardRepository = MemDbCardRepository(), prodDictionaryRepository = PgDbDictionaryRepository(), testDictionaryRepository = MemDbDictionaryRepository(), - prodUserRepository = PgDbUserRepository(), - testUserRepository = MemDbUserRepository(), ) \ No newline at end of file diff --git a/common/src/commonMain/kotlin/AppRepositories.kt b/common/src/commonMain/kotlin/AppRepositories.kt index 307b1719..401492fb 100644 --- a/common/src/commonMain/kotlin/AppRepositories.kt +++ b/common/src/commonMain/kotlin/AppRepositories.kt @@ -3,10 +3,8 @@ package com.gitlab.sszuev.flashcards import com.gitlab.sszuev.flashcards.model.common.AppMode import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository -import com.gitlab.sszuev.flashcards.repositories.DbUserRepository import com.gitlab.sszuev.flashcards.repositories.NoOpDbCardRepository import com.gitlab.sszuev.flashcards.repositories.NoOpDbDictionaryRepository -import com.gitlab.sszuev.flashcards.repositories.NoOpDbUserRepository import com.gitlab.sszuev.flashcards.repositories.NoOpTTSResourceRepository import com.gitlab.sszuev.flashcards.repositories.TTSResourceRepository @@ -15,8 +13,6 @@ data class AppRepositories( private val testTTSClientRepository: TTSResourceRepository = NoOpTTSResourceRepository, private val prodDictionaryRepository: DbDictionaryRepository = NoOpDbDictionaryRepository, private val testDictionaryRepository: DbDictionaryRepository = NoOpDbDictionaryRepository, - private val prodUserRepository: DbUserRepository = NoOpDbUserRepository, - private val testUserRepository: DbUserRepository = NoOpDbUserRepository, private val prodCardRepository: DbCardRepository = NoOpDbCardRepository, private val testCardRepository: DbCardRepository = NoOpDbCardRepository, ) { @@ -25,14 +21,6 @@ data class AppRepositories( val NO_OP_REPOSITORIES = AppRepositories() } - fun userRepository(mode: AppMode): DbUserRepository { - return when(mode) { - AppMode.PROD -> prodUserRepository - AppMode.TEST -> testUserRepository - AppMode.STUB -> NoOpDbUserRepository - } - } - fun dictionaryRepository(mode: AppMode): DbDictionaryRepository { return when (mode) { AppMode.PROD -> prodDictionaryRepository diff --git a/common/src/commonMain/kotlin/CardContext.kt b/common/src/commonMain/kotlin/CardContext.kt index b4fef14f..10e25e9d 100644 --- a/common/src/commonMain/kotlin/CardContext.kt +++ b/common/src/commonMain/kotlin/CardContext.kt @@ -7,7 +7,6 @@ import com.gitlab.sszuev.flashcards.model.common.AppMode import com.gitlab.sszuev.flashcards.model.common.AppRequestId import com.gitlab.sszuev.flashcards.model.common.AppStatus import com.gitlab.sszuev.flashcards.model.common.AppStub -import com.gitlab.sszuev.flashcards.model.common.AppUserEntity import com.gitlab.sszuev.flashcards.model.common.NONE import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardFilter @@ -35,7 +34,6 @@ data class CardContext( override var requestId: AppRequestId = AppRequestId.NONE, override var requestAppAuthId: AppAuthId = AppAuthId.NONE, override var normalizedRequestAppAuthId: AppAuthId = AppAuthId.NONE, - override var contextUserEntity: AppUserEntity = AppUserEntity.EMPTY, // get word resource by id (for TTS) var requestTTSResourceGet: TTSResourceGet = TTSResourceGet.NONE, diff --git a/common/src/commonMain/kotlin/DictionaryContext.kt b/common/src/commonMain/kotlin/DictionaryContext.kt index 36b69b62..e4d78a27 100644 --- a/common/src/commonMain/kotlin/DictionaryContext.kt +++ b/common/src/commonMain/kotlin/DictionaryContext.kt @@ -7,7 +7,6 @@ import com.gitlab.sszuev.flashcards.model.common.AppMode import com.gitlab.sszuev.flashcards.model.common.AppRequestId import com.gitlab.sszuev.flashcards.model.common.AppStatus import com.gitlab.sszuev.flashcards.model.common.AppStub -import com.gitlab.sszuev.flashcards.model.common.AppUserEntity import com.gitlab.sszuev.flashcards.model.common.NONE import com.gitlab.sszuev.flashcards.model.domain.DictionaryEntity import com.gitlab.sszuev.flashcards.model.domain.DictionaryId @@ -28,7 +27,6 @@ data class DictionaryContext( override var requestId: AppRequestId = AppRequestId.NONE, override var requestAppAuthId: AppAuthId = AppAuthId.NONE, override var normalizedRequestAppAuthId: AppAuthId = AppAuthId.NONE, - override var contextUserEntity: AppUserEntity = AppUserEntity.EMPTY, // get all dictionaries' list response: var responseDictionaryEntityList: List = listOf(), diff --git a/common/src/commonMain/kotlin/model/common/AppContext.kt b/common/src/commonMain/kotlin/model/common/AppContext.kt index ba2b6eed..1a0602c4 100644 --- a/common/src/commonMain/kotlin/model/common/AppContext.kt +++ b/common/src/commonMain/kotlin/model/common/AppContext.kt @@ -19,7 +19,6 @@ interface AppContext { // get user: var requestAppAuthId: AppAuthId var normalizedRequestAppAuthId: AppAuthId - var contextUserEntity: AppUserEntity } private val none = Instant.fromEpochMilliseconds(Long.MIN_VALUE) diff --git a/common/src/commonMain/kotlin/model/common/AppUserEntity.kt b/common/src/commonMain/kotlin/model/common/AppUserEntity.kt deleted file mode 100644 index eac3f419..00000000 --- a/common/src/commonMain/kotlin/model/common/AppUserEntity.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.gitlab.sszuev.flashcards.model.common - -data class AppUserEntity(val id: AppUserId, val authId: AppAuthId) { - companion object { - val EMPTY = AppUserEntity(id = AppUserId.NONE, authId = AppAuthId.NONE) - } -} \ No newline at end of file diff --git a/common/src/commonMain/kotlin/model/common/AppUserId.kt b/common/src/commonMain/kotlin/model/common/AppUserId.kt deleted file mode 100644 index 0203c42a..00000000 --- a/common/src/commonMain/kotlin/model/common/AppUserId.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.gitlab.sszuev.flashcards.model.common - -import com.gitlab.sszuev.flashcards.model.Id - -@JvmInline -value class AppUserId(private val id: String) : Id { - override fun asString() = id - - companion object { - val NONE = AppUserId("") - } -} \ No newline at end of file diff --git a/common/src/commonMain/kotlin/repositories/AppUserRepository.kt b/common/src/commonMain/kotlin/repositories/AppUserRepository.kt deleted file mode 100644 index 221d2b9b..00000000 --- a/common/src/commonMain/kotlin/repositories/AppUserRepository.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.gitlab.sszuev.flashcards.repositories - -import com.gitlab.sszuev.flashcards.model.common.AppAuthId -import com.gitlab.sszuev.flashcards.model.common.AppError -import com.gitlab.sszuev.flashcards.model.common.AppUserEntity - -interface AppUserRepository { - fun getUser(authId: AppAuthId): AppUserResponse -} - -interface AppUserResponse { - val user: AppUserEntity - val errors: List -} \ No newline at end of file diff --git a/common/src/commonMain/kotlin/repositories/CommonErrors.kt b/common/src/commonMain/kotlin/repositories/CommonErrors.kt deleted file mode 100644 index 23db4fe3..00000000 --- a/common/src/commonMain/kotlin/repositories/CommonErrors.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.gitlab.sszuev.flashcards.repositories - -import com.gitlab.sszuev.flashcards.model.common.AppAuthId -import com.gitlab.sszuev.flashcards.model.common.AppError - -fun noUserFoundDbError( - operation: String, - uid: AppAuthId, -) = dbError( - operation = operation, - fieldName = uid.asString(), - details = """user with uid="${uid.asString()}" not found""" -) - -fun wrongUserUuidDbError( - operation: String, - uid: AppAuthId, -) = dbError( - operation = operation, - fieldName = uid.asString(), - details = """wrong uuid="${uid.asString()}"""", -) - -fun dbError( - operation: String, - fieldName: String = "", - details: String = "", - exception: Throwable? = null, -) = AppError( - code = "database::$operation", - field = fieldName, - group = "database", - message = if (details.isBlank()) "Error while $operation" else "Error while $operation: $details", - exception = exception -) \ No newline at end of file diff --git a/common/src/commonMain/kotlin/repositories/DbUserRepository.kt b/common/src/commonMain/kotlin/repositories/DbUserRepository.kt deleted file mode 100644 index 9c778e6c..00000000 --- a/common/src/commonMain/kotlin/repositories/DbUserRepository.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.gitlab.sszuev.flashcards.repositories - -import com.gitlab.sszuev.flashcards.model.common.* - -interface DbUserRepository : AppUserRepository { - override fun getUser(authId: AppAuthId): UserEntityDbResponse -} - -data class UserEntityDbResponse( - override val user: AppUserEntity, - override val errors: List = emptyList() -) : AppUserResponse { - companion object { - val EMPTY = UserEntityDbResponse(user = AppUserEntity.EMPTY) - } -} diff --git a/common/src/commonMain/kotlin/repositories/NoOpDbUserRepository.kt b/common/src/commonMain/kotlin/repositories/NoOpDbUserRepository.kt deleted file mode 100644 index 05957934..00000000 --- a/common/src/commonMain/kotlin/repositories/NoOpDbUserRepository.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.gitlab.sszuev.flashcards.repositories - -import com.gitlab.sszuev.flashcards.model.common.AppAuthId - -object NoOpDbUserRepository: DbUserRepository { - override fun getUser(authId: AppAuthId): UserEntityDbResponse { - noOp() - } - - private fun noOp(): Nothing { - error("Must not be called.") - } -} \ No newline at end of file diff --git a/core/src/main/kotlin/processes/Processes.kt b/core/src/main/kotlin/processes/AppErrors.kt similarity index 94% rename from core/src/main/kotlin/processes/Processes.kt rename to core/src/main/kotlin/processes/AppErrors.kt index 3bad72f1..acb4915d 100644 --- a/core/src/main/kotlin/processes/Processes.kt +++ b/core/src/main/kotlin/processes/AppErrors.kt @@ -59,34 +59,34 @@ fun dataError( ) = AppError( code = operation.name, field = fieldName, - group = "core", + group = "data", message = if (details.isBlank()) "Error while ${operation.name}" else "Error while ${operation.name}: $details", exception = exception ) +internal fun AppContext.handleThrowable(operation: AppOperation, ex: Throwable) { + fail( + runError( + operation = operation, + description = "exception", + exception = ex, + ) + ) +} + internal fun runError( operation: AppOperation, fieldName: String = "", description: String = "", exception: Throwable? = null, ) = AppError( - code = "run::$operation", + code = operation.name, field = fieldName, group = "run", - message = if (description.isBlank()) "" else "Error while $operation: $description", + message = if (description.isBlank()) "" else "Error while ${operation.name}: $description", exception = exception ) -internal fun AppContext.handleThrowable(operation: AppOperation, ex: Throwable) { - fail( - runError( - operation = operation, - description = "exception", - exception = ex, - ) - ) -} - internal fun AppContext.fail(error: AppError) { this.status = AppStatus.FAIL this.errors.add(error) diff --git a/core/src/main/kotlin/processes/DictionaryProcessWokers.kt b/core/src/main/kotlin/processes/DictionaryProcessWokers.kt index 8c3d62cf..5bd27195 100644 --- a/core/src/main/kotlin/processes/DictionaryProcessWokers.kt +++ b/core/src/main/kotlin/processes/DictionaryProcessWokers.kt @@ -9,7 +9,6 @@ import com.gitlab.sszuev.flashcards.core.mappers.toDbDictionary import com.gitlab.sszuev.flashcards.core.mappers.toDictionaryEntity import com.gitlab.sszuev.flashcards.core.mappers.toDocumentCard import com.gitlab.sszuev.flashcards.core.mappers.toDocumentDictionary -import com.gitlab.sszuev.flashcards.core.validators.fail import com.gitlab.sszuev.flashcards.corlib.ChainDSL import com.gitlab.sszuev.flashcards.corlib.worker import com.gitlab.sszuev.flashcards.model.common.AppStatus @@ -39,14 +38,7 @@ fun ChainDSL.processGetAllDictionary() = worker { this.status = if (this.errors.isNotEmpty()) AppStatus.FAIL else AppStatus.RUN } onException { - fail( - runError( - operation = DictionaryOperation.GET_ALL_DICTIONARIES, - fieldName = this.contextUserEntity.id.toFieldName(), - description = "exception", - exception = it - ) - ) + this.handleThrowable(DictionaryOperation.GET_ALL_DICTIONARIES, it) } } diff --git a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt index dac47cb5..241695d0 100644 --- a/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt +++ b/core/src/test/kotlin/CardCorProcessorRunCardsTest.kt @@ -6,14 +6,11 @@ import com.gitlab.sszuev.flashcards.core.mappers.toDbCard import com.gitlab.sszuev.flashcards.core.mappers.toDbDictionary import com.gitlab.sszuev.flashcards.dbcommon.mocks.MockDbCardRepository import com.gitlab.sszuev.flashcards.dbcommon.mocks.MockDbDictionaryRepository -import com.gitlab.sszuev.flashcards.dbcommon.mocks.MockDbUserRepository import com.gitlab.sszuev.flashcards.model.common.AppAuthId import com.gitlab.sszuev.flashcards.model.common.AppError import com.gitlab.sszuev.flashcards.model.common.AppMode import com.gitlab.sszuev.flashcards.model.common.AppRequestId import com.gitlab.sszuev.flashcards.model.common.AppStatus -import com.gitlab.sszuev.flashcards.model.common.AppUserEntity -import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardFilter import com.gitlab.sszuev.flashcards.model.domain.CardId @@ -25,10 +22,8 @@ import com.gitlab.sszuev.flashcards.model.domain.Stage import com.gitlab.sszuev.flashcards.model.domain.TTSResourceId import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository -import com.gitlab.sszuev.flashcards.repositories.DbUserRepository import com.gitlab.sszuev.flashcards.repositories.TTSResourceIdResponse import com.gitlab.sszuev.flashcards.repositories.TTSResourceRepository -import com.gitlab.sszuev.flashcards.repositories.UserEntityDbResponse import com.gitlab.sszuev.flashcards.speaker.MockTTSResourceRepository import com.gitlab.sszuev.flashcards.stubs.stubCard import com.gitlab.sszuev.flashcards.stubs.stubCards @@ -72,7 +67,7 @@ internal class CardCorProcessorRunCardsTest { private fun assertUnknownError(context: CardContext, op: CardOperation) { val error = assertSingleError(context, op) - Assertions.assertEquals("run::$op", error.code) + Assertions.assertEquals(op.name, error.code) Assertions.assertEquals("run", error.group) Assertions.assertEquals("Error while $op: exception", error.message) Assertions.assertInstanceOf(TestException::class.java, error.exception) @@ -639,7 +634,7 @@ internal class CardCorProcessorRunCardsTest { Assertions.assertEquals(emptyList(), context.responseCardEntityList) Assertions.assertEquals(1, context.errors.size) - Assertions.assertEquals("run::LEARN_CARDS", context.errors[0].code) + Assertions.assertEquals("LEARN_CARDS", context.errors[0].code) Assertions.assertInstanceOf(TestException::class.java, context.errors[0].exception) } @@ -743,7 +738,7 @@ internal class CardCorProcessorRunCardsTest { ) val expectedError = AppError( - group = "core", + group = "data", code = op.name, field = testCardId.asString(), message = "Error while ${op.name}: dictionary with id=\"${testDictionaryId.asString()}\" " + diff --git a/core/src/test/kotlin/CardCorProcessorRunResourceTest.kt b/core/src/test/kotlin/CardCorProcessorRunResourceTest.kt index abd94ce3..7178093e 100644 --- a/core/src/test/kotlin/CardCorProcessorRunResourceTest.kt +++ b/core/src/test/kotlin/CardCorProcessorRunResourceTest.kt @@ -2,7 +2,6 @@ package com.gitlab.sszuev.flashcards.core import com.gitlab.sszuev.flashcards.AppRepositories import com.gitlab.sszuev.flashcards.CardContext -import com.gitlab.sszuev.flashcards.dbcommon.mocks.MockDbUserRepository import com.gitlab.sszuev.flashcards.model.common.AppAuthId import com.gitlab.sszuev.flashcards.model.common.AppMode import com.gitlab.sszuev.flashcards.model.common.AppRequestId @@ -29,7 +28,6 @@ internal class CardCorProcessorRunResourceTest { val context = CardContext( operation = CardOperation.GET_RESOURCE, repositories = AppRepositories().copy( - testUserRepository = MockDbUserRepository(), testTTSClientRepository = repository ) ) @@ -118,7 +116,7 @@ internal class CardCorProcessorRunResourceTest { Assertions.assertEquals(ResourceEntity.DUMMY, context.responseTTSResourceEntity) val error = context.errors[0] - Assertions.assertEquals("run::${CardOperation.GET_RESOURCE}", error.code) + Assertions.assertEquals(CardOperation.GET_RESOURCE.name, error.code) Assertions.assertEquals("run", error.group) Assertions.assertEquals(testResourceGet.toString(), error.field) Assertions.assertEquals("Error while GET_RESOURCE: no resource found. filter=${testResourceGet}", error.message) @@ -159,7 +157,7 @@ internal class CardCorProcessorRunResourceTest { Assertions.assertEquals(ResourceEntity.DUMMY, context.responseTTSResourceEntity) val error = context.errors[0] - Assertions.assertEquals("run::${CardOperation.GET_RESOURCE}", error.code) + Assertions.assertEquals(CardOperation.GET_RESOURCE.name, error.code) Assertions.assertEquals("run", error.group) Assertions.assertEquals(testResourceGet.toString(), error.field) Assertions.assertEquals("Error while GET_RESOURCE: unexpected exception", error.message) diff --git a/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt b/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt index e3a8e0d8..957b5909 100644 --- a/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt +++ b/core/src/test/kotlin/DictionaryCorProcessorRunTest.kt @@ -7,13 +7,9 @@ import com.gitlab.sszuev.flashcards.core.mappers.toDbDictionary import com.gitlab.sszuev.flashcards.core.normalizers.normalize import com.gitlab.sszuev.flashcards.dbcommon.mocks.MockDbCardRepository import com.gitlab.sszuev.flashcards.dbcommon.mocks.MockDbDictionaryRepository -import com.gitlab.sszuev.flashcards.dbcommon.mocks.MockDbUserRepository -import com.gitlab.sszuev.flashcards.model.common.AppAuthId import com.gitlab.sszuev.flashcards.model.common.AppMode import com.gitlab.sszuev.flashcards.model.common.AppRequestId import com.gitlab.sszuev.flashcards.model.common.AppStatus -import com.gitlab.sszuev.flashcards.model.common.AppUserEntity -import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.DictionaryId import com.gitlab.sszuev.flashcards.model.domain.DictionaryOperation import com.gitlab.sszuev.flashcards.model.domain.LangEntity @@ -21,8 +17,6 @@ import com.gitlab.sszuev.flashcards.model.domain.LangId import com.gitlab.sszuev.flashcards.model.domain.ResourceEntity import com.gitlab.sszuev.flashcards.repositories.DbCardRepository import com.gitlab.sszuev.flashcards.repositories.DbDictionaryRepository -import com.gitlab.sszuev.flashcards.repositories.DbUserRepository -import com.gitlab.sszuev.flashcards.repositories.UserEntityDbResponse import com.gitlab.sszuev.flashcards.stubs.stubCard import com.gitlab.sszuev.flashcards.stubs.stubDictionaries import com.gitlab.sszuev.flashcards.stubs.stubDictionary @@ -33,21 +27,16 @@ import org.junit.jupiter.api.Test internal class DictionaryCorProcessorRunTest { companion object { private val testUserId = stubDictionary.userId - private val testUser = AppUserEntity(AppUserId("42"), testUserId) @Suppress("SameParameterValue") private fun testContext( op: DictionaryOperation, dictionaryRepository: DbDictionaryRepository, - userRepository: DbUserRepository = MockDbUserRepository( - invokeGetUser = { if (it == testUser.authId) UserEntityDbResponse(user = testUser) else throw AssertionError() } - ), cardsRepository: DbCardRepository = MockDbCardRepository(), ): DictionaryContext { val context = DictionaryContext( operation = op, repositories = AppRepositories().copy( - testUserRepository = userRepository, testDictionaryRepository = dictionaryRepository, testCardRepository = cardsRepository, ) diff --git a/core/src/test/kotlin/DictionaryCorProcessorStubTest.kt b/core/src/test/kotlin/DictionaryCorProcessorStubTest.kt index f055a455..d5dd591e 100644 --- a/core/src/test/kotlin/DictionaryCorProcessorStubTest.kt +++ b/core/src/test/kotlin/DictionaryCorProcessorStubTest.kt @@ -1,22 +1,23 @@ package com.gitlab.sszuev.flashcards.core import com.gitlab.sszuev.flashcards.DictionaryContext -import com.gitlab.sszuev.flashcards.model.common.* +import com.gitlab.sszuev.flashcards.model.common.AppError +import com.gitlab.sszuev.flashcards.model.common.AppMode +import com.gitlab.sszuev.flashcards.model.common.AppRequestId +import com.gitlab.sszuev.flashcards.model.common.AppStatus +import com.gitlab.sszuev.flashcards.model.common.AppStub import com.gitlab.sszuev.flashcards.model.domain.DictionaryOperation import com.gitlab.sszuev.flashcards.stubs.stubDictionaries import com.gitlab.sszuev.flashcards.stubs.stubError -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test -import java.util.* +import java.util.UUID -@OptIn(ExperimentalCoroutinesApi::class) internal class DictionaryCorProcessorStubTest { companion object { private val processor = DictionaryCorProcessor() private val requestId = UUID.randomUUID().toString() - private val testUser = AppUserEntity(AppUserId("42"), AppAuthId("xxx")) @Suppress("SameParameterValue") private fun testContext(op: DictionaryOperation, case: AppStub): DictionaryContext { @@ -43,7 +44,6 @@ internal class DictionaryCorProcessorStubTest { @Test fun `test get-all-dictionary success`() = runTest { val context = testContext(DictionaryOperation.GET_ALL_DICTIONARIES, AppStub.SUCCESS) - context.contextUserEntity = testUser processor.execute(context) assertSuccess(context) Assertions.assertEquals(stubDictionaries, context.responseDictionaryEntityList) @@ -52,7 +52,6 @@ internal class DictionaryCorProcessorStubTest { @Test fun `test get-all-dictionaries error`() = runTest { val context = testContext(DictionaryOperation.GET_ALL_DICTIONARIES, AppStub.UNKNOWN_ERROR) - context.contextUserEntity = testUser processor.execute(context) processor.execute(context) assertFail(context, stubError) diff --git a/db-common/src/testFixtures/kotlin/DbUserRepositoryTest.kt b/db-common/src/testFixtures/kotlin/DbUserRepositoryTest.kt deleted file mode 100644 index 7e7820c5..00000000 --- a/db-common/src/testFixtures/kotlin/DbUserRepositoryTest.kt +++ /dev/null @@ -1,54 +0,0 @@ -package com.gitlab.sszuev.flashcards.dbcommon - -import com.gitlab.sszuev.flashcards.model.common.AppAuthId -import com.gitlab.sszuev.flashcards.model.common.AppError -import com.gitlab.sszuev.flashcards.model.common.AppUserEntity -import com.gitlab.sszuev.flashcards.model.common.AppUserId -import com.gitlab.sszuev.flashcards.repositories.DbUserRepository -import com.gitlab.sszuev.flashcards.repositories.UserEntityDbResponse -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Test - -abstract class DbUserRepositoryTest { - - abstract val repository: DbUserRepository - - companion object { - val demo = AppUserEntity( - id = AppUserId(42.toString()), - authId = AppAuthId("c9a414f5-3f75-4494-b664-f4c8b33ff4e6"), - ) - - @Suppress("SameParameterValue") - private fun assertAppError(res: UserEntityDbResponse, uuid: String, op: String): AppError { - Assertions.assertEquals(1, res.errors.size) - val error = res.errors[0] - Assertions.assertEquals("database::$op", error.code) - Assertions.assertEquals(uuid, error.field) - Assertions.assertEquals("database", error.group) - return error - } - } - - @Test - fun `test get user error no found`() { - val uuid = "45a34bd8-5472-491e-8e27-84290314ee38" - val res = repository.getUser(AppAuthId(uuid)) - Assertions.assertEquals(AppUserEntity.EMPTY, res.user) - - val error = assertAppError(res, uuid, "getUser") - Assertions.assertEquals( - """Error while getUser: user with uid="$uuid" not found""", - error.message - ) - Assertions.assertNull(error.exception) - } - - @Test - fun `test get user success`() { - val res = repository.getUser(demo.authId) - Assertions.assertNotSame(demo, res.user) - Assertions.assertEquals(demo, res.user) - Assertions.assertEquals(0, res.errors.size) - } -} \ No newline at end of file diff --git a/db-common/src/testFixtures/kotlin/mocks/MockDbUserRepository.kt b/db-common/src/testFixtures/kotlin/mocks/MockDbUserRepository.kt deleted file mode 100644 index 6b17db86..00000000 --- a/db-common/src/testFixtures/kotlin/mocks/MockDbUserRepository.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.gitlab.sszuev.flashcards.dbcommon.mocks - -import com.gitlab.sszuev.flashcards.model.common.AppAuthId -import com.gitlab.sszuev.flashcards.repositories.DbUserRepository -import com.gitlab.sszuev.flashcards.repositories.UserEntityDbResponse - -class MockDbUserRepository( - private val invokeGetUser: (AppAuthId) -> UserEntityDbResponse = { UserEntityDbResponse.EMPTY } -) : DbUserRepository { - - override fun getUser(authId: AppAuthId): UserEntityDbResponse { - return invokeGetUser(authId) - } -} \ No newline at end of file diff --git a/db-mem/src/main/kotlin/MemDbUserRepository.kt b/db-mem/src/main/kotlin/MemDbUserRepository.kt deleted file mode 100644 index 4d879152..00000000 --- a/db-mem/src/main/kotlin/MemDbUserRepository.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.gitlab.sszuev.flashcards.dbmem - -import com.gitlab.sszuev.flashcards.model.common.AppAuthId -import com.gitlab.sszuev.flashcards.model.common.AppUserEntity -import com.gitlab.sszuev.flashcards.model.common.AppUserId -import com.gitlab.sszuev.flashcards.repositories.DbUserRepository -import com.gitlab.sszuev.flashcards.repositories.UserEntityDbResponse -import com.gitlab.sszuev.flashcards.repositories.noUserFoundDbError -import com.gitlab.sszuev.flashcards.repositories.wrongUserUuidDbError -import java.util.UUID - -class MemDbUserRepository( - dbConfig: MemDbConfig = MemDbConfig(), -) : DbUserRepository { - - private val database = MemDatabase.get(dbConfig.dataLocation) - - override fun getUser(authId: AppAuthId): UserEntityDbResponse { - if (!database.containsUser(authId.asString())) { - return UserEntityDbResponse( - user = AppUserEntity.EMPTY, errors = listOf(noUserFoundDbError("getUser", authId)) - ) - } - val res = AppUserEntity(authId = authId, id = AppUserId("42")) // TODO - return UserEntityDbResponse(user = res) - } -} \ No newline at end of file diff --git a/db-mem/src/test/kotlin/MemDbUserRepositoryTest.kt b/db-mem/src/test/kotlin/MemDbUserRepositoryTest.kt deleted file mode 100644 index 39bd33de..00000000 --- a/db-mem/src/test/kotlin/MemDbUserRepositoryTest.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.gitlab.sszuev.flashcards.dbmem - -import com.gitlab.sszuev.flashcards.dbcommon.DbUserRepositoryTest -import com.gitlab.sszuev.flashcards.repositories.DbUserRepository -import org.junit.jupiter.api.Order - -@Order(1) -internal class MemDbUserRepositoryTest : DbUserRepositoryTest() { - override val repository: DbUserRepository = MemDbUserRepository() -} \ No newline at end of file diff --git a/db-pg/src/main/kotlin/PgDbUserRepository.kt b/db-pg/src/main/kotlin/PgDbUserRepository.kt deleted file mode 100644 index c6639c1f..00000000 --- a/db-pg/src/main/kotlin/PgDbUserRepository.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.gitlab.sszuev.flashcards.dbpg - -import com.gitlab.sszuev.flashcards.dbpg.dao.Dictionaries -import com.gitlab.sszuev.flashcards.dbpg.dao.PgDbDictionary -import com.gitlab.sszuev.flashcards.model.common.AppAuthId -import com.gitlab.sszuev.flashcards.model.common.AppUserEntity -import com.gitlab.sszuev.flashcards.model.common.AppUserId -import com.gitlab.sszuev.flashcards.repositories.DbUserRepository -import com.gitlab.sszuev.flashcards.repositories.UserEntityDbResponse -import com.gitlab.sszuev.flashcards.repositories.noUserFoundDbError - -class PgDbUserRepository( - dbConfig: PgDbConfig = PgDbConfig(), -) : DbUserRepository { - private val connection by lazy { - // lazy, to avoid initialization error when there is no real pg-database - // and memory-storage is used instead - PgDbConnector.connection(dbConfig) - } - - override fun getUser(authId: AppAuthId): UserEntityDbResponse { - return connection.execute { - if (PgDbDictionary.find { Dictionaries.userId eq authId.asString() }.empty()) { - UserEntityDbResponse( - user = AppUserEntity.EMPTY, errors = listOf(noUserFoundDbError("getUser", authId)) - ) - } else { - val res = AppUserEntity(authId = authId, id = AppUserId("42")) // TODO - UserEntityDbResponse(user = res) - } - } - } -} \ No newline at end of file diff --git a/db-pg/src/test/kotlin/PgDbUserRepositoryTest.kt b/db-pg/src/test/kotlin/PgDbUserRepositoryTest.kt deleted file mode 100644 index b5b5ac69..00000000 --- a/db-pg/src/test/kotlin/PgDbUserRepositoryTest.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.gitlab.sszuev.flashcards.dbpg - -import com.gitlab.sszuev.flashcards.dbcommon.DbUserRepositoryTest -import org.junit.jupiter.api.Order - -@Order(1) -internal class PgDbUserRepositoryTest : DbUserRepositoryTest() { - override val repository = PgDbUserRepository(PgTestContainer.config) -} \ No newline at end of file diff --git a/logs-mappers/src/main/kotlin/com/gitlab/sszuev/flashcards/logmappers/FromContext.kt b/logs-mappers/src/main/kotlin/com/gitlab/sszuev/flashcards/logmappers/FromContext.kt index 6ceda6b1..fb75ac75 100644 --- a/logs-mappers/src/main/kotlin/com/gitlab/sszuev/flashcards/logmappers/FromContext.kt +++ b/logs-mappers/src/main/kotlin/com/gitlab/sszuev/flashcards/logmappers/FromContext.kt @@ -13,9 +13,9 @@ import com.gitlab.sszuev.flashcards.logs.models.DictionaryLogResource import com.gitlab.sszuev.flashcards.logs.models.ErrorLogResource import com.gitlab.sszuev.flashcards.logs.models.LogResource import com.gitlab.sszuev.flashcards.logs.models.UserLogResource +import com.gitlab.sszuev.flashcards.model.common.AppAuthId import com.gitlab.sszuev.flashcards.model.common.AppContext import com.gitlab.sszuev.flashcards.model.common.AppError -import com.gitlab.sszuev.flashcards.model.common.AppUserEntity import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardFilter import com.gitlab.sszuev.flashcards.model.domain.CardId @@ -37,7 +37,7 @@ fun AppContext.toLogResource(logId: String) = LogResource( messageTime = Instant.now().toString(), logId = logId, requestId = this.requestId.asString(), - user = this.contextUserEntity.toLog(), + user = this.requestAppAuthId.toLog(), cards = if (this is CardContext) this.toLog() else null, dictionaries = if (this is DictionaryContext) this.toLog() else null, errors = this.errors.takeIf { it.isNotEmpty() }?.map { it.toLog() } @@ -103,9 +103,8 @@ private fun CardLearn.toLog() = CardLearnLogResource( details = this.details.mapKeys { it.key.name } ) -private fun AppUserEntity.toLog() = UserLogResource( - userId = id.asString(), - userUid = authId.asString(), +private fun AppAuthId.toLog() = UserLogResource( + userId = asString(), ) private fun AppError.toLog() = ErrorLogResource( diff --git a/specs/src/main/kotlin/Stubs.kt b/specs/src/main/kotlin/Stubs.kt index 19c99d67..5e7b8ce7 100644 --- a/specs/src/main/kotlin/Stubs.kt +++ b/specs/src/main/kotlin/Stubs.kt @@ -3,7 +3,6 @@ package com.gitlab.sszuev.flashcards.stubs import com.gitlab.sszuev.flashcards.model.common.AppAuthId import com.gitlab.sszuev.flashcards.model.common.AppError import com.gitlab.sszuev.flashcards.model.common.AppStub -import com.gitlab.sszuev.flashcards.model.common.AppUserId import com.gitlab.sszuev.flashcards.model.domain.CardEntity import com.gitlab.sszuev.flashcards.model.domain.CardId import com.gitlab.sszuev.flashcards.model.domain.CardLearn