From d0bc6821d2ae6bbb09a96877c3ba0625d78971a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=92=D0=B5=D0=B4=D0=B5=D0=BD=D1=91=D0=B2?= Date: Mon, 17 Mar 2025 22:26:48 +0700 Subject: [PATCH] module 5 lesson 1 --- gradle/libs.versions.toml | 93 +++++++++++-- ok-lessons/m2l2-coroutines/build.gradle.kts | 2 +- ok-messenger-be/build.gradle.kts | 1 + .../ok-messenger-api-log-v1/build.gradle.kts | 58 +++++++++ .../src/main/kotlin/ContextToLog.kt | 47 +++++++ .../src/main/kotlin/MappersV1FromTransport.kt | 14 +- .../src/main/kotlin/MappersV1ToTransport.kt | 26 +++- .../src/test/kotlin/MapperCreateTest.kt | 6 +- .../src/test/kotlin/MapperDeleteTest.kt | 6 +- .../src/test/kotlin/MapperReadTest.kt | 6 +- .../src/test/kotlin/MapperSearchTest.kt | 6 +- .../src/test/kotlin/MapperUpdateTest.kt | 6 +- .../ok-messenger-api-v1/build.gradle.kts | 5 +- ok-messenger-be/ok-messenger-app/README.md | 24 ++++ .../ok-messenger-app/build.gradle.kts | 55 ++++++++ .../src/main/kotlin/Application.kt | 33 +++++ .../ok-messenger-app/src/main/kotlin/HTTP.kt | 10 ++ .../src/main/kotlin/Monitoring.kt | 11 ++ .../src/main/kotlin/Routing.kt | 29 +++++ .../src/main/kotlin/Serialization.kt | 15 +++ .../src/main/kotlin/base/KtorWsSessionRepo.kt | 23 ++++ .../src/main/kotlin/base/KtorWsSessionV1.kt | 16 +++ .../main/kotlin/common/ControllerHelper.kt | 50 +++++++ .../kotlin/common/MessengerAppSettings.kt | 9 ++ .../kotlin/common/MessengerAppSettingsData.kt | 10 ++ .../kotlin/plugins/GetLoggerProviderConf.kt | 7 + .../main/kotlin/plugins/InitAppSettings.kt | 18 +++ .../src/main/kotlin/v1/ChatController.kt | 22 ++++ .../src/main/kotlin/v1/ControllerHelperV1.kt | 25 ++++ .../src/main/kotlin/v1/Routing.kt | 21 +++ .../src/main/kotlin/v1/WsController.kt | 71 ++++++++++ .../src/main/resources/application.yaml | 10 ++ .../src/main/resources/logback.xml | 122 ++++++++++++++++++ .../src/test/kotlin/ApplicationTest.kt | 21 +++ .../src/test/kotlin/common/ControllerTest.kt | 64 +++++++++ .../src/test/kotlin/stub/V1StubApiTest.kt | 2 + .../kotlin/websocket/V1WebsocketStubTest.kt | 119 +++++++++++++++++ .../ok-messenger-biz/build.gradle.kts | 11 ++ .../src/main/kotlin/MessengerProcessor.kt | 17 +++ .../ok-messenger-common/build.gradle.kts | 1 + .../{ChatContext.kt => MessengerContext.kt} | 6 +- .../commonMain/kotlin/MessengerCorSettings.kt | 13 ++ .../kotlin/helpers/MessengerErrorHelper.kt | 15 +++ .../commonMain/kotlin/models/ChatCommand.kt | 2 + .../src/commonMain/kotlin/models/ChatError.kt | 3 + .../kotlin/models/ChatSearchFilter.kt | 27 +++- .../kotlin/ws/IMessengerWsSession.kt | 12 ++ .../kotlin/ws/IMessengerWsSessionRepo.kt | 17 +++ .../ok-messenger-stubs/build.gradle.kts | 12 ++ .../src/main/kotlin/MessengerChatStub.kt | 22 ++++ .../main/kotlin/MessengerChatStubSample.kt | 56 ++++++++ ok-messenger-be/settings.gradle.kts | 4 + ok-messenger-libs/build.gradle.kts | 31 +++++ ok-messenger-libs/gradle.properties | 3 + .../ok-messenger-lib-logging/build.gradle.kts | 16 +++ .../src/main/kotlin/DefaultMarker.kt | 31 +++++ .../src/main/kotlin/LogWrapperLogback.kt | 87 +++++++++++++ .../src/main/kotlin/LoggerLogback.kt | 21 +++ .../src/main/kotlin/common/ILogWrapper.kt | 113 ++++++++++++++++ .../src/main/kotlin/common/LogLevel.kt | 24 ++++ .../src/main/kotlin/common/LoggerProvider.kt | 37 ++++++ .../src/main/resources/logback.xml | 121 +++++++++++++++++ .../src/test/kotlin/LoggerTest.kt | 64 +++++++++ .../src/test/resources/logback-test.xml | 13 ++ ok-messenger-libs/settings.gradle.kts | 26 ++++ settings.gradle.kts | 2 + specs/specs-log-v1.yaml | 110 ++++++++++++++++ specs/specs-v1.yaml | 10 ++ 68 files changed, 1945 insertions(+), 45 deletions(-) create mode 100644 ok-messenger-be/ok-messenger-api-log-v1/build.gradle.kts create mode 100644 ok-messenger-be/ok-messenger-api-log-v1/src/main/kotlin/ContextToLog.kt create mode 100644 ok-messenger-be/ok-messenger-app/README.md create mode 100644 ok-messenger-be/ok-messenger-app/build.gradle.kts create mode 100644 ok-messenger-be/ok-messenger-app/src/main/kotlin/Application.kt create mode 100644 ok-messenger-be/ok-messenger-app/src/main/kotlin/HTTP.kt create mode 100644 ok-messenger-be/ok-messenger-app/src/main/kotlin/Monitoring.kt create mode 100644 ok-messenger-be/ok-messenger-app/src/main/kotlin/Routing.kt create mode 100644 ok-messenger-be/ok-messenger-app/src/main/kotlin/Serialization.kt create mode 100644 ok-messenger-be/ok-messenger-app/src/main/kotlin/base/KtorWsSessionRepo.kt create mode 100644 ok-messenger-be/ok-messenger-app/src/main/kotlin/base/KtorWsSessionV1.kt create mode 100644 ok-messenger-be/ok-messenger-app/src/main/kotlin/common/ControllerHelper.kt create mode 100644 ok-messenger-be/ok-messenger-app/src/main/kotlin/common/MessengerAppSettings.kt create mode 100644 ok-messenger-be/ok-messenger-app/src/main/kotlin/common/MessengerAppSettingsData.kt create mode 100644 ok-messenger-be/ok-messenger-app/src/main/kotlin/plugins/GetLoggerProviderConf.kt create mode 100644 ok-messenger-be/ok-messenger-app/src/main/kotlin/plugins/InitAppSettings.kt create mode 100644 ok-messenger-be/ok-messenger-app/src/main/kotlin/v1/ChatController.kt create mode 100644 ok-messenger-be/ok-messenger-app/src/main/kotlin/v1/ControllerHelperV1.kt create mode 100644 ok-messenger-be/ok-messenger-app/src/main/kotlin/v1/Routing.kt create mode 100644 ok-messenger-be/ok-messenger-app/src/main/kotlin/v1/WsController.kt create mode 100644 ok-messenger-be/ok-messenger-app/src/main/resources/application.yaml create mode 100644 ok-messenger-be/ok-messenger-app/src/main/resources/logback.xml create mode 100644 ok-messenger-be/ok-messenger-app/src/test/kotlin/ApplicationTest.kt create mode 100644 ok-messenger-be/ok-messenger-app/src/test/kotlin/common/ControllerTest.kt create mode 100644 ok-messenger-be/ok-messenger-app/src/test/kotlin/stub/V1StubApiTest.kt create mode 100644 ok-messenger-be/ok-messenger-app/src/test/kotlin/websocket/V1WebsocketStubTest.kt create mode 100644 ok-messenger-be/ok-messenger-biz/build.gradle.kts create mode 100644 ok-messenger-be/ok-messenger-biz/src/main/kotlin/MessengerProcessor.kt rename ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/{ChatContext.kt => MessengerContext.kt} (74%) create mode 100644 ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/MessengerCorSettings.kt create mode 100644 ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/helpers/MessengerErrorHelper.kt create mode 100644 ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/ws/IMessengerWsSession.kt create mode 100644 ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/ws/IMessengerWsSessionRepo.kt create mode 100644 ok-messenger-be/ok-messenger-stubs/build.gradle.kts create mode 100644 ok-messenger-be/ok-messenger-stubs/src/main/kotlin/MessengerChatStub.kt create mode 100644 ok-messenger-be/ok-messenger-stubs/src/main/kotlin/MessengerChatStubSample.kt create mode 100644 ok-messenger-libs/build.gradle.kts create mode 100644 ok-messenger-libs/gradle.properties create mode 100644 ok-messenger-libs/ok-messenger-lib-logging/build.gradle.kts create mode 100644 ok-messenger-libs/ok-messenger-lib-logging/src/main/kotlin/DefaultMarker.kt create mode 100644 ok-messenger-libs/ok-messenger-lib-logging/src/main/kotlin/LogWrapperLogback.kt create mode 100644 ok-messenger-libs/ok-messenger-lib-logging/src/main/kotlin/LoggerLogback.kt create mode 100644 ok-messenger-libs/ok-messenger-lib-logging/src/main/kotlin/common/ILogWrapper.kt create mode 100644 ok-messenger-libs/ok-messenger-lib-logging/src/main/kotlin/common/LogLevel.kt create mode 100644 ok-messenger-libs/ok-messenger-lib-logging/src/main/kotlin/common/LoggerProvider.kt create mode 100644 ok-messenger-libs/ok-messenger-lib-logging/src/main/resources/logback.xml create mode 100644 ok-messenger-libs/ok-messenger-lib-logging/src/test/kotlin/LoggerTest.kt create mode 100644 ok-messenger-libs/ok-messenger-lib-logging/src/test/resources/logback-test.xml create mode 100644 ok-messenger-libs/settings.gradle.kts create mode 100644 specs/specs-log-v1.yaml diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 45740c2..892116e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,13 +1,36 @@ [versions] kotlin = "2.1.0" kotlinx-coroutines = "1.9.0" -kotlinx-datetime = "0.6.1" +kotlinx-datetime = "0.6.2" kotlinx-serialization = "1.6.3" -okhhtp = "4.12.0" -jackson-module = "2.18.2" + +# Http +okhttp = "4.12.0" + +# Serialization +jackson = "2.18.2" + +# Spec generator +openapi-generator = "7.11.0" + +# Logging slf4j = "2.0.9" -logback-classic = "1.4.11" -openapi-generator = "7.3.0" +logback = "1.5.3" +logback-appender = "1.8.8" +kermit = "2.0.3" +logstash = "7.4" +fluentd = "0.3.4" + +# Framework +ktor = "3.0.3" + +#Testing +testcontainers = "1.19.7" +kotest = "6.0.0.M2" +mockito = "5.2.1" + +# Docker +muschko = "9.4.0" # BASE jvm-compiler = "17" @@ -20,17 +43,69 @@ java-gradle-plugin = { id = "java-gradle-plugin" } openapi-generator = { id = "org.openapi.generator", version.ref = "openapi-generator" } kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +# Ktor +ktor = { id = "io.ktor.plugin", version.ref = "ktor" } + +# Docker +muschko-remote = { id = "com.bmuschko.docker-remote-api", version.ref = "muschko" } +muschko-java = { id = "com.bmuschko.docker-java-application", version.ref = "muschko" } + +[bundles] +kotest = ["kotest-junit5", "kotest-core", "kotest-property"] + [libraries] kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } kotlin-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" } kotlin-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" } -kotlin-jackson-module = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson-module" } -kotlin-jackson-datatype = { module = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310", version.ref = "jackson-module" } kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinx-serialization" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } -okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhhtp" } + +jackson-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" } +jackson-datatype = { module = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310", version.ref = "jackson" } +jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" } + +okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } + +# Ktor +ktor-network = { module = "io.ktor:ktor-network", version.ref = "ktor" } +ktor-serialization-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } +ktor-serialization-jackson = { module = "io.ktor:ktor-serialization-jackson", version.ref = "ktor" } +ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } +ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" } +ktor-client-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" } +ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" } +ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" } +ktor-server-headers-response = { module = "io.ktor:ktor-server-auto-head-response", version.ref = "ktor" } +ktor-server-headers-caching = { module = "io.ktor:ktor-server-caching-headers", version.ref = "ktor" } +ktor-server-headers-default = { module = "io.ktor:ktor-server-default-headers", version.ref = "ktor" } +ktor-server-cors = { module = "io.ktor:ktor-server-cors", version.ref = "ktor" } +ktor-server-yaml = { module = "io.ktor:ktor-server-config-yaml", version.ref = "ktor" } +ktor-server-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" } +ktor-server-calllogging = { module = "io.ktor:ktor-server-call-logging", version.ref = "ktor" } +ktor-server-websocket = { module = "io.ktor:ktor-server-websockets", version.ref = "ktor" } +ktor-server-test = { module = "io.ktor:ktor-server-test-host", version.ref = "ktor" } + +# Message Queues +rabbitmq-client = { module = "com.rabbitmq:amqp-client", version = "5.20.0" } +kafka-client = { module = "org.apache.kafka:kafka-clients", version = "3.7.0" } + +# Testing +kotest-junit5 = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" } +kotest-core = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" } +kotest-property = { module = "io.kotest:kotest-property", version.ref = "kotest" } +mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "mockito" } + +testcontainers-core = { module = "org.testcontainers:testcontainers", version.ref = "testcontainers" } +testcontainers-rabbitmq = { module = "org.testcontainers:rabbitmq", version.ref = "testcontainers" } + +# Logging slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } -logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback-classic" } +logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } +logback-appenders = { module = "com.sndyuk:logback-more-appenders", version.ref = "logback-appender" } +logback-logstash = { module = "net.logstash.logback:logstash-logback-encoder", version.ref = "logstash" } +kermit = { module = "co.touchlab:kermit", version.ref = "kermit" } +logger-fluentd = { module = "org.fluentd:fluent-logger", version.ref = "fluentd" } + plugin-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } plugin-binaryCompatibilityValidator = "org.jetbrains.kotlinx:binary-compatibility-validator:0.13.2" \ No newline at end of file diff --git a/ok-lessons/m2l2-coroutines/build.gradle.kts b/ok-lessons/m2l2-coroutines/build.gradle.kts index 30d1dd4..cb73609 100644 --- a/ok-lessons/m2l2-coroutines/build.gradle.kts +++ b/ok-lessons/m2l2-coroutines/build.gradle.kts @@ -14,7 +14,7 @@ dependencies { // Homework Hard implementation(libs.okhttp) // http client - implementation(libs.kotlin.jackson.module) // from string to object + implementation(libs.jackson.kotlin) // from string to object testImplementation(kotlin("test")) } diff --git a/ok-messenger-be/build.gradle.kts b/ok-messenger-be/build.gradle.kts index e07eb2b..0739f31 100644 --- a/ok-messenger-be/build.gradle.kts +++ b/ok-messenger-be/build.gradle.kts @@ -20,6 +20,7 @@ subprojects { ext { val specDir = layout.projectDirectory.dir("../specs") set("spec-v1", specDir.file("specs-v1.yaml").toString()) + set("spec-log-v1", specDir.file("specs-log-v1.yaml").toString()) } tasks { diff --git a/ok-messenger-be/ok-messenger-api-log-v1/build.gradle.kts b/ok-messenger-be/ok-messenger-api-log-v1/build.gradle.kts new file mode 100644 index 0000000..0e7c220 --- /dev/null +++ b/ok-messenger-be/ok-messenger-api-log-v1/build.gradle.kts @@ -0,0 +1,58 @@ +plugins { + id("build-jvm") + alias(libs.plugins.openapi.generator) +} + +sourceSets { + main { + java.srcDir(layout.buildDirectory.dir("generate-resources/main/src/main/kotlin")) + } +} + +openApiGenerate { + val openapiGroup = "${rootProject.group}.api.log.v1" + generatorName.set("kotlin") + packageName.set(openapiGroup) + apiPackage.set("$openapiGroup.api") + modelPackage.set("$openapiGroup.models") + inputSpec.set(rootProject.ext["spec-log-v1"] as String) + + /** + * Use only models + * Doc: https://openapi-generator.tech/docs/globals + */ + globalProperties.apply { + put("models", "") + put("modelDocs", "false") + } + + /** + * Additional parameters from + * https://github.com/OpenAPITools/openapi-generator/blob/master/docs/generators/kotlin.md + */ + configOptions.set( + mapOf( + "dateLibrary" to "string", + "enumPropertyNaming" to "UPPERCASE", + "serializationLibrary" to "jackson", + "collectionType" to "list" + ) + ) +} + +dependencies { + implementation(kotlin("stdlib")) + implementation(libs.kotlin.datetime) + implementation(libs.kotlinx.serialization.json) + implementation(libs.jackson.kotlin) + implementation(libs.jackson.datatype) + implementation(project(":ok-messenger-common")) + + testImplementation(kotlin("test-junit")) +} + +tasks { + filter { it.name.startsWith("compile") }.forEach { + it.dependsOn(openApiGenerate) + } +} \ No newline at end of file diff --git a/ok-messenger-be/ok-messenger-api-log-v1/src/main/kotlin/ContextToLog.kt b/ok-messenger-be/ok-messenger-api-log-v1/src/main/kotlin/ContextToLog.kt new file mode 100644 index 0000000..8667f36 --- /dev/null +++ b/ok-messenger-be/ok-messenger-api-log-v1/src/main/kotlin/ContextToLog.kt @@ -0,0 +1,47 @@ +package ru.otus.messenger.api.log.v1.mapper + +import kotlinx.datetime.Clock +import ru.otus.messenger.api.log.v1.models.* +import ru.otus.messenger.common.MessengerContext +import ru.otus.messenger.common.models.* + +fun MessengerContext.toLog(logId: String) = CommonLogModel( + messageTime = Clock.System.now().toString(), + logId = logId, + source = "ok-messenger", + chat = toChatLog(), + errors = errors.map { it.toLog() }, +) + +private fun MessengerContext.toChatLog(): ChatLogModel? { + val emptyReport = MessengerChat() + return ChatLogModel( + requestId = requestId.takeIf { it != RequestId.NONE }?.asString(), + requestChat = chatRequest.takeIf { it != emptyReport }?.toLog(), + requestSearch = chatFilterRequest.takeIf { it != ChatSearchFilter.NONE }?.toLog(), + responseChat = chatResponse.takeIf { it != emptyReport }?.toLog(), + responseChats = chatsResponse.takeIf { it.isNotEmpty() }?.filter { it != emptyReport }?.map { it.toLog() }, + ).takeIf { it != ChatLogModel() } +} + +private fun ChatSearchFilter.toLog() = ChatSearchLog( + searchFields = searchFields.joinToString("\t") { it.toString() }, +) + +private fun ChatError.toLog() = ErrorLogModel( + message = message.takeIf { it.isNotBlank() }, + field = field.takeIf { it.isNotBlank() }, + code = code.takeIf { it.isNotBlank() }, + level = level.name, +) + +private fun MessengerChat.toLog() = ChatLog( + chatId = id.takeIf { it != ChatId.NONE }?.asString(), + title = title.takeIf { it.isNotBlank() }, + description = description.takeIf { it.isNotBlank() }, + type = type.takeIf { it != ChatType.NONE }.toString(), + mode = mode.takeIf { it != ChatMode.NONE }.toString(), + ownerId = ownerId.takeIf { it != ChatOwnerId.NONE }?.asString(), + participants = participants.takeIf { it.isNotEmpty() }?.map { it.asString() }?.toSet(), + metadata = metadata.takeIf { it != ChatMetadata.NONE }?.asString() +) diff --git a/ok-messenger-be/ok-messenger-api-v1-mappers/src/main/kotlin/MappersV1FromTransport.kt b/ok-messenger-be/ok-messenger-api-v1-mappers/src/main/kotlin/MappersV1FromTransport.kt index 8eb6c77..e2330ae 100644 --- a/ok-messenger-be/ok-messenger-api-v1-mappers/src/main/kotlin/MappersV1FromTransport.kt +++ b/ok-messenger-be/ok-messenger-api-v1-mappers/src/main/kotlin/MappersV1FromTransport.kt @@ -5,10 +5,10 @@ import kotlinx.serialization.json.jsonObject import ru.otus.messenger.api.v1.mappers.exceptions.UnknownRequestClass import ru.otus.messenger.api.v1.models.* import ru.otus.messenger.common.models.* -import ru.otus.messenger.common.ChatContext +import ru.otus.messenger.common.MessengerContext import ru.otus.messenger.common.stubs.Stubs -fun ChatContext.fromTransport(request: IRequest) = when (request) { +fun MessengerContext.fromTransport(request: IRequest) = when (request) { is ChatCreateRequest -> fromTransport(request) is ChatReadRequest -> fromTransport(request) is ChatDeleteRequest -> fromTransport(request) @@ -40,35 +40,35 @@ private fun Debug?.transportToStubCase(): Stubs = when (this?.stub) { null -> Stubs.NONE } -fun ChatContext.fromTransport(request: ChatCreateRequest) { +fun MessengerContext.fromTransport(request: ChatCreateRequest) { command = ChatCommand.CREATE chatRequest = request.chat?.toInternal() ?: MessengerChat() workMode = request.debug.transportToWorkMode() stubCase = request.debug.transportToStubCase() } -fun ChatContext.fromTransport(request: ChatReadRequest) { +fun MessengerContext.fromTransport(request: ChatReadRequest) { command = ChatCommand.READ chatRequest = request.chatId.toChatId().toInternal() workMode = request.debug.transportToWorkMode() stubCase = request.debug.transportToStubCase() } -fun ChatContext.fromTransport(request: ChatDeleteRequest) { +fun MessengerContext.fromTransport(request: ChatDeleteRequest) { command = ChatCommand.DELETE chatRequest = request.chatId.toChatId().toInternal() workMode = request.debug.transportToWorkMode() stubCase = request.debug.transportToStubCase() } -fun ChatContext.fromTransport(request: ChatUpdateRequest) { +fun MessengerContext.fromTransport(request: ChatUpdateRequest) { command = ChatCommand.UPDATE chatRequest = request.chat?.toInternal() ?: MessengerChat() workMode = request.debug.transportToWorkMode() stubCase = request.debug.transportToStubCase() } -fun ChatContext.fromTransport(request: ChatSearchRequest) { +fun MessengerContext.fromTransport(request: ChatSearchRequest) { command = ChatCommand.SEARCH chatRequest = request.criteria?.toInternal() ?: MessengerChat() workMode = request.debug.transportToWorkMode() diff --git a/ok-messenger-be/ok-messenger-api-v1-mappers/src/main/kotlin/MappersV1ToTransport.kt b/ok-messenger-be/ok-messenger-api-v1-mappers/src/main/kotlin/MappersV1ToTransport.kt index 00d092a..a3dc21d 100644 --- a/ok-messenger-be/ok-messenger-api-v1-mappers/src/main/kotlin/MappersV1ToTransport.kt +++ b/ok-messenger-be/ok-messenger-api-v1-mappers/src/main/kotlin/MappersV1ToTransport.kt @@ -3,44 +3,56 @@ package ru.otus.messenger.api.v1.mappers import kotlinx.datetime.Instant import ru.otus.messenger.api.v1.models.* import ru.otus.messenger.common.models.* -import ru.otus.messenger.common.ChatContext +import ru.otus.messenger.common.MessengerContext import ru.otus.messenger.common.NONE import ru.otus.messenger.common.exceptions.UnknownChatCommand -fun ChatContext.toTransportChat(): IResponse = when (val cmd = command) { +fun MessengerContext.toTransportChat(): IResponse = when (val cmd = command) { ChatCommand.CREATE -> toTransportCreate() ChatCommand.READ -> toTransportRead() ChatCommand.DELETE -> toTransportDelete() ChatCommand.SEARCH -> toTransportSearch() ChatCommand.UPDATE -> toTransportUpdate() + ChatCommand.INIT -> toTransportInit() + ChatCommand.FINISH -> toTransportFinish() ChatCommand.NONE -> throw UnknownChatCommand(cmd) } -fun ChatContext.toTransportCreate() = ChatCreateResponse( +fun MessengerContext.toTransportInit() = ChatInitResponse( + result = state.toResult(), + errors = errors.toTransportErrors(), +) + +fun MessengerContext.toTransportFinish() = ChatInitResponse( + result = state.toResult(), + errors = errors.toTransportErrors(), +) + +fun MessengerContext.toTransportCreate() = ChatCreateResponse( result = state.toResult(), errors = errors.toTransportErrors(), chat = chatResponse.toTransportChat() ) -fun ChatContext.toTransportRead() = ChatReadResponse( +fun MessengerContext.toTransportRead() = ChatReadResponse( result = state.toResult(), errors = errors.toTransportErrors(), chat = chatResponse.toTransportChat() ) -fun ChatContext.toTransportDelete() = ChatDeleteResponse( +fun MessengerContext.toTransportDelete() = ChatDeleteResponse( result = state.toResult(), errors = errors.toTransportErrors(), ) -fun ChatContext.toTransportSearch() = ChatSearchResponse( +fun MessengerContext.toTransportSearch() = ChatSearchResponse( result = state.toResult(), errors = errors.toTransportErrors(), chats = chatsResponse.toTransportChats() ) -fun ChatContext.toTransportUpdate() = ChatUpdateResponse( +fun MessengerContext.toTransportUpdate() = ChatUpdateResponse( result = state.toResult(), errors = errors.toTransportErrors(), chat = chatResponse.toTransportChat() diff --git a/ok-messenger-be/ok-messenger-api-v1-mappers/src/test/kotlin/MapperCreateTest.kt b/ok-messenger-be/ok-messenger-api-v1-mappers/src/test/kotlin/MapperCreateTest.kt index 56537af..ff1c06c 100644 --- a/ok-messenger-be/ok-messenger-api-v1-mappers/src/test/kotlin/MapperCreateTest.kt +++ b/ok-messenger-be/ok-messenger-api-v1-mappers/src/test/kotlin/MapperCreateTest.kt @@ -10,7 +10,7 @@ import ru.otus.messenger.api.v1.models.ChatCreateResponse import ru.otus.messenger.api.v1.models.Debug import ru.otus.messenger.api.v1.models.DebugMode import ru.otus.messenger.api.v1.models.DebugStubs -import ru.otus.messenger.common.ChatContext +import ru.otus.messenger.common.MessengerContext import ru.otus.messenger.common.models.ChatCommand import ru.otus.messenger.common.models.ChatError import ru.otus.messenger.common.models.ChatMode @@ -39,7 +39,7 @@ class MapperCreateTest { ) ) - val context = ChatContext() + val context = MessengerContext() context.fromTransport(req) assertEquals(Stubs.SUCCESS, context.stubCase) @@ -51,7 +51,7 @@ class MapperCreateTest { @Test fun toTransport() { - val context = ChatContext( + val context = MessengerContext( requestId = RequestId(UUID.randomUUID().toString()), command = ChatCommand.CREATE, state = ChatState.RUNNING, diff --git a/ok-messenger-be/ok-messenger-api-v1-mappers/src/test/kotlin/MapperDeleteTest.kt b/ok-messenger-be/ok-messenger-api-v1-mappers/src/test/kotlin/MapperDeleteTest.kt index 4c79bf5..c2a5beb 100644 --- a/ok-messenger-be/ok-messenger-api-v1-mappers/src/test/kotlin/MapperDeleteTest.kt +++ b/ok-messenger-be/ok-messenger-api-v1-mappers/src/test/kotlin/MapperDeleteTest.kt @@ -9,7 +9,7 @@ import ru.otus.messenger.api.v1.models.Debug import ru.otus.messenger.api.v1.models.DebugMode import ru.otus.messenger.api.v1.models.DebugStubs import ru.otus.messenger.api.v1.models.ResponseResult -import ru.otus.messenger.common.ChatContext +import ru.otus.messenger.common.MessengerContext import ru.otus.messenger.common.models.ChatCommand import ru.otus.messenger.common.models.ChatError import ru.otus.messenger.common.models.ChatMode @@ -30,7 +30,7 @@ class MapperDeleteTest { chatId = "chat-id", ) - val context = ChatContext() + val context = MessengerContext() context.fromTransport(req) assertEquals(Stubs.SUCCESS, context.stubCase) @@ -43,7 +43,7 @@ class MapperDeleteTest { @Test fun toTransport() { - val context = ChatContext( + val context = MessengerContext( requestId = RequestId(UUID.randomUUID().toString()), command = ChatCommand.DELETE, state = ChatState.RUNNING, diff --git a/ok-messenger-be/ok-messenger-api-v1-mappers/src/test/kotlin/MapperReadTest.kt b/ok-messenger-be/ok-messenger-api-v1-mappers/src/test/kotlin/MapperReadTest.kt index fe88c83..9ba1b12 100644 --- a/ok-messenger-be/ok-messenger-api-v1-mappers/src/test/kotlin/MapperReadTest.kt +++ b/ok-messenger-be/ok-messenger-api-v1-mappers/src/test/kotlin/MapperReadTest.kt @@ -9,7 +9,7 @@ import ru.otus.messenger.api.v1.models.ChatReadResponse import ru.otus.messenger.api.v1.models.Debug import ru.otus.messenger.api.v1.models.DebugMode import ru.otus.messenger.api.v1.models.DebugStubs -import ru.otus.messenger.common.ChatContext +import ru.otus.messenger.common.MessengerContext import ru.otus.messenger.common.models.ChatCommand import ru.otus.messenger.common.models.ChatError import ru.otus.messenger.common.models.ChatMode @@ -32,7 +32,7 @@ class MapperReadTest { chatId = "chat-id", ) - val context = ChatContext() + val context = MessengerContext() context.fromTransport(req) assertEquals(Stubs.SUCCESS, context.stubCase) @@ -45,7 +45,7 @@ class MapperReadTest { @Test fun toTransport() { - val context = ChatContext( + val context = MessengerContext( requestId = RequestId(UUID.randomUUID().toString()), command = ChatCommand.READ, state = ChatState.RUNNING, diff --git a/ok-messenger-be/ok-messenger-api-v1-mappers/src/test/kotlin/MapperSearchTest.kt b/ok-messenger-be/ok-messenger-api-v1-mappers/src/test/kotlin/MapperSearchTest.kt index 3ac215a..f1ef0c5 100644 --- a/ok-messenger-be/ok-messenger-api-v1-mappers/src/test/kotlin/MapperSearchTest.kt +++ b/ok-messenger-be/ok-messenger-api-v1-mappers/src/test/kotlin/MapperSearchTest.kt @@ -10,7 +10,7 @@ import ru.otus.messenger.api.v1.models.ChatSearchResponse import ru.otus.messenger.api.v1.models.Debug import ru.otus.messenger.api.v1.models.DebugMode import ru.otus.messenger.api.v1.models.DebugStubs -import ru.otus.messenger.common.ChatContext +import ru.otus.messenger.common.MessengerContext import ru.otus.messenger.common.models.ChatCommand import ru.otus.messenger.common.models.ChatError import ru.otus.messenger.common.models.ChatMode @@ -38,7 +38,7 @@ class MapperSearchTest { ) ) - val context = ChatContext() + val context = MessengerContext() context.fromTransport(req) assertEquals(Stubs.SUCCESS, context.stubCase) @@ -50,7 +50,7 @@ class MapperSearchTest { @Test fun toTransport() { - val context = ChatContext( + val context = MessengerContext( requestId = RequestId(UUID.randomUUID().toString()), command = ChatCommand.SEARCH, state = ChatState.RUNNING, diff --git a/ok-messenger-be/ok-messenger-api-v1-mappers/src/test/kotlin/MapperUpdateTest.kt b/ok-messenger-be/ok-messenger-api-v1-mappers/src/test/kotlin/MapperUpdateTest.kt index 58dc8a8..1ec4671 100644 --- a/ok-messenger-be/ok-messenger-api-v1-mappers/src/test/kotlin/MapperUpdateTest.kt +++ b/ok-messenger-be/ok-messenger-api-v1-mappers/src/test/kotlin/MapperUpdateTest.kt @@ -10,7 +10,7 @@ import ru.otus.messenger.api.v1.models.ChatUpdateResponse import ru.otus.messenger.api.v1.models.Debug import ru.otus.messenger.api.v1.models.DebugMode import ru.otus.messenger.api.v1.models.DebugStubs -import ru.otus.messenger.common.ChatContext +import ru.otus.messenger.common.MessengerContext import ru.otus.messenger.common.models.ChatCommand import ru.otus.messenger.common.models.ChatError import ru.otus.messenger.common.models.ChatMode @@ -44,7 +44,7 @@ class MapperUpdateTest { ) ) - val context = ChatContext() + val context = MessengerContext() context.fromTransport(req) assertEquals(Stubs.SUCCESS, context.stubCase) @@ -57,7 +57,7 @@ class MapperUpdateTest { @Test fun toTransport() { - val context = ChatContext( + val context = MessengerContext( requestId = RequestId(UUID.randomUUID().toString()), command = ChatCommand.UPDATE, state = ChatState.RUNNING, diff --git a/ok-messenger-be/ok-messenger-api-v1/build.gradle.kts b/ok-messenger-be/ok-messenger-api-v1/build.gradle.kts index 1fa9fda..2eb8c53 100644 --- a/ok-messenger-be/ok-messenger-api-v1/build.gradle.kts +++ b/ok-messenger-be/ok-messenger-api-v1/build.gradle.kts @@ -44,8 +44,9 @@ openApiGenerate { dependencies { implementation(kotlin("stdlib")) - implementation(libs.kotlin.jackson.module) - implementation(libs.kotlin.jackson.datatype) + implementation(libs.kotlin.datetime) + implementation(libs.jackson.kotlin) + implementation(libs.jackson.datatype) testImplementation(kotlin("test-junit")) } diff --git a/ok-messenger-be/ok-messenger-app/README.md b/ok-messenger-be/ok-messenger-app/README.md new file mode 100644 index 0000000..ae4a721 --- /dev/null +++ b/ok-messenger-be/ok-messenger-app/README.md @@ -0,0 +1,24 @@ +# Модуль ok-messenger-app + +Ktor server application + +## Building & Running + +To build or run the project, use one of the following tasks: + +| Task | Description | +|-------------------------------|----------------------------------------------------------------------| +| `./gradlew test` | Run the tests | +| `./gradlew build` | Build everything | +| `buildFatJar` | Build an executable JAR of the server with all dependencies included | +| `buildImage` | Build the docker image to use with the fat JAR | +| `publishImageToLocalRegistry` | Publish the docker image locally | +| `run` | Run the server | +| `runDocker` | Run using the local docker image | + +If the server starts successfully, you'll see the following output: + +``` +2024-12-04 14:32:45.584 [main] INFO Application - Application started in 0.303 seconds. +2024-12-04 14:32:45.682 [main] INFO Application - Responding at http://0.0.0.0:8080 +``` \ No newline at end of file diff --git a/ok-messenger-be/ok-messenger-app/build.gradle.kts b/ok-messenger-be/ok-messenger-app/build.gradle.kts new file mode 100644 index 0000000..ca29afe --- /dev/null +++ b/ok-messenger-be/ok-messenger-app/build.gradle.kts @@ -0,0 +1,55 @@ +plugins { + id("build-jvm") + alias(libs.plugins.ktor) + alias(libs.plugins.muschko.remote) +} + +application { + mainClass.set("io.ktor.server.cio.EngineMain") +} + +ktor { + docker { + localImageName.set(project.name) + imageTag.set(project.version.toString()) + jreVersion.set(JavaVersion.VERSION_21) + } +} + +jib { + container.mainClass = application.mainClass.get() +} + +dependencies { + implementation(kotlin("stdlib")) + implementation(libs.ktor.server.core) + implementation(libs.ktor.server.netty) + implementation(libs.ktor.server.cors) + implementation(libs.ktor.server.yaml) + implementation(libs.ktor.server.negotiation) + implementation(libs.ktor.server.headers.default) + implementation(libs.ktor.server.headers.response) + implementation(libs.ktor.server.headers.caching) + implementation(libs.ktor.serialization.jackson) + implementation(libs.ktor.server.calllogging) + implementation(libs.ktor.server.websocket) + + // transport models + implementation(project(":ok-messenger-common")) + implementation(project(":ok-messenger-api-v1")) + implementation(project(":ok-messenger-api-v1-mappers")) + + // logic + implementation(project(":ok-messenger-biz")) + + // stubs + implementation(project(":ok-messenger-stubs")) + + // logging + implementation(project(":ok-messenger-api-log-v1")) + implementation("ru.otus.messenger.libs:ok-messenger-lib-logging") + + testImplementation(kotlin("test-junit")) + testImplementation(libs.ktor.server.test) + testImplementation(libs.ktor.client.negotiation) +} \ No newline at end of file diff --git a/ok-messenger-be/ok-messenger-app/src/main/kotlin/Application.kt b/ok-messenger-be/ok-messenger-app/src/main/kotlin/Application.kt new file mode 100644 index 0000000..3c11d06 --- /dev/null +++ b/ok-messenger-be/ok-messenger-app/src/main/kotlin/Application.kt @@ -0,0 +1,33 @@ +package ru.otus.messenger.app + +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.netty.EngineMain +import io.ktor.server.plugins.cors.routing.* +import ru.otus.messenger.app.common.MessengerAppSettings +import ru.otus.messenger.app.plugins.initAppSettings + +fun main(args: Array) = EngineMain.main(args) + +fun Application.module( + appSettings: MessengerAppSettings = initAppSettings(), +) { + install(CORS) { + allowMethod(HttpMethod.Options) + allowMethod(HttpMethod.Put) + allowMethod(HttpMethod.Delete) + allowMethod(HttpMethod.Patch) + allowHeader(HttpHeaders.Authorization) + allowHeader("MyCustomHeader") + allowCredentials = true + /* TODO + Это временное решение, оно опасно. + В боевом приложении здесь должны быть конкретные настройки + */ + anyHost() + } + configureHTTP() + configureSerialization() + + configureRouting(appSettings) +} \ No newline at end of file diff --git a/ok-messenger-be/ok-messenger-app/src/main/kotlin/HTTP.kt b/ok-messenger-be/ok-messenger-app/src/main/kotlin/HTTP.kt new file mode 100644 index 0000000..e01166a --- /dev/null +++ b/ok-messenger-be/ok-messenger-app/src/main/kotlin/HTTP.kt @@ -0,0 +1,10 @@ +package ru.otus.messenger.app + +import io.ktor.server.application.* +import io.ktor.server.plugins.cachingheaders.* +import io.ktor.server.plugins.defaultheaders.* + +fun Application.configureHTTP() { + install(CachingHeaders) + install(DefaultHeaders) +} \ No newline at end of file diff --git a/ok-messenger-be/ok-messenger-app/src/main/kotlin/Monitoring.kt b/ok-messenger-be/ok-messenger-app/src/main/kotlin/Monitoring.kt new file mode 100644 index 0000000..1313723 --- /dev/null +++ b/ok-messenger-be/ok-messenger-app/src/main/kotlin/Monitoring.kt @@ -0,0 +1,11 @@ +package ru.otus.messenger.app + +import io.ktor.server.application.* +import io.ktor.server.plugins.calllogging.* +import org.slf4j.event.Level + +fun Application.configureMonitoring() { + install(CallLogging) { + level = Level.INFO + } +} diff --git a/ok-messenger-be/ok-messenger-app/src/main/kotlin/Routing.kt b/ok-messenger-be/ok-messenger-app/src/main/kotlin/Routing.kt new file mode 100644 index 0000000..aa3a4d4 --- /dev/null +++ b/ok-messenger-be/ok-messenger-app/src/main/kotlin/Routing.kt @@ -0,0 +1,29 @@ +package ru.otus.messenger.app + +import io.ktor.server.application.* +import io.ktor.server.plugins.autohead.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import io.ktor.server.websocket.* +import ru.otus.messenger.app.common.MessengerAppSettings +import ru.otus.messenger.app.v1.v1Chat +import ru.otus.messenger.app.v1.wsHandlerV1 + +fun Application.configureRouting(appSettings: MessengerAppSettings) { + install(AutoHeadResponse) + install(WebSockets) + + routing { + get("/") { + call.respondText("Hello World!") + } + + route("v1") { + v1Chat(appSettings) + + webSocket("/ws") { + wsHandlerV1(appSettings) + } + } + } +} diff --git a/ok-messenger-be/ok-messenger-app/src/main/kotlin/Serialization.kt b/ok-messenger-be/ok-messenger-app/src/main/kotlin/Serialization.kt new file mode 100644 index 0000000..b28c857 --- /dev/null +++ b/ok-messenger-be/ok-messenger-app/src/main/kotlin/Serialization.kt @@ -0,0 +1,15 @@ +package ru.otus.messenger.app + +import io.ktor.serialization.jackson.* +import io.ktor.server.application.* +import io.ktor.server.plugins.contentnegotiation.* +import ru.otus.messenger.api.v1.apiV1Mapper + +fun Application.configureSerialization() { + install(ContentNegotiation) { + jackson { + setConfig(apiV1Mapper.serializationConfig) + setConfig(apiV1Mapper.deserializationConfig) + } + } +} \ No newline at end of file diff --git a/ok-messenger-be/ok-messenger-app/src/main/kotlin/base/KtorWsSessionRepo.kt b/ok-messenger-be/ok-messenger-app/src/main/kotlin/base/KtorWsSessionRepo.kt new file mode 100644 index 0000000..947a250 --- /dev/null +++ b/ok-messenger-be/ok-messenger-app/src/main/kotlin/base/KtorWsSessionRepo.kt @@ -0,0 +1,23 @@ +package ru.otus.messenger.app.base + +import ru.otus.messenger.common.ws.IMessengerWsSession +import ru.otus.messenger.common.ws.IMessengerWsSessionRepo + +class KtorWsSessionRepo(): IMessengerWsSessionRepo { + private val sessions: MutableSet = mutableSetOf() + override fun add(session: IMessengerWsSession) { + sessions.add(session) + } + + override fun clearAll() { + sessions.clear() + } + + override fun remove(session: IMessengerWsSession) { + sessions.remove(session) + } + + override suspend fun sendAll(obj: T) { + sessions.forEach { it.send(obj) } + } +} diff --git a/ok-messenger-be/ok-messenger-app/src/main/kotlin/base/KtorWsSessionV1.kt b/ok-messenger-be/ok-messenger-app/src/main/kotlin/base/KtorWsSessionV1.kt new file mode 100644 index 0000000..8ac1d51 --- /dev/null +++ b/ok-messenger-be/ok-messenger-app/src/main/kotlin/base/KtorWsSessionV1.kt @@ -0,0 +1,16 @@ +package ru.otus.messenger.app.base + +import io.ktor.websocket.Frame +import io.ktor.websocket.WebSocketSession +import ru.otus.messenger.api.v1.apiV1ResponseSerialize +import ru.otus.messenger.api.v1.models.IResponse +import ru.otus.messenger.common.ws.IMessengerWsSession + +data class KtorWsSessionV1( + private val session: WebSocketSession +) : IMessengerWsSession { + override suspend fun send(obj: T) { + require(obj is IResponse) + session.send(Frame.Text(apiV1ResponseSerialize(obj))) + } +} diff --git a/ok-messenger-be/ok-messenger-app/src/main/kotlin/common/ControllerHelper.kt b/ok-messenger-be/ok-messenger-app/src/main/kotlin/common/ControllerHelper.kt new file mode 100644 index 0000000..33ba7d2 --- /dev/null +++ b/ok-messenger-be/ok-messenger-app/src/main/kotlin/common/ControllerHelper.kt @@ -0,0 +1,50 @@ +package ru.otus.messenger.app.common + +import kotlinx.datetime.Clock +import ru.otus.messenger.api.log.v1.mapper.toLog +import ru.otus.messenger.common.MessengerContext +import ru.otus.messenger.common.helpers.asMessengerError +import ru.otus.messenger.common.models.ChatCommand +import ru.otus.messenger.common.models.ChatState +import kotlin.reflect.KClass + +suspend inline fun MessengerAppSettings.controllerHelper( + crossinline getRequest: suspend MessengerContext.() -> Unit, + crossinline toResponse: suspend MessengerContext.() -> T, + clazz: KClass<*>, + logId: String, +): T { + val logger = corSettings.loggerProvider.logger(clazz) + val ctx = MessengerContext( + timeStart = Clock.System.now(), + ) + return try { + ctx.getRequest() + logger.info( + msg = "Request $logId started for ${clazz.simpleName}", + marker = "BIZ", + data = ctx.toLog(logId) + ) + processor.exec(ctx) + logger.info( + msg = "Request $logId processed for ${clazz.simpleName}", + marker = "BIZ", + data = ctx.toLog(logId) + ) + ctx.toResponse() + } catch (e: Throwable) { + logger.error( + msg = "Request $logId failed for ${clazz.simpleName}", + marker = "BIZ", + data = ctx.toLog(logId), + e = e, + ) + ctx.state = ChatState.FAILING + ctx.errors.add(e.asMessengerError()) + processor.exec(ctx) + if (ctx.command == ChatCommand.NONE) { + ctx.command = ChatCommand.READ + } + ctx.toResponse() + } +} diff --git a/ok-messenger-be/ok-messenger-app/src/main/kotlin/common/MessengerAppSettings.kt b/ok-messenger-be/ok-messenger-app/src/main/kotlin/common/MessengerAppSettings.kt new file mode 100644 index 0000000..b7cd6bd --- /dev/null +++ b/ok-messenger-be/ok-messenger-app/src/main/kotlin/common/MessengerAppSettings.kt @@ -0,0 +1,9 @@ +package ru.otus.messenger.app.common + +import ru.otus.messenger.biz.MessengerProcessor +import ru.otus.messenger.common.MessengerCorSettings + +interface MessengerAppSettings { + val processor: MessengerProcessor + val corSettings: MessengerCorSettings +} \ No newline at end of file diff --git a/ok-messenger-be/ok-messenger-app/src/main/kotlin/common/MessengerAppSettingsData.kt b/ok-messenger-be/ok-messenger-app/src/main/kotlin/common/MessengerAppSettingsData.kt new file mode 100644 index 0000000..2003464 --- /dev/null +++ b/ok-messenger-be/ok-messenger-app/src/main/kotlin/common/MessengerAppSettingsData.kt @@ -0,0 +1,10 @@ +package ru.otus.messenger.app.common + +import ru.otus.messenger.biz.MessengerProcessor +import ru.otus.messenger.common.MessengerCorSettings + +data class MessengerAppSettingsData( + val appUrls: List = emptyList(), + override val corSettings: MessengerCorSettings = MessengerCorSettings(), + override val processor: MessengerProcessor = MessengerProcessor(corSettings), +): MessengerAppSettings diff --git a/ok-messenger-be/ok-messenger-app/src/main/kotlin/plugins/GetLoggerProviderConf.kt b/ok-messenger-be/ok-messenger-app/src/main/kotlin/plugins/GetLoggerProviderConf.kt new file mode 100644 index 0000000..b9911c7 --- /dev/null +++ b/ok-messenger-be/ok-messenger-app/src/main/kotlin/plugins/GetLoggerProviderConf.kt @@ -0,0 +1,7 @@ +package ru.otus.messenger.app.plugins + +import io.ktor.server.application.* +import ru.otus.messenger.logging.common.LoggerProvider +import ru.otus.messenger.logging.loggerLogback + +fun Application.getLoggerProviderConf(): LoggerProvider = LoggerProvider { loggerLogback(it) } \ No newline at end of file diff --git a/ok-messenger-be/ok-messenger-app/src/main/kotlin/plugins/InitAppSettings.kt b/ok-messenger-be/ok-messenger-app/src/main/kotlin/plugins/InitAppSettings.kt new file mode 100644 index 0000000..593e345 --- /dev/null +++ b/ok-messenger-be/ok-messenger-app/src/main/kotlin/plugins/InitAppSettings.kt @@ -0,0 +1,18 @@ +package ru.otus.messenger.app.plugins + +import io.ktor.server.application.* +import ru.otus.messenger.app.common.MessengerAppSettings +import ru.otus.messenger.app.common.MessengerAppSettingsData +import ru.otus.messenger.biz.MessengerProcessor +import ru.otus.messenger.common.MessengerCorSettings + +fun Application.initAppSettings(): MessengerAppSettings { + val corSettings = MessengerCorSettings( + loggerProvider = getLoggerProviderConf(), + ) + return MessengerAppSettingsData( + appUrls = environment.config.propertyOrNull("ktor.urls")?.getList() ?: emptyList(), + corSettings = corSettings, + processor = MessengerProcessor(corSettings), + ) +} \ No newline at end of file diff --git a/ok-messenger-be/ok-messenger-app/src/main/kotlin/v1/ChatController.kt b/ok-messenger-be/ok-messenger-app/src/main/kotlin/v1/ChatController.kt new file mode 100644 index 0000000..7018c6b --- /dev/null +++ b/ok-messenger-be/ok-messenger-app/src/main/kotlin/v1/ChatController.kt @@ -0,0 +1,22 @@ +package ru.otus.messenger.app.v1 + +import io.ktor.server.application.* +import ru.otus.messenger.api.v1.models.* +import ru.otus.messenger.app.common.MessengerAppSettings +import kotlin.reflect.KClass + +val clCreate: KClass<*> = ApplicationCall::createChat::class +suspend fun ApplicationCall.createChat(appSettings: MessengerAppSettings) = + processV1(appSettings, clCreate,"create") + +val clRead: KClass<*> = ApplicationCall::readChat::class +suspend fun ApplicationCall.readChat(appSettings: MessengerAppSettings) = + processV1(appSettings, clRead, "read") + +val clDelete: KClass<*> = ApplicationCall::deleteChat::class +suspend fun ApplicationCall.deleteChat(appSettings: MessengerAppSettings) = + processV1(appSettings, clDelete, "delete") + +val clSearch: KClass<*> = ApplicationCall::searchChat::class +suspend fun ApplicationCall.searchChat(appSettings: MessengerAppSettings) = + processV1(appSettings, clSearch, "search") diff --git a/ok-messenger-be/ok-messenger-app/src/main/kotlin/v1/ControllerHelperV1.kt b/ok-messenger-be/ok-messenger-app/src/main/kotlin/v1/ControllerHelperV1.kt new file mode 100644 index 0000000..d149ffb --- /dev/null +++ b/ok-messenger-be/ok-messenger-app/src/main/kotlin/v1/ControllerHelperV1.kt @@ -0,0 +1,25 @@ +package ru.otus.messenger.app.v1 + +import io.ktor.server.application.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import ru.otus.messenger.api.v1.models.IRequest +import ru.otus.messenger.api.v1.models.IResponse +import ru.otus.messenger.app.common.controllerHelper +import ru.otus.messenger.app.common.MessengerAppSettings +import ru.otus.messenger.api.v1.mappers.fromTransport +import ru.otus.messenger.api.v1.mappers.toTransportChat +import kotlin.reflect.KClass + +suspend inline fun ApplicationCall.processV1( + appSettings: MessengerAppSettings, + clazz: KClass<*>, + logId: String, +) = appSettings.controllerHelper( + { + fromTransport(receive()) + }, + { respond(toTransportChat()) }, + clazz, + logId, +) diff --git a/ok-messenger-be/ok-messenger-app/src/main/kotlin/v1/Routing.kt b/ok-messenger-be/ok-messenger-app/src/main/kotlin/v1/Routing.kt new file mode 100644 index 0000000..b41586d --- /dev/null +++ b/ok-messenger-be/ok-messenger-app/src/main/kotlin/v1/Routing.kt @@ -0,0 +1,21 @@ +package ru.otus.messenger.app.v1 + +import io.ktor.server.routing.* +import ru.otus.messenger.app.common.MessengerAppSettings + +fun Route.v1Chat(appSettings: MessengerAppSettings) { + route("chat") { + post("create") { + call.createChat(appSettings) + } + post("read") { + call.readChat(appSettings) + } + post("delete") { + call.deleteChat(appSettings) + } + post("search") { + call.searchChat(appSettings) + } + } +} \ No newline at end of file diff --git a/ok-messenger-be/ok-messenger-app/src/main/kotlin/v1/WsController.kt b/ok-messenger-be/ok-messenger-app/src/main/kotlin/v1/WsController.kt new file mode 100644 index 0000000..a02cfdb --- /dev/null +++ b/ok-messenger-be/ok-messenger-app/src/main/kotlin/v1/WsController.kt @@ -0,0 +1,71 @@ +package ru.otus.messenger.app.v1 + +import com.fasterxml.jackson.module.kotlin.readValue +import io.ktor.websocket.* +import kotlinx.coroutines.channels.ClosedReceiveChannelException +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.receiveAsFlow +import ru.otus.messenger.api.v1.apiV1Mapper +import ru.otus.messenger.api.v1.mappers.fromTransport +import ru.otus.messenger.api.v1.mappers.toTransportInit +import ru.otus.messenger.api.v1.mappers.toTransportChat +import ru.otus.messenger.api.v1.models.IRequest +import ru.otus.messenger.app.base.KtorWsSessionV1 +import ru.otus.messenger.app.common.MessengerAppSettings +import ru.otus.messenger.app.common.controllerHelper +import ru.otus.messenger.common.models.ChatCommand +import kotlin.reflect.KClass + +private val clWsV1: KClass<*> = WebSocketSession::wsHandlerV1::class +suspend fun WebSocketSession.wsHandlerV1(appSettings: MessengerAppSettings) = with(KtorWsSessionV1(this)) { + val sessions = appSettings.corSettings.wsSessions + sessions.add(this) + + // Handle init request + appSettings.controllerHelper( + { + command = ChatCommand.INIT + wsSession = this@with + }, + { outgoing.send(Frame.Text(apiV1Mapper.writeValueAsString(toTransportInit()))) }, + clWsV1, + "wsV1-init" + ) + + // Handle flow + incoming.receiveAsFlow().mapNotNull { + val frame = it as? Frame.Text ?: return@mapNotNull + // Handle without flow destruction + try { + appSettings.controllerHelper( + { + fromTransport(apiV1Mapper.readValue(frame.readText())) + wsSession = this@with + }, + { + val result = apiV1Mapper.writeValueAsString(toTransportChat()) + // If change request, response is sent to everyone + outgoing.send(Frame.Text(result)) + }, + clWsV1, + "wsV1-handle" + ) + + } catch (_: ClosedReceiveChannelException) { + sessions.remove(this@with) + } finally { + // Handle finish request + appSettings.controllerHelper( + { + command = ChatCommand.FINISH + wsSession = this@with + }, + { }, + clWsV1, + "wsV1-finish" + ) + sessions.remove(this@with) + } + }.collect() +} diff --git a/ok-messenger-be/ok-messenger-app/src/main/resources/application.yaml b/ok-messenger-be/ok-messenger-app/src/main/resources/application.yaml new file mode 100644 index 0000000..1da55ba --- /dev/null +++ b/ok-messenger-be/ok-messenger-app/src/main/resources/application.yaml @@ -0,0 +1,10 @@ +ktor: + development: true + deployment: + port: 8080 + watch: + - classes + - resources + application: + modules: + - ru.otus.messenger.app.ApplicationKt.module \ No newline at end of file diff --git a/ok-messenger-be/ok-messenger-app/src/main/resources/logback.xml b/ok-messenger-be/ok-messenger-app/src/main/resources/logback.xml new file mode 100644 index 0000000..5c8cc8c --- /dev/null +++ b/ok-messenger-be/ok-messenger-app/src/main/resources/logback.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level[%marker] %logger{36} - %msg%n%mdc%n + + + + + + + + + + + + app.logs + + ${LOGS_FB_HOSTS} + ${LOGS_FB_PORT} + 20 + + + + + + + + { + "component": "${SERVICE_NAME}", + "container-id": "${HOSTNAME}" + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ok-messenger-be/ok-messenger-app/src/test/kotlin/ApplicationTest.kt b/ok-messenger-be/ok-messenger-app/src/test/kotlin/ApplicationTest.kt new file mode 100644 index 0000000..e6889e7 --- /dev/null +++ b/ok-messenger-be/ok-messenger-app/src/test/kotlin/ApplicationTest.kt @@ -0,0 +1,21 @@ +package ru.otus.messenger.app + +import io.ktor.client.request.get +import io.ktor.http.HttpStatusCode +import io.ktor.server.testing.testApplication +import kotlin.test.assertEquals +import org.junit.Test + +class ApplicationTest { + + @Test + fun testRoot() = testApplication { + application { + module() + } + client.get("/").apply { + assertEquals(HttpStatusCode.Companion.OK, status) + } + } + +} \ No newline at end of file diff --git a/ok-messenger-be/ok-messenger-app/src/test/kotlin/common/ControllerTest.kt b/ok-messenger-be/ok-messenger-app/src/test/kotlin/common/ControllerTest.kt new file mode 100644 index 0000000..ce80042 --- /dev/null +++ b/ok-messenger-be/ok-messenger-app/src/test/kotlin/common/ControllerTest.kt @@ -0,0 +1,64 @@ +package ru.otus.messenger.app.common + +import java.util.UUID +import kotlinx.coroutines.test.runTest +import ru.otus.messenger.api.v1.mappers.fromTransport +import ru.otus.messenger.api.v1.mappers.toTransportChat +import ru.otus.messenger.api.v1.models.* +import ru.otus.messenger.biz.MessengerProcessor +import ru.otus.messenger.common.MessengerCorSettings +import kotlin.test.Test +import kotlin.test.assertEquals + +class ControllerTest { + private val request = ChatCreateRequest( + chat = ChatCreateRequestAllOfChat( + title = "New chat title", + description = "New chat description", + type = ChatCreateRequestAllOfChat.Type.GROUP, + mode = ChatCreateRequestAllOfChat.Mode.PERSONAL, + ownerId = UUID.randomUUID().toString(), + participants = setOf(), + metadata = """ + { + "organization": "BlancLabs", + "sampleName": "B26", + "analyte": "DNA" + } + """.trimIndent() + ), + debug = Debug(mode = DebugMode.STUB, stub = DebugStubs.SUCCESS) + ) + + private val appSettings: MessengerAppSettings = object : MessengerAppSettings { + override val corSettings: MessengerCorSettings = MessengerCorSettings() + override val processor: MessengerProcessor = MessengerProcessor(corSettings) + } + + class TestApplicationCall(private val request: IRequest) { + var response: IResponse? = null + + @Suppress("UNCHECKED_CAST") + fun receive(): T = request as T + fun respond(response: IResponse) { + this.response = response + } + } + + private suspend fun TestApplicationCall.createReport(appSettings: MessengerAppSettings) { + val response = appSettings.controllerHelper( + { fromTransport(receive()) }, + { toTransportChat() }, + ControllerTest::class, + "controller-v1-test" + ) + respond(response) + } + + @Test + fun ktorHelperTest() = runTest { + val testApp = TestApplicationCall(request).apply { createReport(appSettings) } + val response = testApp.response as ChatCreateResponse + assertEquals(ResponseResult.SUCCESS, response.result) + } +} diff --git a/ok-messenger-be/ok-messenger-app/src/test/kotlin/stub/V1StubApiTest.kt b/ok-messenger-be/ok-messenger-app/src/test/kotlin/stub/V1StubApiTest.kt new file mode 100644 index 0000000..52457e0 --- /dev/null +++ b/ok-messenger-be/ok-messenger-app/src/test/kotlin/stub/V1StubApiTest.kt @@ -0,0 +1,2 @@ +package ru.otus.messenger.app.stub + diff --git a/ok-messenger-be/ok-messenger-app/src/test/kotlin/websocket/V1WebsocketStubTest.kt b/ok-messenger-be/ok-messenger-app/src/test/kotlin/websocket/V1WebsocketStubTest.kt new file mode 100644 index 0000000..6b1f05f --- /dev/null +++ b/ok-messenger-be/ok-messenger-app/src/test/kotlin/websocket/V1WebsocketStubTest.kt @@ -0,0 +1,119 @@ +package ru.otus.messenger.app.websocket + +import io.ktor.client.plugins.websocket.* +import io.ktor.serialization.jackson.* +import io.ktor.server.testing.* +import java.util.UUID +import kotlinx.coroutines.withTimeout +import ru.otus.messenger.api.v1.models.* +import ru.otus.messenger.app.common.MessengerAppSettingsData +import ru.otus.messenger.app.module +import ru.otus.messenger.common.MessengerCorSettings +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs + +class V1WebsocketStubTest { + + @Test + fun createStub() { + val request = ChatCreateRequest( + chat = ChatCreateRequestAllOfChat( + title = "New chat title", + description = "New chat description", + type = ChatCreateRequestAllOfChat.Type.CHANNEL, + mode = ChatCreateRequestAllOfChat.Mode.WORK, + ownerId = UUID.randomUUID().toString(), + participants = setOf(UUID.randomUUID().toString()), + metadata = """ + { + "organization": "BlancLabs", + "sampleName": "B26", + "analyte": "DNA" + } + """.trimIndent(), + ), + debug = Debug( + mode = DebugMode.STUB, + stub = DebugStubs.SUCCESS + ) + ) + + testMethod(request) { + assertEquals(ResponseResult.SUCCESS, it.result) + } + } + + @Test + fun readStub() { + val request = ChatReadRequest( + chatId = UUID.randomUUID().toString(), + debug = Debug( + mode = DebugMode.STUB, + stub = DebugStubs.SUCCESS + ) + ) + + testMethod(request) { + assertEquals(ResponseResult.SUCCESS, it.result) + } + } + + @Test + fun deleteStub() { + val request = ChatDeleteRequest( + chatId = UUID.randomUUID().toString(), + debug = Debug( + mode = DebugMode.STUB, + stub = DebugStubs.SUCCESS + ) + ) + + testMethod(request) { + assertEquals(ResponseResult.SUCCESS, it.result) + } + } + + @Test + fun searchStub() { + val request = ChatSearchRequest( + criteria = ChatSearchRequestAllOfCriteria( + title = "Chat search title", + type = ChatSearchRequestAllOfCriteria.Type.CHANNEL, + mode = ChatSearchRequestAllOfCriteria.Mode.WORK, + ), + debug = Debug( + mode = DebugMode.STUB, + stub = DebugStubs.SUCCESS + ) + ) + + testMethod(request) { + assertEquals(ResponseResult.SUCCESS, it.result) + } + } + + private inline fun testMethod( + request: IRequest, + crossinline assertBlock: (T) -> Unit + ) = testApplication { + application { module(MessengerAppSettingsData(corSettings = MessengerCorSettings())) } + val client = createClient { + install(WebSockets) { + contentConverter = JacksonWebsocketContentConverter() + } + } + + client.webSocket("/v1/ws") { + withTimeout(3000) { + val response = receiveDeserialized() as T + assertIs(response) + } + sendSerialized(request) + withTimeout(3000) { + val response = receiveDeserialized() as T + assertBlock(response) + } + } + } +} diff --git a/ok-messenger-be/ok-messenger-biz/build.gradle.kts b/ok-messenger-be/ok-messenger-biz/build.gradle.kts new file mode 100644 index 0000000..e4e2ef4 --- /dev/null +++ b/ok-messenger-be/ok-messenger-biz/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id("build-jvm") +} + +dependencies { + implementation(kotlin("stdlib")) + implementation(project(":ok-messenger-common")) + implementation(project(":ok-messenger-stubs")) + + testImplementation(kotlin("test-junit")) +} \ No newline at end of file diff --git a/ok-messenger-be/ok-messenger-biz/src/main/kotlin/MessengerProcessor.kt b/ok-messenger-be/ok-messenger-biz/src/main/kotlin/MessengerProcessor.kt new file mode 100644 index 0000000..d4e797d --- /dev/null +++ b/ok-messenger-be/ok-messenger-biz/src/main/kotlin/MessengerProcessor.kt @@ -0,0 +1,17 @@ +package ru.otus.messenger.biz + +import ru.otus.messenger.common.MessengerContext +import ru.otus.messenger.common.MessengerCorSettings +import ru.otus.messenger.common.models.ChatMode +import ru.otus.messenger.common.models.ChatState +import ru.otus.messenger.common.models.ChatType +import ru.otus.messenger.stubs.MessengerChatStub + +@Suppress("unused", "RedundantSuspendModifier") +class MessengerProcessor(val corSettings: MessengerCorSettings) { + suspend fun exec(ctx: MessengerContext) { + ctx.chatResponse = MessengerChatStub.get() + ctx.chatsResponse = MessengerChatStub.prepareSearchList("New chat", ChatType.GROUP, ChatMode.PERSONAL).toMutableList() + ctx.state = ChatState.RUNNING + } +} \ No newline at end of file diff --git a/ok-messenger-be/ok-messenger-common/build.gradle.kts b/ok-messenger-be/ok-messenger-common/build.gradle.kts index 5e6f071..0b55f1d 100644 --- a/ok-messenger-be/ok-messenger-common/build.gradle.kts +++ b/ok-messenger-be/ok-messenger-common/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { implementation(kotlin("stdlib")) implementation(libs.kotlin.datetime) implementation(libs.kotlinx.serialization.json) + api("ru.otus.messenger.libs:ok-messenger-lib-logging") testImplementation(kotlin("test-junit")) } \ No newline at end of file diff --git a/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/ChatContext.kt b/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/MessengerContext.kt similarity index 74% rename from ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/ChatContext.kt rename to ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/MessengerContext.kt index fdaf66c..a780d93 100644 --- a/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/ChatContext.kt +++ b/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/MessengerContext.kt @@ -3,19 +3,21 @@ package ru.otus.messenger.common import kotlinx.datetime.Instant import ru.otus.messenger.common.models.* import ru.otus.messenger.common.stubs.Stubs +import ru.otus.messenger.common.ws.IMessengerWsSession -data class ChatContext( +data class MessengerContext( var command: ChatCommand = ChatCommand.NONE, var state: ChatState = ChatState.NONE, val errors: MutableList = mutableListOf(), var workMode: WorkMode = WorkMode.PROD, var stubCase: Stubs = Stubs.NONE, + var wsSession: IMessengerWsSession = IMessengerWsSession.NONE, var requestId: RequestId = RequestId.NONE, var timeStart: Instant = Instant.NONE, var chatRequest: MessengerChat = MessengerChat(), - var chatFilterRequest: ChatSearchFilter = ChatSearchFilter(), + var chatFilterRequest: ChatSearchFilter = ChatSearchFilter.NONE, var chatResponse: MessengerChat = MessengerChat(), var chatsResponse: MutableList = mutableListOf(), diff --git a/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/MessengerCorSettings.kt b/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/MessengerCorSettings.kt new file mode 100644 index 0000000..c10a15d --- /dev/null +++ b/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/MessengerCorSettings.kt @@ -0,0 +1,13 @@ +package ru.otus.messenger.common + +import ru.otus.messenger.common.ws.IMessengerWsSessionRepo +import ru.otus.messenger.logging.common.LoggerProvider + +data class MessengerCorSettings( + val loggerProvider: LoggerProvider = LoggerProvider(), + val wsSessions: IMessengerWsSessionRepo = IMessengerWsSessionRepo.NONE, +) { + companion object { + val NONE = MessengerCorSettings() + } +} \ No newline at end of file diff --git a/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/helpers/MessengerErrorHelper.kt b/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/helpers/MessengerErrorHelper.kt new file mode 100644 index 0000000..857e76e --- /dev/null +++ b/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/helpers/MessengerErrorHelper.kt @@ -0,0 +1,15 @@ +package ru.otus.messenger.common.helpers + +import ru.otus.messenger.common.models.ChatError + +fun Throwable.asMessengerError( + code: String = "unknown", + group: String = "exceptions", + message: String = this.message ?: "", +) = ChatError( + code = code, + group = group, + field = "", + message = message, + exception = this, +) \ No newline at end of file diff --git a/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/models/ChatCommand.kt b/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/models/ChatCommand.kt index cf5890c..debecfe 100644 --- a/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/models/ChatCommand.kt +++ b/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/models/ChatCommand.kt @@ -7,4 +7,6 @@ enum class ChatCommand { DELETE, SEARCH, UPDATE, + INIT, + FINISH, } \ No newline at end of file diff --git a/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/models/ChatError.kt b/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/models/ChatError.kt index 271404f..9de3e35 100644 --- a/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/models/ChatError.kt +++ b/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/models/ChatError.kt @@ -1,9 +1,12 @@ package ru.otus.messenger.common.models +import ru.otus.messenger.logging.common.LogLevel + data class ChatError( val code: String = "", val group: String = "", val field: String = "", val message: String = "", + val level: LogLevel = LogLevel.ERROR, val exception: Throwable? = null, ) \ No newline at end of file diff --git a/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/models/ChatSearchFilter.kt b/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/models/ChatSearchFilter.kt index e255b21..baa7eab 100644 --- a/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/models/ChatSearchFilter.kt +++ b/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/models/ChatSearchFilter.kt @@ -1,7 +1,30 @@ package ru.otus.messenger.common.models data class ChatSearchFilter( - var searchString: String = "", + var searchFields: List = emptyList(), var ownerId: ChatOwnerId = ChatOwnerId.NONE, var type: ChatType = ChatType.NONE, -) \ No newline at end of file + var mode: ChatMode = ChatMode.NONE, +) { + interface SearchField { + val fieldName: String + val action: SearchAction + } + + enum class SearchAction { + CONTAINS, + EQUALS, + MORE, + LESS + } + + data class StringSearchField( + override val fieldName: String, + override val action: SearchAction = SearchAction.CONTAINS, + val stringValue: String, + ) : SearchField + + companion object { + val NONE = ChatSearchFilter() + } +} \ No newline at end of file diff --git a/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/ws/IMessengerWsSession.kt b/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/ws/IMessengerWsSession.kt new file mode 100644 index 0000000..38f4684 --- /dev/null +++ b/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/ws/IMessengerWsSession.kt @@ -0,0 +1,12 @@ +package ru.otus.messenger.common.ws + +interface IMessengerWsSession { + suspend fun send(obj: T) + companion object { + val NONE = object : IMessengerWsSession { + override suspend fun send(obj: T) { + + } + } + } +} \ No newline at end of file diff --git a/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/ws/IMessengerWsSessionRepo.kt b/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/ws/IMessengerWsSessionRepo.kt new file mode 100644 index 0000000..dc64411 --- /dev/null +++ b/ok-messenger-be/ok-messenger-common/src/commonMain/kotlin/ws/IMessengerWsSessionRepo.kt @@ -0,0 +1,17 @@ +package ru.otus.messenger.common.ws + +interface IMessengerWsSessionRepo { + fun add(session: IMessengerWsSession) + fun clearAll() + fun remove(session: IMessengerWsSession) + suspend fun sendAll(obj: K) + + companion object { + val NONE = object : IMessengerWsSessionRepo { + override fun add(session: IMessengerWsSession) {} + override fun clearAll() {} + override fun remove(session: IMessengerWsSession) {} + override suspend fun sendAll(obj: K) {} + } + } +} \ No newline at end of file diff --git a/ok-messenger-be/ok-messenger-stubs/build.gradle.kts b/ok-messenger-be/ok-messenger-stubs/build.gradle.kts new file mode 100644 index 0000000..133e5ae --- /dev/null +++ b/ok-messenger-be/ok-messenger-stubs/build.gradle.kts @@ -0,0 +1,12 @@ +plugins { + id("build-jvm") +} + +dependencies { + implementation(kotlin("stdlib")) + implementation(libs.kotlin.datetime) + implementation(libs.kotlinx.serialization.json) + implementation(project(":ok-messenger-common")) + + testImplementation(kotlin("test-junit")) +} \ No newline at end of file diff --git a/ok-messenger-be/ok-messenger-stubs/src/main/kotlin/MessengerChatStub.kt b/ok-messenger-be/ok-messenger-stubs/src/main/kotlin/MessengerChatStub.kt new file mode 100644 index 0000000..7c2b738 --- /dev/null +++ b/ok-messenger-be/ok-messenger-stubs/src/main/kotlin/MessengerChatStub.kt @@ -0,0 +1,22 @@ +package ru.otus.messenger.stubs + +import ru.otus.messenger.common.models.ChatMode +import ru.otus.messenger.common.models.ChatType +import ru.otus.messenger.common.models.MessengerChat +import ru.otus.messenger.stubs.MessengerChatStubSample.CHAT_SAMPLE_1 +import ru.otus.messenger.stubs.MessengerChatStubSample.CHAT_SAMPLE_2 + +object MessengerChatStub { + fun get(): MessengerChat = CHAT_SAMPLE_1.copy() + + fun prepareResult(block: MessengerChat.() -> Unit): MessengerChat = get().apply(block) + + fun prepareSearchList( + chatTitle: String, + chatType: ChatType, + chatMode: ChatMode, + ) = listOf( + CHAT_SAMPLE_1, + CHAT_SAMPLE_2 + ).filter { it.title == chatTitle && it.type == chatType && it.mode == chatMode } +} \ No newline at end of file diff --git a/ok-messenger-be/ok-messenger-stubs/src/main/kotlin/MessengerChatStubSample.kt b/ok-messenger-be/ok-messenger-stubs/src/main/kotlin/MessengerChatStubSample.kt new file mode 100644 index 0000000..8dc1d75 --- /dev/null +++ b/ok-messenger-be/ok-messenger-stubs/src/main/kotlin/MessengerChatStubSample.kt @@ -0,0 +1,56 @@ +package ru.otus.messenger.stubs + +import java.util.UUID +import kotlin.time.Duration.Companion.hours +import kotlinx.datetime.Instant +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import ru.otus.messenger.common.models.* + +object MessengerChatStubSample { + val chatId = UUID.randomUUID().toString() + val chatOwnerId = UUID.randomUUID().toString() + val participants = MutableList(3) { ChatUserId(UUID.randomUUID().toString()) } + private val timestamp = Instant.parse("2025-01-31T01:30:00.000-05:00") + + val CHAT_SAMPLE_1: MessengerChat + get() = MessengerChat( + id = ChatId(chatId), + title = "New chat", + description = "New chat description", + type = ChatType.GROUP, + mode = ChatMode.PERSONAL, + ownerId = ChatOwnerId(chatOwnerId), + participants = (participants + ChatUserId(chatOwnerId)).toMutableSet(), + createdAt = timestamp, + updatedAt = timestamp.plus(10.hours), + isArchived = ChatArchiveFlag(false), + metadata = ChatMetadata( + buildJsonObject { + put("sampleId", "uuid4") + put("testParam", "test") + } + ) + ) + + val CHAT_SAMPLE_2: MessengerChat + get() = MessengerChat( + id = ChatId(chatId), + title = "New chat", + description = "New chat description", + type = ChatType.GROUP, + mode = ChatMode.WORK, + ownerId = ChatOwnerId(chatOwnerId), + participants = (participants + ChatUserId(chatOwnerId)).toMutableSet(), + createdAt = timestamp.plus(100.hours), + updatedAt = timestamp.plus(101.hours), + isArchived = ChatArchiveFlag(false), + metadata = ChatMetadata( + buildJsonObject { + put("sampleId", "uuid5") + put("testParam", "test") + put("organization", "BlancLabs") + } + ) + ) +} \ No newline at end of file diff --git a/ok-messenger-be/settings.gradle.kts b/ok-messenger-be/settings.gradle.kts index 8b504c6..8fca97b 100644 --- a/ok-messenger-be/settings.gradle.kts +++ b/ok-messenger-be/settings.gradle.kts @@ -23,6 +23,10 @@ rootProject.name = "ok-messenger-be" //enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") +include(":ok-messenger-api-log-v1") include(":ok-messenger-api-v1") include(":ok-messenger-api-v1-mappers") include(":ok-messenger-common") +include(":ok-messenger-stubs") +include(":ok-messenger-app") +include(":ok-messenger-biz") diff --git a/ok-messenger-libs/build.gradle.kts b/ok-messenger-libs/build.gradle.kts new file mode 100644 index 0000000..a253721 --- /dev/null +++ b/ok-messenger-libs/build.gradle.kts @@ -0,0 +1,31 @@ +plugins { + alias(libs.plugins.jvm) apply false +} + +group = "ru.otus.messenger.libs" +version = "0.0.1" + +allprojects { + repositories { + mavenCentral() + } +} + +subprojects { + group = rootProject.group + version = rootProject.version +} + +ext { + val specDir = layout.projectDirectory.dir("../specs") + set("spec-v1", specDir.file("specs-v1.yaml").toString()) +} + +tasks { + arrayOf("build", "clean", "check").forEach {tsk -> + create(tsk) { + group = "build" + dependsOn(subprojects.map { it.getTasksByName(tsk,false)}) + } + } +} \ No newline at end of file diff --git a/ok-messenger-libs/gradle.properties b/ok-messenger-libs/gradle.properties new file mode 100644 index 0000000..28db788 --- /dev/null +++ b/ok-messenger-libs/gradle.properties @@ -0,0 +1,3 @@ +kotlin.code.style=official +kotlin.native.ignoreDisabledTargets=true +#kotlin.native.cacheKind.linuxX64=none diff --git a/ok-messenger-libs/ok-messenger-lib-logging/build.gradle.kts b/ok-messenger-libs/ok-messenger-lib-logging/build.gradle.kts new file mode 100644 index 0000000..c1d8b5b --- /dev/null +++ b/ok-messenger-libs/ok-messenger-lib-logging/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + id("build-jvm") +} + +dependencies { + implementation(kotlin("stdlib")) + implementation(libs.kotlin.coroutines) + implementation(libs.kotlin.datetime) + implementation(libs.logback.classic) + implementation(libs.logback.logstash) + api(libs.logback.appenders) + api(libs.logger.fluentd) + + testImplementation(kotlin("test-junit")) + testImplementation(libs.kotlin.coroutines.test) +} \ No newline at end of file diff --git a/ok-messenger-libs/ok-messenger-lib-logging/src/main/kotlin/DefaultMarker.kt b/ok-messenger-libs/ok-messenger-lib-logging/src/main/kotlin/DefaultMarker.kt new file mode 100644 index 0000000..a37eaad --- /dev/null +++ b/ok-messenger-libs/ok-messenger-lib-logging/src/main/kotlin/DefaultMarker.kt @@ -0,0 +1,31 @@ +package ru.otus.messenger.logging + +import org.slf4j.Marker + +/** + * Реализация SLF4J маркера логов для маркировки различных типов логов + */ +class DefaultMarker( + private val name: String, + private val submarkers: List = emptyList() +): Marker { + override fun getName(): String = name + + override fun add(reference: Marker) {} + + override fun remove(reference: Marker): Boolean = false + + @Deprecated("Deprecated in Java", ReplaceWith("hasReferences()")) + override fun hasChildren(): Boolean = hasReferences() + + override fun hasReferences(): Boolean = submarkers.isNotEmpty() + + override fun iterator(): Iterator = submarkers.iterator() + + override fun contains(other: Marker): Boolean = submarkers.contains(other) + + override fun contains(name: String): Boolean = submarkers.any { it.name == name } + + override fun toString(): String = arrayOf(name, *submarkers.toTypedArray()).joinToString(",") + +} diff --git a/ok-messenger-libs/ok-messenger-lib-logging/src/main/kotlin/LogWrapperLogback.kt b/ok-messenger-libs/ok-messenger-lib-logging/src/main/kotlin/LogWrapperLogback.kt new file mode 100644 index 0000000..dc928e5 --- /dev/null +++ b/ok-messenger-libs/ok-messenger-lib-logging/src/main/kotlin/LogWrapperLogback.kt @@ -0,0 +1,87 @@ +package ru.otus.messenger.logging + +import ch.qos.logback.classic.Logger +import net.logstash.logback.argument.StructuredArguments +import org.slf4j.Marker +import org.slf4j.event.KeyValuePair +import org.slf4j.event.Level +import org.slf4j.event.LoggingEvent +import ru.otus.messenger.logging.common.ILogWrapper +import ru.otus.messenger.logging.common.LogLevel +import java.time.Instant + +class LogWrapperLogback( + /** + * Экземпляр логера (Logback) + */ + val logger: Logger, + /** + * Идентификатор логера. Пробрасывается в Logback и замещает loggerClass. Также используется в сообщения + * логера о входе и выходе из функции. + */ + override val loggerId: String = logger.name, +) : ILogWrapper { + /** + * Основная функция для логирования + */ + private fun log( + msg: String = "", + level: Level = Level.TRACE, + marker: Marker = DefaultMarker("DEV"), + e: Throwable? = null, + data: Any? = null, + objs: Map? = null, + ) { + logger.log(object : LoggingEvent { + override fun getThrowable() = e + override fun getTimeStamp(): Long = Instant.now().toEpochMilli() + override fun getThreadName(): String = Thread.currentThread().name + override fun getMessage(): String = msg + override fun getArguments(): MutableList = argumentArray.toMutableList() + override fun getArgumentArray(): Array = data + ?.let { d -> + listOfNotNull( + objs?.map { StructuredArguments.keyValue(it.key, it.value) }?.toTypedArray(), + StructuredArguments.keyValue("data", d), + ).toTypedArray() + } + ?: objs?.mapNotNull { StructuredArguments.keyValue(it.key, it.value) }?.toTypedArray() + ?: emptyArray() + + override fun getMarkers(): MutableList = mutableListOf(marker) + override fun getKeyValuePairs(): MutableList = objs + ?.mapNotNull { + it.let { KeyValuePair(it.key, it.value) } + } + ?.toMutableList() + ?: mutableListOf() + + override fun getLevel(): Level = level + override fun getLoggerName(): String = logger.name + }) + } + + override fun log( + msg: String, + level: LogLevel, + marker: String, + e: Throwable?, + data: Any?, + objs: Map?, + ) = log( + msg = msg, + level = level.toSlf(), + marker = DefaultMarker(marker), + e = e, + data = data, + objs = objs, + ) + + private fun LogLevel.toSlf() = when (this) { + LogLevel.ERROR -> Level.ERROR + LogLevel.WARN -> Level.WARN + LogLevel.INFO -> Level.INFO + LogLevel.DEBUG -> Level.DEBUG + LogLevel.TRACE -> Level.TRACE + } +} diff --git a/ok-messenger-libs/ok-messenger-lib-logging/src/main/kotlin/LoggerLogback.kt b/ok-messenger-libs/ok-messenger-lib-logging/src/main/kotlin/LoggerLogback.kt new file mode 100644 index 0000000..838c873 --- /dev/null +++ b/ok-messenger-libs/ok-messenger-lib-logging/src/main/kotlin/LoggerLogback.kt @@ -0,0 +1,21 @@ +package ru.otus.messenger.logging + +import ch.qos.logback.classic.Logger +import org.slf4j.LoggerFactory +import ru.otus.messenger.logging.common.ILogWrapper +import kotlin.reflect.KClass + +/** + * Generate internal MpLogContext logger + * + * @param logger Logback instance from [LoggerFactory.getLogger()] + */ +fun loggerLogback(logger: Logger): ILogWrapper = LogWrapperLogback( + logger = logger, + loggerId = logger.name, +) + +fun loggerLogback(clazz: KClass<*>): ILogWrapper = loggerLogback(LoggerFactory.getLogger(clazz.java) as Logger) + +@Suppress("unused") +fun loggerLogback(loggerId: String): ILogWrapper = loggerLogback(LoggerFactory.getLogger(loggerId) as Logger) diff --git a/ok-messenger-libs/ok-messenger-lib-logging/src/main/kotlin/common/ILogWrapper.kt b/ok-messenger-libs/ok-messenger-lib-logging/src/main/kotlin/common/ILogWrapper.kt new file mode 100644 index 0000000..22e6c50 --- /dev/null +++ b/ok-messenger-libs/ok-messenger-lib-logging/src/main/kotlin/common/ILogWrapper.kt @@ -0,0 +1,113 @@ +package ru.otus.messenger.logging.common + +import kotlinx.datetime.Clock +import kotlin.time.measureTimedValue + +@Suppress("unused") +interface ILogWrapper: AutoCloseable { + val loggerId: String + + fun log( + msg: String = "", + level: LogLevel = LogLevel.TRACE, + marker: String = "DEV", + e: Throwable? = null, + data: Any? = null, + objs: Map? = null, + ) + + fun error( + msg: String = "", + marker: String = "DEV", + e: Throwable? = null, + data: Any? = null, + objs: Map? = null, + ) = log(msg, LogLevel.ERROR, marker, e, data, objs) + + fun info( + msg: String = "", + marker: String = "DEV", + data: Any? = null, + objs: Map? = null, + ) = log(msg, LogLevel.INFO, marker, null, data, objs) + + fun debug( + msg: String = "", + marker: String = "DEV", + data: Any? = null, + objs: Map? = null, + ) = log(msg, LogLevel.DEBUG, marker, null, data, objs) + + /** + * Функция обертка для выполнения прикладного кода с логированием перед выполнением и после + */ + suspend fun doWithLogging( + id: String = "", + level: LogLevel = LogLevel.INFO, + block: suspend () -> T, + ): T = try { + log("Started $loggerId $id", level) + val (res, diffTime) = measureTimedValue { block() } + + log( + msg = "Finished $loggerId $id", + level = level, + objs = mapOf("metricHandleTime" to diffTime.toIsoString()) + ) + res + } catch (e: Throwable) { + log( + msg = "Failed $loggerId $id", + level = LogLevel.ERROR, + e = e + ) + throw e + } + + /** + * Функция обертка для выполнения прикладного кода с логированием ошибки + */ + suspend fun doWithErrorLogging( + id: String = "", + throwRequired: Boolean = true, + block: suspend () -> T, + ): T? = try { + val result = block() + result + } catch (e: Throwable) { + log( + msg = "Failed $loggerId $id", + level = LogLevel.ERROR, + e = e + ) + if (throwRequired) throw e else null + } + + override fun close() {} + + companion object { + val DEFAULT = object: ILogWrapper { + override val loggerId: String = "NONE" + + override fun log( + msg: String, + level: LogLevel, + marker: String, + e: Throwable?, + data: Any?, + objs: Map?, + ) { + val markerString = marker + .takeIf { it.isNotBlank() } + ?.let { " ($it)" } + val args = listOfNotNull( + "${Clock.System.now()} [${level.name}]$markerString: $msg", + e?.let { "${it.message ?: "Unknown reason"}:\n${it.stackTraceToString()}" }, + data.toString(), + objs.toString(), + ) + println(args.joinToString("\n")) + } + } + } +} diff --git a/ok-messenger-libs/ok-messenger-lib-logging/src/main/kotlin/common/LogLevel.kt b/ok-messenger-libs/ok-messenger-lib-logging/src/main/kotlin/common/LogLevel.kt new file mode 100644 index 0000000..36a1156 --- /dev/null +++ b/ok-messenger-libs/ok-messenger-lib-logging/src/main/kotlin/common/LogLevel.kt @@ -0,0 +1,24 @@ +package ru.otus.messenger.logging.common + +enum class LogLevel( + private val levelInt: Int, + private val levelStr: String, +) { + ERROR(40, "ERROR"), + WARN(30, "WARN"), + INFO(20, "INFO"), + DEBUG(10, "DEBUG"), + TRACE(0, "TRACE"); + + @Suppress("unused") + fun toInt(): Int { + return levelInt + } + + /** + * Returns the string representation of this Level. + */ + override fun toString(): String { + return levelStr + } +} diff --git a/ok-messenger-libs/ok-messenger-lib-logging/src/main/kotlin/common/LoggerProvider.kt b/ok-messenger-libs/ok-messenger-lib-logging/src/main/kotlin/common/LoggerProvider.kt new file mode 100644 index 0000000..c2476ad --- /dev/null +++ b/ok-messenger-libs/ok-messenger-lib-logging/src/main/kotlin/common/LoggerProvider.kt @@ -0,0 +1,37 @@ +package ru.otus.messenger.logging.common + +import kotlin.reflect.KClass +import kotlin.reflect.KFunction + +/** + * Инициализирует выбранный логер + * + * ```kotlin + * // Обычно логер вызывается вот так + * val logger = LoggerFactory.getLogger(this::class.java) + * // Мы создаем экземпляр логер-провайдера вот так + * val loggerProvider = MkpLoggerProvider { clazz -> mpLoggerLogback(clazz) } + * + * // В дальнейшем будем использовать этот экземпляр вот так: + * val logger = loggerProvider.logger(this::class) + * logger.info("My log") + * ``` + */ +class LoggerProvider( + private val provider: (String) -> ILogWrapper = { ILogWrapper.DEFAULT } +) { + /** + * Инициализирует и возвращает экземпляр логера + */ + fun logger(loggerId: String): ILogWrapper = provider(loggerId) + + /** + * Инициализирует и возвращает экземпляр логера + */ + fun logger(clazz: KClass<*>): ILogWrapper = provider(clazz.qualifiedName ?: clazz.simpleName ?: "(unknown)") + + /** + * Инициализирует и возвращает экземпляр логера + */ + fun logger(function: KFunction<*>): ILogWrapper = provider(function.name) +} diff --git a/ok-messenger-libs/ok-messenger-lib-logging/src/main/resources/logback.xml b/ok-messenger-libs/ok-messenger-lib-logging/src/main/resources/logback.xml new file mode 100644 index 0000000..0d4a388 --- /dev/null +++ b/ok-messenger-libs/ok-messenger-lib-logging/src/main/resources/logback.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level[%marker] %logger{36} - %msg%n%mdc%n + + + + + + + + + + + + app.logs + + ${LOGS_FB_HOSTS} + ${LOGS_FB_PORT} + 20 + + + + + + + + { + "component": "${SERVICE_NAME}", + "container-id": "${HOSTNAME}" + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ok-messenger-libs/ok-messenger-lib-logging/src/test/kotlin/LoggerTest.kt b/ok-messenger-libs/ok-messenger-lib-logging/src/test/kotlin/LoggerTest.kt new file mode 100644 index 0000000..a7b30d1 --- /dev/null +++ b/ok-messenger-libs/ok-messenger-lib-logging/src/test/kotlin/LoggerTest.kt @@ -0,0 +1,64 @@ +package ru.otus.messenger.logging + +import kotlinx.coroutines.runBlocking +import org.slf4j.LoggerFactory +import java.io.ByteArrayOutputStream +import java.io.PrintStream +import kotlin.test.Test +import kotlin.test.assertTrue + +class LoggerTest { + private val logId = "test-logger" + private val logger = LoggerFactory.getLogger("xx") + data class Xx(val x: String = "sdf") + + + @Test + fun slf4jTest() { + @Suppress("LoggingPlaceholderCountMatchesArgumentCount") + logger.info("ggg {} {} {}", 1, "sdf", Xx(), Xx("234234")) + // ------------это objs - ! ! ! + // -----------------------------------------это data- ! + } + + @Test + fun `logger init`() { + val output = invokeLogger { + println("Some action") + } + + assertTrue(Regex("Started .* $logId.*").containsMatchIn(output.toString())) + assertTrue(output.toString().contains("Some action")) + assertTrue(Regex("Finished .* $logId.*").containsMatchIn(output.toString())) + } + + @Test + fun `logger fails`() { + val output = invokeLogger { + throw RuntimeException("Some action") + } + + assertTrue(Regex("Started .* $logId.*").containsMatchIn(output.toString())) + assertTrue(Regex("Failed .* $logId.*").containsMatchIn(output.toString())) + } + + private fun invokeLogger(block: suspend () -> Unit): ByteArrayOutputStream { + val outputStreamCaptor = outputStreamCaptor() + + try { + runBlocking { + val logger = loggerLogback(this::class) + logger.doWithLogging(logId, block = block) + } + } catch (ignore: RuntimeException) { + } + + return outputStreamCaptor + } + + private fun outputStreamCaptor(): ByteArrayOutputStream { + return ByteArrayOutputStream().apply { + System.setOut(PrintStream(this)) + } + } +} \ No newline at end of file diff --git a/ok-messenger-libs/ok-messenger-lib-logging/src/test/resources/logback-test.xml b/ok-messenger-libs/ok-messenger-lib-logging/src/test/resources/logback-test.xml new file mode 100644 index 0000000..0f6ebdf --- /dev/null +++ b/ok-messenger-libs/ok-messenger-lib-logging/src/test/resources/logback-test.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level[%marker] %logger{36} - %msg%n%mdc%n + + + + + + + + \ No newline at end of file diff --git a/ok-messenger-libs/settings.gradle.kts b/ok-messenger-libs/settings.gradle.kts new file mode 100644 index 0000000..8a204bf --- /dev/null +++ b/ok-messenger-libs/settings.gradle.kts @@ -0,0 +1,26 @@ +rootProject.name = "ok-messenger-libs" + +dependencyResolutionManagement { + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} + +pluginManagement { + includeBuild("../build-plugin") + plugins { + id("build-jvm") apply false + } + repositories { + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" +} + +include(":ok-messenger-lib-logging") \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index bc99388..c6da0bb 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -11,3 +11,5 @@ includeBuild("build-plugin") includeBuild("ok-lessons") includeBuild("ok-messenger-be") + +includeBuild("ok-messenger-libs") diff --git a/specs/specs-log-v1.yaml b/specs/specs-log-v1.yaml new file mode 100644 index 0000000..648784f --- /dev/null +++ b/specs/specs-log-v1.yaml @@ -0,0 +1,110 @@ +openapi: 3.0.4 +info: + title: Messenger log models + description: Models for logging services + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.0 + +paths: {} + +components: + schemas: + CommonLogModel: + title: Общая модель лога + description: Общая модель лога для всех микросервисов системы + type: object + properties: + messageTime: + type: string + logId: + type: string + source: + type: string + chat: + $ref: '#/components/schemas/ChatLogModel' + errors: + type: array + items: + $ref: '#/components/schemas/ErrorLogModel' + + ChatLogOperation: + type: string + enum: + - create + - read + - update + - delete + - search + - init + - finish + + ChatLogModel: + title: Модель лога для микросервиса Messenger + type: object + properties: + requestId: + type: string + operation: + $ref: '#/components/schemas/ChatLogOperation' + requestChat: + $ref: '#/components/schemas/ChatLog' + requestSearch: + $ref: '#/components/schemas/ChatSearchLog' + responseChat: + $ref: '#/components/schemas/ChatLog' + responseChats: + type: array + items: + $ref: '#/components/schemas/ChatLog' + + ErrorLogModel: + title: Модель лога для ошибки + type: object + properties: + message: + type: string + field: + type: string + code: + type: string + level: + type: string + + ChatLog: + title: chat log model + type: object + properties: + chatId: + type: string + title: + type: string + description: + type: string + type: + type: string + mode: + type: string + owner_id: + type: string + participants: + type: array + uniqueItems: true + items: + type: string + metadata: + type: string + format: json + + ChatSearchLog: + title: log model for filter request + properties: + searchFields: + type: string + + ChatUpdateLog: + title: log model for update request + properties: + updateFieldName: + type: string diff --git a/specs/specs-v1.yaml b/specs/specs-v1.yaml index 71857a4..491ceed 100644 --- a/specs/specs-v1.yaml +++ b/specs/specs-v1.yaml @@ -167,6 +167,8 @@ components: search: '#/components/schemas/ChatSearchResponse' update: '#/components/schemas/ChatUpdateResponse' delete: '#/components/schemas/ChatDeleteResponse' + init: '#/components/schemas/ChatInitResponse' + finish: '#/components/schemas/ChatFinishResponse' # Основная схема объекта Chat Chat: @@ -370,6 +372,14 @@ components: allOf: - $ref: '#/components/schemas/IResponse' + ChatInitResponse: + allOf: + - $ref: '#/components/schemas/IResponse' + + ChatFinishResponse: + allOf: + - $ref: '#/components/schemas/IResponse' + # STUBS ====================== DebugMode: type: string