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.client")
|
||||||
api project(":postssystem.features.content.text.client")
|
api project(":postssystem.features.content.text.client")
|
||||||
api project(":postssystem.features.content.binary.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.common:$microutils_version"
|
||||||
api "dev.inmo:micro_utils.fsm.repos.common:$microutils_version"
|
api "dev.inmo:micro_utils.fsm.repos.common:$microutils_version"
|
||||||
api "dev.inmo:micro_utils.crypto:$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.client.settings.auth.DefaultAuthSettings
|
||||||
import dev.inmo.postssystem.features.common.common.SerializersModuleConfigurator
|
import dev.inmo.postssystem.features.common.common.SerializersModuleConfigurator
|
||||||
import dev.inmo.postssystem.features.common.common.singleWithRandomQualifier
|
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.ContentSerializersModuleConfigurator
|
||||||
import dev.inmo.postssystem.features.content.common.OtherContentSerializerModuleConfigurator
|
import dev.inmo.postssystem.features.content.common.OtherContentSerializerModuleConfigurator
|
||||||
import dev.inmo.postssystem.features.content.text.common.TextContentSerializerModuleConfigurator
|
import dev.inmo.postssystem.features.content.text.common.TextContentSerializerModuleConfigurator
|
||||||
import dev.inmo.postssystem.features.status.client.StatusFeatureClient
|
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 io.ktor.client.HttpClient
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -43,6 +44,7 @@ import org.koin.core.context.startKoin
|
|||||||
import org.koin.core.module.Module
|
import org.koin.core.module.Module
|
||||||
import org.koin.core.qualifier.*
|
import org.koin.core.qualifier.*
|
||||||
import org.koin.core.scope.Scope
|
import org.koin.core.scope.Scope
|
||||||
|
import org.koin.dsl.binds
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val UIScopeQualifier = StringQualifier("CoroutineScopeUI")
|
val UIScopeQualifier = StringQualifier("CoroutineScopeUI")
|
||||||
@ -69,7 +71,6 @@ fun baseKoin(
|
|||||||
module {
|
module {
|
||||||
singleWithRandomQualifier<ContentSerializersModuleConfigurator.Element> { OtherContentSerializerModuleConfigurator }
|
singleWithRandomQualifier<ContentSerializersModuleConfigurator.Element> { OtherContentSerializerModuleConfigurator }
|
||||||
singleWithRandomQualifier<ContentSerializersModuleConfigurator.Element> { TextContentSerializerModuleConfigurator }
|
singleWithRandomQualifier<ContentSerializersModuleConfigurator.Element> { TextContentSerializerModuleConfigurator }
|
||||||
singleWithRandomQualifier<ContentSerializersModuleConfigurator.Element> { BinaryContentSerializerModuleConfigurator }
|
|
||||||
singleWithRandomQualifier<SerializersModuleConfigurator.Element> { ContentSerializersModuleConfigurator(getAll()) }
|
singleWithRandomQualifier<SerializersModuleConfigurator.Element> { ContentSerializersModuleConfigurator(getAll()) }
|
||||||
single { SerializersModuleConfigurator(getAll()) }
|
single { SerializersModuleConfigurator(getAll()) }
|
||||||
|
|
||||||
@ -133,5 +134,9 @@ fun getAuthorizedFeaturesDIModule(
|
|||||||
single<ReadFilesStorage> { ClientReadFilesStorage(get(serverUrlQualifier), get(), get()) }
|
single<ReadFilesStorage> { ClientReadFilesStorage(get(serverUrlQualifier), get(), get()) }
|
||||||
single<ReadUsersStorage> { UsersStorageKtorClient(get(serverUrlQualifier), get()) }
|
single<ReadUsersStorage> { UsersStorageKtorClient(get(serverUrlQualifier), get()) }
|
||||||
single<RolesStorage<Role>> { ClientRolesStorage(get(serverUrlQualifier), get(), Role.serializer()) }
|
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.pagination.*
|
||||||
import dev.inmo.micro_utils.repos.UpdatedValuePair
|
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.common.*
|
||||||
import dev.inmo.postssystem.features.content.server.storage.ServerContentStorage
|
import dev.inmo.postssystem.features.content.server.storage.ServerContentStorage
|
||||||
import dev.inmo.postssystem.features.files.common.*
|
import dev.inmo.postssystem.features.files.common.*
|
||||||
@ -12,7 +11,7 @@ import kotlinx.coroutines.flow.map
|
|||||||
|
|
||||||
class BinaryServerContentStorage(
|
class BinaryServerContentStorage(
|
||||||
private val filesStorage: FilesStorage
|
private val filesStorage: FilesStorage
|
||||||
) : ServerContentStorage<DefaultBinaryContent> {
|
) : ServerContentStorage<BinaryContent> {
|
||||||
private val FileId.asContentId
|
private val FileId.asContentId
|
||||||
get() = ContentId(string)
|
get() = ContentId(string)
|
||||||
private val ContentId.asFileId
|
private val ContentId.asFileId
|
||||||
@ -20,13 +19,13 @@ class BinaryServerContentStorage(
|
|||||||
private val FullFileInfoStorageWrapper.asRegisteredContent
|
private val FullFileInfoStorageWrapper.asRegisteredContent
|
||||||
get() = RegisteredContent(
|
get() = RegisteredContent(
|
||||||
id.asContentId,
|
id.asContentId,
|
||||||
DefaultBinaryContent(
|
BinaryContent(
|
||||||
fileInfo.name,
|
fileInfo.name,
|
||||||
fileInfo.mimeType,
|
fileInfo.mimeType,
|
||||||
fileInfo.inputProvider
|
fileInfo.inputProvider
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
private val DefaultBinaryContent.asFullFileInfo
|
private val BinaryContent.asFullFileInfo
|
||||||
get() = FullFileInfo(
|
get() = FullFileInfo(
|
||||||
filename,
|
filename,
|
||||||
mimeType,
|
mimeType,
|
||||||
@ -36,7 +35,7 @@ class BinaryServerContentStorage(
|
|||||||
override val newObjectsFlow: Flow<RegisteredContent> = filesStorage.newObjectsFlow.map { it.asRegisteredContent }
|
override val newObjectsFlow: Flow<RegisteredContent> = filesStorage.newObjectsFlow.map { it.asRegisteredContent }
|
||||||
override val updatedObjectsFlow: Flow<RegisteredContent> = filesStorage.updatedObjectsFlow.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(
|
return filesStorage.create(
|
||||||
values.map { it.asFullFileInfo }
|
values.map { it.asFullFileInfo }
|
||||||
).map { it.asRegisteredContent }
|
).map { it.asRegisteredContent }
|
||||||
@ -46,14 +45,14 @@ class BinaryServerContentStorage(
|
|||||||
filesStorage.deleteById(ids.map { it.asFileId })
|
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(
|
return filesStorage.update(
|
||||||
id.asFileId,
|
id.asFileId,
|
||||||
value.asFullFileInfo
|
value.asFullFileInfo
|
||||||
) ?.asRegisteredContent
|
) ?.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(
|
return filesStorage.update(
|
||||||
values.map { (id, content) ->
|
values.map { (id, content) ->
|
||||||
id.asFileId to content.asFullFileInfo
|
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.common.FileName
|
||||||
import dev.inmo.micro_utils.mime_types.MimeType
|
import dev.inmo.micro_utils.mime_types.MimeType
|
||||||
import dev.inmo.postssystem.features.common.common.SimpleInputProvider
|
import dev.inmo.postssystem.features.common.common.SimpleInputProvider
|
||||||
|
import kotlinx.serialization.PolymorphicSerializer
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlin.jvm.JvmInline
|
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
|
* This type represents some binary data which can be sent with multipart and deserialized from it
|
||||||
*/
|
*/
|
||||||
interface BinaryContent : Content {
|
data class BinaryContent(
|
||||||
val filename: FileName
|
val filename: FileName,
|
||||||
val mimeType: MimeType
|
val mimeType: MimeType,
|
||||||
val inputProvider: SimpleInputProvider
|
val inputProvider: SimpleInputProvider
|
||||||
}
|
) : Content
|
||||||
|
|
||||||
|
val ContentSerializer = PolymorphicSerializer(Content::class)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Content which is already registered in database. Using its [id] you can retrieve all known
|
* 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
|
kotlin_serialisation_core_version=1.3.2
|
||||||
|
|
||||||
koin_version=3.1.2
|
koin_version=3.1.2
|
||||||
microutils_version=0.9.1
|
microutils_version=0.9.4
|
||||||
ktor_version=1.6.7
|
ktor_version=1.6.7
|
||||||
logback_version=1.2.10
|
logback_version=1.2.10
|
||||||
uuid_version=0.3.1
|
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
|
# Server
|
||||||
|
|
||||||
|
@ -23,6 +23,8 @@ dependencies {
|
|||||||
api project(":postssystem.features.content.binary.server")
|
api project(":postssystem.features.content.binary.server")
|
||||||
api project(":postssystem.features.publication.server")
|
api project(":postssystem.features.publication.server")
|
||||||
|
|
||||||
|
api project(":postssystem.services.posts.server")
|
||||||
|
|
||||||
api project(":postssystem.targets.telegram.publication.server")
|
api project(":postssystem.targets.telegram.publication.server")
|
||||||
|
|
||||||
api "io.ktor:ktor-server-netty:$ktor_version"
|
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.keyvalue.ExposedKeyValueRepo
|
||||||
import dev.inmo.micro_utils.repos.exposed.onetomany.ExposedOneToManyKeyValueRepo
|
import dev.inmo.micro_utils.repos.exposed.onetomany.ExposedOneToManyKeyValueRepo
|
||||||
import dev.inmo.postssystem.features.common.common.*
|
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.binary.server.BinaryServerContentStorage
|
||||||
import dev.inmo.postssystem.features.content.common.*
|
import dev.inmo.postssystem.features.content.common.*
|
||||||
import dev.inmo.postssystem.features.content.server.storage.ServerContentStorage
|
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.posts.server.ServerPostsStorage
|
||||||
import dev.inmo.postssystem.features.publication.server.PublicationManager
|
import dev.inmo.postssystem.features.publication.server.PublicationManager
|
||||||
import dev.inmo.postssystem.features.publication.server.PublicationTarget
|
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 dev.inmo.postssystem.targets.telegram.publication.server.PublicationTargetTelegram
|
||||||
import io.ktor.application.featureOrNull
|
import io.ktor.application.featureOrNull
|
||||||
import io.ktor.application.log
|
import io.ktor.application.log
|
||||||
@ -86,7 +86,6 @@ fun getDIModule(
|
|||||||
return module {
|
return module {
|
||||||
singleWithRandomQualifier<ContentSerializersModuleConfigurator.Element> { OtherContentSerializerModuleConfigurator }
|
singleWithRandomQualifier<ContentSerializersModuleConfigurator.Element> { OtherContentSerializerModuleConfigurator }
|
||||||
singleWithRandomQualifier<ContentSerializersModuleConfigurator.Element> { TextContentSerializerModuleConfigurator }
|
singleWithRandomQualifier<ContentSerializersModuleConfigurator.Element> { TextContentSerializerModuleConfigurator }
|
||||||
singleWithRandomQualifier<ContentSerializersModuleConfigurator.Element> { BinaryContentSerializerModuleConfigurator }
|
|
||||||
singleWithRandomQualifier<SerializersModuleConfigurator.Element> { ContentSerializersModuleConfigurator(getAll()) }
|
singleWithRandomQualifier<SerializersModuleConfigurator.Element> { ContentSerializersModuleConfigurator(getAll()) }
|
||||||
single { SerializersModuleConfigurator(getAll()) }
|
single { SerializersModuleConfigurator(getAll()) }
|
||||||
|
|
||||||
@ -184,6 +183,7 @@ fun getDIModule(
|
|||||||
singleWithBinds { UsersStorageServerRoutesConfigurator(get(), get()) }
|
singleWithBinds { UsersStorageServerRoutesConfigurator(get(), get()) }
|
||||||
singleWithBinds { RolesStorageReadServerRoutesConfigurator<Role>(get(), RoleSerializer, get()) }
|
singleWithBinds { RolesStorageReadServerRoutesConfigurator<Role>(get(), RoleSerializer, get()) }
|
||||||
singleWithBinds { RolesManagerRolesStorageServerRoutesConfigurator(get(), get()) }
|
singleWithBinds { RolesManagerRolesStorageServerRoutesConfigurator(get(), get()) }
|
||||||
|
singleWithBinds { ServerPostsServiceRoutingConfigurator(get(), get(), get()) }
|
||||||
|
|
||||||
singleWithBinds { ClientStaticRoutingConfiguration(get<Config>().clientStatic) }
|
singleWithBinds { ClientStaticRoutingConfiguration(get<Config>().clientStatic) }
|
||||||
singleWithBinds {
|
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 {
|
dependencies {
|
||||||
api project(":postssystem.features.common.common")
|
api project(":postssystem.features.common.common")
|
||||||
api project(":postssystem.features.posts.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
|
package dev.inmo.postssystem.services.posts.common
|
||||||
|
|
||||||
const val postsRootPath = "posts"
|
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.content.server")
|
||||||
api project(":postssystem.features.posts.server")
|
api project(":postssystem.features.posts.server")
|
||||||
api project(":postssystem.features.users.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