add writable parts of server and client

This commit is contained in:
InsanusMokrassar 2019-10-17 21:34:52 +06:00
parent a3cc3e8537
commit 3e7a2c1f0d
9 changed files with 153 additions and 19 deletions

View File

@ -32,6 +32,7 @@ dependencies {
api project(":ClientServerCommon")
api "io.ktor:ktor-client:$ktor_version"
api "io.ktor:ktor-client-websockets:$ktor_version"
}
compileKotlin {

View File

@ -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<T>(
private val client: HttpClient,
private val host: String,
private val port: Int,
private val path: String,
private val dataConverter: suspend (String) -> T
) : Flow<T> {
@InternalCoroutinesApi
override suspend fun collect(collector: FlowCollector<T>) {
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())
)
}
}
}
}
}
}

View File

@ -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<Post>
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
override val postDeletedFlow: Flow<Post>
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
override val postUpdatedFlow: Flow<Post>
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<Post> = postEventFlow(eventPostsCreatedAddress)
override val postDeletedFlow: Flow<Post> = postEventFlow(eventPostsDeletedAddress)
override val postUpdatedFlow: Flow<Post> = postEventFlow(eventPostsUpdatedAddress)
override suspend fun createPost(content: PostContents): Post? = client.post<SimplePost>(
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)
}
}

View File

@ -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
//) {

View File

@ -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
)

View File

@ -1,7 +1,5 @@
package com.insanusmokrassar.postssystem.core.content
import org.joda.time.DateTime
typealias ContentId = String
interface Content {

View File

@ -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 {

View File

@ -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 <T> ApplicationCall.answer(
@InternalSerializationApi
internal suspend fun <T : Any> ApplicationCall.answer(
serializer: KSerializer<T>,
answerObject: T
answerObject: T?
) = respondText(ContentType.Application.Json) {
Json.plain.stringify(serializer, answerObject)
Json.plain.stringify(NullableSerializer(serializer), answerObject)
}
internal suspend fun ApplicationCall.answerBadRequest(

View File

@ -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<Post>) {
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<PostContents>() ?.also { contents ->
val post = writePostsAPI.createPost(contents)
call.answer(SimplePost.serializer(), post ?.asSimplePost)
} ?: call.answerBadRequest("Contents (List of Content)")
}
post(deletePostAddress) {
call.receiveOrNull<PostId>() ?.also { postId ->
call.answer(BooleanSerializer, writePostsAPI.deletePost(postId))
} ?: call.answerBadRequest("Post Id (String)")
}
post(deletePostAddress) {
call.receiveOrNull<UpdatePostRequest>() ?.also { (postId, contents) ->
call.answer(BooleanSerializer, writePostsAPI.updatePostContent(postId, contents))
} ?: call.answerBadRequest("Post Id (String)")
}
}