From 3e7a2c1f0dd945a764c1b1d92951badc61f69a21 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Thu, 17 Oct 2019 21:34:52 +0600 Subject: [PATCH] add writable parts of server and client --- ClientPart/build.gradle | 1 + .../postssystem/core/client/EventFlow.kt | 40 ++++++++++++++ .../core/client/WritableHttpPostsAPI.kt | 46 +++++++++++----- .../core/clientserver/common/Paths.kt | 8 +++ .../common/models/UpdatePostRequest.kt | 11 ++++ .../postssystem/core/content/Content.kt | 2 - ServerPart/build.gradle | 1 + .../postssystem/core/server/Commons.kt | 9 ++-- .../postssystem/core/server/WriteModules.kt | 54 +++++++++++++++++++ 9 files changed, 153 insertions(+), 19 deletions(-) create mode 100644 ClientPart/src/main/kotlin/com/insanusmokrassar/postssystem/core/client/EventFlow.kt create mode 100644 ClientServerCommon/src/main/kotlin/com/insanusmokrassar/postssystem/core/clientserver/common/models/UpdatePostRequest.kt create mode 100644 ServerPart/src/main/kotlin/com/insanusmokrassar/postssystem/core/server/WriteModules.kt diff --git a/ClientPart/build.gradle b/ClientPart/build.gradle index 1eccead8..dc5d4327 100644 --- a/ClientPart/build.gradle +++ b/ClientPart/build.gradle @@ -32,6 +32,7 @@ dependencies { api project(":ClientServerCommon") api "io.ktor:ktor-client:$ktor_version" + api "io.ktor:ktor-client-websockets:$ktor_version" } compileKotlin { diff --git a/ClientPart/src/main/kotlin/com/insanusmokrassar/postssystem/core/client/EventFlow.kt b/ClientPart/src/main/kotlin/com/insanusmokrassar/postssystem/core/client/EventFlow.kt new file mode 100644 index 00000000..55d2a593 --- /dev/null +++ b/ClientPart/src/main/kotlin/com/insanusmokrassar/postssystem/core/client/EventFlow.kt @@ -0,0 +1,40 @@ +package com.insanusmokrassar.postssystem.core.client + +import io.ktor.client.HttpClient +import io.ktor.client.features.websocket.ws +import io.ktor.http.HttpMethod +import io.ktor.http.cio.websocket.Frame +import io.ktor.http.cio.websocket.readText +import kotlinx.coroutines.InternalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector + +class EventFlow( + private val client: HttpClient, + private val host: String, + private val port: Int, + private val path: String, + private val dataConverter: suspend (String) -> T +) : Flow { + @InternalCoroutinesApi + override suspend fun collect(collector: FlowCollector) { + client.ws( + method = HttpMethod.Get, + host = host, + port = port, + path = path + ) { + var frame = incoming.receiveOrClosed() + while (!frame.isClosed) { + val frameVal = frame.value + when (frameVal) { + is Frame.Text -> { + collector.emit( + dataConverter(frameVal.readText()) + ) + } + } + } + } + } +} diff --git a/ClientPart/src/main/kotlin/com/insanusmokrassar/postssystem/core/client/WritableHttpPostsAPI.kt b/ClientPart/src/main/kotlin/com/insanusmokrassar/postssystem/core/client/WritableHttpPostsAPI.kt index fbcb394a..93d7ee64 100644 --- a/ClientPart/src/main/kotlin/com/insanusmokrassar/postssystem/core/client/WritableHttpPostsAPI.kt +++ b/ClientPart/src/main/kotlin/com/insanusmokrassar/postssystem/core/client/WritableHttpPostsAPI.kt @@ -1,28 +1,46 @@ package com.insanusmokrassar.postssystem.core.client import com.insanusmokrassar.postssystem.core.api.WritePostsAPI +import com.insanusmokrassar.postssystem.core.clientserver.common.* +import com.insanusmokrassar.postssystem.core.clientserver.common.models.UpdatePostRequest import com.insanusmokrassar.postssystem.core.post.* +import io.ktor.client.HttpClient +import io.ktor.client.request.post import kotlinx.coroutines.flow.Flow +import kotlinx.serialization.json.Json class WritableHttpPostsAPI( - + private val hostUrl: String, + private val port: Int, + private val client: HttpClient ) : WritePostsAPI { - override val postCreatedFlow: Flow - get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates. - override val postDeletedFlow: Flow - get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates. - override val postUpdatedFlow: Flow - get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates. - - override suspend fun createPost(content: PostContents): Post? { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + private inline fun postEventFlow(url: String) = EventFlow( + client, + hostUrl, + port, + "/$url" + ) { + Json.plain.parse(SimplePost.serializer(), it) } - override suspend fun deletePost(id: PostId): Boolean { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + override val postCreatedFlow: Flow = postEventFlow(eventPostsCreatedAddress) + override val postDeletedFlow: Flow = postEventFlow(eventPostsDeletedAddress) + override val postUpdatedFlow: Flow = postEventFlow(eventPostsUpdatedAddress) + + override suspend fun createPost(content: PostContents): Post? = client.post( + createPostAddress + ) { + body = content } - override suspend fun updatePostContent(postId: PostId, content: PostContents): Boolean { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + override suspend fun deletePost(id: PostId): Boolean = client.post(deletePostAddress) { + body = id + } + + override suspend fun updatePostContent( + postId: PostId, + content: PostContents + ): Boolean = client.post(createPostAddress) { + body = UpdatePostRequest(postId, content) } } \ No newline at end of file diff --git a/ClientServerCommon/src/main/kotlin/com/insanusmokrassar/postssystem/core/clientserver/common/Paths.kt b/ClientServerCommon/src/main/kotlin/com/insanusmokrassar/postssystem/core/clientserver/common/Paths.kt index b8b9f875..ab49b8f9 100644 --- a/ClientServerCommon/src/main/kotlin/com/insanusmokrassar/postssystem/core/clientserver/common/Paths.kt +++ b/ClientServerCommon/src/main/kotlin/com/insanusmokrassar/postssystem/core/clientserver/common/Paths.kt @@ -5,6 +5,14 @@ const val getPostsByContentIdAddress = "core/posts/get/content_id" const val getPostsByDatesAddress = "core/posts/get/dates" const val getPostsByPaginationAddress = "core/posts/get/pagination" +const val eventPostsCreatedAddress = "core/posts/event/created" +const val eventPostsUpdatedAddress = "core/posts/event/updated" +const val eventPostsDeletedAddress = "core/posts/event/deleted" + +const val createPostAddress = "core/posts/create" +const val updatePostAddress = "core/posts/update" +const val deletePostAddress = "core/posts/delete" + //class ReadPaths( // val baseAddress: String //) { diff --git a/ClientServerCommon/src/main/kotlin/com/insanusmokrassar/postssystem/core/clientserver/common/models/UpdatePostRequest.kt b/ClientServerCommon/src/main/kotlin/com/insanusmokrassar/postssystem/core/clientserver/common/models/UpdatePostRequest.kt new file mode 100644 index 00000000..8e062b5e --- /dev/null +++ b/ClientServerCommon/src/main/kotlin/com/insanusmokrassar/postssystem/core/clientserver/common/models/UpdatePostRequest.kt @@ -0,0 +1,11 @@ +package com.insanusmokrassar.postssystem.core.clientserver.common.models + +import com.insanusmokrassar.postssystem.core.post.PostContents +import com.insanusmokrassar.postssystem.core.post.PostId +import kotlinx.serialization.Serializable + +@Serializable +data class UpdatePostRequest( + val id: PostId, + val content: PostContents +) diff --git a/Core/src/main/kotlin/com/insanusmokrassar/postssystem/core/content/Content.kt b/Core/src/main/kotlin/com/insanusmokrassar/postssystem/core/content/Content.kt index 3d0e5793..69ed6956 100644 --- a/Core/src/main/kotlin/com/insanusmokrassar/postssystem/core/content/Content.kt +++ b/Core/src/main/kotlin/com/insanusmokrassar/postssystem/core/content/Content.kt @@ -1,7 +1,5 @@ package com.insanusmokrassar.postssystem.core.content -import org.joda.time.DateTime - typealias ContentId = String interface Content { diff --git a/ServerPart/build.gradle b/ServerPart/build.gradle index 1142d1e2..36af0bc4 100644 --- a/ServerPart/build.gradle +++ b/ServerPart/build.gradle @@ -33,6 +33,7 @@ dependencies { api "io.ktor:ktor-server:$ktor_version" api "io.ktor:ktor-server-core:$ktor_version" + api "io.ktor:ktor-websockets:$ktor_version" } compileKotlin { diff --git a/ServerPart/src/main/kotlin/com/insanusmokrassar/postssystem/core/server/Commons.kt b/ServerPart/src/main/kotlin/com/insanusmokrassar/postssystem/core/server/Commons.kt index b9dfdb3f..b2355b1d 100644 --- a/ServerPart/src/main/kotlin/com/insanusmokrassar/postssystem/core/server/Commons.kt +++ b/ServerPart/src/main/kotlin/com/insanusmokrassar/postssystem/core/server/Commons.kt @@ -8,8 +8,10 @@ import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode import io.ktor.response.respond import io.ktor.response.respondText +import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.KSerializer import kotlinx.serialization.internal.ArrayListSerializer +import kotlinx.serialization.internal.NullableSerializer import kotlinx.serialization.json.Json @@ -27,11 +29,12 @@ internal val Post.asSimplePost else -> SimplePost(id, content, meta) } -internal suspend fun ApplicationCall.answer( +@InternalSerializationApi +internal suspend fun ApplicationCall.answer( serializer: KSerializer, - answerObject: T + answerObject: T? ) = respondText(ContentType.Application.Json) { - Json.plain.stringify(serializer, answerObject) + Json.plain.stringify(NullableSerializer(serializer), answerObject) } internal suspend fun ApplicationCall.answerBadRequest( diff --git a/ServerPart/src/main/kotlin/com/insanusmokrassar/postssystem/core/server/WriteModules.kt b/ServerPart/src/main/kotlin/com/insanusmokrassar/postssystem/core/server/WriteModules.kt new file mode 100644 index 00000000..d8ee7907 --- /dev/null +++ b/ServerPart/src/main/kotlin/com/insanusmokrassar/postssystem/core/server/WriteModules.kt @@ -0,0 +1,54 @@ +package com.insanusmokrassar.postssystem.core.server + +import com.insanusmokrassar.postssystem.core.api.WritePostsAPI +import com.insanusmokrassar.postssystem.core.clientserver.common.* +import com.insanusmokrassar.postssystem.core.clientserver.common.models.UpdatePostRequest +import com.insanusmokrassar.postssystem.core.post.* +import io.ktor.application.call +import io.ktor.http.cio.websocket.Frame +import io.ktor.request.receiveOrNull +import io.ktor.routing.Route +import io.ktor.routing.post +import io.ktor.websocket.webSocket +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect +import kotlinx.serialization.InternalSerializationApi +import kotlinx.serialization.internal.BooleanSerializer +import kotlinx.serialization.json.Json + +private inline fun Route.createWebsocket(path: String, flow: Flow) { + webSocket("/$path") { + flow.collect { + val simplePost = it.asSimplePost + outgoing.send(Frame.Text(Json.plain.stringify(SimplePost.serializer(), simplePost))) + } + } +} + +@InternalSerializationApi +fun Route.includePostsCoreWriteModules( + writePostsAPI: WritePostsAPI +) { + createWebsocket(eventPostsCreatedAddress, writePostsAPI.postCreatedFlow) + createWebsocket(eventPostsDeletedAddress, writePostsAPI.postDeletedFlow) + createWebsocket(eventPostsUpdatedAddress, writePostsAPI.postUpdatedFlow) + + post(createPostAddress) { + call.receiveOrNull() ?.also { contents -> + val post = writePostsAPI.createPost(contents) + + call.answer(SimplePost.serializer(), post ?.asSimplePost) + } ?: call.answerBadRequest("Contents (List of Content)") + } + + post(deletePostAddress) { + call.receiveOrNull() ?.also { postId -> + call.answer(BooleanSerializer, writePostsAPI.deletePost(postId)) + } ?: call.answerBadRequest("Post Id (String)") + } + post(deletePostAddress) { + call.receiveOrNull() ?.also { (postId, contents) -> + call.answer(BooleanSerializer, writePostsAPI.updatePostContent(postId, contents)) + } ?: call.answerBadRequest("Post Id (String)") + } +} \ No newline at end of file