add integration with posts creating

This commit is contained in:
2022-01-22 13:30:49 +06:00
parent d1fa0242fa
commit 5e61c2a770
22 changed files with 548 additions and 43 deletions

View File

@@ -14,6 +14,8 @@ kotlin {
api project(":postssystem.features.content.server")
api project(":postssystem.features.posts.server")
api project(":postssystem.features.users.server")
api "dev.inmo:micro_utils.common:$microutils_version"
api "org.jetbrains.kotlinx:kotlinx-serialization-properties:$kotlin_serialisation_core_version"
}
}
}

View File

@@ -0,0 +1,49 @@
package dev.inmo.postssystem.services.posts.server
import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.repos.create
import dev.inmo.micro_utils.repos.deleteById
import dev.inmo.postssystem.features.content.common.Content
import dev.inmo.postssystem.features.content.common.ContentId
import dev.inmo.postssystem.features.content.server.storage.ServerContentStorage
import dev.inmo.postssystem.features.posts.common.*
import dev.inmo.postssystem.features.posts.server.ServerPostsStorage
import dev.inmo.postssystem.services.posts.common.FullNewPost
import dev.inmo.postssystem.services.posts.common.WritePostsService
class DefaultWritePostsService(
private val postsStorage: ServerPostsStorage,
private val contentStorage: ServerContentStorage<Content>
) : WritePostsService {
override suspend fun create(newPost: FullNewPost): RegisteredPost? {
val contentIds = contentStorage.create(newPost.content).map { it.id }
return postsStorage.create(NewPost(contentIds)).firstOrNull()
}
override suspend fun update(postId: PostId, content: List<Either<ContentId, Content>>): RegisteredPost? {
if (!postsStorage.contains(postId)) {
return null
}
val newContent = content.mapNotNull {
when (it) {
is EitherFirst -> {
it.t1
}
is EitherSecond -> {
contentStorage.create(it.t2).firstOrNull() ?.id
}
}
}
return postsStorage.update(
postId,
NewPost(newContent)
)
}
override suspend fun remove(postId: PostId) {
return postsStorage.deleteById(postId)
}
}

View File

@@ -0,0 +1,61 @@
package dev.inmo.postssystem.services.posts.server
import dev.inmo.micro_utils.common.FileName
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
import dev.inmo.micro_utils.ktor.common.decodeHex
import dev.inmo.postssystem.features.content.common.ContentSerializer
import dev.inmo.postssystem.services.posts.common.FullNewPost
import io.ktor.application.ApplicationCall
import io.ktor.application.call
import io.ktor.http.content.PartData
import io.ktor.request.receiveMultipart
import io.ktor.util.asStream
import io.ktor.util.pipeline.PipelineContext
import io.ktor.utils.io.core.use
suspend fun PipelineContext<Unit, ApplicationCall>.downloadFullNewPost(
serialFormat: StandardKtorSerialFormat
): FullNewPost {
val multipart = call.receiveMultipart()
val map = mutableMapOf<String, Any>()
var part = multipart.readPart()
while (part != null) {
val name = part.name
val capturedPart = part
when {
name == null -> {}
capturedPart is PartData.FormItem -> {
map[name] = serialFormat.decodeHex(
ContentSerializer,
capturedPart.value
)
}
capturedPart is PartData.FileItem -> {
val filename = FileName(capturedPart.originalFileName ?: error("File name is unknown for default part"))
val resultInput = MPPFile.createTempFile(
filename.nameWithoutExtension.let {
var resultName = it
while (resultName.length < 3) {
resultName += "_"
}
resultName
},
".${filename.extension}"
).apply {
outputStream().use { fileStream ->
capturedPart.provider().asStream().copyTo(fileStream)
}
}
}
else -> {}
}
part = multipart.readPart()
}
}

View File

@@ -0,0 +1,211 @@
package dev.inmo.postssystem.services.posts.server
import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.ktor.common.decodeHex
import dev.inmo.micro_utils.ktor.server.*
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
import dev.inmo.micro_utils.mime_types.findBuiltinMimeType
import dev.inmo.micro_utils.repos.ktor.common.crud.createRouting
import dev.inmo.micro_utils.repos.ktor.common.crud.updateRouting
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.removeRoute
import dev.inmo.micro_utils.repos.ktor.server.crud.configureReadStandardCrudRepoRoutes
import dev.inmo.postssystem.features.content.common.*
import dev.inmo.postssystem.features.posts.common.*
import dev.inmo.postssystem.services.posts.common.*
import io.ktor.application.ApplicationCall
import io.ktor.application.call
import io.ktor.auth.authenticate
import io.ktor.http.content.PartData
import io.ktor.request.isMultipart
import io.ktor.request.receiveMultipart
import io.ktor.routing.*
import io.ktor.util.asStream
import io.ktor.util.pipeline.PipelineContext
import io.ktor.utils.io.core.use
import io.ktor.utils.io.streams.asInput
import kotlinx.serialization.builtins.*
class ServerPostsServiceRoutingConfigurator(
private val readPostsService: ReadPostsService,
private val writePostsService: WritePostsService? = readPostsService as? WritePostsService,
private val unifiedRouter: UnifiedRouter
) : ApplicationRoutingConfigurator.Element {
private val contentEitherSerializer = EitherSerializer(ContentId.serializer(), ContentSerializer)
private val contentsEitherSerializer = ListSerializer(contentEitherSerializer)
private val contentsSerializer = ListSerializer(ContentSerializer)
private suspend fun PipelineContext<Unit, ApplicationCall>.receiveContents(): List<Content> {
return unifiedRouter.run {
if (call.request.isMultipart()) {
val multipart = call.receiveMultipart()
val list = mutableListOf<Pair<String, Content>>()
var part = multipart.readPart()
while (part != null) {
val name = part.name
val capturedPart = part
when {
name == null -> {}
capturedPart is PartData.FormItem -> {
list.add(
name to unifiedRouter.serialFormat.decodeHex(
ContentSerializer,
capturedPart.value
)
)
}
capturedPart is PartData.FileItem -> {
val filename = capturedPart.originalFileName ?.let(::FileName) ?: error("File name is unknown for default part")
val mimeType = capturedPart.contentType ?.let {
findBuiltinMimeType("${it.contentType}/${it.contentSubtype}")
} ?: error("File type is unknown for default part")
val resultInput = MPPFile.createTempFile(
filename.nameWithoutExtension.let {
var resultName = it
while (resultName.length < 3) {
resultName += "_"
}
resultName
},
".${filename.extension}"
).apply {
outputStream().use { fileStream ->
capturedPart.provider().asStream().copyTo(fileStream)
}
}
list.add(
name to BinaryContent(
filename,
mimeType
) { resultInput.inputStream().asInput() }
)
}
else -> {}
}
part = multipart.readPart()
}
list.sortedBy { it.first }.map { it.second }
} else {
uniload(contentsSerializer)
}
}
}
private suspend fun PipelineContext<Unit, ApplicationCall>.receiveContentsEithers(): List<Either<ContentId, Content>> {
return unifiedRouter.run {
if (call.request.isMultipart()) {
val multipart = call.receiveMultipart()
val list = mutableListOf<Pair<String, Either<ContentId, Content>>>()
var part = multipart.readPart()
while (part != null) {
val name = part.name
val capturedPart = part
when {
name == null -> {}
capturedPart is PartData.FormItem -> {
list.add(
name to unifiedRouter.serialFormat.decodeHex(
contentEitherSerializer,
capturedPart.value
)
)
}
capturedPart is PartData.FileItem -> {
val filename = capturedPart.originalFileName ?.let(::FileName) ?: error("File name is unknown for default part")
val mimeType = capturedPart.contentType ?.let {
findBuiltinMimeType("${it.contentType}/${it.contentSubtype}")
} ?: error("File type is unknown for default part")
val resultInput = MPPFile.createTempFile(
filename.nameWithoutExtension.let {
var resultName = it
while (resultName.length < 3) {
resultName += "_"
}
resultName
},
".${filename.extension}"
).apply {
outputStream().use { fileStream ->
capturedPart.provider().asStream().copyTo(fileStream)
}
}
list.add(
name to BinaryContent(
filename,
mimeType
) { resultInput.inputStream().asInput() }.either()
)
}
else -> {}
}
part = multipart.readPart()
}
list.sortedBy { it.first }.map { it.second }
} else {
uniload(contentsEitherSerializer)
}
}
}
override fun Route.invoke() {
authenticate {
route(postsRootPath) {
configureReadStandardCrudRepoRoutes(
readPostsService,
RegisteredPost.serializer(),
RegisteredPost.serializer().nullable,
PostId.serializer(),
unifiedRouter
)
writePostsService ?.let {
unifiedRouter.apply {
post(createRouting) {
val data = receiveContents()
unianswer(
RegisteredPost.serializer().nullable,
writePostsService.create(FullNewPost(data))
)
}
post(updateRouting) {
val postId = call.decodeUrlQueryValueOrSendError(postsPostIdParameter, PostId.serializer()) ?: return@post
val data = receiveContentsEithers()
unianswer(
RegisteredPost.serializer().nullable,
writePostsService.update(
postId,
data
)
)
}
post(removeRoute) {
val postId = uniload(PostId.serializer())
unianswer(
Unit.serializer(),
writePostsService.remove(postId)
)
}
}
}
}
}
}
}