add integration with posts creating
This commit is contained in:
parent
d1fa0242fa
commit
5e61c2a770
@ -23,6 +23,9 @@ kotlin {
|
||||
api project(":postssystem.features.content.client")
|
||||
api project(":postssystem.features.content.text.client")
|
||||
api project(":postssystem.features.content.binary.client")
|
||||
|
||||
api project(":postssystem.services.posts.client")
|
||||
|
||||
api "dev.inmo:micro_utils.fsm.common:$microutils_version"
|
||||
api "dev.inmo:micro_utils.fsm.repos.common:$microutils_version"
|
||||
api "dev.inmo:micro_utils.crypto:$microutils_version"
|
||||
|
@ -25,11 +25,12 @@ import dev.inmo.postssystem.client.settings.auth.AuthSettings
|
||||
import dev.inmo.postssystem.client.settings.auth.DefaultAuthSettings
|
||||
import dev.inmo.postssystem.features.common.common.SerializersModuleConfigurator
|
||||
import dev.inmo.postssystem.features.common.common.singleWithRandomQualifier
|
||||
import dev.inmo.postssystem.features.content.binary.common.BinaryContentSerializerModuleConfigurator
|
||||
import dev.inmo.postssystem.features.content.common.ContentSerializersModuleConfigurator
|
||||
import dev.inmo.postssystem.features.content.common.OtherContentSerializerModuleConfigurator
|
||||
import dev.inmo.postssystem.features.content.text.common.TextContentSerializerModuleConfigurator
|
||||
import dev.inmo.postssystem.features.status.client.StatusFeatureClient
|
||||
import dev.inmo.postssystem.services.posts.client.ClientPostsService
|
||||
import dev.inmo.postssystem.services.posts.common.*
|
||||
import io.ktor.client.HttpClient
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -43,6 +44,7 @@ import org.koin.core.context.startKoin
|
||||
import org.koin.core.module.Module
|
||||
import org.koin.core.qualifier.*
|
||||
import org.koin.core.scope.Scope
|
||||
import org.koin.dsl.binds
|
||||
import org.koin.dsl.module
|
||||
|
||||
val UIScopeQualifier = StringQualifier("CoroutineScopeUI")
|
||||
@ -69,7 +71,6 @@ fun baseKoin(
|
||||
module {
|
||||
singleWithRandomQualifier<ContentSerializersModuleConfigurator.Element> { OtherContentSerializerModuleConfigurator }
|
||||
singleWithRandomQualifier<ContentSerializersModuleConfigurator.Element> { TextContentSerializerModuleConfigurator }
|
||||
singleWithRandomQualifier<ContentSerializersModuleConfigurator.Element> { BinaryContentSerializerModuleConfigurator }
|
||||
singleWithRandomQualifier<SerializersModuleConfigurator.Element> { ContentSerializersModuleConfigurator(getAll()) }
|
||||
single { SerializersModuleConfigurator(getAll()) }
|
||||
|
||||
@ -133,5 +134,9 @@ fun getAuthorizedFeaturesDIModule(
|
||||
single<ReadFilesStorage> { ClientReadFilesStorage(get(serverUrlQualifier), get(), get()) }
|
||||
single<ReadUsersStorage> { UsersStorageKtorClient(get(serverUrlQualifier), get()) }
|
||||
single<RolesStorage<Role>> { ClientRolesStorage(get(serverUrlQualifier), get(), Role.serializer()) }
|
||||
single<PostsService> { ClientPostsService(get(serverUrlQualifier), get()) } binds arrayOf(
|
||||
ReadPostsService::class,
|
||||
WritePostsService::class
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +0,0 @@
|
||||
package dev.inmo.postssystem.features.content.binary.common
|
||||
|
||||
import dev.inmo.postssystem.features.content.common.Content
|
||||
import dev.inmo.postssystem.features.content.common.ContentSerializersModuleConfigurator
|
||||
import kotlinx.serialization.modules.PolymorphicModuleBuilder
|
||||
|
||||
object BinaryContentSerializerModuleConfigurator : ContentSerializersModuleConfigurator.Element {
|
||||
override fun PolymorphicModuleBuilder<Content>.invoke() {
|
||||
subclass(DefaultBinaryContent::class, DefaultBinaryContent.serializer())
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package dev.inmo.postssystem.features.content.binary.common
|
||||
|
||||
import dev.inmo.micro_utils.common.FileName
|
||||
import dev.inmo.micro_utils.mime_types.MimeType
|
||||
import dev.inmo.postssystem.features.common.common.SimpleInputProvider
|
||||
import dev.inmo.postssystem.features.content.common.BinaryContent
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class DefaultBinaryContent(
|
||||
override val filename: FileName,
|
||||
override val mimeType: MimeType,
|
||||
override val inputProvider: SimpleInputProvider
|
||||
) : BinaryContent
|
@ -2,7 +2,6 @@ package dev.inmo.postssystem.features.content.binary.server
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.repos.UpdatedValuePair
|
||||
import dev.inmo.postssystem.features.content.binary.common.DefaultBinaryContent
|
||||
import dev.inmo.postssystem.features.content.common.*
|
||||
import dev.inmo.postssystem.features.content.server.storage.ServerContentStorage
|
||||
import dev.inmo.postssystem.features.files.common.*
|
||||
@ -12,7 +11,7 @@ import kotlinx.coroutines.flow.map
|
||||
|
||||
class BinaryServerContentStorage(
|
||||
private val filesStorage: FilesStorage
|
||||
) : ServerContentStorage<DefaultBinaryContent> {
|
||||
) : ServerContentStorage<BinaryContent> {
|
||||
private val FileId.asContentId
|
||||
get() = ContentId(string)
|
||||
private val ContentId.asFileId
|
||||
@ -20,13 +19,13 @@ class BinaryServerContentStorage(
|
||||
private val FullFileInfoStorageWrapper.asRegisteredContent
|
||||
get() = RegisteredContent(
|
||||
id.asContentId,
|
||||
DefaultBinaryContent(
|
||||
BinaryContent(
|
||||
fileInfo.name,
|
||||
fileInfo.mimeType,
|
||||
fileInfo.inputProvider
|
||||
)
|
||||
)
|
||||
private val DefaultBinaryContent.asFullFileInfo
|
||||
private val BinaryContent.asFullFileInfo
|
||||
get() = FullFileInfo(
|
||||
filename,
|
||||
mimeType,
|
||||
@ -36,7 +35,7 @@ class BinaryServerContentStorage(
|
||||
override val newObjectsFlow: Flow<RegisteredContent> = filesStorage.newObjectsFlow.map { it.asRegisteredContent }
|
||||
override val updatedObjectsFlow: Flow<RegisteredContent> = filesStorage.updatedObjectsFlow.map { it.asRegisteredContent }
|
||||
|
||||
override suspend fun create(values: List<DefaultBinaryContent>): List<RegisteredContent> {
|
||||
override suspend fun create(values: List<BinaryContent>): List<RegisteredContent> {
|
||||
return filesStorage.create(
|
||||
values.map { it.asFullFileInfo }
|
||||
).map { it.asRegisteredContent }
|
||||
@ -46,14 +45,14 @@ class BinaryServerContentStorage(
|
||||
filesStorage.deleteById(ids.map { it.asFileId })
|
||||
}
|
||||
|
||||
override suspend fun update(id: ContentId, value: DefaultBinaryContent): RegisteredContent? {
|
||||
override suspend fun update(id: ContentId, value: BinaryContent): RegisteredContent? {
|
||||
return filesStorage.update(
|
||||
id.asFileId,
|
||||
value.asFullFileInfo
|
||||
) ?.asRegisteredContent
|
||||
}
|
||||
|
||||
override suspend fun update(values: List<UpdatedValuePair<ContentId, DefaultBinaryContent>>): List<RegisteredContent> {
|
||||
override suspend fun update(values: List<UpdatedValuePair<ContentId, BinaryContent>>): List<RegisteredContent> {
|
||||
return filesStorage.update(
|
||||
values.map { (id, content) ->
|
||||
id.asFileId to content.asFullFileInfo
|
||||
|
@ -3,6 +3,7 @@ package dev.inmo.postssystem.features.content.common
|
||||
import dev.inmo.micro_utils.common.FileName
|
||||
import dev.inmo.micro_utils.mime_types.MimeType
|
||||
import dev.inmo.postssystem.features.common.common.SimpleInputProvider
|
||||
import kotlinx.serialization.PolymorphicSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.jvm.JvmInline
|
||||
|
||||
@ -26,11 +27,13 @@ interface SimpleContent : Content
|
||||
/**
|
||||
* This type represents some binary data which can be sent with multipart and deserialized from it
|
||||
*/
|
||||
interface BinaryContent : Content {
|
||||
val filename: FileName
|
||||
val mimeType: MimeType
|
||||
data class BinaryContent(
|
||||
val filename: FileName,
|
||||
val mimeType: MimeType,
|
||||
val inputProvider: SimpleInputProvider
|
||||
}
|
||||
) : Content
|
||||
|
||||
val ContentSerializer = PolymorphicSerializer(Content::class)
|
||||
|
||||
/**
|
||||
* Content which is already registered in database. Using its [id] you can retrieve all known
|
||||
|
@ -13,13 +13,13 @@ kotlin_version=1.6.10
|
||||
kotlin_serialisation_core_version=1.3.2
|
||||
|
||||
koin_version=3.1.2
|
||||
microutils_version=0.9.1
|
||||
microutils_version=0.9.4
|
||||
ktor_version=1.6.7
|
||||
logback_version=1.2.10
|
||||
uuid_version=0.3.1
|
||||
klock_version=2.4.10
|
||||
klock_version=2.4.12
|
||||
|
||||
tgbotapi_version=0.38.1
|
||||
tgbotapi_version=0.38.3
|
||||
|
||||
# Server
|
||||
|
||||
|
@ -23,6 +23,8 @@ dependencies {
|
||||
api project(":postssystem.features.content.binary.server")
|
||||
api project(":postssystem.features.publication.server")
|
||||
|
||||
api project(":postssystem.services.posts.server")
|
||||
|
||||
api project(":postssystem.targets.telegram.publication.server")
|
||||
|
||||
api "io.ktor:ktor-server-netty:$ktor_version"
|
||||
|
@ -25,7 +25,6 @@ import dev.inmo.micro_utils.ktor.server.createKtorServer
|
||||
import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.exposed.onetomany.ExposedOneToManyKeyValueRepo
|
||||
import dev.inmo.postssystem.features.common.common.*
|
||||
import dev.inmo.postssystem.features.content.binary.common.BinaryContentSerializerModuleConfigurator
|
||||
import dev.inmo.postssystem.features.content.binary.server.BinaryServerContentStorage
|
||||
import dev.inmo.postssystem.features.content.common.*
|
||||
import dev.inmo.postssystem.features.content.server.storage.ServerContentStorage
|
||||
@ -36,6 +35,7 @@ import dev.inmo.postssystem.features.posts.server.ExposedServerPostsStorage
|
||||
import dev.inmo.postssystem.features.posts.server.ServerPostsStorage
|
||||
import dev.inmo.postssystem.features.publication.server.PublicationManager
|
||||
import dev.inmo.postssystem.features.publication.server.PublicationTarget
|
||||
import dev.inmo.postssystem.services.posts.server.ServerPostsServiceRoutingConfigurator
|
||||
import dev.inmo.postssystem.targets.telegram.publication.server.PublicationTargetTelegram
|
||||
import io.ktor.application.featureOrNull
|
||||
import io.ktor.application.log
|
||||
@ -86,7 +86,6 @@ fun getDIModule(
|
||||
return module {
|
||||
singleWithRandomQualifier<ContentSerializersModuleConfigurator.Element> { OtherContentSerializerModuleConfigurator }
|
||||
singleWithRandomQualifier<ContentSerializersModuleConfigurator.Element> { TextContentSerializerModuleConfigurator }
|
||||
singleWithRandomQualifier<ContentSerializersModuleConfigurator.Element> { BinaryContentSerializerModuleConfigurator }
|
||||
singleWithRandomQualifier<SerializersModuleConfigurator.Element> { ContentSerializersModuleConfigurator(getAll()) }
|
||||
single { SerializersModuleConfigurator(getAll()) }
|
||||
|
||||
@ -184,6 +183,7 @@ fun getDIModule(
|
||||
singleWithBinds { UsersStorageServerRoutesConfigurator(get(), get()) }
|
||||
singleWithBinds { RolesStorageReadServerRoutesConfigurator<Role>(get(), RoleSerializer, get()) }
|
||||
singleWithBinds { RolesManagerRolesStorageServerRoutesConfigurator(get(), get()) }
|
||||
singleWithBinds { ServerPostsServiceRoutingConfigurator(get(), get(), get()) }
|
||||
|
||||
singleWithBinds { ClientStaticRoutingConfiguration(get<Config>().clientStatic) }
|
||||
singleWithBinds {
|
||||
|
@ -0,0 +1,11 @@
|
||||
package dev.inmo.postssystem.services.posts.client
|
||||
|
||||
import dev.inmo.micro_utils.ktor.client.UnifiedRequester
|
||||
import dev.inmo.postssystem.services.posts.common.*
|
||||
|
||||
class ClientPostsService(
|
||||
baseUrl: String,
|
||||
unifiedRequester: UnifiedRequester
|
||||
) : PostsService,
|
||||
ReadPostsService by ClientReadPostsService(baseUrl, unifiedRequester),
|
||||
WritePostsService by ClientWritePostsService(baseUrl, unifiedRequester)
|
@ -0,0 +1,22 @@
|
||||
package dev.inmo.postssystem.services.posts.client
|
||||
|
||||
import dev.inmo.micro_utils.ktor.client.UnifiedRequester
|
||||
import dev.inmo.micro_utils.ktor.common.buildStandardUrl
|
||||
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.client.crud.KtorReadStandardCrudRepo
|
||||
import dev.inmo.postssystem.features.posts.common.PostId
|
||||
import dev.inmo.postssystem.features.posts.common.RegisteredPost
|
||||
import dev.inmo.postssystem.services.posts.common.ReadPostsService
|
||||
import dev.inmo.postssystem.services.posts.common.postsRootPath
|
||||
import kotlinx.serialization.builtins.nullable
|
||||
|
||||
class ClientReadPostsService(
|
||||
private val baseUrl: String,
|
||||
private val unifiedRequester: UnifiedRequester
|
||||
) : ReadPostsService, ReadCRUDRepo<RegisteredPost, PostId> by KtorReadStandardCrudRepo(
|
||||
buildStandardUrl(baseUrl, postsRootPath),
|
||||
unifiedRequester,
|
||||
RegisteredPost.serializer(),
|
||||
RegisteredPost.serializer().nullable,
|
||||
PostId.serializer()
|
||||
)
|
@ -0,0 +1,123 @@
|
||||
package dev.inmo.postssystem.services.posts.client
|
||||
|
||||
import dev.inmo.micro_utils.common.*
|
||||
import dev.inmo.micro_utils.ktor.client.UnifiedRequester
|
||||
import dev.inmo.micro_utils.ktor.common.buildStandardUrl
|
||||
import dev.inmo.micro_utils.ktor.common.encodeHex
|
||||
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.postssystem.features.content.common.*
|
||||
import dev.inmo.postssystem.features.posts.common.PostId
|
||||
import dev.inmo.postssystem.features.posts.common.RegisteredPost
|
||||
import dev.inmo.postssystem.services.posts.common.*
|
||||
import io.ktor.client.request.forms.InputProvider
|
||||
import io.ktor.client.request.forms.formData
|
||||
import io.ktor.client.request.headers
|
||||
import io.ktor.client.request.post
|
||||
import io.ktor.http.HttpHeaders
|
||||
import kotlinx.serialization.builtins.*
|
||||
|
||||
class ClientWritePostsService(
|
||||
private val baseUrl: String,
|
||||
private val unifiedRequester: UnifiedRequester
|
||||
) : WritePostsService {
|
||||
private val root = buildStandardUrl(baseUrl, postsRootPath)
|
||||
|
||||
private val contentEitherSerializer = EitherSerializer(ContentId.serializer(), ContentSerializer)
|
||||
private val contentsEitherSerializer = ListSerializer(contentEitherSerializer)
|
||||
private val contentsSerializer = ListSerializer(ContentSerializer)
|
||||
|
||||
private val createFullPath = buildStandardUrl(
|
||||
root,
|
||||
createRouting
|
||||
)
|
||||
private val removeFullPath = buildStandardUrl(
|
||||
root,
|
||||
removeRoute
|
||||
)
|
||||
|
||||
override suspend fun create(newPost: FullNewPost): RegisteredPost? {
|
||||
return if (newPost.content.any { it is BinaryContent }) {
|
||||
val answer = unifiedRequester.client.post<ByteArray>(createFullPath) {
|
||||
formData {
|
||||
newPost.content.forEachIndexed { i, content ->
|
||||
when (content) {
|
||||
is BinaryContent -> append(
|
||||
i.toString(),
|
||||
InputProvider(block = content.inputProvider),
|
||||
headers {
|
||||
append(HttpHeaders.ContentType, content.mimeType.raw)
|
||||
append(HttpHeaders.ContentDisposition, "filename=\"${content.filename.name}\"")
|
||||
}.build()
|
||||
)
|
||||
else -> append(
|
||||
i.toString(),
|
||||
unifiedRequester.serialFormat.encodeHex(ContentSerializer, content)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
unifiedRequester.serialFormat.decodeFromByteArray(RegisteredPost.serializer().nullable, answer)
|
||||
} else {
|
||||
unifiedRequester.unipost(
|
||||
createFullPath,
|
||||
contentsSerializer to newPost.content,
|
||||
RegisteredPost.serializer().nullable
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun update(
|
||||
postId: PostId,
|
||||
content: List<Either<ContentId, Content>>
|
||||
): RegisteredPost? {
|
||||
return if (content.any { it.optionalT2.data is BinaryContent }) {
|
||||
val answer = unifiedRequester.client.post<ByteArray>(createFullPath) {
|
||||
formData {
|
||||
content.forEachIndexed { i, eitherContent ->
|
||||
eitherContent.onFirst {
|
||||
append(
|
||||
i.toString(),
|
||||
unifiedRequester.serialFormat.encodeHex(contentEitherSerializer, it.either())
|
||||
)
|
||||
}.onSecond {
|
||||
when (it) {
|
||||
is BinaryContent -> append(
|
||||
i.toString(),
|
||||
InputProvider(block = it.inputProvider),
|
||||
headers {
|
||||
append(HttpHeaders.ContentType, it.mimeType.raw)
|
||||
append(HttpHeaders.ContentDisposition, "filename=\"${it.filename.name}\"")
|
||||
}.build()
|
||||
)
|
||||
else -> append(
|
||||
i.toString(),
|
||||
unifiedRequester.serialFormat.encodeHex(contentEitherSerializer, it.either())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
unifiedRequester.serialFormat.decodeFromByteArray(RegisteredPost.serializer().nullable, answer)
|
||||
} else {
|
||||
unifiedRequester.unipost(
|
||||
buildStandardUrl(
|
||||
root,
|
||||
updateRouting,
|
||||
postsPostIdParameter to unifiedRequester.encodeUrlQueryValue(PostId.serializer(), postId)
|
||||
),
|
||||
contentsEitherSerializer to content,
|
||||
RegisteredPost.serializer().nullable
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun remove(postId: PostId) = unifiedRequester.unipost(
|
||||
removeFullPath,
|
||||
PostId.serializer() to postId,
|
||||
Unit.serializer()
|
||||
)
|
||||
}
|
@ -12,6 +12,8 @@ kotlin {
|
||||
dependencies {
|
||||
api project(":postssystem.features.common.common")
|
||||
api project(":postssystem.features.posts.common")
|
||||
api "dev.inmo:micro_utils.repos.common:$microutils_version"
|
||||
api "dev.inmo:micro_utils.repos.ktor.client:$microutils_version"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
package dev.inmo.postssystem.services.posts.common
|
||||
|
||||
const val postsRootPath = "posts"
|
||||
|
||||
const val postsPostIdParameter = "postId"
|
||||
|
@ -0,0 +1,9 @@
|
||||
package dev.inmo.postssystem.services.posts.common
|
||||
|
||||
import dev.inmo.postssystem.features.content.common.Content
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class FullNewPost(
|
||||
val content: List<Content>
|
||||
)
|
@ -0,0 +1,3 @@
|
||||
package dev.inmo.postssystem.services.posts.common
|
||||
|
||||
interface PostsService : ReadPostsService, WritePostsService
|
@ -0,0 +1,7 @@
|
||||
package dev.inmo.postssystem.services.posts.common
|
||||
|
||||
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||
import dev.inmo.postssystem.features.posts.common.PostId
|
||||
import dev.inmo.postssystem.features.posts.common.RegisteredPost
|
||||
|
||||
interface ReadPostsService : ReadCRUDRepo<RegisteredPost, PostId>
|
@ -0,0 +1,16 @@
|
||||
package dev.inmo.postssystem.services.posts.common
|
||||
|
||||
import dev.inmo.micro_utils.common.Either
|
||||
import dev.inmo.postssystem.features.content.common.Content
|
||||
import dev.inmo.postssystem.features.content.common.ContentId
|
||||
import dev.inmo.postssystem.features.posts.common.PostId
|
||||
import dev.inmo.postssystem.features.posts.common.RegisteredPost
|
||||
|
||||
interface WritePostsService {
|
||||
suspend fun create(newPost: FullNewPost): RegisteredPost?
|
||||
suspend fun update(
|
||||
postId: PostId,
|
||||
content: List<Either<ContentId, Content>>
|
||||
): RegisteredPost?
|
||||
suspend fun remove(postId: PostId)
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user