fully rewrite content
This commit is contained in:
parent
20e3fd6934
commit
5c1f5c0bba
@ -7,25 +7,25 @@ import dev.inmo.postssystem.core.post.*
|
|||||||
import dev.inmo.postssystem.core.post.repo.PostsRepo
|
import dev.inmo.postssystem.core.post.repo.PostsRepo
|
||||||
import dev.inmo.postssystem.core.publishing.*
|
import dev.inmo.postssystem.core.publishing.*
|
||||||
|
|
||||||
class BusinessPostCreatingCase(
|
//class BusinessPostCreatingCase(
|
||||||
private val postsRepo: PostsRepo,
|
// private val postsRepo: PostsRepo,
|
||||||
private val contentRepo: ContentRepo,
|
// private val contentRepo: ContentRepo,
|
||||||
private val publishingRegistrar: PublishingRegistrar,
|
// private val publishingRegistrar: PublishingRegistrar,
|
||||||
private val postKeyGenerator: PostKeyGenerator = { _, _ -> uuid4().toString() },
|
// private val postKeyGenerator: PostKeyGenerator = { _, _ -> uuid4().toString() },
|
||||||
private val publishingKeyReceiverGetter: PublishingKeyReceiverGetter
|
// private val publishingKeyReceiverGetter: PublishingKeyReceiverGetter
|
||||||
) : PostCreatingCase {
|
//) : PostCreatingCase {
|
||||||
override suspend fun createPost(postContent: List<Content>, triggerId: TriggerId?): RegisteredPost? {
|
// override suspend fun createPost(postContent: List<Content>, triggerId: TriggerId?): RegisteredPost? {
|
||||||
val content = postContent.mapNotNull { contentRepo.registerContent(it) }
|
// val content = postContent.mapNotNull { contentRepo.registerContent(it) }
|
||||||
val post = postsRepo.createPost(SimplePost(content.map { it.id })) ?: return null
|
// val post = postsRepo.createPost(SimplePost(content.map { it.id })) ?: return null
|
||||||
|
//
|
||||||
triggerId ?.let {
|
// triggerId ?.let {
|
||||||
val key = postKeyGenerator(post.id, triggerId)
|
// val key = postKeyGenerator(post.id, triggerId)
|
||||||
|
//
|
||||||
if (publishingRegistrar.registerTriggerForPost(key, post.id)) {
|
// if (publishingRegistrar.registerTriggerForPost(key, post.id)) {
|
||||||
publishingKeyReceiverGetter(it) ?.acceptKey(post.id, key)
|
// publishingKeyReceiverGetter(it) ?.acceptKey(post.id, key)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
return post
|
// return post
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
@ -14,6 +14,7 @@ kotlin {
|
|||||||
|
|
||||||
api "com.soywiz.korlibs.klock:klock:$klockVersion"
|
api "com.soywiz.korlibs.klock:klock:$klockVersion"
|
||||||
api "dev.inmo:micro_utils.common:$microutils_version"
|
api "dev.inmo:micro_utils.common:$microutils_version"
|
||||||
|
api "dev.inmo:micro_utils.coroutines:$microutils_version"
|
||||||
api "dev.inmo:micro_utils.repos.common:$microutils_version"
|
api "dev.inmo:micro_utils.repos.common:$microutils_version"
|
||||||
api "dev.inmo:micro_utils.mime_types:$microutils_version"
|
api "dev.inmo:micro_utils.mime_types:$microutils_version"
|
||||||
}
|
}
|
||||||
|
@ -22,21 +22,7 @@ data class SpecialContent(
|
|||||||
val internalId: ContentId
|
val internalId: ContentId
|
||||||
) : Content
|
) : Content
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class TextContent(
|
|
||||||
val text: String
|
|
||||||
) : Content
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class BinaryContent(
|
|
||||||
val mimeType: MimeType,
|
|
||||||
val originalFileName: String,
|
|
||||||
@Serializable(ByteArrayAllocatorSerializer::class)
|
|
||||||
val dataAllocator: ByteArrayAllocator
|
|
||||||
) : Content
|
|
||||||
|
|
||||||
val BinaryContent.isImage: Boolean
|
|
||||||
get() = mimeType is KnownMimeTypes.Image
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
@ -1,26 +1,12 @@
|
|||||||
package dev.inmo.postssystem.core.content.api
|
package dev.inmo.postssystem.core.content.api.business
|
||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import dev.inmo.micro_utils.repos.Repo
|
|
||||||
import dev.inmo.micro_utils.repos.UpdatedValuePair
|
import dev.inmo.micro_utils.repos.UpdatedValuePair
|
||||||
import dev.inmo.postssystem.core.content.*
|
import dev.inmo.postssystem.core.content.*
|
||||||
|
import dev.inmo.postssystem.core.content.api.*
|
||||||
import dev.inmo.postssystem.core.generateContentId
|
import dev.inmo.postssystem.core.generateContentId
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
interface BusinessContentRepoReadHelpInterface : Repo {
|
|
||||||
suspend fun getKeysByPagination(pagination: Pagination): PaginationResult<ContentId>
|
|
||||||
suspend fun contains(contentId: ContentId): Boolean
|
|
||||||
suspend fun getType(contentId: ContentId): AdapterType?
|
|
||||||
suspend fun count(): Long
|
|
||||||
}
|
|
||||||
interface BusinessContentRepoWriteHelpInterface : Repo {
|
|
||||||
suspend fun deleteContentId(contentId: ContentId)
|
|
||||||
suspend fun saveType(contentId: ContentId, type: AdapterType): Boolean
|
|
||||||
}
|
|
||||||
interface BusinessContentRepoHelpInterface : BusinessContentRepoReadHelpInterface, BusinessContentRepoWriteHelpInterface
|
|
||||||
|
|
||||||
typealias AdapterType = String
|
|
||||||
|
|
||||||
interface BusinessContentRepoContentAdapter {
|
interface BusinessContentRepoContentAdapter {
|
||||||
val type: AdapterType
|
val type: AdapterType
|
||||||
suspend fun storeContent(contentId: ContentId, content: Content): Boolean
|
suspend fun storeContent(contentId: ContentId, content: Content): Boolean
|
||||||
@ -30,7 +16,7 @@ interface BusinessContentRepoContentAdapter {
|
|||||||
|
|
||||||
class BusinessReadContentRepo(
|
class BusinessReadContentRepo(
|
||||||
adapters: List<BusinessContentRepoContentAdapter>,
|
adapters: List<BusinessContentRepoContentAdapter>,
|
||||||
private val helperRepo: BusinessContentRepoReadHelpInterface,
|
private val helperRepo: BusinessContentRepoReadHelper,
|
||||||
) : ReadContentRepo {
|
) : ReadContentRepo {
|
||||||
private val adaptersMap: Map<String, BusinessContentRepoContentAdapter> = adapters.map {
|
private val adaptersMap: Map<String, BusinessContentRepoContentAdapter> = adapters.map {
|
||||||
it.type to it
|
it.type to it
|
||||||
@ -61,7 +47,7 @@ class BusinessReadContentRepo(
|
|||||||
|
|
||||||
class BusinessWriteContentRepo(
|
class BusinessWriteContentRepo(
|
||||||
private val adapters: List<BusinessContentRepoContentAdapter>,
|
private val adapters: List<BusinessContentRepoContentAdapter>,
|
||||||
private val helperRepo: BusinessContentRepoHelpInterface
|
private val helperRepo: BusinessContentRepoHelper
|
||||||
) : WriteContentRepo {
|
) : WriteContentRepo {
|
||||||
private val adaptersMap = adapters.map { it.type to it }.toMap()
|
private val adaptersMap = adapters.map { it.type to it }.toMap()
|
||||||
private val _deletedObjectsIdsFlow = MutableSharedFlow<ContentId>()
|
private val _deletedObjectsIdsFlow = MutableSharedFlow<ContentId>()
|
||||||
@ -110,7 +96,7 @@ class BusinessWriteContentRepo(
|
|||||||
|
|
||||||
class BusinessContentRepo(
|
class BusinessContentRepo(
|
||||||
adapters: List<BusinessContentRepoContentAdapter>,
|
adapters: List<BusinessContentRepoContentAdapter>,
|
||||||
helperRepo: BusinessContentRepoHelpInterface
|
helperRepo: BusinessContentRepoHelper
|
||||||
) : ContentRepo, ReadContentRepo by BusinessReadContentRepo(
|
) : ContentRepo, ReadContentRepo by BusinessReadContentRepo(
|
||||||
adapters,
|
adapters,
|
||||||
helperRepo
|
helperRepo
|
@ -0,0 +1,59 @@
|
|||||||
|
package dev.inmo.postssystem.core.content.api.business
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.repos.*
|
||||||
|
import dev.inmo.postssystem.core.content.ContentId
|
||||||
|
|
||||||
|
typealias AdapterType = String
|
||||||
|
|
||||||
|
interface BusinessContentRepoReadHelper : Repo {
|
||||||
|
suspend fun getKeysByPagination(pagination: Pagination): PaginationResult<ContentId>
|
||||||
|
suspend fun contains(contentId: ContentId): Boolean
|
||||||
|
suspend fun getType(contentId: ContentId): AdapterType?
|
||||||
|
suspend fun count(): Long
|
||||||
|
}
|
||||||
|
interface BusinessContentRepoWriteHelper : Repo {
|
||||||
|
suspend fun deleteContentId(contentId: ContentId)
|
||||||
|
suspend fun saveType(contentId: ContentId, type: AdapterType): Boolean
|
||||||
|
}
|
||||||
|
interface BusinessContentRepoHelper : BusinessContentRepoReadHelper, BusinessContentRepoWriteHelper
|
||||||
|
|
||||||
|
class KeyValueBusinessContentRepoReadHelper(
|
||||||
|
private val keyValueRepo: ReadStandardKeyValueRepo<ContentId, AdapterType>
|
||||||
|
) : BusinessContentRepoReadHelper {
|
||||||
|
override suspend fun getKeysByPagination(pagination: Pagination): PaginationResult<ContentId> = keyValueRepo.keys(pagination)
|
||||||
|
override suspend fun contains(contentId: ContentId): Boolean = keyValueRepo.contains(contentId)
|
||||||
|
override suspend fun getType(contentId: ContentId): AdapterType? = keyValueRepo.get(contentId)
|
||||||
|
override suspend fun count(): Long = keyValueRepo.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
class KeyValueBusinessContentRepoWriteHelper(
|
||||||
|
private val keyValueRepo: WriteStandardKeyValueRepo<ContentId, AdapterType>
|
||||||
|
) : BusinessContentRepoWriteHelper {
|
||||||
|
override suspend fun deleteContentId(contentId: ContentId) { keyValueRepo.unset(contentId) }
|
||||||
|
|
||||||
|
override suspend fun saveType(contentId: ContentId, type: AdapterType): Boolean {
|
||||||
|
keyValueRepo.set(contentId, type)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class KeyValueBusinessContentRepoHelper(
|
||||||
|
private val keyValueRepo: StandardKeyValueRepo<ContentId, AdapterType>
|
||||||
|
) : BusinessContentRepoHelper, BusinessContentRepoReadHelper by KeyValueBusinessContentRepoReadHelper(
|
||||||
|
keyValueRepo
|
||||||
|
), BusinessContentRepoWriteHelper by KeyValueBusinessContentRepoWriteHelper(
|
||||||
|
keyValueRepo
|
||||||
|
)
|
||||||
|
|
||||||
|
fun StandardKeyValueRepo<ContentId, AdapterType>.asBusinessContentRepo(
|
||||||
|
adapters: List<BusinessContentRepoContentAdapter>
|
||||||
|
) = BusinessContentRepo(
|
||||||
|
adapters,
|
||||||
|
KeyValueBusinessContentRepoHelper(this)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun StandardKeyValueRepo<ContentId, AdapterType>.asBusinessContentRepo(
|
||||||
|
vararg adapters: BusinessContentRepoContentAdapter
|
||||||
|
) = asBusinessContentRepo(adapters.toList())
|
@ -0,0 +1,29 @@
|
|||||||
|
package dev.inmo.postssystem.core.content.api.business.content_adapters
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.*
|
||||||
|
import dev.inmo.postssystem.core.content.Content
|
||||||
|
import dev.inmo.postssystem.core.content.ContentId
|
||||||
|
import dev.inmo.postssystem.core.content.api.business.AdapterType
|
||||||
|
import dev.inmo.postssystem.core.content.api.business.BusinessContentRepoContentAdapter
|
||||||
|
|
||||||
|
class KeyValueBusinessContentRepoAdapter<T>(
|
||||||
|
override val type: AdapterType,
|
||||||
|
private val keyValueRepo: StandardKeyValueRepo<ContentId, T>,
|
||||||
|
private val contentToData: suspend (Content) -> T?,
|
||||||
|
private val dataToContent: suspend (T) -> Content
|
||||||
|
) : BusinessContentRepoContentAdapter {
|
||||||
|
override suspend fun storeContent(contentId: ContentId, content: Content): Boolean {
|
||||||
|
keyValueRepo.set(contentId, contentToData(content) ?: return false)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getContent(contentId: ContentId): Content? {
|
||||||
|
return dataToContent(
|
||||||
|
keyValueRepo.get(contentId) ?: return null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun removeContent(contentId: ContentId) {
|
||||||
|
keyValueRepo.unset(contentId)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
package dev.inmo.postssystem.core.content.api.business.content_adapters.binary
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.*
|
||||||
|
import dev.inmo.postssystem.core.content.Content
|
||||||
|
import dev.inmo.postssystem.core.content.ContentId
|
||||||
|
import dev.inmo.postssystem.core.content.api.business.AdapterType
|
||||||
|
import dev.inmo.postssystem.core.content.api.business.BusinessContentRepoContentAdapter
|
||||||
|
import dev.inmo.postssystem.core.content.api.business.content_adapters.KeyValueBusinessContentRepoAdapter
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
|
private val format = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
|
class BinaryBusinessContentRepoContentAdapter(
|
||||||
|
private val dataStore: KeyValueRepo<String, String>,
|
||||||
|
private val filesStore: KeyValueRepo<String, ByteArray>,
|
||||||
|
private val removeOnAbsentInOneOfStores: Boolean = false
|
||||||
|
) : BusinessContentRepoContentAdapter {
|
||||||
|
override val type: AdapterType
|
||||||
|
get() = "binary"
|
||||||
|
|
||||||
|
override suspend fun storeContent(contentId: ContentId, content: Content): Boolean {
|
||||||
|
(content as? BinaryContent) ?.also {
|
||||||
|
filesStore.set(it.originalFileName, it.dataAllocator())
|
||||||
|
dataStore.set(
|
||||||
|
contentId,
|
||||||
|
format.encodeToString(BinaryContent.serializer(), it.copy { ByteArray(0) })
|
||||||
|
)
|
||||||
|
} ?: return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getContent(contentId: ContentId): Content? {
|
||||||
|
return filesStore.get(contentId) ?.let {
|
||||||
|
val serializedData = dataStore.get(contentId)
|
||||||
|
if (serializedData != null) {
|
||||||
|
format.decodeFromString(BinaryContent.serializer(), serializedData).copy {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} ?: null.also {
|
||||||
|
if (removeOnAbsentInOneOfStores) {
|
||||||
|
filesStore.unset(contentId)
|
||||||
|
dataStore.unset(contentId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun removeContent(contentId: ContentId) {
|
||||||
|
filesStore.unset(contentId)
|
||||||
|
dataStore.unset(contentId)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package dev.inmo.postssystem.core.content.api.business.content_adapters.binary
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.ByteArrayAllocator
|
||||||
|
import dev.inmo.micro_utils.common.ByteArrayAllocatorSerializer
|
||||||
|
import dev.inmo.micro_utils.mime_types.KnownMimeTypes
|
||||||
|
import dev.inmo.micro_utils.mime_types.MimeType
|
||||||
|
import dev.inmo.postssystem.core.content.Content
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class BinaryContent(
|
||||||
|
val mimeType: MimeType,
|
||||||
|
val originalFileName: String,
|
||||||
|
@Serializable(ByteArrayAllocatorSerializer::class)
|
||||||
|
val dataAllocator: ByteArrayAllocator
|
||||||
|
) : Content
|
||||||
|
|
||||||
|
val BinaryContent.isImage: Boolean
|
||||||
|
get() = mimeType is KnownMimeTypes.Image
|
@ -0,0 +1,15 @@
|
|||||||
|
package dev.inmo.postssystem.core.content.api.business.content_adapters.text
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.*
|
||||||
|
import dev.inmo.postssystem.core.content.ContentId
|
||||||
|
import dev.inmo.postssystem.core.content.api.business.BusinessContentRepoContentAdapter
|
||||||
|
import dev.inmo.postssystem.core.content.api.business.content_adapters.KeyValueBusinessContentRepoAdapter
|
||||||
|
|
||||||
|
class TextBusinessContentRepoContentAdapter(
|
||||||
|
private val keyValueRepo: StandardKeyValueRepo<ContentId, String>
|
||||||
|
) : BusinessContentRepoContentAdapter by KeyValueBusinessContentRepoAdapter(
|
||||||
|
"regularText",
|
||||||
|
keyValueRepo,
|
||||||
|
{ (it as? TextContent) ?.text },
|
||||||
|
{ TextContent(it) }
|
||||||
|
)
|
@ -0,0 +1,9 @@
|
|||||||
|
package dev.inmo.postssystem.core.content.api.business.content_adapters.text
|
||||||
|
|
||||||
|
import dev.inmo.postssystem.core.content.Content
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class TextContent(
|
||||||
|
val text: String
|
||||||
|
) : Content
|
@ -1,11 +1,26 @@
|
|||||||
package dev.inmo.postssystem.core.api
|
package dev.inmo.postssystem.core.api
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.ByteArrayAllocator
|
||||||
|
import dev.inmo.micro_utils.mime_types.KnownMimeTypes
|
||||||
import dev.inmo.postssystem.core.content.*
|
import dev.inmo.postssystem.core.content.*
|
||||||
|
import dev.inmo.postssystem.core.content.api.business.content_adapters.binary.BinaryContent
|
||||||
|
import dev.inmo.postssystem.core.content.api.business.content_adapters.text.TextContent
|
||||||
import dev.inmo.postssystem.core.generateContentId
|
import dev.inmo.postssystem.core.generateContentId
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.modules.SerializersModule
|
||||||
|
import kotlinx.serialization.modules.polymorphic
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
private val jsonFormat = Json {
|
||||||
|
serializersModule = SerializersModule {
|
||||||
|
polymorphic(Content::class) {
|
||||||
|
subclass(TextContent::class, TextContent.serializer())
|
||||||
|
subclass(BinaryContent::class, BinaryContent.serializer())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ContentSerialization {
|
class ContentSerialization {
|
||||||
private val simpleTextTestEntries = 10
|
private val simpleTextTestEntries = 10
|
||||||
private val simpleSpecialTestEntries = 10
|
private val simpleSpecialTestEntries = 10
|
||||||
@ -15,7 +30,9 @@ class ContentSerialization {
|
|||||||
val contents = (0 until simpleTextTestEntries).map {
|
val contents = (0 until simpleTextTestEntries).map {
|
||||||
TextContent("Example$it")
|
TextContent("Example$it")
|
||||||
} + (0 until simpleSpecialTestEntries).map {
|
} + (0 until simpleSpecialTestEntries).map {
|
||||||
SpecialContent("$it")
|
BinaryContent(KnownMimeTypes.Any, "$it.example") {
|
||||||
|
byteArrayOf(it.toByte())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val registeredContentFakes = contents.map { content ->
|
val registeredContentFakes = contents.map { content ->
|
||||||
@ -26,13 +43,33 @@ class ContentSerialization {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val stringified = registeredContentFakes.map {
|
val stringified = registeredContentFakes.map {
|
||||||
Json.encodeToString(RegisteredContent.serializer(), it)
|
jsonFormat.encodeToString(RegisteredContent.serializer(), it)
|
||||||
}
|
}
|
||||||
|
|
||||||
val parsed = stringified.map {
|
val parsed = stringified.map {
|
||||||
Json.decodeFromString(RegisteredContent.serializer(), it)
|
jsonFormat.decodeFromString(RegisteredContent.serializer(), it)
|
||||||
}
|
}
|
||||||
|
|
||||||
parsed.forEachIndexed { i, registeredContent -> assertEquals(registeredContentFakes[i], registeredContent) }
|
parsed.forEachIndexed { i, registeredContent ->
|
||||||
|
val content = registeredContent.content
|
||||||
|
assertEquals(registeredContentFakes[i].id, registeredContent.id)
|
||||||
|
when (content) {
|
||||||
|
is TextContent -> assertEquals(registeredContentFakes[i].content, content)
|
||||||
|
is BinaryContent -> {
|
||||||
|
val expectedContent = registeredContentFakes[i].content as BinaryContent
|
||||||
|
val fakeByteArrayAllocator: ByteArrayAllocator = { byteArrayOf() }
|
||||||
|
assertEquals(
|
||||||
|
expectedContent.copy(dataAllocator = fakeByteArrayAllocator),
|
||||||
|
content.copy(dataAllocator = fakeByteArrayAllocator)
|
||||||
|
)
|
||||||
|
val expectedData = expectedContent.dataAllocator()
|
||||||
|
val parsedData = content.dataAllocator()
|
||||||
|
assertEquals(expectedData.size, parsedData.size)
|
||||||
|
expectedData.withIndex().forEach { (i, byte) ->
|
||||||
|
assertEquals(byte, parsedData[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,79 @@
|
|||||||
|
package dev.inmo.postssystem.core.content.api.business.content_adapters.binary
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.doOutsideOfCoroutine
|
||||||
|
import dev.inmo.micro_utils.coroutines.safelyWithoutExceptions
|
||||||
|
import dev.inmo.micro_utils.pagination.*
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.set
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class FilesStoreRepoAdapter(
|
||||||
|
private val filesRepo: KeyValueRepo<String, File>,
|
||||||
|
private val temporalFilesFolder: File
|
||||||
|
) : KeyValueRepo<String, ByteArray> {
|
||||||
|
private val File.asByteArray
|
||||||
|
get() = readBytes()
|
||||||
|
override val onNewValue: Flow<Pair<String, ByteArray>> = filesRepo.onNewValue.map { (filename, file) ->
|
||||||
|
filename to file.asByteArray
|
||||||
|
}
|
||||||
|
override val onValueRemoved: Flow<String> = filesRepo.onValueRemoved
|
||||||
|
|
||||||
|
init {
|
||||||
|
temporalFilesFolder.mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun contains(key: String): Boolean = filesRepo.contains(key)
|
||||||
|
|
||||||
|
override suspend fun count(): Long = filesRepo.count()
|
||||||
|
|
||||||
|
override suspend fun get(k: String): ByteArray? = filesRepo.get(k) ?.asByteArray
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
v: ByteArray,
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<String> = emptyPaginationResult()
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<String> = filesRepo.keys(pagination, reversed)
|
||||||
|
|
||||||
|
override suspend fun set(toSet: Map<String, ByteArray>) {
|
||||||
|
supervisorScope {
|
||||||
|
toSet.map { (filename, bytes) ->
|
||||||
|
launch {
|
||||||
|
safelyWithoutExceptions {
|
||||||
|
val file = File(temporalFilesFolder, filename).also {
|
||||||
|
it.delete()
|
||||||
|
doOutsideOfCoroutine {
|
||||||
|
it.createNewFile()
|
||||||
|
it.writeBytes(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filesRepo.set(filename, file)
|
||||||
|
doOutsideOfCoroutine { file.delete() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.joinAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unset(toUnset: List<String>) = filesRepo.unset(toUnset)
|
||||||
|
|
||||||
|
override suspend fun values(
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<ByteArray> = filesRepo.values(pagination, reversed).let {
|
||||||
|
PaginationResult(
|
||||||
|
it.page,
|
||||||
|
it.pagesNumber,
|
||||||
|
it.results.map { it.readBytes() },
|
||||||
|
it.size
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,154 +0,0 @@
|
|||||||
package dev.inmo.postssystem.core.exposed
|
|
||||||
|
|
||||||
import dev.inmo.postssystem.core.content.*
|
|
||||||
import dev.inmo.postssystem.core.content.api.ContentRepo
|
|
||||||
import dev.inmo.postssystem.core.exposed.content.*
|
|
||||||
import dev.inmo.postssystem.core.generateContentId
|
|
||||||
import dev.inmo.micro_utils.pagination.*
|
|
||||||
import kotlinx.coroutines.channels.BroadcastChannel
|
|
||||||
import kotlinx.coroutines.channels.Channel
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.asFlow
|
|
||||||
import org.jetbrains.exposed.sql.*
|
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
|
||||||
|
|
||||||
private val Content.type
|
|
||||||
get() = when (this) {
|
|
||||||
is TextContent -> "text"
|
|
||||||
is BinaryContent -> "binary"
|
|
||||||
is SpecialContent -> "special"
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ContentRepoDatabaseTable(
|
|
||||||
private val database: Database,
|
|
||||||
tableName: String = "",
|
|
||||||
private val textHolder: ContentHolderRepo<TextContent>,
|
|
||||||
private val binaryHolder: ContentHolderRepo<BinaryContent>,
|
|
||||||
private val specialHolder: ContentHolderRepo<SpecialContent>
|
|
||||||
) : Table(tableName), ContentRepo, ContentHolderRepo<Content> {
|
|
||||||
internal val idColumn = text("_id")
|
|
||||||
internal val typeColumn = text("type")
|
|
||||||
|
|
||||||
init {
|
|
||||||
transaction(
|
|
||||||
db = database
|
|
||||||
) {
|
|
||||||
SchemaUtils.createMissingTablesAndColumns(this@ContentRepoDatabaseTable)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val contentCreatedBroadcastChannel = BroadcastChannel<RegisteredContent>(Channel.BUFFERED)
|
|
||||||
private val contentDeletedBroadcastChannel = BroadcastChannel<RegisteredContent>(Channel.BUFFERED)
|
|
||||||
override val contentCreatedFlow: Flow<RegisteredContent> = contentCreatedBroadcastChannel.asFlow()
|
|
||||||
override val contentDeletedFlow: Flow<RegisteredContent> = contentDeletedBroadcastChannel.asFlow()
|
|
||||||
|
|
||||||
private val String.holder
|
|
||||||
get() = when (this) {
|
|
||||||
"text" -> textHolder
|
|
||||||
"binary" -> binaryHolder
|
|
||||||
"special" -> specialHolder
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun putContent(id: ContentId, content: Content) {
|
|
||||||
when (content) {
|
|
||||||
is TextContent -> textHolder.putContent(id, content)
|
|
||||||
is BinaryContent -> binaryHolder.putContent(id, content)
|
|
||||||
is SpecialContent -> specialHolder.putContent(id, content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
override suspend fun getContent(id: ContentId): Content? = transaction(
|
|
||||||
db = database
|
|
||||||
) {
|
|
||||||
select { idColumn.eq(id) }.limit(1).firstOrNull() ?.get(typeColumn)
|
|
||||||
} ?.holder ?.getContent(id)
|
|
||||||
|
|
||||||
override suspend fun removeContent(id: ContentId) {
|
|
||||||
transaction(
|
|
||||||
db = database
|
|
||||||
) {
|
|
||||||
select { idColumn.eq(id) }.limit(1).firstOrNull() ?.get(typeColumn)
|
|
||||||
} ?.holder ?.removeContent(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun registerContent(content: Content): RegisteredContent? {
|
|
||||||
val id = generateContentId()
|
|
||||||
val type = content.type
|
|
||||||
return transaction(
|
|
||||||
db = database
|
|
||||||
) {
|
|
||||||
insert {
|
|
||||||
it[idColumn] = id
|
|
||||||
it[typeColumn] = type
|
|
||||||
}.getOrNull(idColumn)
|
|
||||||
} ?.let { id ->
|
|
||||||
putContent(id, content)
|
|
||||||
RegisteredContent(
|
|
||||||
id,
|
|
||||||
content
|
|
||||||
)
|
|
||||||
} ?.also {
|
|
||||||
contentCreatedBroadcastChannel.send(it)
|
|
||||||
} ?: null.also {
|
|
||||||
removeContent(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
override suspend fun deleteContent(id: ContentId): Boolean {
|
|
||||||
val content = getContentById(id) ?: return false
|
|
||||||
return transaction(
|
|
||||||
db = database
|
|
||||||
) {
|
|
||||||
deleteWhere {
|
|
||||||
idColumn.eq(id)
|
|
||||||
} > 0
|
|
||||||
}.also {
|
|
||||||
if (it) {
|
|
||||||
removeContent(id)
|
|
||||||
contentDeletedBroadcastChannel.send(content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ResultRow.asRegisteredContent(content: Content): RegisteredContent? = get(idColumn).let {
|
|
||||||
RegisteredContent(
|
|
||||||
it,
|
|
||||||
content
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getContentsIds(): Set<ContentId> {
|
|
||||||
return transaction(
|
|
||||||
db = database
|
|
||||||
) {
|
|
||||||
selectAll().map { it[idColumn] }
|
|
||||||
}.toSet()
|
|
||||||
}
|
|
||||||
override suspend fun getContentById(id: ContentId): RegisteredContent? {
|
|
||||||
val content = getContent(id) ?: return null
|
|
||||||
return transaction(
|
|
||||||
db = database
|
|
||||||
) {
|
|
||||||
select { idColumn.eq(id) }.limit(1).firstOrNull() ?.asRegisteredContent(content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
override suspend fun getContentByPagination(pagination: Pagination): PaginationResult<RegisteredContent> {
|
|
||||||
return transaction(
|
|
||||||
db = database
|
|
||||||
) {
|
|
||||||
selectAll().count() to selectAll().paginate(pagination).map { it[idColumn] }
|
|
||||||
}.let { (count, results) ->
|
|
||||||
results.mapNotNull { RegisteredContent(it, getContent(it) ?: return@mapNotNull null) }.createPaginationResult(
|
|
||||||
pagination,
|
|
||||||
count
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ExposedContentRepo (
|
|
||||||
database: Database,
|
|
||||||
tableName: String = "",
|
|
||||||
textHolder: ContentHolderRepo<TextContent> = TextContentHolderRepo(database),
|
|
||||||
binaryHolder: ContentHolderRepo<BinaryContent> = BinaryContentHolderRepo(database),
|
|
||||||
specialHolder: ContentHolderRepo<SpecialContent> = SpecialContentHolderRepo(database)
|
|
||||||
) : ContentRepo by ContentRepoDatabaseTable(database, tableName, textHolder, binaryHolder, specialHolder)
|
|
@ -1,67 +0,0 @@
|
|||||||
package dev.inmo.postssystem.core.exposed.content
|
|
||||||
|
|
||||||
import dev.inmo.postssystem.core.content.BinaryContent
|
|
||||||
import dev.inmo.postssystem.core.content.ContentId
|
|
||||||
import dev.inmo.micro_utils.mime_types.mimeType
|
|
||||||
import org.jetbrains.exposed.sql.*
|
|
||||||
import org.jetbrains.exposed.sql.statements.api.ExposedBlob
|
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
|
||||||
|
|
||||||
private class BinaryContentHolderRepoTable(
|
|
||||||
private val database: Database
|
|
||||||
) : ContentHolderRepo<BinaryContent>, Table() {
|
|
||||||
private val idColumn = text("id")
|
|
||||||
private val dataColumn = blob("data")
|
|
||||||
private val mimeColumn = text("mimeType")
|
|
||||||
private val originalFileNameColumn = text("filename")
|
|
||||||
override val primaryKey: PrimaryKey = PrimaryKey(idColumn)
|
|
||||||
|
|
||||||
init {
|
|
||||||
transaction (
|
|
||||||
db = database
|
|
||||||
) {
|
|
||||||
SchemaUtils.createMissingTablesAndColumns(this@BinaryContentHolderRepoTable)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getContent(id: ContentId): BinaryContent? = transaction (
|
|
||||||
db = database
|
|
||||||
) {
|
|
||||||
select {
|
|
||||||
idColumn.eq(id)
|
|
||||||
}.limit(1).firstOrNull() ?.let {
|
|
||||||
val bytes = it[dataColumn].bytes
|
|
||||||
BinaryContent(
|
|
||||||
mimeType(it[mimeColumn]),
|
|
||||||
it[originalFileNameColumn]
|
|
||||||
) {
|
|
||||||
bytes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun removeContent(id: ContentId) {
|
|
||||||
transaction(
|
|
||||||
db = database
|
|
||||||
) {
|
|
||||||
deleteWhere { idColumn.eq(id) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun putContent(id: ContentId, content: BinaryContent) {
|
|
||||||
transaction(
|
|
||||||
db = database
|
|
||||||
) {
|
|
||||||
insert {
|
|
||||||
it[idColumn] = id
|
|
||||||
it[originalFileNameColumn] = content.originalFileName
|
|
||||||
it[mimeColumn] = content.mimeType.raw
|
|
||||||
it[dataColumn] = ExposedBlob(content.dataAllocator())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BinaryContentHolderRepo(
|
|
||||||
database: Database
|
|
||||||
) : ContentHolderRepo<BinaryContent> by BinaryContentHolderRepoTable(database)
|
|
@ -1,10 +0,0 @@
|
|||||||
package dev.inmo.postssystem.core.exposed.content
|
|
||||||
|
|
||||||
import dev.inmo.postssystem.core.content.Content
|
|
||||||
import dev.inmo.postssystem.core.content.ContentId
|
|
||||||
|
|
||||||
interface ContentHolderRepo<T : Content> {
|
|
||||||
suspend fun getContent(id: ContentId) : T?
|
|
||||||
suspend fun removeContent(id: ContentId)
|
|
||||||
suspend fun putContent(id: ContentId, content: T)
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
package dev.inmo.postssystem.core.exposed.content
|
|
||||||
|
|
||||||
import dev.inmo.postssystem.core.content.ContentId
|
|
||||||
import dev.inmo.postssystem.core.content.SpecialContent
|
|
||||||
import org.jetbrains.exposed.sql.*
|
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
|
||||||
|
|
||||||
private class SpecialContentHolderRepoTable(
|
|
||||||
private val database: Database
|
|
||||||
) : ContentHolderRepo<SpecialContent>, Table() {
|
|
||||||
private val idColumn = text("id")
|
|
||||||
private val internalIdColumn = text("internalId")
|
|
||||||
override val primaryKey: PrimaryKey = PrimaryKey(idColumn)
|
|
||||||
|
|
||||||
init {
|
|
||||||
transaction(
|
|
||||||
db = database
|
|
||||||
) {
|
|
||||||
SchemaUtils.createMissingTablesAndColumns(this@SpecialContentHolderRepoTable)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getContent(id: ContentId): SpecialContent? = transaction(
|
|
||||||
db = database
|
|
||||||
) {
|
|
||||||
select {
|
|
||||||
idColumn.eq(id)
|
|
||||||
}.limit(1).firstOrNull() ?.get(internalIdColumn) ?.let {
|
|
||||||
SpecialContent(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun removeContent(id: ContentId) {
|
|
||||||
transaction(
|
|
||||||
db = database
|
|
||||||
) {
|
|
||||||
deleteWhere { idColumn.eq(id) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun putContent(id: ContentId, content: SpecialContent) {
|
|
||||||
transaction(
|
|
||||||
db = database
|
|
||||||
) {
|
|
||||||
insert {
|
|
||||||
it[idColumn] = id
|
|
||||||
it[internalIdColumn] = content.internalId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SpecialContentHolderRepo(
|
|
||||||
database: Database
|
|
||||||
) : ContentHolderRepo<SpecialContent> by SpecialContentHolderRepoTable(database)
|
|
@ -1,55 +0,0 @@
|
|||||||
package dev.inmo.postssystem.core.exposed.content
|
|
||||||
|
|
||||||
import dev.inmo.postssystem.core.content.ContentId
|
|
||||||
import dev.inmo.postssystem.core.content.TextContent
|
|
||||||
import org.jetbrains.exposed.sql.*
|
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
|
||||||
|
|
||||||
private class TextContentHolderRepoTable(
|
|
||||||
private val database: Database
|
|
||||||
) : ContentHolderRepo<TextContent>, Table() {
|
|
||||||
private val idColumn = text("id")
|
|
||||||
private val textColumn = text("text")
|
|
||||||
override val primaryKey: PrimaryKey = PrimaryKey(idColumn)
|
|
||||||
|
|
||||||
init {
|
|
||||||
transaction(
|
|
||||||
db = database
|
|
||||||
) {
|
|
||||||
SchemaUtils.createMissingTablesAndColumns(this@TextContentHolderRepoTable)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getContent(id: ContentId): TextContent? = transaction(
|
|
||||||
db = database
|
|
||||||
) {
|
|
||||||
select {
|
|
||||||
idColumn.eq(id)
|
|
||||||
}.limit(1).firstOrNull() ?.get(textColumn) ?.let {
|
|
||||||
TextContent(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun removeContent(id: ContentId) {
|
|
||||||
transaction(
|
|
||||||
db = database
|
|
||||||
) {
|
|
||||||
deleteWhere { idColumn.eq(id) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun putContent(id: ContentId, content: TextContent) {
|
|
||||||
transaction(
|
|
||||||
db = database
|
|
||||||
) {
|
|
||||||
insert {
|
|
||||||
it[idColumn] = id
|
|
||||||
it[textColumn] = content.text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TextContentHolderRepo(
|
|
||||||
database: Database
|
|
||||||
) : ContentHolderRepo<TextContent> by TextContentHolderRepoTable(database)
|
|
@ -1,7 +1,15 @@
|
|||||||
package dev.inmo.postssystem.core.exposed
|
package dev.inmo.postssystem.core.exposed
|
||||||
|
|
||||||
import dev.inmo.postssystem.core.content.TextContent
|
import dev.inmo.micro_utils.repos.create
|
||||||
import kotlinx.coroutines.runBlocking
|
import dev.inmo.micro_utils.repos.deleteById
|
||||||
|
import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedKeyValueRepo
|
||||||
|
import dev.inmo.postssystem.core.content.ContentId
|
||||||
|
import dev.inmo.postssystem.core.content.api.business.AdapterType
|
||||||
|
import dev.inmo.postssystem.core.content.api.business.asBusinessContentRepo
|
||||||
|
import dev.inmo.postssystem.core.content.api.business.content_adapters.text.TextBusinessContentRepoContentAdapter
|
||||||
|
import dev.inmo.postssystem.core.content.api.business.content_adapters.text.TextContent
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import org.jetbrains.exposed.sql.Database
|
import org.jetbrains.exposed.sql.Database
|
||||||
import org.jetbrains.exposed.sql.transactions.transactionManager
|
import org.jetbrains.exposed.sql.transactions.transactionManager
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -25,33 +33,49 @@ class ExposedContentRepoCommonTests {
|
|||||||
it.delete()
|
it.delete()
|
||||||
it.deleteOnExit()
|
it.deleteOnExit()
|
||||||
}
|
}
|
||||||
ExposedContentRepo(
|
val database = Database.Companion.connect("jdbc:sqlite:$it", driver = "org.sqlite.JDBC").also {
|
||||||
Database.Companion.connect("jdbc:sqlite:$it", driver = "org.sqlite.JDBC").also {
|
|
||||||
it.transactionManager.defaultIsolationLevel = Connection.TRANSACTION_SERIALIZABLE
|
it.transactionManager.defaultIsolationLevel = Connection.TRANSACTION_SERIALIZABLE
|
||||||
}
|
}
|
||||||
|
ExposedKeyValueRepo<ContentId, AdapterType>(
|
||||||
|
database,
|
||||||
|
{ text("contentId") },
|
||||||
|
{ text("adapterType") },
|
||||||
|
"ContentRepo"
|
||||||
|
).asBusinessContentRepo(
|
||||||
|
TextBusinessContentRepoContentAdapter(
|
||||||
|
ExposedKeyValueRepo<ContentId, String>(
|
||||||
|
database,
|
||||||
|
{ text("contentId") },
|
||||||
|
{ text("text") },
|
||||||
|
"TextContentRepo"
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val results = apis.mapIndexed { i, api ->
|
val results = apis.mapIndexed { i, api ->
|
||||||
val content = runBlocking { api.registerContent(TextContent(i.toString())) }
|
val expectedContent = TextContent(i.toString())
|
||||||
assert(content != null)
|
val contents = runBlocking { api.create(TextContent(i.toString())) }
|
||||||
val ids = runBlocking { api.getContentsIds() }
|
val idsCount = runBlocking { api.count() }
|
||||||
assertEquals(ids.size, 1)
|
assertEquals(idsCount, 1)
|
||||||
content!!
|
assert(contents.isNotEmpty())
|
||||||
|
assertEquals(
|
||||||
|
expectedContent,
|
||||||
|
contents.first().content
|
||||||
|
)
|
||||||
|
contents.first()
|
||||||
}
|
}
|
||||||
|
|
||||||
results.forEachIndexed { i, content ->
|
results.forEachIndexed { i, content ->
|
||||||
apis.forEachIndexed { j, api ->
|
apis.forEachIndexed { j, api ->
|
||||||
assert(
|
assert(
|
||||||
runBlocking {
|
runBlocking {
|
||||||
api.getContentById(content.id) == (if (i != j) null else content)
|
api.getById(content.id) == (if (i != j) null else content)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
assert(
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
api.deleteContent(content.id) == (i == j)
|
api.deleteById(content.id)
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,23 +5,23 @@ import dev.inmo.postssystem.core.ktor.contentRootRoute
|
|||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.features.websocket.WebSockets
|
import io.ktor.client.features.websocket.WebSockets
|
||||||
|
|
||||||
class ContentRepoKtorClient private constructor(
|
//class ContentRepoKtorClient private constructor(
|
||||||
readContentRepo: ReadContentRepo,
|
// readContentRepo: ReadContentRepo,
|
||||||
writeContentRepo: WriteContentRepo
|
// writeContentRepo: WriteContentRepo
|
||||||
) : ContentRepo, ReadContentRepo by readContentRepo, WriteContentRepo by writeContentRepo {
|
//) : ContentRepo, ReadContentRepo by readContentRepo, WriteContentRepo by writeContentRepo {
|
||||||
constructor(
|
// constructor(
|
||||||
baseUrl: String,
|
// baseUrl: String,
|
||||||
client: HttpClient = HttpClient {
|
// client: HttpClient = HttpClient {
|
||||||
install(WebSockets)
|
// install(WebSockets)
|
||||||
}
|
// }
|
||||||
) : this(
|
// ) : this(
|
||||||
ReadContentRepoKtorClient(
|
// ReadContentRepoKtorClient(
|
||||||
"$baseUrl/$contentRootRoute",
|
// "$baseUrl/$contentRootRoute",
|
||||||
client
|
// client
|
||||||
),
|
// ),
|
||||||
WriteContentRepoKtorClient(
|
// WriteContentRepoKtorClient(
|
||||||
"$baseUrl/$contentRootRoute",
|
// "$baseUrl/$contentRootRoute",
|
||||||
client
|
// client
|
||||||
)
|
// )
|
||||||
)
|
// )
|
||||||
}
|
//}
|
||||||
|
@ -12,23 +12,23 @@ import io.ktor.client.HttpClient
|
|||||||
import io.ktor.client.request.get
|
import io.ktor.client.request.get
|
||||||
import kotlinx.serialization.builtins.nullable
|
import kotlinx.serialization.builtins.nullable
|
||||||
|
|
||||||
class ReadContentRepoKtorClient(
|
//class ReadContentRepoKtorClient(
|
||||||
private val baseUrl: String,
|
// private val baseUrl: String,
|
||||||
private val client: HttpClient = HttpClient()
|
// private val client: HttpClient = HttpClient()
|
||||||
) : ReadContentRepo {
|
//) : ReadContentRepo {
|
||||||
override suspend fun getContentsIds(): Set<ContentId> = client.get<ByteArray>(
|
// override suspend fun getContentsIds(): Set<ContentId> = client.get<ByteArray>(
|
||||||
"$baseUrl/$getContentsIdsRoute"
|
// "$baseUrl/$getContentsIdsRoute"
|
||||||
).let {
|
// ).let {
|
||||||
standardKtorSerialFormat.decodeFromByteArray(contentIdsSerializer, it)
|
// standardKtorSerialFormat.decodeFromByteArray(contentIdsSerializer, it)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
override suspend fun getContentById(id: ContentId): RegisteredContent? = client.uniget(
|
// override suspend fun getContentById(id: ContentId): RegisteredContent? = client.uniget(
|
||||||
"$baseUrl/$getContentByIdRoute/$id",
|
// "$baseUrl/$getContentByIdRoute/$id",
|
||||||
RegisteredContent.serializer().nullable
|
// RegisteredContent.serializer().nullable
|
||||||
)
|
// )
|
||||||
|
//
|
||||||
override suspend fun getContentByPagination(pagination: Pagination): PaginationResult<RegisteredContent> = client.uniget(
|
// override suspend fun getContentByPagination(pagination: Pagination): PaginationResult<RegisteredContent> = client.uniget(
|
||||||
"$baseUrl/$getContentByPaginationRoute".includeQueryParams(pagination.asUrlQueryParts),
|
// "$baseUrl/$getContentByPaginationRoute".includeQueryParams(pagination.asUrlQueryParts),
|
||||||
registeredContentPaginationResultSerializer
|
// registeredContentPaginationResultSerializer
|
||||||
)
|
// )
|
||||||
}
|
//}
|
@ -9,29 +9,29 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import kotlinx.serialization.builtins.nullable
|
import kotlinx.serialization.builtins.nullable
|
||||||
import kotlinx.serialization.builtins.serializer
|
import kotlinx.serialization.builtins.serializer
|
||||||
|
|
||||||
class WriteContentRepoKtorClient(
|
//class WriteContentRepoKtorClient(
|
||||||
private val baseUrl: String,
|
// private val baseUrl: String,
|
||||||
private val client: HttpClient = HttpClient()
|
// private val client: HttpClient = HttpClient()
|
||||||
) : WriteContentRepo {
|
//) : WriteContentRepo {
|
||||||
override val contentCreatedFlow: Flow<RegisteredContent> = client.createStandardWebsocketFlow(
|
// override val contentCreatedFlow: Flow<RegisteredContent> = client.createStandardWebsocketFlow(
|
||||||
"$baseUrl/$contentCreatedFlowRoute",
|
// "$baseUrl/$contentCreatedFlowRoute",
|
||||||
deserializer = RegisteredContent.serializer()
|
// deserializer = RegisteredContent.serializer()
|
||||||
)
|
// )
|
||||||
|
//
|
||||||
override val contentDeletedFlow: Flow<RegisteredContent> = client.createStandardWebsocketFlow(
|
// override val contentDeletedFlow: Flow<RegisteredContent> = client.createStandardWebsocketFlow(
|
||||||
"$baseUrl/$contentDeletedFlowRoute",
|
// "$baseUrl/$contentDeletedFlowRoute",
|
||||||
deserializer = RegisteredContent.serializer()
|
// deserializer = RegisteredContent.serializer()
|
||||||
)
|
// )
|
||||||
|
//
|
||||||
override suspend fun registerContent(content: Content): RegisteredContent? = client.unipost(
|
// override suspend fun registerContent(content: Content): RegisteredContent? = client.unipost(
|
||||||
"$baseUrl/$registerContentRoute",
|
// "$baseUrl/$registerContentRoute",
|
||||||
BodyPair(Content.serializer(), content),
|
// BodyPair(Content.serializer(), content),
|
||||||
RegisteredContent.serializer().nullable
|
// RegisteredContent.serializer().nullable
|
||||||
)
|
// )
|
||||||
|
//
|
||||||
override suspend fun deleteContent(id: ContentId): Boolean = client.unipost(
|
// override suspend fun deleteContent(id: ContentId): Boolean = client.unipost(
|
||||||
"$baseUrl/$deleteContentRoute",
|
// "$baseUrl/$deleteContentRoute",
|
||||||
BodyPair(ContentId.serializer(), id),
|
// BodyPair(ContentId.serializer(), id),
|
||||||
Boolean.serializer()
|
// Boolean.serializer()
|
||||||
)
|
// )
|
||||||
}
|
//}
|
@ -1,26 +0,0 @@
|
|||||||
package dev.inmo.postssystem.core.ktor.server.content
|
|
||||||
|
|
||||||
import dev.inmo.postssystem.core.content.api.ContentRepo
|
|
||||||
import dev.inmo.postssystem.core.ktor.contentRootRoute
|
|
||||||
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
|
|
||||||
import io.ktor.routing.Route
|
|
||||||
import io.ktor.routing.route
|
|
||||||
|
|
||||||
fun Route.configureContentRepoRoutes(
|
|
||||||
proxyTo: ContentRepo
|
|
||||||
) {
|
|
||||||
route(
|
|
||||||
contentRootRoute
|
|
||||||
) {
|
|
||||||
configureReadContentRepoRoutes(proxyTo)
|
|
||||||
configureWriteContentRepoRoutes(proxyTo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ContentRepoRoutingConfigurator(
|
|
||||||
private val proxyTo: ContentRepo
|
|
||||||
) : ApplicationRoutingConfigurator.Element {
|
|
||||||
override fun Route.invoke() {
|
|
||||||
configureContentRepoRoutes(proxyTo)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
package dev.inmo.postssystem.core.ktor.server.content
|
|
||||||
|
|
||||||
import dev.inmo.postssystem.core.content.ContentId
|
|
||||||
import dev.inmo.postssystem.core.content.RegisteredContent
|
|
||||||
import dev.inmo.postssystem.core.content.api.ReadContentRepo
|
|
||||||
import dev.inmo.postssystem.core.ktor.*
|
|
||||||
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
|
|
||||||
import dev.inmo.micro_utils.ktor.server.getParameterOrSendError
|
|
||||||
import dev.inmo.micro_utils.ktor.server.unianswer
|
|
||||||
import dev.inmo.micro_utils.pagination.extractPagination
|
|
||||||
import io.ktor.application.call
|
|
||||||
import io.ktor.routing.Route
|
|
||||||
import io.ktor.routing.get
|
|
||||||
import kotlinx.serialization.builtins.nullable
|
|
||||||
|
|
||||||
fun Route.configureReadContentRepoRoutes(
|
|
||||||
proxyTo: ReadContentRepo
|
|
||||||
) {
|
|
||||||
get(getContentsIdsRoute) {
|
|
||||||
call.unianswer(
|
|
||||||
contentIdsSerializer,
|
|
||||||
proxyTo.getContentsIds()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
get("$getContentByIdRoute/{id}") {
|
|
||||||
val id: ContentId = call.getParameterOrSendError("id") ?: return@get
|
|
||||||
|
|
||||||
call.unianswer(
|
|
||||||
RegisteredContent.serializer().nullable,
|
|
||||||
proxyTo.getContentById(id)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
get(getContentByPaginationRoute) {
|
|
||||||
val pagination = call.request.queryParameters.extractPagination
|
|
||||||
|
|
||||||
call.unianswer(
|
|
||||||
registeredContentPaginationResultSerializer,
|
|
||||||
proxyTo.getContentByPagination(pagination)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ReadContentRepoRoutingConfigurator(
|
|
||||||
private val proxyTo: ReadContentRepo
|
|
||||||
) : ApplicationRoutingConfigurator.Element {
|
|
||||||
override fun Route.invoke() {
|
|
||||||
configureReadContentRepoRoutes(proxyTo)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
package dev.inmo.postssystem.core.ktor.server.content
|
|
||||||
|
|
||||||
import dev.inmo.postssystem.core.content.*
|
|
||||||
import dev.inmo.postssystem.core.content.api.WriteContentRepo
|
|
||||||
import dev.inmo.postssystem.core.ktor.*
|
|
||||||
import dev.inmo.micro_utils.ktor.server.*
|
|
||||||
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
|
|
||||||
import io.ktor.application.call
|
|
||||||
import io.ktor.routing.Route
|
|
||||||
import io.ktor.routing.post
|
|
||||||
import kotlinx.serialization.builtins.nullable
|
|
||||||
import kotlinx.serialization.builtins.serializer
|
|
||||||
|
|
||||||
fun Route.configureWriteContentRepoRoutes(
|
|
||||||
proxyTo: WriteContentRepo
|
|
||||||
) {
|
|
||||||
includeWebsocketHandling(contentCreatedFlowRoute, proxyTo.contentCreatedFlow, RegisteredContent.serializer())
|
|
||||||
includeWebsocketHandling(contentDeletedFlowRoute, proxyTo.contentDeletedFlow, RegisteredContent.serializer())
|
|
||||||
post(registerContentRoute) {
|
|
||||||
val content = call.uniload(Content.serializer())
|
|
||||||
val registered = proxyTo.registerContent(content)
|
|
||||||
call.unianswer(
|
|
||||||
RegisteredContent.serializer().nullable,
|
|
||||||
registered
|
|
||||||
)
|
|
||||||
}
|
|
||||||
post(deleteContentRoute) {
|
|
||||||
val contentId = call.uniload(ContentId.serializer())
|
|
||||||
val isDeleted = proxyTo.deleteContent(contentId)
|
|
||||||
call.unianswer(
|
|
||||||
Boolean.serializer(),
|
|
||||||
isDeleted
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class WriteContentRepoRoutingConfigurator(
|
|
||||||
private val proxyTo: WriteContentRepo
|
|
||||||
) : ApplicationRoutingConfigurator.Element {
|
|
||||||
override fun Route.invoke() {
|
|
||||||
configureWriteContentRepoRoutes(proxyTo)
|
|
||||||
}
|
|
||||||
}
|
|
@ -19,7 +19,7 @@ uuidVersion=0.2.3
|
|||||||
exposed_version=0.28.1
|
exposed_version=0.28.1
|
||||||
test_sqlite_version=3.32.3.2
|
test_sqlite_version=3.32.3.2
|
||||||
|
|
||||||
microutils_version=0.4.5
|
microutils_version=0.4.6
|
||||||
|
|
||||||
javax_activation_version=1.1.1
|
javax_activation_version=1.1.1
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user