diff --git a/features/content/binary/common/build.gradle b/features/content/binary/common/build.gradle index f4369eca..df874449 100644 --- a/features/content/binary/common/build.gradle +++ b/features/content/binary/common/build.gradle @@ -12,6 +12,7 @@ kotlin { dependencies { api project(":postssystem.features.common.common") api project(":postssystem.features.content.common") + api "dev.inmo:micro_utils.mime_types:$microutils_version" } } } diff --git a/features/content/binary/common/src/commonMain/kotlin/dev/inmo/postssystem/features/content/binary/common/BinaryContent.kt b/features/content/binary/common/src/commonMain/kotlin/dev/inmo/postssystem/features/content/binary/common/BinaryContent.kt index 8c5ea30a..d35f11e4 100644 --- a/features/content/binary/common/src/commonMain/kotlin/dev/inmo/postssystem/features/content/binary/common/BinaryContent.kt +++ b/features/content/binary/common/src/commonMain/kotlin/dev/inmo/postssystem/features/content/binary/common/BinaryContent.kt @@ -1,9 +1,14 @@ package dev.inmo.postssystem.features.content.binary.common +import dev.inmo.micro_utils.common.ByteArrayAllocator +import dev.inmo.micro_utils.common.FileName +import dev.inmo.micro_utils.mime_types.MimeType import dev.inmo.postssystem.features.content.common.Content import kotlinx.serialization.Serializable @Serializable data class BinaryContent( - val bytes: ByteArray -) : Content \ No newline at end of file + val filename: FileName, + val mimeType: MimeType, + val bytesAllocator: ByteArrayAllocator +) : Content diff --git a/features/content/common/src/commonMain/kotlin/dev/inmo/postssystem/features/content/common/Content.kt b/features/content/common/src/commonMain/kotlin/dev/inmo/postssystem/features/content/common/Content.kt index d8214e74..a43bb569 100644 --- a/features/content/common/src/commonMain/kotlin/dev/inmo/postssystem/features/content/common/Content.kt +++ b/features/content/common/src/commonMain/kotlin/dev/inmo/postssystem/features/content/common/Content.kt @@ -2,7 +2,8 @@ package dev.inmo.postssystem.features.content.common import kotlinx.serialization.Serializable -typealias ContentId = String +@Serializable +value class ContentId(val string: String) /** * Content which is planned to be registered in database diff --git a/features/content/server/src/jvmMain/kotlin/dev/inmo/postssystem/features/content/server/ServerContentStorage.kt b/features/content/server/src/jvmMain/kotlin/dev/inmo/postssystem/features/content/server/ServerContentStorage.kt new file mode 100644 index 00000000..ff424af6 --- /dev/null +++ b/features/content/server/src/jvmMain/kotlin/dev/inmo/postssystem/features/content/server/ServerContentStorage.kt @@ -0,0 +1,6 @@ +package dev.inmo.postssystem.features.content.server + +import dev.inmo.micro_utils.repos.CRUDRepo +import dev.inmo.postssystem.features.content.common.* + +interface ServerContentStorage : ServerReadContentStorage, ServerWriteContentStorage, CRUDRepo diff --git a/features/content/server/src/jvmMain/kotlin/dev/inmo/postssystem/features/content/server/ServerReadContentStorage.kt b/features/content/server/src/jvmMain/kotlin/dev/inmo/postssystem/features/content/server/ServerReadContentStorage.kt new file mode 100644 index 00000000..5375edd5 --- /dev/null +++ b/features/content/server/src/jvmMain/kotlin/dev/inmo/postssystem/features/content/server/ServerReadContentStorage.kt @@ -0,0 +1,7 @@ +package dev.inmo.postssystem.features.content.server + +import dev.inmo.micro_utils.repos.ReadCRUDRepo +import dev.inmo.postssystem.features.content.common.ContentId +import dev.inmo.postssystem.features.content.common.RegisteredContent + +interface ServerReadContentStorage : ReadCRUDRepo diff --git a/features/content/server/src/jvmMain/kotlin/dev/inmo/postssystem/features/content/server/ServerWriteContentStorage.kt b/features/content/server/src/jvmMain/kotlin/dev/inmo/postssystem/features/content/server/ServerWriteContentStorage.kt new file mode 100644 index 00000000..d70e1f9c --- /dev/null +++ b/features/content/server/src/jvmMain/kotlin/dev/inmo/postssystem/features/content/server/ServerWriteContentStorage.kt @@ -0,0 +1,6 @@ +package dev.inmo.postssystem.features.content.server + +import dev.inmo.micro_utils.repos.WriteCRUDRepo +import dev.inmo.postssystem.features.content.common.* + +interface ServerWriteContentStorage : WriteCRUDRepo diff --git a/features/publication/client/build.gradle b/features/publication/client/build.gradle new file mode 100644 index 00000000..64cb6901 --- /dev/null +++ b/features/publication/client/build.gradle @@ -0,0 +1,18 @@ +plugins { + id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.kotlin.plugin.serialization" + id "com.android.library" +} + +apply from: "$mppProjectWithSerializationPresetPath" + +kotlin { + sourceSets { + commonMain { + dependencies { + api project(":postssystem.features.publication.common") + api project(":postssystem.features.common.client") + } + } + } +} diff --git a/features/publication/client/src/main/AndroidManifest.xml b/features/publication/client/src/main/AndroidManifest.xml new file mode 100644 index 00000000..a785962d --- /dev/null +++ b/features/publication/client/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/features/publication/common/build.gradle b/features/publication/common/build.gradle new file mode 100644 index 00000000..3a9a28ae --- /dev/null +++ b/features/publication/common/build.gradle @@ -0,0 +1,19 @@ +plugins { + id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.kotlin.plugin.serialization" + id "com.android.library" +} + +apply from: "$mppProjectWithSerializationPresetPath" + +kotlin { + sourceSets { + commonMain { + dependencies { + api project(":postssystem.features.common.common") + api project(":postssystem.features.content.common") + api project(":postssystem.features.posts.common") + } + } + } +} diff --git a/features/publication/common/src/main/AndroidManifest.xml b/features/publication/common/src/main/AndroidManifest.xml new file mode 100644 index 00000000..7a641a46 --- /dev/null +++ b/features/publication/common/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/features/publication/server/build.gradle b/features/publication/server/build.gradle new file mode 100644 index 00000000..e035cf3a --- /dev/null +++ b/features/publication/server/build.gradle @@ -0,0 +1,19 @@ +plugins { + id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.kotlin.plugin.serialization" +} + +apply from: "$mppJavaProjectPresetPath" + +kotlin { + sourceSets { + commonMain { + dependencies { + api project(":postssystem.features.publication.common") + api project(":postssystem.features.common.server") + api project(":postssystem.features.content.server") + api project(":postssystem.features.posts.server") + } + } + } +} diff --git a/features/publication/server/src/jvmMain/kotlin/dev/inmo/postssystem/features/publication/server/PublicationManager.kt b/features/publication/server/src/jvmMain/kotlin/dev/inmo/postssystem/features/publication/server/PublicationManager.kt new file mode 100644 index 00000000..d70feba5 --- /dev/null +++ b/features/publication/server/src/jvmMain/kotlin/dev/inmo/postssystem/features/publication/server/PublicationManager.kt @@ -0,0 +1,33 @@ +package dev.inmo.postssystem.features.publication.server + +import dev.inmo.micro_utils.coroutines.asyncSafelyWithoutExceptions +import dev.inmo.postssystem.features.content.server.ServerReadContentStorage +import dev.inmo.postssystem.features.posts.common.PostId +import dev.inmo.postssystem.features.posts.server.ServerReadPostsStorage +import kotlinx.coroutines.* + +class PublicationManager ( + private val targets: List, + private val postsRepo: ServerReadPostsStorage, + private val contentRepo: ServerReadContentStorage, + private val scope: CoroutineScope +) { + suspend fun publish( + postId: PostId + ) { + val post = postsRepo.getById(postId) ?: return + val content = post.content.map { + scope.async { + contentRepo.getById(it) + } + }.awaitAll().filterNotNull() + + val publicationPost = PublicationPost(post, content) + + targets.map { + scope.asyncSafelyWithoutExceptions { + it.publish(publicationPost) + } + }.awaitAll() + } +} diff --git a/features/publication/server/src/jvmMain/kotlin/dev/inmo/postssystem/features/publication/server/PublicationPost.kt b/features/publication/server/src/jvmMain/kotlin/dev/inmo/postssystem/features/publication/server/PublicationPost.kt new file mode 100644 index 00000000..04133ce3 --- /dev/null +++ b/features/publication/server/src/jvmMain/kotlin/dev/inmo/postssystem/features/publication/server/PublicationPost.kt @@ -0,0 +1,11 @@ +package dev.inmo.postssystem.features.publication.server + +import dev.inmo.postssystem.features.content.common.RegisteredContent +import dev.inmo.postssystem.features.posts.common.RegisteredPost +import kotlinx.serialization.Serializable + +@Serializable +data class PublicationPost( + val post: RegisteredPost, + val content: List +) diff --git a/features/publication/server/src/jvmMain/kotlin/dev/inmo/postssystem/features/publication/server/PublicationTarget.kt b/features/publication/server/src/jvmMain/kotlin/dev/inmo/postssystem/features/publication/server/PublicationTarget.kt new file mode 100644 index 00000000..220b9a50 --- /dev/null +++ b/features/publication/server/src/jvmMain/kotlin/dev/inmo/postssystem/features/publication/server/PublicationTarget.kt @@ -0,0 +1,5 @@ +package dev.inmo.postssystem.features.publication.server + +fun interface PublicationTarget { + suspend fun publish(post: PublicationPost) +} diff --git a/gradle.properties b/gradle.properties index c8c5ba68..a2d9d4cd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,6 +19,8 @@ logback_version=1.2.10 uuid_version=0.3.1 klock_version=2.4.10 +tgbotapi_version=0.38.1 + # Server kotlin_exposed_version=0.37.2 diff --git a/server/build.gradle b/server/build.gradle index 50cf3367..f4549406 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -21,6 +21,10 @@ dependencies { api project(":postssystem.features.content.server") api project(":postssystem.features.content.text.server") api project(":postssystem.features.content.binary.server") + api project(":postssystem.features.publication.server") + + api project(":postssystem.targets.telegram.publication.server") + api "io.ktor:ktor-server-netty:$ktor_version" api "io.ktor:ktor-websockets:$ktor_version" api "org.jetbrains.exposed:exposed-jdbc:$kotlin_exposed_version" diff --git a/server/src/main/java/dev/inmo/postssystem/server/DI.kt b/server/src/main/java/dev/inmo/postssystem/server/DI.kt index 6fc9f6ff..53ceed25 100644 --- a/server/src/main/java/dev/inmo/postssystem/server/DI.kt +++ b/server/src/main/java/dev/inmo/postssystem/server/DI.kt @@ -29,6 +29,9 @@ import dev.inmo.postssystem.features.content.binary.common.BinaryContentSerializ 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.publication.server.PublicationManager +import dev.inmo.postssystem.features.publication.server.PublicationTarget +import dev.inmo.postssystem.targets.telegram.publication.server.PublicationTargetTelegram import io.ktor.application.featureOrNull import io.ktor.application.log import io.ktor.routing.Route @@ -129,6 +132,11 @@ fun getDIModule( } singleWithBinds> { RolesAggregator(getAll()) } + // Publication targets + singleWithRandomQualifier { PublicationTargetTelegram(get(), get()) } + + single { PublicationManager(getAll(), get(), get(), get()) } + // Roles checkers single>(StringQualifier(RolesManagerRolesChecker.key)) { RolesManagerRolesChecker } diff --git a/settings.gradle b/settings.gradle index 9d7c6da3..24cd3219 100644 --- a/settings.gradle +++ b/settings.gradle @@ -45,6 +45,12 @@ String[] includes = [ ":features:posts:client", ":features:posts:server", + ":features:publication:common", + ":features:publication:client", + ":features:publication:server", + + ":targets:telegram:publication:server", + ":server", ":client", ] diff --git a/targets/telegram/publication/server/build.gradle b/targets/telegram/publication/server/build.gradle new file mode 100644 index 00000000..862f96d5 --- /dev/null +++ b/targets/telegram/publication/server/build.gradle @@ -0,0 +1,22 @@ +plugins { + id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.kotlin.plugin.serialization" +} + +apply from: "$mppJavaProjectPresetPath" + +kotlin { + sourceSets { + commonMain { + dependencies { + api project(":postssystem.features.common.server") + api project(":postssystem.features.publication.server") + + api project(":postssystem.features.content.binary.server") + api project(":postssystem.features.content.text.server") + + api "dev.inmo:tgbotapi:$tgbotapi_version" + } + } + } +} diff --git a/targets/telegram/publication/server/src/jvmMain/kotlin/dev/inmo/postssystem/targets/telegram/publication/server/PublicationTargetTelegram.kt b/targets/telegram/publication/server/src/jvmMain/kotlin/dev/inmo/postssystem/targets/telegram/publication/server/PublicationTargetTelegram.kt new file mode 100644 index 00000000..5163390f --- /dev/null +++ b/targets/telegram/publication/server/src/jvmMain/kotlin/dev/inmo/postssystem/targets/telegram/publication/server/PublicationTargetTelegram.kt @@ -0,0 +1,54 @@ +package dev.inmo.postssystem.targets.telegram.publication.server + +import dev.inmo.micro_utils.mime_types.KnownMimeTypes +import dev.inmo.postssystem.features.content.binary.common.BinaryContent +import dev.inmo.postssystem.features.content.text.common.TextContent +import dev.inmo.postssystem.features.publication.server.PublicationPost +import dev.inmo.postssystem.features.publication.server.PublicationTarget +import dev.inmo.tgbotapi.bot.TelegramBot +import dev.inmo.tgbotapi.extensions.utils.shortcuts.executeUnsafe +import dev.inmo.tgbotapi.requests.abstracts.MultipartFile +import dev.inmo.tgbotapi.requests.abstracts.asMultipartFile +import dev.inmo.tgbotapi.requests.send.SendTextMessage +import dev.inmo.tgbotapi.requests.send.media.* +import dev.inmo.tgbotapi.types.ChatId +import dev.inmo.tgbotapi.utils.StorageFile +import kotlinx.coroutines.delay + +class PublicationTargetTelegram( + private val bot: TelegramBot, + private val targetChatId: ChatId +) : PublicationTarget { + override suspend fun publish(post: PublicationPost) { + post.content.mapNotNull { + val content = it.content + when (content) { + is BinaryContent -> { + val storageFile by lazy { + StorageFile(content.filename.name, content.bytesAllocator()).asMultipartFile() + } + when (content.mimeType) { + is KnownMimeTypes.Image.Jpeg, + is KnownMimeTypes.Image.Png -> { + SendPhoto(targetChatId, storageFile) + } + is KnownMimeTypes.Video.Mp4 -> { + SendVideo(targetChatId, storageFile) + } + is KnownMimeTypes.Audio.Mpeg -> { + SendAudio(targetChatId, storageFile) + } + else -> null + } + } + is TextContent -> { + SendTextMessage(targetChatId, content.text) + } + else -> null + } + }.forEach { request -> + bot.executeUnsafe(request, 3, 1000L) + delay(100L) + } + } +}