diff --git a/README.md b/README.md index 21b866c..73fe275 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Marketplace -- это площадка, на которой пользовате 1. [Функциональные требования](./docs/02-analysis/01-functional-requiremens.md) 2. [Нефункциональные требования](./docs/02-analysis/02-nonfunctional-requirements.md) 3. DevOps - 1. [Файлы сборки](./deploy) + 1. [Файлы сборки](./deploy) 4. Архитектура 1. [ADR](docs/03-architecture/01-adrs.md) 2. [Описание API](docs/03-architecture/02-api.md) @@ -34,15 +34,26 @@ Marketplace -- это площадка, на которой пользовате ## Подпроекты для занятий по языку Kotlin 1. Модуль 1: Введение в Kotlin - 1. [m1l1-first](lessons/m1l1-first) - Вводное занятие, создание первой программы на Kotlin - 2. [m1l2-basic](lessons/m1l2-basic) - Основные конструкции Kotlin - 3. [m1l3-func](lessons/m1l3-func) - Функциональные элементы Kotlin - 4. [m1l4-oop](lessons/m1l4-oop) - Объектно-ориентированное программирование + 1. [m1l1-first](lessons/m1l1-first) - Вводное занятие, создание первой программы на Kotlin + 2. [m1l2-basic](lessons/m1l2-basic) - Основные конструкции Kotlin + 3. [m1l3-func](lessons/m1l3-func) - Функциональные элементы Kotlin + 4. [m1l4-oop](lessons/m1l4-oop) - Объектно-ориентированное программирование 2. Модуль 2: Расширенные возможности Kotlin - 1. [m2l1-dsl](lessons/m2l1-dsl) - Предметно ориентированные языки (DSL) + 1. [m2l1-dsl](lessons/m2l1-dsl) - Предметно ориентированные языки (DSL) 2. [m2l2-coroutines](lessons/m2l2-coroutines) - Асинхронное и многопоточное программирование с корутинами - 3. [m2l3-flows](lessons/m2l3-flows) - Асинхронное и многопоточное программирование с Sequence и Flow + 3. [m2l3-flows](lessons/m2l3-flows) - Асинхронное и многопоточное программирование с Sequence и Flow 4. [m2l4-kmp](lessons/m2l4-kmp) - Мультиплатформенная разработка - 5. m2l5 - Интероперабельность Kotlin с другими языками + 5. m2l5 - Интероперабельность Kotlin с другими языками 1. [m2l5-1-interop](lessons/m2l5-1-interop) - Интероперабельность Kotlin Native с C 2. [m2l5-2-jni](lessons/m2l5-2-jni) - Интероперабельность Kotlin JVM с C + +## Проектные модули + +### Транспортные модели, API + +1. [specs](specs) - описание API в форме OpenAPI-спецификаций +2. [ok-marketplace-api-v1-jackson](ok-marketplace-be/ok-marketplace-api-v1-jackson) - Генерация первой версии + транспортных модеелй с Jackson +3. [ok-marketplace-api-v2-kmp](ok-marketplace-be/ok-marketplace-api-v2-kmp) - Генерация второй версии транспортных + моделей с KMP + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 179e6b5..f0714d8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,15 +1,30 @@ [versions] kotlin = "2.1.0" +kotlinx-datetime = "0.6.1" +kotlinx-serialization = "1.6.3" + +binaryCompabilityValidator = "0.13.2" + +openapi-generator = "7.3.0" +jackson = "2.16.1" # BASE jvm-compiler = "17" jvm-language = "21" [libraries] plugin-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } -plugin-binaryCompatibilityValidator = "org.jetbrains.kotlinx:binary-compatibility-validator:0.13.2" -kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version = "0.6.1" } +plugin-binaryCompatibilityValidator = { module = "org.jetbrains.kotlinx:binary-compatibility-validator", version.ref = "binaryCompabilityValidator" } +kotlinx-datetime = {module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime"} +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"} + +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"} [plugins] -kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } -kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +kotlin-multiplatform = {id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin"} +kotlin-jvm = {id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin"} +openapi-generator = {id = "org.openapi.generator", version.ref = "openapi-generator"} +crowdproj-generator = {id = "com.crowdproj.generator", version = "0.2.0"} +kotlinx-serialization = {id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin"} diff --git a/ok-marketplace-be/build.gradle.kts b/ok-marketplace-be/build.gradle.kts index 476bd15..b9690ab 100644 --- a/ok-marketplace-be/build.gradle.kts +++ b/ok-marketplace-be/build.gradle.kts @@ -3,7 +3,7 @@ plugins { alias(libs.plugins.kotlin.multiplatform) apply false } -group = "com.otus.otuskotlin.marketplace" +group = "ru.otus.otuskotlin.marketplace" version = "0.0.1" allprojects { @@ -17,18 +17,17 @@ subprojects { version = rootProject.version } +ext { + val specDir = layout.projectDirectory.dir("../specs") + set("spec-v1", specDir.file("specs-ad-v1.yaml").toString()) + set("spec-v2", specDir.file("specs-ad-v2.yaml").toString()) +} + tasks { - create("build") { - group = "build" - dependsOn(project(":ok-marketplace-tmp").getTasksByName("build",false)) - } - create("check") { - group = "verification" - subprojects.forEach { proj -> - println("PROJ $proj") - proj.getTasksByName("check", false).also { - this@create.dependsOn(it) - } + arrayOf("build", "clean", "check").forEach {tsk -> + create(tsk) { + group = "build" + dependsOn(subprojects.map { it.getTasksByName(tsk,false)}) } } } diff --git a/ok-marketplace-be/ok-marketplace-api-v1-jackson/build.gradle.kts b/ok-marketplace-be/ok-marketplace-api-v1-jackson/build.gradle.kts new file mode 100644 index 0000000..f47fc75 --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-api-v1-jackson/build.gradle.kts @@ -0,0 +1,59 @@ +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.v1" + generatorName.set("kotlin") // Это и есть активный генератор + packageName.set(openapiGroup) + apiPackage.set("$openapiGroup.api") + modelPackage.set("$openapiGroup.models") + invokerPackage.set("$openapiGroup.invoker") +// inputSpec.set("$specDir/specs-ad-v1.yaml") + inputSpec.set(rootProject.ext["spec-v1"] as String) + + /** + * Здесь указываем, что нам нужны только модели, все остальное не нужно + * https://openapi-generator.tech/docs/globals + */ + globalProperties.apply { + put("models", "") + put("modelDocs", "false") + } + + /** + * Настройка дополнительных параметров из документации по генератору + * 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.jackson.kotlin) + implementation(libs.jackson.datatype) + testImplementation(kotlin("test-junit")) +} + +tasks { + compileKotlin { + dependsOn(openApiGenerate) + } +} diff --git a/ok-marketplace-be/ok-marketplace-api-v1-jackson/src/main/kotlin/ApiV1Mapper.kt b/ok-marketplace-be/ok-marketplace-api-v1-jackson/src/main/kotlin/ApiV1Mapper.kt new file mode 100644 index 0000000..2a6f843 --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-api-v1-jackson/src/main/kotlin/ApiV1Mapper.kt @@ -0,0 +1,26 @@ +package ru.otus.otuskotlin.marketplace.api.v1 + +import com.fasterxml.jackson.databind.MapperFeature +import com.fasterxml.jackson.databind.json.JsonMapper +import ru.otus.otuskotlin.marketplace.api.v1.models.IRequest +import ru.otus.otuskotlin.marketplace.api.v1.models.IResponse +val apiV1Mapper = JsonMapper.builder().run { +// configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + enable(MapperFeature.USE_BASE_TYPE_AS_DEFAULT_IMPL) +// setSerializationInclusion(JsonInclude.Include.NON_NULL) + build() +} + +@Suppress("unused") +fun apiV1RequestSerialize(request: IRequest): String = apiV1Mapper.writeValueAsString(request) + +@Suppress("UNCHECKED_CAST", "unused") +fun apiV1RequestDeserialize(json: String): T = + apiV1Mapper.readValue(json, IRequest::class.java) as T + +@Suppress("unused") +fun apiV1ResponseSerialize(response: IResponse): String = apiV1Mapper.writeValueAsString(response) + +@Suppress("UNCHECKED_CAST", "unused") +fun apiV1ResponseDeserialize(json: String): T = + apiV1Mapper.readValue(json, IResponse::class.java) as T diff --git a/ok-marketplace-be/ok-marketplace-api-v1-jackson/src/test/kotlin/RequestV1SerializationTest.kt b/ok-marketplace-be/ok-marketplace-api-v1-jackson/src/test/kotlin/RequestV1SerializationTest.kt new file mode 100644 index 0000000..bd5168d --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-api-v1-jackson/src/test/kotlin/RequestV1SerializationTest.kt @@ -0,0 +1,49 @@ +package ru.otus.otuskotlin.marketplace.api.v1 + +import ru.otus.otuskotlin.marketplace.api.v1.models.* +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertEquals + +class RequestV1SerializationTest { + private val request = AdCreateRequest( + debug = AdDebug( + mode = AdRequestDebugMode.STUB, + stub = AdRequestDebugStubs.BAD_TITLE + ), + ad = AdCreateObject( + title = "ad title", + description = "ad description", + adType = DealSide.DEMAND, + visibility = AdVisibility.PUBLIC, + ) + ) + + @Test + fun serialize() { + val json = apiV1Mapper.writeValueAsString(request) + + assertContains(json, Regex("\"title\":\\s*\"ad title\"")) + assertContains(json, Regex("\"mode\":\\s*\"stub\"")) + assertContains(json, Regex("\"stub\":\\s*\"badTitle\"")) + assertContains(json, Regex("\"requestType\":\\s*\"create\"")) + } + + @Test + fun deserialize() { + val json = apiV1Mapper.writeValueAsString(request) + val obj = apiV1Mapper.readValue(json, IRequest::class.java) as AdCreateRequest + + assertEquals(request, obj) + } + + @Test + fun deserializeNaked() { + val jsonString = """ + {"ad": null} + """.trimIndent() + val obj = apiV1Mapper.readValue(jsonString, AdCreateRequest::class.java) + + assertEquals(null, obj.ad) + } +} diff --git a/ok-marketplace-be/ok-marketplace-api-v1-jackson/src/test/kotlin/ResponseV1SerializationTest.kt b/ok-marketplace-be/ok-marketplace-api-v1-jackson/src/test/kotlin/ResponseV1SerializationTest.kt new file mode 100644 index 0000000..9c9b378 --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-api-v1-jackson/src/test/kotlin/ResponseV1SerializationTest.kt @@ -0,0 +1,33 @@ +package ru.otus.otuskotlin.marketplace.api.v1 + +import ru.otus.otuskotlin.marketplace.api.v1.models.* +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertEquals + +class ResponseV1SerializationTest { + private val response = AdCreateResponse( + ad = AdResponseObject( + title = "ad title", + description = "ad description", + adType = DealSide.DEMAND, + visibility = AdVisibility.PUBLIC, + ) + ) + + @Test + fun serialize() { + val json = apiV1Mapper.writeValueAsString(response) + + assertContains(json, Regex("\"title\":\\s*\"ad title\"")) + assertContains(json, Regex("\"responseType\":\\s*\"create\"")) + } + + @Test + fun deserialize() { + val json = apiV1Mapper.writeValueAsString(response) + val obj = apiV1Mapper.readValue(json, IResponse::class.java) as AdCreateResponse + + assertEquals(response, obj) + } +} diff --git a/ok-marketplace-be/ok-marketplace-api-v2-kmp/build.gradle.kts b/ok-marketplace-be/ok-marketplace-api-v2-kmp/build.gradle.kts new file mode 100644 index 0000000..7766775 --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-api-v2-kmp/build.gradle.kts @@ -0,0 +1,48 @@ +import org.openapitools.generator.gradle.plugin.tasks.GenerateTask + +plugins { + id("build-kmp") + alias(libs.plugins.crowdproj.generator) + alias(libs.plugins.kotlinx.serialization) +} + +crowdprojGenerate { + packageName.set("${project.group}.api.v2") + inputSpec.set(rootProject.ext["spec-v2"] as String) +} + +kotlin { + sourceSets { + val serializationVersion: String by project + val commonMain by getting { + kotlin.srcDirs(layout.buildDirectory.dir("generate-resources/src/commonMain/kotlin")) + dependencies { + implementation(kotlin("stdlib-common")) + + implementation(libs.kotlinx.serialization.core) + implementation(libs.kotlinx.serialization.json) + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test-common")) + implementation(kotlin("test-annotations-common")) + } + } + val jvmTest by getting { + dependencies { + implementation(kotlin("test-junit")) + } + } + } +} + +tasks { + val openApiGenerateTask: GenerateTask = getByName("openApiGenerate", GenerateTask::class) { + outputDir.set(layout.buildDirectory.file("generate-resources").get().toString()) + finalizedBy("compileCommonMainKotlinMetadata") + } + filter { it.name.startsWith("compile") }.forEach { + it.dependsOn(openApiGenerateTask) + } +} diff --git a/ok-marketplace-be/ok-marketplace-api-v2-kmp/src/commonMain/kotlin/MkplAdApiSerializer.kt b/ok-marketplace-be/ok-marketplace-api-v2-kmp/src/commonMain/kotlin/MkplAdApiSerializer.kt new file mode 100644 index 0000000..7547ca0 --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-api-v2-kmp/src/commonMain/kotlin/MkplAdApiSerializer.kt @@ -0,0 +1,27 @@ +@file:Suppress("unused") + +package ru.otus.otuskotlin.marketplace.api.v2 + +import kotlinx.serialization.json.Json +import ru.otus.otuskotlin.marketplace.api.v2.models.IRequest +import ru.otus.otuskotlin.marketplace.api.v2.models.IResponse + +@Suppress("JSON_FORMAT_REDUNDANT_DEFAULT") +val apiV2Mapper = Json { +// ignoreUnknownKeys = true +} + +@Suppress("UNCHECKED_CAST") +fun apiV2RequestDeserialize(json: String) = + apiV2Mapper.decodeFromString(json) as T + +fun apiV2ResponseSerialize(obj: IResponse): String = + apiV2Mapper.encodeToString(IResponse.serializer(), obj) + +@Suppress("UNCHECKED_CAST") +fun apiV2ResponseDeserialize(json: String) = + apiV2Mapper.decodeFromString(json) as T + +@Suppress("unused") +fun apiV2RequestSerialize(obj: IRequest): String = + apiV2Mapper.encodeToString(IRequest.serializer(), obj) diff --git a/ok-marketplace-be/ok-marketplace-api-v2-kmp/src/commonTest/kotlin/RequestV2SerializationTest.kt b/ok-marketplace-be/ok-marketplace-api-v2-kmp/src/commonTest/kotlin/RequestV2SerializationTest.kt new file mode 100644 index 0000000..768a685 --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-api-v2-kmp/src/commonTest/kotlin/RequestV2SerializationTest.kt @@ -0,0 +1,51 @@ +package ru.otus.otuskotlin.marketplace.api.v2 + +import kotlinx.serialization.encodeToString +import ru.otus.otuskotlin.marketplace.api.v2.models.* +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertEquals + +class RequestV2SerializationTest { + private val request: IRequest = AdCreateRequest( + debug = AdDebug( + mode = AdRequestDebugMode.STUB, + stub = AdRequestDebugStubs.BAD_TITLE + ), + ad = AdCreateObject( + title = "ad title", + description = "ad description", + adType = DealSide.DEMAND, + visibility = AdVisibility.PUBLIC, + ) + ) + + @Test + fun serialize() { + val json = apiV2Mapper.encodeToString(IRequest.serializer(), request) + + println(json) + + assertContains(json, Regex("\"title\":\\s*\"ad title\"")) + assertContains(json, Regex("\"mode\":\\s*\"stub\"")) + assertContains(json, Regex("\"stub\":\\s*\"badTitle\"")) + assertContains(json, Regex("\"requestType\":\\s*\"create\"")) + } + + @Test + fun deserialize() { + val json = apiV2Mapper.encodeToString(request) + val obj = apiV2Mapper.decodeFromString(json) as AdCreateRequest + + assertEquals(request, obj) + } + @Test + fun deserializeNaked() { + val jsonString = """ + {"ad": null} + """.trimIndent() + val obj = apiV2Mapper.decodeFromString(jsonString) + + assertEquals(null, obj.ad) + } +} diff --git a/ok-marketplace-be/ok-marketplace-api-v2-kmp/src/commonTest/kotlin/ResponseV2SerializationTest.kt b/ok-marketplace-be/ok-marketplace-api-v2-kmp/src/commonTest/kotlin/ResponseV2SerializationTest.kt new file mode 100644 index 0000000..3bf9b83 --- /dev/null +++ b/ok-marketplace-be/ok-marketplace-api-v2-kmp/src/commonTest/kotlin/ResponseV2SerializationTest.kt @@ -0,0 +1,38 @@ +package ru.otus.otuskotlin.marketplace.api.v2 + +import kotlinx.serialization.encodeToString +import ru.otus.otuskotlin.marketplace.api.v2.models.* +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertEquals + +class ResponseV2SerializationTest { + private val response: IResponse = AdCreateResponse( + ad = AdResponseObject( + title = "ad title", + description = "ad description", + adType = DealSide.DEMAND, + visibility = AdVisibility.PUBLIC, + ) + ) + + @Test + fun serialize() { +// val json = apiV2Mapper.encodeToString(AdRequestSerializer1, request) +// val json = apiV2Mapper.encodeToString(RequestSerializers.create, request) + val json = apiV2Mapper.encodeToString(response) + + println(json) + + assertContains(json, Regex("\"title\":\\s*\"ad title\"")) + assertContains(json, Regex("\"responseType\":\\s*\"create\"")) + } + + @Test + fun deserialize() { + val json = apiV2Mapper.encodeToString(response) + val obj = apiV2Mapper.decodeFromString(json) as AdCreateResponse + + assertEquals(response, obj) + } +} diff --git a/ok-marketplace-be/ok-marketplace-tmp/build.gradle.kts b/ok-marketplace-be/ok-marketplace-tmp/build.gradle.kts index c46e47a..e69de29 100644 --- a/ok-marketplace-be/ok-marketplace-tmp/build.gradle.kts +++ b/ok-marketplace-be/ok-marketplace-tmp/build.gradle.kts @@ -1,3 +0,0 @@ -plugins { - id("build-jvm") -} \ No newline at end of file diff --git a/ok-marketplace-be/settings.gradle.kts b/ok-marketplace-be/settings.gradle.kts index 3d5c637..6a8d805 100644 --- a/ok-marketplace-be/settings.gradle.kts +++ b/ok-marketplace-be/settings.gradle.kts @@ -28,4 +28,5 @@ plugins { //implementation(projects.m2l5Gradle.sub1.ssub1) enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") -include(":ok-marketplace-tmp") +include(":ok-marketplace-api-v1-jackson") +include(":ok-marketplace-api-v2-kmp") diff --git a/specs/specs-ad-v1.yaml b/specs/specs-ad-v1.yaml new file mode 100644 index 0000000..631d39c --- /dev/null +++ b/specs/specs-ad-v1.yaml @@ -0,0 +1,452 @@ +openapi: 3.0.4 +info: + title: "Marketplace ${VERSION_APP}" + description: This is a place where sellers and buyers meat each other + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.0 +servers: + - url: http://localhost:8080/v1 +tags: + - name: ad + description: Объявление (о покупке или продаже) +paths: + /ad/create: + post: + tags: + - ad + summary: Create ad + operationId: adCreate + requestBody: + description: Request body + content: + application/json: + schema: + $ref: '#/components/schemas/AdCreateRequest' + required: true + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/AdCreateResponse' + /ad/read: + post: + tags: + - ad + summary: Read ad + operationId: adRead + requestBody: + description: Request body + content: + application/json: + schema: + $ref: '#/components/schemas/AdReadRequest' + required: true + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/AdReadResponse' + /ad/update: + post: + tags: + - ad + summary: Update ad + operationId: adUpdate + requestBody: + description: Request body + content: + application/json: + schema: + $ref: '#/components/schemas/AdUpdateRequest' + required: true + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/AdUpdateResponse' + /ad/delete: + post: + tags: + - ad + summary: Delete ad + operationId: adDelete + requestBody: + description: Request body + content: + application/json: + schema: + $ref: '#/components/schemas/AdDeleteRequest' + required: true + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/AdDeleteResponse' + /ad/search: + post: + tags: + - ad + summary: Search ad + operationId: adSearch + requestBody: + description: Request body + content: + application/json: + schema: + $ref: '#/components/schemas/AdSearchRequest' + required: true + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/AdSearchResponse' + /ad/offers: + post: + tags: + - ad + summary: Search offers + operationId: adOffers + requestBody: + description: Request body + content: + application/json: + schema: + $ref: '#/components/schemas/AdOffersRequest' + required: true + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/AdOffersResponse' + +components: + schemas: + + IRequest: + type: object + description: Базовый интерфейс для всех запросов + properties: + requestType: + type: string + description: Поле-дескриминатор для вычисления типа запроса + example: create + discriminator: + propertyName: requestType + mapping: + create: '#/components/schemas/AdCreateRequest' + read: '#/components/schemas/AdReadRequest' + update: '#/components/schemas/AdUpdateRequest' + delete: '#/components/schemas/AdDeleteRequest' + search: '#/components/schemas/AdSearchRequest' + offers: '#/components/schemas/AdOffersRequest' + + Error: + type: object + properties: + code: + type: string + group: + type: string + field: + type: string + message: + type: string + + ResponseResult: + type: string + enum: + - success + - error + + IResponse: + type: object + description: Базовый интерфейс для всех ответов + properties: + responseType: + type: string + description: Поле-дескриминатор для вычисления типа запроса + example: create + result: + $ref: '#/components/schemas/ResponseResult' + errors: + type: array + items: + $ref: '#/components/schemas/Error' + + discriminator: + propertyName: responseType + mapping: + create: '#/components/schemas/AdCreateResponse' + read: '#/components/schemas/AdReadResponse' + update: '#/components/schemas/AdUpdateResponse' + delete: '#/components/schemas/AdDeleteResponse' + search: '#/components/schemas/AdSearchResponse' + offers: '#/components/schemas/AdOffersResponse' + init: '#/components/schemas/AdInitResponse' + + + UserId: + type: string + description: Идентификатор пользователя + AdId: + type: string + description: Идентификатор объявления + AdLock: + type: string + description: Версия оптимистичной блокировки + + BaseAd: + type: object + description: Объект описывает свойства, одинаковые для create и update + properties: + title: + type: string + description: Заголовок объявления + description: + type: string + description: Описание объявления + adType: + $ref: '#/components/schemas/DealSide' + visibility: + $ref: '#/components/schemas/AdVisibility' + + DealSide: + type: string + description: 'Сторона сделки: спрос или предложение' + enum: + - demand + - supply + + AdVisibility: + type: string + description: 'Тип видимости объявления. Возможные значения: видит только владелец, только зарегистрированный в системе пользователь, видимо всем' + enum: + - ownerOnly + - registeredOnly + - public + + AdInitResponse: + allOf: + - $ref: '#/components/schemas/IResponse' + + AdCreateObject: + allOf: + - $ref: '#/components/schemas/BaseAd' + + AdCreateRequest: + allOf: + - $ref: '#/components/schemas/IRequest' + - $ref: '#/components/schemas/AdRequestDebug' + - type: object + properties: + ad: + $ref: '#/components/schemas/AdCreateObject' + + AdReadObject: + allOf: + - type: object + properties: + id: + $ref: '#/components/schemas/AdId' + + AdReadRequest: + allOf: + - $ref: '#/components/schemas/IRequest' + - $ref: '#/components/schemas/AdRequestDebug' + - type: object + properties: + ad: + $ref: '#/components/schemas/AdReadObject' + + AdUpdateObject: + allOf: + - $ref: '#/components/schemas/BaseAd' + - type: object + properties: + id: + $ref: '#/components/schemas/AdId' + lock: + $ref: '#/components/schemas/AdLock' + + AdUpdateRequest: + allOf: + - $ref: '#/components/schemas/IRequest' + - $ref: '#/components/schemas/AdRequestDebug' + - type: object + properties: + ad: + $ref: '#/components/schemas/AdUpdateObject' + + AdDeleteObject: + allOf: + - type: object + properties: + id: + $ref: '#/components/schemas/AdId' + lock: + $ref: '#/components/schemas/AdLock' + + AdDeleteRequest: + allOf: + - $ref: '#/components/schemas/IRequest' + - $ref: '#/components/schemas/AdRequestDebug' + - type: object + properties: + ad: + $ref: '#/components/schemas/AdDeleteObject' + + AdSearchFilter: + type: object + description: Набор фильтров для поиска + properties: + searchString: + type: string + description: Поисковая строка, которая будет искаться в объявлениях + + AdSearchRequest: + allOf: + - $ref: '#/components/schemas/IRequest' + - $ref: '#/components/schemas/AdRequestDebug' + - type: object + properties: + adFilter: + $ref: '#/components/schemas/AdSearchFilter' + + AdOffersObject: + $ref: '#/components/schemas/AdReadObject' + + AdOffersRequest: + allOf: + - $ref: '#/components/schemas/IRequest' + - $ref: '#/components/schemas/AdRequestDebug' + - type: object + properties: + ad: + $ref: '#/components/schemas/AdOffersObject' + + AdResponseObject: + allOf: + - $ref: '#/components/schemas/BaseAd' + - type: object + description: Объект, который возвращается в ответе бэкенда + properties: + id: + $ref: '#/components/schemas/AdId' + ownerId: + $ref: '#/components/schemas/UserId' + lock: + $ref: '#/components/schemas/AdLock' + permissions: + type: array + uniqueItems: true + items: + $ref: '#/components/schemas/AdPermissions' + + AdPermissions: + type: string + description: Доступы для клиента для операций над объявлением + enum: + - read + - update + - delete + - makeVisiblePublic + - makeVisibleOwn + - makeVisibleGroup + + AdResponseSingle: + allOf: + - type: object + description: Ответ с одним объектом объявления + properties: + ad: + $ref: '#/components/schemas/AdResponseObject' + + AdResponseMulti: + allOf: + - type: object + description: Список найденных объектов + properties: + ads: + type: array + items: + $ref: '#/components/schemas/AdResponseObject' + + AdCreateResponse: + allOf: + - $ref: '#/components/schemas/IResponse' + - $ref: '#/components/schemas/AdResponseSingle' + + AdReadResponse: + allOf: + - $ref: '#/components/schemas/IResponse' + - $ref: '#/components/schemas/AdResponseSingle' + + AdUpdateResponse: + allOf: + - $ref: '#/components/schemas/IResponse' + - $ref: '#/components/schemas/AdResponseSingle' + + AdDeleteResponse: + allOf: + - $ref: '#/components/schemas/IResponse' + - $ref: '#/components/schemas/AdResponseSingle' + + AdSearchResponse: + allOf: + - $ref: '#/components/schemas/IResponse' + - $ref: '#/components/schemas/AdResponseMulti' + + AdOffersResponse: + allOf: + - $ref: '#/components/schemas/IResponse' + - $ref: '#/components/schemas/AdResponseSingle' + - $ref: '#/components/schemas/AdResponseMulti' + + # STUBS ====================== + AdRequestDebugMode: + type: string + enum: + - prod + - test + - stub + + AdRequestDebug: + type: object + properties: + debug: + $ref: '#/components/schemas/AdDebug' + + AdDebug: + type: object + properties: + mode: + $ref: '#/components/schemas/AdRequestDebugMode' + stub: + $ref: '#/components/schemas/AdRequestDebugStubs' + + AdRequestDebugStubs: + type: string + description: Перечисления всех стабов + enum: + - success + - notFound + - badId + - badTitle + - badDescription + - badVisibility + - cannotDelete + - badSearchString diff --git a/specs/specs-ad-v2.yaml b/specs/specs-ad-v2.yaml new file mode 100644 index 0000000..ea16a7c --- /dev/null +++ b/specs/specs-ad-v2.yaml @@ -0,0 +1,457 @@ +openapi: 3.0.4 +info: + title: "Marketplace ${VERSION_APP}" + description: This is a place where sellers and buyers meat each other + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.0 +servers: + - url: http://localhost:8080/v2 +tags: + - name: ad + description: Объявление (о покупке или продаже) +paths: + /ad/create: + post: + tags: + - ad + summary: Create ad + operationId: adCreate + requestBody: + description: Request body + content: + application/json: + schema: + $ref: '#/components/schemas/AdCreateRequest' + required: true + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/AdCreateResponse' + /ad/read: + post: + tags: + - ad + summary: Read ad + operationId: adRead + requestBody: + description: Request body + content: + application/json: + schema: + $ref: '#/components/schemas/AdReadRequest' + required: true + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/AdReadResponse' + /ad/update: + post: + tags: + - ad + summary: Update ad + operationId: adUpdate + requestBody: + description: Request body + content: + application/json: + schema: + $ref: '#/components/schemas/AdUpdateRequest' + required: true + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/AdUpdateResponse' + /ad/delete: + post: + tags: + - ad + summary: Delete ad + operationId: adDelete + requestBody: + description: Request body + content: + application/json: + schema: + $ref: '#/components/schemas/AdDeleteRequest' + required: true + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/AdDeleteResponse' + /ad/search: + post: + tags: + - ad + summary: Search ad + operationId: adSearch + requestBody: + description: Request body + content: + application/json: + schema: + $ref: '#/components/schemas/AdSearchRequest' + required: true + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/AdSearchResponse' + /ad/offers: + post: + tags: + - ad + summary: Search offers + operationId: adOffers + requestBody: + description: Request body + content: + application/json: + schema: + $ref: '#/components/schemas/AdOffersRequest' + required: true + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/AdOffersResponse' + +components: + schemas: + + IRequest: + type: object + description: Базовый интерфейс для всех запросов + properties: + requestType: + type: string + description: Поле-дескриминатор для вычисления типа запроса + example: create + discriminator: + propertyName: requestType + mapping: + create: '#/components/schemas/AdCreateRequest' + read: '#/components/schemas/AdReadRequest' + update: '#/components/schemas/AdUpdateRequest' + delete: '#/components/schemas/AdDeleteRequest' + search: '#/components/schemas/AdSearchRequest' + offers: '#/components/schemas/AdOffersRequest' + + Error: + type: object + properties: + code: + type: string + group: + type: string + field: + type: string + message: + type: string + + ResponseResult: + type: string + enum: + - success + - error + + IResponse: + type: object + description: Базовый интерфейс для всех ответов + properties: + responseType: + type: string + description: Поле-дескриминатор для вычисления типа запроса + example: create + result: + $ref: '#/components/schemas/ResponseResult' + errors: + type: array + items: + $ref: '#/components/schemas/Error' + + discriminator: + propertyName: responseType + mapping: + create: '#/components/schemas/AdCreateResponse' + read: '#/components/schemas/AdReadResponse' + update: '#/components/schemas/AdUpdateResponse' + delete: '#/components/schemas/AdDeleteResponse' + search: '#/components/schemas/AdSearchResponse' + offers: '#/components/schemas/AdOffersResponse' + init: '#/components/schemas/AdInitResponse' + + + UserId: + type: string + description: Идентификатор пользователя + AdId: + type: string + description: Идентификатор объявления + AdLock: + type: string + description: Версия оптимистичной блокировки + ProductId: + type: string + description: Идентификатор модели продукта, к которому относится объявление + + BaseAd: + type: object + description: Объект описывает свойства, одинаковые для create и update + properties: + title: + type: string + description: Заголовок объявления + description: + type: string + description: Описание объявления + adType: + $ref: '#/components/schemas/DealSide' + visibility: + $ref: '#/components/schemas/AdVisibility' + productId: + $ref: '#/components/schemas/ProductId' + + DealSide: + type: string + description: 'Сторона сделки: спрос или предложение' + enum: + - demand + - supply + + AdVisibility: + type: string + description: 'Тип видимости объявления. Возможные значения: видит только владелец, только зарегистрированный в системе пользователь, видимо всем' + enum: + - ownerOnly + - registeredOnly + - public + + AdInitResponse: + allOf: + - $ref: '#/components/schemas/IResponse' + + AdCreateObject: + allOf: + - $ref: '#/components/schemas/BaseAd' + + AdCreateRequest: + allOf: + - $ref: '#/components/schemas/IRequest' + - $ref: '#/components/schemas/AdRequestDebug' + - type: object + properties: + ad: + $ref: '#/components/schemas/AdCreateObject' + + AdReadObject: + allOf: + - type: object + properties: + id: + $ref: '#/components/schemas/AdId' + + AdReadRequest: + allOf: + - $ref: '#/components/schemas/IRequest' + - $ref: '#/components/schemas/AdRequestDebug' + - type: object + properties: + ad: + $ref: '#/components/schemas/AdReadObject' + + AdUpdateObject: + allOf: + - $ref: '#/components/schemas/BaseAd' + - type: object + properties: + id: + $ref: '#/components/schemas/AdId' + lock: + $ref: '#/components/schemas/AdLock' + + AdUpdateRequest: + allOf: + - $ref: '#/components/schemas/IRequest' + - $ref: '#/components/schemas/AdRequestDebug' + - type: object + properties: + ad: + $ref: '#/components/schemas/AdUpdateObject' + + AdDeleteObject: + allOf: + - type: object + properties: + id: + $ref: '#/components/schemas/AdId' + lock: + $ref: '#/components/schemas/AdLock' + + AdDeleteRequest: + allOf: + - $ref: '#/components/schemas/IRequest' + - $ref: '#/components/schemas/AdRequestDebug' + - type: object + properties: + ad: + $ref: '#/components/schemas/AdDeleteObject' + + AdSearchFilter: + type: object + description: Набор фильтров для поиска + properties: + searchString: + type: string + description: Поисковая строка, которая будет искаться в объявлениях + + AdSearchRequest: + allOf: + - $ref: '#/components/schemas/IRequest' + - $ref: '#/components/schemas/AdRequestDebug' + - type: object + properties: + adFilter: + $ref: '#/components/schemas/AdSearchFilter' + + AdOffersObject: + $ref: '#/components/schemas/AdReadObject' + + AdOffersRequest: + allOf: + - $ref: '#/components/schemas/IRequest' + - $ref: '#/components/schemas/AdRequestDebug' + - type: object + properties: + ad: + $ref: '#/components/schemas/AdOffersObject' + + AdResponseObject: + allOf: + - $ref: '#/components/schemas/BaseAd' + - type: object + description: Объект, который возвращается в ответе бэкенда + properties: + id: + $ref: '#/components/schemas/AdId' + ownerId: + $ref: '#/components/schemas/UserId' + lock: + $ref: '#/components/schemas/AdLock' + permissions: + type: array + uniqueItems: true + items: + $ref: '#/components/schemas/AdPermissions' + + AdPermissions: + type: string + description: Доступы для клиента для операций над объявлением + enum: + - read + - update + - delete + - makeVisiblePublic + - makeVisibleOwn + - makeVisibleGroup + + AdResponseSingle: + allOf: + - type: object + description: Ответ с одним объектом объявления + properties: + ad: + $ref: '#/components/schemas/AdResponseObject' + + AdResponseMulti: + allOf: + - type: object + description: Список найденных объектов + properties: + ads: + type: array + items: + $ref: '#/components/schemas/AdResponseObject' + + AdCreateResponse: + allOf: + - $ref: '#/components/schemas/IResponse' + - $ref: '#/components/schemas/AdResponseSingle' + + AdReadResponse: + allOf: + - $ref: '#/components/schemas/IResponse' + - $ref: '#/components/schemas/AdResponseSingle' + + AdUpdateResponse: + allOf: + - $ref: '#/components/schemas/IResponse' + - $ref: '#/components/schemas/AdResponseSingle' + + AdDeleteResponse: + allOf: + - $ref: '#/components/schemas/IResponse' + - $ref: '#/components/schemas/AdResponseSingle' + + AdSearchResponse: + allOf: + - $ref: '#/components/schemas/IResponse' + - $ref: '#/components/schemas/AdResponseMulti' + + AdOffersResponse: + allOf: + - $ref: '#/components/schemas/IResponse' + - $ref: '#/components/schemas/AdResponseSingle' + - $ref: '#/components/schemas/AdResponseMulti' + + # STUBS ====================== + AdRequestDebugMode: + type: string + enum: + - prod + - test + - stub + + AdRequestDebug: + type: object + properties: + debug: + $ref: '#/components/schemas/AdDebug' + + AdDebug: + type: object + properties: + mode: + $ref: '#/components/schemas/AdRequestDebugMode' + stub: + $ref: '#/components/schemas/AdRequestDebugStubs' + + AdRequestDebugStubs: + type: string + description: Перечисления всех стабов + enum: + - success + - notFound + - badId + - badTitle + - badDescription + - badVisibility + - cannotDelete + - badSearchString