From fd5659dd3bf9fe46c5802c6599b4112022c6072b Mon Sep 17 00:00:00 2001 From: Maksim Mishenko Date: Sat, 5 Aug 2023 00:20:02 +0600 Subject: [PATCH 1/6] [+] create contract module with dto, api, ktor plugin --- contract/build.gradle.kts | 22 ++++++++ .../band/effective/office/network/api/Api.kt | 54 +++++++++++++++++++ .../office/network/dto/BookingInfo.kt | 9 ++++ .../office/network/dto/IntegrationDTO.kt | 6 +++ .../office/network/dto/SuccessResponse.kt | 5 ++ .../effective/office/network/dto/UserDTO.kt | 10 ++++ .../office/network/dto/UtilityDTO.kt | 8 +++ .../office/network/dto/WorkspaceDTO.kt | 8 +++ .../effective/office/network/model/Either.kt | 6 +++ .../office/network/model/ErrorResponse.kt | 15 ++++++ .../office/utils/KtorEitherPlagin.kt | 23 ++++++++ .../effective/office/utils/KtorEtherClient.kt | 36 +++++++++++++ settings.gradle.kts | 3 +- 13 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 contract/build.gradle.kts create mode 100644 contract/src/main/kotlin/band/effective/office/network/api/Api.kt create mode 100644 contract/src/main/kotlin/band/effective/office/network/dto/BookingInfo.kt create mode 100644 contract/src/main/kotlin/band/effective/office/network/dto/IntegrationDTO.kt create mode 100644 contract/src/main/kotlin/band/effective/office/network/dto/SuccessResponse.kt create mode 100644 contract/src/main/kotlin/band/effective/office/network/dto/UserDTO.kt create mode 100644 contract/src/main/kotlin/band/effective/office/network/dto/UtilityDTO.kt create mode 100644 contract/src/main/kotlin/band/effective/office/network/dto/WorkspaceDTO.kt create mode 100644 contract/src/main/kotlin/band/effective/office/network/model/Either.kt create mode 100644 contract/src/main/kotlin/band/effective/office/network/model/ErrorResponse.kt create mode 100644 contract/src/main/kotlin/band/effective/office/utils/KtorEitherPlagin.kt create mode 100644 contract/src/main/kotlin/band/effective/office/utils/KtorEtherClient.kt diff --git a/contract/build.gradle.kts b/contract/build.gradle.kts new file mode 100644 index 00000000..96c3b81e --- /dev/null +++ b/contract/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + id(Plugins.Android.plugin) + id(Plugins.MultiplatformCompose.plugin) + id(Plugins.Kotlin.plugin) + id(Plugins.Parcelize.plugin) + id(Plugins.Libres.plugin) +} + +kotlin { + android { + compilations.all { + kotlinOptions { + jvmTarget = "1.8" + } + } + } + dependencies{ + api(Dependencies.Ktor.Client.Core) + implementation(Dependencies.KotlinxSerialization.json) + implementation("io.ktor:ktor-client-cio:2.3.2") + } +} \ No newline at end of file diff --git a/contract/src/main/kotlin/band/effective/office/network/api/Api.kt b/contract/src/main/kotlin/band/effective/office/network/api/Api.kt new file mode 100644 index 00000000..0f2d98c6 --- /dev/null +++ b/contract/src/main/kotlin/band/effective/office/network/api/Api.kt @@ -0,0 +1,54 @@ +package band.effective.office.network.api + +import band.effective.office.network.dto.BookingInfo +import band.effective.office.network.dto.SuccessResponse +import band.effective.office.network.dto.UserDTO +import band.effective.office.network.dto.WorkspaceDTO +import band.effective.office.network.model.Either +import band.effective.office.network.model.ErrorResponse +import kotlinx.coroutines.flow.Flow + +interface Api { + /**Get workspace by id + * @param id workspace id + * @return if response is success when return workspace info*/ + suspend fun getWorkspace(id: String): Either + + /**Get all workspace current type + * @param tag workspace type. Meeting or regular + * @return if response is success when return list of workspaces*/ + suspend fun getWorkspaces(tag: String): Either> + + /**Get user by id + * @param id user id + * @return if response is success when return user info*/ + suspend fun getUser(id: String): Either + + /**Get all users + * @return if response is success when return users list*/ + suspend fun getUsers(): Either> + + /**Get user's bookings*/ + suspend fun getBookingsByUser(userId: String): Either> + + /**Booking workspace*/ + suspend fun booking(bookingInfo: BookingInfo): Either + + /**Update booking info*/ + suspend fun changeBooking( + bookingId: String, + bookingInfo: BookingInfo + ): Either + + /**Delete booking*/ + suspend fun deleteBooking( + bookingId: String, + bookingInfo: BookingInfo + ): Either + + /**Subscribe on workspace info updates*/ + suspend fun subscribeOnWorkspaceUpdates(id: String): Flow> + + /**Subscribe on organizers list updates*/ + suspend fun subscribeOnOrganizersList(): Flow>> +} \ No newline at end of file diff --git a/contract/src/main/kotlin/band/effective/office/network/dto/BookingInfo.kt b/contract/src/main/kotlin/band/effective/office/network/dto/BookingInfo.kt new file mode 100644 index 00000000..027ee10c --- /dev/null +++ b/contract/src/main/kotlin/band/effective/office/network/dto/BookingInfo.kt @@ -0,0 +1,9 @@ +package band.effective.office.network.dto + +data class BookingInfo( + val id: String, + val begin: Long, + val end: Long, + val owner: UserDTO, + val participants: List +) \ No newline at end of file diff --git a/contract/src/main/kotlin/band/effective/office/network/dto/IntegrationDTO.kt b/contract/src/main/kotlin/band/effective/office/network/dto/IntegrationDTO.kt new file mode 100644 index 00000000..32dbc9e4 --- /dev/null +++ b/contract/src/main/kotlin/band/effective/office/network/dto/IntegrationDTO.kt @@ -0,0 +1,6 @@ +package band.effective.office.network.dto + +data class IntegrationDTO( + val name: String, + val value: String +) \ No newline at end of file diff --git a/contract/src/main/kotlin/band/effective/office/network/dto/SuccessResponse.kt b/contract/src/main/kotlin/band/effective/office/network/dto/SuccessResponse.kt new file mode 100644 index 00000000..6637b077 --- /dev/null +++ b/contract/src/main/kotlin/band/effective/office/network/dto/SuccessResponse.kt @@ -0,0 +1,5 @@ +package band.effective.office.network.dto + +data class SuccessResponse( + val status: String +) \ No newline at end of file diff --git a/contract/src/main/kotlin/band/effective/office/network/dto/UserDTO.kt b/contract/src/main/kotlin/band/effective/office/network/dto/UserDTO.kt new file mode 100644 index 00000000..9b90c383 --- /dev/null +++ b/contract/src/main/kotlin/band/effective/office/network/dto/UserDTO.kt @@ -0,0 +1,10 @@ +package band.effective.office.network.dto + +data class UserDTO( + val active: Boolean, + val avatarUrl: String, + val fullName: String, + val id: String, + val integrations: List, + val role: String +) \ No newline at end of file diff --git a/contract/src/main/kotlin/band/effective/office/network/dto/UtilityDTO.kt b/contract/src/main/kotlin/band/effective/office/network/dto/UtilityDTO.kt new file mode 100644 index 00000000..292c0f0e --- /dev/null +++ b/contract/src/main/kotlin/band/effective/office/network/dto/UtilityDTO.kt @@ -0,0 +1,8 @@ +package band.effective.office.network.dto + +data class UtilityDTO( + val count: Int, + val iconUrl: String, + val id: String, + val name: String +) \ No newline at end of file diff --git a/contract/src/main/kotlin/band/effective/office/network/dto/WorkspaceDTO.kt b/contract/src/main/kotlin/band/effective/office/network/dto/WorkspaceDTO.kt new file mode 100644 index 00000000..9577d17d --- /dev/null +++ b/contract/src/main/kotlin/band/effective/office/network/dto/WorkspaceDTO.kt @@ -0,0 +1,8 @@ +package band.effective.office.network.dto + +// TODO(Mishnko Maksim): tablet must get events list in workspace +data class WorkspaceDTO( + val id: String, + val name: String, + val utilities: List +) \ No newline at end of file diff --git a/contract/src/main/kotlin/band/effective/office/network/model/Either.kt b/contract/src/main/kotlin/band/effective/office/network/model/Either.kt new file mode 100644 index 00000000..62ab0cc4 --- /dev/null +++ b/contract/src/main/kotlin/band/effective/office/network/model/Either.kt @@ -0,0 +1,6 @@ +package band.effective.office.network.model + +sealed interface Either { + data class Error(val error: ErrorType) : Either + data class Success(val data: DataType) : Either +} \ No newline at end of file diff --git a/contract/src/main/kotlin/band/effective/office/network/model/ErrorResponse.kt b/contract/src/main/kotlin/band/effective/office/network/model/ErrorResponse.kt new file mode 100644 index 00000000..d03e55bb --- /dev/null +++ b/contract/src/main/kotlin/band/effective/office/network/model/ErrorResponse.kt @@ -0,0 +1,15 @@ +package band.effective.office.network.model + +data class ErrorResponse(val code: Int, val description: String) { + companion object { + fun getResponse(code: Int): ErrorResponse { + val description = when (code) { + 404 -> "Not found" + in 400..499 -> "Pet not found" + in 500..599 -> "Server error" + else -> "Unknown error" + } + return ErrorResponse(code, description) + } + } +} \ No newline at end of file diff --git a/contract/src/main/kotlin/band/effective/office/utils/KtorEitherPlagin.kt b/contract/src/main/kotlin/band/effective/office/utils/KtorEitherPlagin.kt new file mode 100644 index 00000000..e1fbfed2 --- /dev/null +++ b/contract/src/main/kotlin/band/effective/office/utils/KtorEitherPlagin.kt @@ -0,0 +1,23 @@ +package band.effective.office.utils + +import band.effective.office.network.model.Either +import band.effective.office.network.model.ErrorResponse +import io.ktor.client.plugins.api.createClientPlugin +import io.ktor.utils.io.readUTF8Line +import kotlinx.serialization.json.Json +import kotlinx.serialization.serializer + +val KtorEitherPlugin = createClientPlugin("KtorEitherPlugin") { + transformResponseBody { response, content, requestedType -> + if (response.status.value in 200..299) { + Either.Success( + Json.decodeFromString( + serializer(requestedType.kotlinType!!.arguments[1].type!!), + content.readUTF8Line()!! + ) + ) + } else { + Either.Error(ErrorResponse.getResponse(response.status.value)) + } + } +} \ No newline at end of file diff --git a/contract/src/main/kotlin/band/effective/office/utils/KtorEtherClient.kt b/contract/src/main/kotlin/band/effective/office/utils/KtorEtherClient.kt new file mode 100644 index 00000000..c27f4f3e --- /dev/null +++ b/contract/src/main/kotlin/band/effective/office/utils/KtorEtherClient.kt @@ -0,0 +1,36 @@ +package band.effective.office.utils + +import band.effective.office.network.model.Either +import band.effective.office.network.model.ErrorResponse +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.engine.cio.CIO +import io.ktor.client.request.HttpRequestBuilder +import io.ktor.client.request.delete +import io.ktor.client.request.get +import io.ktor.client.request.post +import io.ktor.client.request.put + +object KtorEtherClient { + val httpClient = HttpClient(CIO) { + install(KtorEitherPlugin) + } + + enum class RestMethod { Get, Post, Delete, Put } + + suspend inline fun securityResponse( + urlString: String, + block: HttpRequestBuilder.() -> Unit = {}, + method: RestMethod = RestMethod.Get + ): Either = + try { + when (method) { + RestMethod.Get -> httpClient.get(urlString, block) + RestMethod.Post -> httpClient.post(urlString, block) + RestMethod.Delete -> httpClient.delete(urlString, block) + RestMethod.Put -> httpClient.put(urlString, block) + }.body() + } catch (e: Exception) { + Either.Error(ErrorResponse(code = 0, description = e.message ?: "Error")) + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index b8f418f9..9e18d722 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,4 +10,5 @@ include(":tabletApp:features:network") include(":tabletApp:features:domain") include(":tabletApp:features:core") include(":tabletApp:features:freeNegotiationsScreen") -include("wheel-picker-compose") \ No newline at end of file +include("wheel-picker-compose") +include("contract") \ No newline at end of file -- GitLab From 0ad4a03ec659d5dabc7e26b88776d27755233628 Mon Sep 17 00:00:00 2001 From: Maksim Mishenko Date: Sat, 5 Aug 2023 23:26:53 +0600 Subject: [PATCH 2/6] [+] add implementation --- contract/build.gradle.kts | 23 +++- contract/src/androidMain/AndroidManifest.xml | 4 + .../band/effective/office/network/api/Api.kt | 9 +- .../office/network/api/impl/ApiImpl.kt | 102 ++++++++++++++++ .../office/network/api/impl/ApiMock.kt | 115 ++++++++++++++++++ .../office/network/dto/BookingInfo.kt | 5 +- .../office/network/dto/IntegrationDTO.kt | 0 .../office/network/dto/SuccessResponse.kt | 0 .../effective/office/network/dto/UserDTO.kt | 0 .../office/network/dto/UtilityDTO.kt | 0 .../office/network/dto/WorkspaceDTO.kt | 0 .../effective/office/network/model/Either.kt | 0 .../office/network/model/ErrorResponse.kt | 0 .../office/utils/KtorEitherPlagin.kt | 0 .../effective/office/utils/KtorEtherClient.kt | 2 +- .../effective/office/utils/MockFactory.kt | 107 ++++++++++++++++ 16 files changed, 359 insertions(+), 8 deletions(-) create mode 100644 contract/src/androidMain/AndroidManifest.xml rename contract/src/{main => commonMain}/kotlin/band/effective/office/network/api/Api.kt (86%) create mode 100644 contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiImpl.kt create mode 100644 contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiMock.kt rename contract/src/{main => commonMain}/kotlin/band/effective/office/network/dto/BookingInfo.kt (58%) rename contract/src/{main => commonMain}/kotlin/band/effective/office/network/dto/IntegrationDTO.kt (100%) rename contract/src/{main => commonMain}/kotlin/band/effective/office/network/dto/SuccessResponse.kt (100%) rename contract/src/{main => commonMain}/kotlin/band/effective/office/network/dto/UserDTO.kt (100%) rename contract/src/{main => commonMain}/kotlin/band/effective/office/network/dto/UtilityDTO.kt (100%) rename contract/src/{main => commonMain}/kotlin/band/effective/office/network/dto/WorkspaceDTO.kt (100%) rename contract/src/{main => commonMain}/kotlin/band/effective/office/network/model/Either.kt (100%) rename contract/src/{main => commonMain}/kotlin/band/effective/office/network/model/ErrorResponse.kt (100%) rename contract/src/{main => commonMain}/kotlin/band/effective/office/utils/KtorEitherPlagin.kt (100%) rename contract/src/{main => commonMain}/kotlin/band/effective/office/utils/KtorEtherClient.kt (96%) create mode 100644 contract/src/commonMain/kotlin/band/effective/office/utils/MockFactory.kt diff --git a/contract/build.gradle.kts b/contract/build.gradle.kts index 96c3b81e..5fe6b895 100644 --- a/contract/build.gradle.kts +++ b/contract/build.gradle.kts @@ -1,9 +1,25 @@ plugins { - id(Plugins.Android.plugin) - id(Plugins.MultiplatformCompose.plugin) + id(Plugins.AndroidLib.plugin) id(Plugins.Kotlin.plugin) id(Plugins.Parcelize.plugin) - id(Plugins.Libres.plugin) +} + +android { + namespace = "band.effective.office.contract" + compileSdk = 33 + + defaultConfig { + minSdk = 26 + targetSdk = 33 + } + sourceSets["main"].apply { + manifest.srcFile("src/androidMain/AndroidManifest.xml") + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + } kotlin { @@ -17,6 +33,7 @@ kotlin { dependencies{ api(Dependencies.Ktor.Client.Core) implementation(Dependencies.KotlinxSerialization.json) + implementation(Dependencies.KotlinxDatetime.kotlinxDatetime) implementation("io.ktor:ktor-client-cio:2.3.2") } } \ No newline at end of file diff --git a/contract/src/androidMain/AndroidManifest.xml b/contract/src/androidMain/AndroidManifest.xml new file mode 100644 index 00000000..f750b513 --- /dev/null +++ b/contract/src/androidMain/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/contract/src/main/kotlin/band/effective/office/network/api/Api.kt b/contract/src/commonMain/kotlin/band/effective/office/network/api/Api.kt similarity index 86% rename from contract/src/main/kotlin/band/effective/office/network/api/Api.kt rename to contract/src/commonMain/kotlin/band/effective/office/network/api/Api.kt index 0f2d98c6..576fd8cc 100644 --- a/contract/src/main/kotlin/band/effective/office/network/api/Api.kt +++ b/contract/src/commonMain/kotlin/band/effective/office/network/api/Api.kt @@ -31,6 +31,9 @@ interface Api { /**Get user's bookings*/ suspend fun getBookingsByUser(userId: String): Either> + /**Get bookings in workspace*/ + suspend fun getBookingsByWorkspaces(workspaceId: String): Either> + /**Booking workspace*/ suspend fun booking(bookingInfo: BookingInfo): Either @@ -42,8 +45,7 @@ interface Api { /**Delete booking*/ suspend fun deleteBooking( - bookingId: String, - bookingInfo: BookingInfo + bookingId: String ): Either /**Subscribe on workspace info updates*/ @@ -51,4 +53,7 @@ interface Api { /**Subscribe on organizers list updates*/ suspend fun subscribeOnOrganizersList(): Flow>> + + /**Subscribe on bookings list updates*/ + suspend fun subscribeOnBookingsList(workspaceId: String): Flow>> } \ No newline at end of file diff --git a/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiImpl.kt b/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiImpl.kt new file mode 100644 index 00000000..bc64e206 --- /dev/null +++ b/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiImpl.kt @@ -0,0 +1,102 @@ +package band.effective.office.network.api.impl + +import band.effective.office.network.api.Api +import band.effective.office.network.dto.BookingInfo +import band.effective.office.network.dto.SuccessResponse +import band.effective.office.network.dto.UserDTO +import band.effective.office.network.dto.WorkspaceDTO +import band.effective.office.network.model.Either +import band.effective.office.network.model.ErrorResponse +import band.effective.office.utils.KtorEtherClient +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow + +class ApiImpl : Api { + private val client = KtorEtherClient + private val baseUrl: String = "https://d5do2upft1rficrbubot.apigw.yandexcloud.net" + override suspend fun getWorkspace(id: String): Either = + client.securityResponse("$baseUrl/workspaces") { + url { + parameters.append("id", id) + } + } + + override suspend fun getWorkspaces(tag: String): Either> = + client.securityResponse("$baseUrl/workspaces") { + url { + parameters.append("tag", tag) + } + } + + override suspend fun getUser(id: String): Either = + client.securityResponse("$baseUrl/users") { + url { + parameters.append("id", id) + } + } + + override suspend fun getUsers(): Either> = + client.securityResponse("$baseUrl/users") + + //TODO(Maksim Mishenko): Request not exist in swagger + override suspend fun getBookingsByUser(userId: String): Either> = + Either.Error(ErrorResponse(code = 601, description = "Request not exist in swagger")) + + //TODO(Maksim Mishenko): Request not exist in swagger + override suspend fun getBookingsByWorkspaces(workspaceId: String): Either> = + Either.Error(ErrorResponse(code = 601, description = "Request not exist in swagger")) + + //TODO(Maksim Mishenko): Request not exist in swagger + override suspend fun booking(bookingInfo: BookingInfo): Either = + Either.Error(ErrorResponse(code = 601, description = "Request not exist in swagger")) + + //TODO(Maksim Mishenko): Request not exist in swagger + override suspend fun changeBooking( + bookingId: String, + bookingInfo: BookingInfo + ): Either = + Either.Error(ErrorResponse(code = 601, description = "Request not exist in swagger")) + + //TODO(Maksim Mishenko): Request not exist in swagger + override suspend fun deleteBooking(bookingId: String): Either = + Either.Error(ErrorResponse(code = 601, description = "Request not exist in swagger")) + + //TODO(Maksim Mishenko): Request not exist in swagger + override suspend fun subscribeOnWorkspaceUpdates(id: String): Flow> = + flow { + emit( + Either.Error( + ErrorResponse( + code = 601, + description = "Request not exist in swagger" + ) + ) + ) + } + + //TODO(Maksim Mishenko): Request not exist in swagger + override suspend fun subscribeOnOrganizersList(): Flow>> = + flow { + emit( + Either.Error( + ErrorResponse( + code = 601, + description = "Request not exist in swagger" + ) + ) + ) + } + + //TODO(Maksim Mishenko): Request not exist in swagger + override suspend fun subscribeOnBookingsList(workspaceId: String): Flow>> = + flow { + emit( + Either.Error( + ErrorResponse( + code = 601, + description = "Request not exist in swagger" + ) + ) + ) + } +} \ No newline at end of file diff --git a/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiMock.kt b/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiMock.kt new file mode 100644 index 00000000..2c0e9bc1 --- /dev/null +++ b/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiMock.kt @@ -0,0 +1,115 @@ +package band.effective.office.network.api.impl + +import band.effective.office.network.api.Api +import band.effective.office.network.dto.BookingInfo +import band.effective.office.network.dto.SuccessResponse +import band.effective.office.network.dto.UserDTO +import band.effective.office.network.dto.WorkspaceDTO +import band.effective.office.network.model.Either +import band.effective.office.network.model.ErrorResponse +import band.effective.office.utils.MockFactory +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +class ApiMock(private val realApi: Api, private val mockFactory: MockFactory) : Api { + var getRealResponse: Boolean = false + private val workspaces = mockFactory.workspaces() + private val meetingRooms = mockFactory.meetingRooms() + private val users = MutableStateFlow(mockFactory.users()) + private val bookings = MutableStateFlow(mockFactory.bookings()) + private val successResponse = mockFactory.success() + + private fun response(mock: T?, realResponse: Either) = + with(getRealResponse) { + when { + this -> realResponse + mock == null -> Either.Error(ErrorResponse.getResponse(404)) + realResponse is Either.Error && realResponse.error.code in 600..699 -> Either.Success( + mock + ) + + else -> Either.Success(mock) + } + } + + override suspend fun getWorkspace(id: String): Either = response( + mock = (workspaces + meetingRooms).firstOrNull() { it.id == id }, + realResponse = realApi.getWorkspace(id) + ) + + override suspend fun getWorkspaces(tag: String): Either> = + response( + mock = if (tag == "meeting") meetingRooms else workspaces, + realResponse = realApi.getWorkspaces(tag = tag) + ) + + override suspend fun getUser(id: String): Either = response( + mock = users.value.firstOrNull { it.id == id }, + realResponse = realApi.getUser(id) + ) + + override suspend fun getUsers(): Either> = response( + mock = users.value, + realResponse = realApi.getUsers() + ) + + override suspend fun getBookingsByUser(userId: String): Either> = + response( + mock = bookings.value.filter { it.ownerId == userId }, + realResponse = realApi.getBookingsByUser(userId = userId) + ) + + override suspend fun getBookingsByWorkspaces(workspaceId: String): Either> = + response( + mock = bookings.value.filter { it.workspaceId == workspaceId }, + realResponse = realApi.getBookingsByWorkspaces(workspaceId = workspaceId) + ) + + override suspend fun booking(bookingInfo: BookingInfo): Either = + response( + mock = successResponse.apply { bookings.update { it + bookingInfo } }, + realResponse = realApi.booking(bookingInfo) + ) + + override suspend fun changeBooking( + bookingId: String, + bookingInfo: BookingInfo + ): Either = response( + mock = successResponse.apply { bookings.update { it.map { element -> if (element.id == bookingId) bookingInfo else element } } }, + realResponse = realApi.changeBooking(bookingId, bookingInfo) + ) + + override suspend fun deleteBooking( + bookingId: String + ): Either = response( + mock = successResponse.apply { bookings.update { it.filter { element -> element.id != bookingId } } }, + realResponse = realApi.deleteBooking(bookingId) + ) + + override suspend fun subscribeOnWorkspaceUpdates(id: String): Flow> = + flow { + realApi.subscribeOnWorkspaceUpdates(id).collect { if (getRealResponse) emit(it) } + } + + override suspend fun subscribeOnOrganizersList(): Flow>> = + flow { + coroutineScope { + launch { users.collect { if (!getRealResponse) emit(Either.Success(it)) } } + launch { + realApi.subscribeOnOrganizersList().collect { if (getRealResponse) emit(it) } + } + } + } + + override suspend fun subscribeOnBookingsList(workspaceId: String): Flow>> = + flow { + coroutineScope { + launch { bookings.collect { if (!getRealResponse) emit(Either.Success(it.filter { item -> item.workspaceId == workspaceId })) } } + launch { realApi.subscribeOnBookingsList(workspaceId).collect { if (getRealResponse) emit(it) } } + } + } +} \ No newline at end of file diff --git a/contract/src/main/kotlin/band/effective/office/network/dto/BookingInfo.kt b/contract/src/commonMain/kotlin/band/effective/office/network/dto/BookingInfo.kt similarity index 58% rename from contract/src/main/kotlin/band/effective/office/network/dto/BookingInfo.kt rename to contract/src/commonMain/kotlin/band/effective/office/network/dto/BookingInfo.kt index 027ee10c..a8a9a78a 100644 --- a/contract/src/main/kotlin/band/effective/office/network/dto/BookingInfo.kt +++ b/contract/src/commonMain/kotlin/band/effective/office/network/dto/BookingInfo.kt @@ -4,6 +4,7 @@ data class BookingInfo( val id: String, val begin: Long, val end: Long, - val owner: UserDTO, - val participants: List + val ownerId: String, + val participants: List, + val workspaceId: String ) \ No newline at end of file diff --git a/contract/src/main/kotlin/band/effective/office/network/dto/IntegrationDTO.kt b/contract/src/commonMain/kotlin/band/effective/office/network/dto/IntegrationDTO.kt similarity index 100% rename from contract/src/main/kotlin/band/effective/office/network/dto/IntegrationDTO.kt rename to contract/src/commonMain/kotlin/band/effective/office/network/dto/IntegrationDTO.kt diff --git a/contract/src/main/kotlin/band/effective/office/network/dto/SuccessResponse.kt b/contract/src/commonMain/kotlin/band/effective/office/network/dto/SuccessResponse.kt similarity index 100% rename from contract/src/main/kotlin/band/effective/office/network/dto/SuccessResponse.kt rename to contract/src/commonMain/kotlin/band/effective/office/network/dto/SuccessResponse.kt diff --git a/contract/src/main/kotlin/band/effective/office/network/dto/UserDTO.kt b/contract/src/commonMain/kotlin/band/effective/office/network/dto/UserDTO.kt similarity index 100% rename from contract/src/main/kotlin/band/effective/office/network/dto/UserDTO.kt rename to contract/src/commonMain/kotlin/band/effective/office/network/dto/UserDTO.kt diff --git a/contract/src/main/kotlin/band/effective/office/network/dto/UtilityDTO.kt b/contract/src/commonMain/kotlin/band/effective/office/network/dto/UtilityDTO.kt similarity index 100% rename from contract/src/main/kotlin/band/effective/office/network/dto/UtilityDTO.kt rename to contract/src/commonMain/kotlin/band/effective/office/network/dto/UtilityDTO.kt diff --git a/contract/src/main/kotlin/band/effective/office/network/dto/WorkspaceDTO.kt b/contract/src/commonMain/kotlin/band/effective/office/network/dto/WorkspaceDTO.kt similarity index 100% rename from contract/src/main/kotlin/band/effective/office/network/dto/WorkspaceDTO.kt rename to contract/src/commonMain/kotlin/band/effective/office/network/dto/WorkspaceDTO.kt diff --git a/contract/src/main/kotlin/band/effective/office/network/model/Either.kt b/contract/src/commonMain/kotlin/band/effective/office/network/model/Either.kt similarity index 100% rename from contract/src/main/kotlin/band/effective/office/network/model/Either.kt rename to contract/src/commonMain/kotlin/band/effective/office/network/model/Either.kt diff --git a/contract/src/main/kotlin/band/effective/office/network/model/ErrorResponse.kt b/contract/src/commonMain/kotlin/band/effective/office/network/model/ErrorResponse.kt similarity index 100% rename from contract/src/main/kotlin/band/effective/office/network/model/ErrorResponse.kt rename to contract/src/commonMain/kotlin/band/effective/office/network/model/ErrorResponse.kt diff --git a/contract/src/main/kotlin/band/effective/office/utils/KtorEitherPlagin.kt b/contract/src/commonMain/kotlin/band/effective/office/utils/KtorEitherPlagin.kt similarity index 100% rename from contract/src/main/kotlin/band/effective/office/utils/KtorEitherPlagin.kt rename to contract/src/commonMain/kotlin/band/effective/office/utils/KtorEitherPlagin.kt diff --git a/contract/src/main/kotlin/band/effective/office/utils/KtorEtherClient.kt b/contract/src/commonMain/kotlin/band/effective/office/utils/KtorEtherClient.kt similarity index 96% rename from contract/src/main/kotlin/band/effective/office/utils/KtorEtherClient.kt rename to contract/src/commonMain/kotlin/band/effective/office/utils/KtorEtherClient.kt index c27f4f3e..a6fae317 100644 --- a/contract/src/main/kotlin/band/effective/office/utils/KtorEtherClient.kt +++ b/contract/src/commonMain/kotlin/band/effective/office/utils/KtorEtherClient.kt @@ -20,8 +20,8 @@ object KtorEtherClient { suspend inline fun securityResponse( urlString: String, + method: RestMethod = RestMethod.Get, block: HttpRequestBuilder.() -> Unit = {}, - method: RestMethod = RestMethod.Get ): Either = try { when (method) { diff --git a/contract/src/commonMain/kotlin/band/effective/office/utils/MockFactory.kt b/contract/src/commonMain/kotlin/band/effective/office/utils/MockFactory.kt new file mode 100644 index 00000000..c0870a75 --- /dev/null +++ b/contract/src/commonMain/kotlin/band/effective/office/utils/MockFactory.kt @@ -0,0 +1,107 @@ +package band.effective.office.utils + +import band.effective.office.network.dto.BookingInfo +import band.effective.office.network.dto.SuccessResponse +import band.effective.office.network.dto.UserDTO +import band.effective.office.network.dto.UtilityDTO +import band.effective.office.network.dto.WorkspaceDTO +import kotlinx.datetime.Clock +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toInstant +import kotlinx.datetime.todayIn +import kotlin.random.Random + +class MockFactory { + private fun getTime(hours: Int = 0, minutes: Int = 0) = + Clock.System.todayIn(TimeZone.currentSystemDefault()).run { + LocalDateTime( + year = year, + monthNumber = monthNumber, + dayOfMonth = dayOfMonth, + hour = hours, + minute = minutes + ).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds() + } + + private fun lanUtility() = UtilityDTO( + count = Random.nextInt(0, 20), + iconUrl = "", + id = "", + name = "lan" + ) + + private fun placeUtility() = UtilityDTO( + count = Random.nextInt(0, 20), + iconUrl = "", + id = "", + name = "place" + ) + + private fun tvUtility() = UtilityDTO( + count = 1, + iconUrl = "", + id = "", + name = "place" + ) + + private fun user(name: String, role: String) = UserDTO( + active = Random.nextBoolean(), + avatarUrl = "", + fullName = name, + id = name, + integrations = listOf(), + role = role + ) + + private fun booking(owner: String, start: Pair, finish: Pair, workspace: String) = + BookingInfo( + id = "${Random.nextInt(10000)}", + begin = getTime(start.first, start.second), + end = getTime(finish.first, finish.second), + ownerId = owner, + participants = listOf(), + workspaceId = workspace + ) + + fun workspaces() = listOf() + + fun meetingRooms() = listOf( + WorkspaceDTO( + id = "Sirius", + name = "Sirius", + utilities = listOf(lanUtility(), placeUtility()) + ), + WorkspaceDTO( + id = "Pluto", + name = "Pluto", + utilities = listOf(lanUtility(), placeUtility()) + ), + WorkspaceDTO(id = "Moon", name = "Moon", utilities = listOf(lanUtility(), placeUtility())), + WorkspaceDTO( + id = "Antares", + name = "Antares", + utilities = listOf(lanUtility(), placeUtility()) + ), + WorkspaceDTO( + id = "Sun", + name = "Sun", + utilities = listOf(lanUtility(), placeUtility(), tvUtility()) + ) + ) + + private val names = listOf("Ольга Белозерова", "Матвей Авгуль", "Лилия Акентьева") + + fun users() = names.map { user(it, "ADMIN") } + + fun bookings() = names.mapIndexed { index, name -> + booking( + owner = name, + start = Pair(11 + index, (index % 2) * 30), + finish = Pair(12 + index, (index % 2) * 30), + workspace = "Sirius" + ) + } + + fun success() = SuccessResponse(status = "ok") +} \ No newline at end of file -- GitLab From 3a32df05f54c26049c46ceb0aa43202c683229b4 Mon Sep 17 00:00:00 2001 From: Maksim Mishenko Date: Sun, 6 Aug 2023 13:57:23 +0600 Subject: [PATCH 3/6] [~] fix functions name --- .../kotlin/band/effective/office/network/api/Api.kt | 4 ++-- .../band/effective/office/network/api/impl/ApiImpl.kt | 6 +++--- .../band/effective/office/network/api/impl/ApiMock.kt | 10 +++++----- .../effective/office/network/model/ErrorResponse.kt | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/contract/src/commonMain/kotlin/band/effective/office/network/api/Api.kt b/contract/src/commonMain/kotlin/band/effective/office/network/api/Api.kt index 576fd8cc..8f30b4d9 100644 --- a/contract/src/commonMain/kotlin/band/effective/office/network/api/Api.kt +++ b/contract/src/commonMain/kotlin/band/effective/office/network/api/Api.kt @@ -35,10 +35,10 @@ interface Api { suspend fun getBookingsByWorkspaces(workspaceId: String): Either> /**Booking workspace*/ - suspend fun booking(bookingInfo: BookingInfo): Either + suspend fun createBooking(bookingInfo: BookingInfo): Either /**Update booking info*/ - suspend fun changeBooking( + suspend fun updateBooking( bookingId: String, bookingInfo: BookingInfo ): Either diff --git a/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiImpl.kt b/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiImpl.kt index bc64e206..b85fa5e9 100644 --- a/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiImpl.kt +++ b/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiImpl.kt @@ -47,11 +47,11 @@ class ApiImpl : Api { Either.Error(ErrorResponse(code = 601, description = "Request not exist in swagger")) //TODO(Maksim Mishenko): Request not exist in swagger - override suspend fun booking(bookingInfo: BookingInfo): Either = + override suspend fun createBooking(bookingInfo: BookingInfo): Either = Either.Error(ErrorResponse(code = 601, description = "Request not exist in swagger")) //TODO(Maksim Mishenko): Request not exist in swagger - override suspend fun changeBooking( + override suspend fun updateBooking( bookingId: String, bookingInfo: BookingInfo ): Either = @@ -87,7 +87,7 @@ class ApiImpl : Api { ) } - //TODO(Maksim Mishenko): Request not exist in swagger + //TODO(Maksim Mрегьюлар воркспейсамishenko): Request not exist in swagger override suspend fun subscribeOnBookingsList(workspaceId: String): Flow>> = flow { emit( diff --git a/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiMock.kt b/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiMock.kt index 2c0e9bc1..cdc3cfa8 100644 --- a/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiMock.kt +++ b/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiMock.kt @@ -15,7 +15,7 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -class ApiMock(private val realApi: Api, private val mockFactory: MockFactory) : Api { +class ApiMock(private val realApi: Api, mockFactory: MockFactory) : Api { var getRealResponse: Boolean = false private val workspaces = mockFactory.workspaces() private val meetingRooms = mockFactory.meetingRooms() @@ -69,18 +69,18 @@ class ApiMock(private val realApi: Api, private val mockFactory: MockFactory) : realResponse = realApi.getBookingsByWorkspaces(workspaceId = workspaceId) ) - override suspend fun booking(bookingInfo: BookingInfo): Either = + override suspend fun createBooking(bookingInfo: BookingInfo): Either = response( mock = successResponse.apply { bookings.update { it + bookingInfo } }, - realResponse = realApi.booking(bookingInfo) + realResponse = realApi.createBooking(bookingInfo) ) - override suspend fun changeBooking( + override suspend fun updateBooking( bookingId: String, bookingInfo: BookingInfo ): Either = response( mock = successResponse.apply { bookings.update { it.map { element -> if (element.id == bookingId) bookingInfo else element } } }, - realResponse = realApi.changeBooking(bookingId, bookingInfo) + realResponse = realApi.updateBooking(bookingId, bookingInfo) ) override suspend fun deleteBooking( diff --git a/contract/src/commonMain/kotlin/band/effective/office/network/model/ErrorResponse.kt b/contract/src/commonMain/kotlin/band/effective/office/network/model/ErrorResponse.kt index d03e55bb..8a49037e 100644 --- a/contract/src/commonMain/kotlin/band/effective/office/network/model/ErrorResponse.kt +++ b/contract/src/commonMain/kotlin/band/effective/office/network/model/ErrorResponse.kt @@ -5,7 +5,7 @@ data class ErrorResponse(val code: Int, val description: String) { fun getResponse(code: Int): ErrorResponse { val description = when (code) { 404 -> "Not found" - in 400..499 -> "Pet not found" + in 400..499 -> "Client error" in 500..599 -> "Server error" else -> "Unknown error" } -- GitLab From d41c9727a9fdc1f2a2460d85d43dc260d1954602 Mon Sep 17 00:00:00 2001 From: Maksim Mishenko Date: Sun, 6 Aug 2023 14:03:32 +0600 Subject: [PATCH 4/6] [~] fix response function in ApiMock --- .../band/effective/office/network/api/impl/ApiMock.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiMock.kt b/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiMock.kt index cdc3cfa8..f89c285e 100644 --- a/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiMock.kt +++ b/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiMock.kt @@ -26,16 +26,15 @@ class ApiMock(private val realApi: Api, mockFactory: MockFactory) : Api { private fun response(mock: T?, realResponse: Either) = with(getRealResponse) { when { - this -> realResponse + this && !(realResponse.requestNotExist()) -> realResponse mock == null -> Either.Error(ErrorResponse.getResponse(404)) - realResponse is Either.Error && realResponse.error.code in 600..699 -> Either.Success( - mock - ) - + realResponse.requestNotExist() -> Either.Success(mock) else -> Either.Success(mock) } } + private fun Either.requestNotExist() = this is Either.Error && error.code in 600..699 + override suspend fun getWorkspace(id: String): Either = response( mock = (workspaces + meetingRooms).firstOrNull() { it.id == id }, realResponse = realApi.getWorkspace(id) -- GitLab From 20459abb13a034047cae106d7e4313ae9d351178 Mon Sep 17 00:00:00 2001 From: Maksim Mishenko Date: Mon, 7 Aug 2023 00:40:46 +0600 Subject: [PATCH 5/6] [~] fix build.gradle.kts --- contract/build.gradle.kts | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/contract/build.gradle.kts b/contract/build.gradle.kts index 5fe6b895..6b866819 100644 --- a/contract/build.gradle.kts +++ b/contract/build.gradle.kts @@ -30,10 +30,20 @@ kotlin { } } } - dependencies{ - api(Dependencies.Ktor.Client.Core) - implementation(Dependencies.KotlinxSerialization.json) - implementation(Dependencies.KotlinxDatetime.kotlinxDatetime) - implementation("io.ktor:ktor-client-cio:2.3.2") + sourceSets { + val commonMain by getting { + dependencies { + api(Dependencies.Ktor.Client.Core) + implementation(Dependencies.KotlinxSerialization.json) + implementation(Dependencies.KotlinxDatetime.kotlinxDatetime) + implementation(Dependencies.Ktor.Client.CIO) + + } + } + val androidMain by getting { + dependencies { + + } + } } } \ No newline at end of file -- GitLab From 2c474578b1539395e51157b8b28aad9e7912d375 Mon Sep 17 00:00:00 2001 From: Maksim Mishenko Date: Mon, 7 Aug 2023 00:42:52 +0600 Subject: [PATCH 6/6] [~] optimize contract --- .../kotlin/band/effective/office/network/api/Api.kt | 1 - .../effective/office/network/api/impl/ApiImpl.kt | 1 - .../effective/office/network/api/impl/ApiMock.kt | 13 ++++++++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/contract/src/commonMain/kotlin/band/effective/office/network/api/Api.kt b/contract/src/commonMain/kotlin/band/effective/office/network/api/Api.kt index 8f30b4d9..0a3de1d2 100644 --- a/contract/src/commonMain/kotlin/band/effective/office/network/api/Api.kt +++ b/contract/src/commonMain/kotlin/band/effective/office/network/api/Api.kt @@ -39,7 +39,6 @@ interface Api { /**Update booking info*/ suspend fun updateBooking( - bookingId: String, bookingInfo: BookingInfo ): Either diff --git a/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiImpl.kt b/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiImpl.kt index b85fa5e9..c9b05126 100644 --- a/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiImpl.kt +++ b/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiImpl.kt @@ -52,7 +52,6 @@ class ApiImpl : Api { //TODO(Maksim Mishenko): Request not exist in swagger override suspend fun updateBooking( - bookingId: String, bookingInfo: BookingInfo ): Either = Either.Error(ErrorResponse(code = 601, description = "Request not exist in swagger")) diff --git a/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiMock.kt b/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiMock.kt index f89c285e..15b55f1e 100644 --- a/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiMock.kt +++ b/contract/src/commonMain/kotlin/band/effective/office/network/api/impl/ApiMock.kt @@ -33,7 +33,8 @@ class ApiMock(private val realApi: Api, mockFactory: MockFactory) : Api { } } - private fun Either.requestNotExist() = this is Either.Error && error.code in 600..699 + private fun Either.requestNotExist() = + this is Either.Error && error.code in 600..699 override suspend fun getWorkspace(id: String): Either = response( mock = (workspaces + meetingRooms).firstOrNull() { it.id == id }, @@ -75,11 +76,10 @@ class ApiMock(private val realApi: Api, mockFactory: MockFactory) : Api { ) override suspend fun updateBooking( - bookingId: String, bookingInfo: BookingInfo ): Either = response( - mock = successResponse.apply { bookings.update { it.map { element -> if (element.id == bookingId) bookingInfo else element } } }, - realResponse = realApi.updateBooking(bookingId, bookingInfo) + mock = successResponse.apply { bookings.update { it.map { element -> if (element.id == bookingInfo.id) bookingInfo else element } } }, + realResponse = realApi.updateBooking(bookingInfo) ) override suspend fun deleteBooking( @@ -108,7 +108,10 @@ class ApiMock(private val realApi: Api, mockFactory: MockFactory) : Api { flow { coroutineScope { launch { bookings.collect { if (!getRealResponse) emit(Either.Success(it.filter { item -> item.workspaceId == workspaceId })) } } - launch { realApi.subscribeOnBookingsList(workspaceId).collect { if (getRealResponse) emit(it) } } + launch { + realApi.subscribeOnBookingsList(workspaceId) + .collect { if (getRealResponse) emit(it) } + } } } } \ No newline at end of file -- GitLab