add integration with posts creating
This commit is contained in:
client
features/content
binary
common
src
commonMain
kotlin
dev
inmo
postssystem
features
content
server
src
jvmMain
kotlin
dev
inmo
postssystem
features
content
binary
common
src
commonMain
kotlin
dev
inmo
postssystem
features
content
common
server
services/posts
client
src
commonMain
kotlin
dev
inmo
postssystem
services
common
server
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
49
services/posts/server/src/jvmMain/kotlin/dev/inmo/postssystem/services/posts/server/DefaultWritePostsService.kt
Normal file
49
services/posts/server/src/jvmMain/kotlin/dev/inmo/postssystem/services/posts/server/DefaultWritePostsService.kt
Normal 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)
|
||||
}
|
||||
}
|
61
services/posts/server/src/jvmMain/kotlin/dev/inmo/postssystem/services/posts/server/DownloadFullNewPost.kt
Normal file
61
services/posts/server/src/jvmMain/kotlin/dev/inmo/postssystem/services/posts/server/DownloadFullNewPost.kt
Normal 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()
|
||||
}
|
||||
|
||||
|
||||
}
|
211
services/posts/server/src/jvmMain/kotlin/dev/inmo/postssystem/services/posts/server/ServerPostsServiceRoutingConfigurator.kt
Normal file
211
services/posts/server/src/jvmMain/kotlin/dev/inmo/postssystem/services/posts/server/ServerPostsServiceRoutingConfigurator.kt
Normal 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)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user