add storing of content

This commit is contained in:
2022-01-07 23:38:55 +06:00
parent 3661c1ca73
commit 92ab01ee9d
18 changed files with 377 additions and 37 deletions

View File

@@ -10,7 +10,8 @@ kotlin {
commonMain {
dependencies {
api project(":postssystem.features.content.binary.common")
api project(":postssystem.features.common.server")
api project(":postssystem.features.content.server")
api project(":postssystem.features.files.server")
}
}
}

View File

@@ -0,0 +1,85 @@
package dev.inmo.postssystem.features.content.binary.server
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.UpdatedValuePair
import dev.inmo.postssystem.features.content.binary.common.BinaryContent
import dev.inmo.postssystem.features.content.common.*
import dev.inmo.postssystem.features.content.server.ServerContentStorage
import dev.inmo.postssystem.features.files.common.*
import dev.inmo.postssystem.features.files.common.storage.FilesStorage
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
class BinaryServerContentStorage(
private val filesStorage: FilesStorage
) : ServerContentStorage<BinaryContent> {
private val FileId.asContentId
get() = ContentId(string)
private val ContentId.asFileId
get() = FileId(string)
private val FullFileInfoStorageWrapper.asRegisteredContent
get() = RegisteredContent(
id.asContentId,
BinaryContent(
fileInfo.name,
fileInfo.mimeType,
fileInfo.byteArrayAllocator
)
)
private val BinaryContent.asFullFileInfo
get() = FullFileInfo(
filename,
mimeType,
bytesAllocator
)
override val deletedObjectsIdsFlow: Flow<ContentId> = filesStorage.deletedObjectsIdsFlow.map { it.asContentId }
override val newObjectsFlow: Flow<RegisteredContent> = filesStorage.newObjectsFlow.map { it.asRegisteredContent }
override val updatedObjectsFlow: Flow<RegisteredContent> = filesStorage.updatedObjectsFlow.map { it.asRegisteredContent }
override suspend fun create(values: List<BinaryContent>): List<RegisteredContent> {
return filesStorage.create(
values.map { it.asFullFileInfo }
).map { it.asRegisteredContent }
}
override suspend fun deleteById(ids: List<ContentId>) {
filesStorage.deleteById(ids.map { it.asFileId })
}
override suspend fun update(id: ContentId, value: BinaryContent): RegisteredContent? {
return filesStorage.update(
id.asFileId,
value.asFullFileInfo
) ?.asRegisteredContent
}
override suspend fun update(values: List<UpdatedValuePair<ContentId, BinaryContent>>): List<RegisteredContent> {
return filesStorage.update(
values.map { (id, content) ->
id.asFileId to content.asFullFileInfo
}
).map {
it.asRegisteredContent
}
}
override suspend fun contains(id: ContentId): Boolean {
return filesStorage.contains(id.asFileId)
}
override suspend fun count(): Long = filesStorage.count()
override suspend fun getById(id: ContentId): RegisteredContent? {
return filesStorage.getFullFileInfo(id.asFileId) ?.asRegisteredContent
}
override suspend fun getByPagination(pagination: Pagination): PaginationResult<RegisteredContent> {
return filesStorage.getByPagination(pagination).let {
it.changeResultsUnchecked(
it.results.mapNotNull {
filesStorage.getFullFileInfo(it.id) ?.asRegisteredContent
}
)
}
}
}

View File

@@ -11,6 +11,7 @@ kotlin {
dependencies {
api project(":postssystem.features.content.common")
api project(":postssystem.features.common.server")
api project(":postssystem.features.content.server")
}
}
}

View File

@@ -3,4 +3,4 @@ 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<RegisteredContent, ContentId, Content>
interface ServerContentStorage<T: Content> : ServerReadContentStorage, ServerWriteContentStorage<T>, CRUDRepo<RegisteredContent, ContentId, T>

View File

@@ -0,0 +1,117 @@
package dev.inmo.postssystem.features.content.server
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.UpdatedValuePair
import dev.inmo.postssystem.features.content.common.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
class ServerContentStorageAggregator(
private val storages: List<ServerContentStorageWrapper<*>>,
private val scope: CoroutineScope
) : ServerContentStorage<Content> {
override val deletedObjectsIdsFlow: Flow<ContentId>
get() = TODO("Not yet implemented")
override val newObjectsFlow: Flow<RegisteredContent>
get() = TODO("Not yet implemented")
override val updatedObjectsFlow: Flow<RegisteredContent>
get() = TODO("Not yet implemented")
override suspend fun create(values: List<Content>): List<RegisteredContent> {
return values.groupBy {
storages.firstOrNull { storage -> storage.mayHandle(it) }
}.let {
(it - null) as Map<ServerContentStorageWrapper<*>, List<Content>>
}.flatMap { (storage, content) ->
storage.create(content)
}
}
override suspend fun deleteById(ids: List<ContentId>) {
storages.map {
scope.launch { it.deleteById(ids) }
}.joinAll()
}
override suspend fun update(id: ContentId, value: Content): RegisteredContent? {
return storages.mapNotNull {
it.takeIf { it.mayHandle(value) }
}.firstNotNullOfOrNull { it.update(id, value) }
}
override suspend fun update(values: List<UpdatedValuePair<ContentId, Content>>): List<RegisteredContent> {
return values.groupBy { (_, content) ->
storages.firstOrNull { storage -> storage.mayHandle(content) }
}.let {
(it - null) as Map<ServerContentStorageWrapper<*>, List<UpdatedValuePair<ContentId, Content>>>
}.flatMap { (storage, values) ->
storage.update(values)
}
}
override suspend fun contains(id: ContentId): Boolean {
val contains = CompletableDeferred<Boolean>()
storages.map {
scope.launch {
if (it.contains(id)) {
contains.complete(true)
}
}.also { job ->
contains.invokeOnCompletion { job.cancel() }
}
}.joinAll()
return if (contains.isCompleted) {
contains.getCompleted()
} else {
return false
}
}
override suspend fun count(): Long {
return storages.map {
scope.async {
it.count()
}
}.awaitAll().sum()
}
override suspend fun getById(id: ContentId): RegisteredContent? {
val result = CompletableDeferred<RegisteredContent>()
storages.map {
scope.launch {
val content = it.getById(id)
if (content != null) {
result.complete(content)
}
}.also { job ->
result.invokeOnCompletion { job.cancel() }
}
}.joinAll()
return if (result.isCompleted) {
result.getCompleted()
} else {
return null
}
}
override suspend fun getByPagination(
pagination: Pagination
): PaginationResult<RegisteredContent> {
val currentResults = mutableListOf<RegisteredContent>()
for (it in storages) {
val currentPagination = PaginationByIndexes((currentResults.size + pagination.firstIndex), pagination.lastIndex)
val wrapperResults = it.getByPagination(currentPagination)
currentResults.addAll(wrapperResults.results)
if (currentResults.size >= pagination.size) {
break
}
}
return currentResults.createPaginationResult(pagination, count())
}
}

View File

@@ -0,0 +1,47 @@
package dev.inmo.postssystem.features.content.server
import dev.inmo.micro_utils.repos.UpdatedValuePair
import dev.inmo.postssystem.features.content.common.*
import kotlinx.coroutines.flow.Flow
import kotlin.reflect.KClass
class ServerContentStorageWrapper<T: Content>(
private val originalStorage: ServerContentStorage<T>,
private val klass: KClass<T>
): ServerContentStorage<Content>, ServerReadContentStorage by originalStorage {
override val deletedObjectsIdsFlow: Flow<ContentId> by originalStorage::deletedObjectsIdsFlow
override val newObjectsFlow: Flow<RegisteredContent> by originalStorage::newObjectsFlow
override val updatedObjectsFlow: Flow<RegisteredContent> by originalStorage::updatedObjectsFlow
fun mayHandle(content: Content) = klass.isInstance(content)
@Suppress("UNCHECKED_CAST")
private fun asHandlableContent(content: Content) = if (mayHandle(content)) content as T else null
override suspend fun create(values: List<Content>): List<RegisteredContent> {
return originalStorage.create(
values.mapNotNull {
asHandlableContent(it)
}
)
}
override suspend fun deleteById(ids: List<ContentId>) = originalStorage.deleteById(ids)
override suspend fun update(id: ContentId, value: Content): RegisteredContent? {
return originalStorage.update(id, asHandlableContent(value) ?: return null)
}
override suspend fun update(values: List<UpdatedValuePair<ContentId, Content>>): List<RegisteredContent> {
return originalStorage.update(
values.mapNotNull { (id, content) ->
id to (asHandlableContent(content) ?: return@mapNotNull null)
}
)
}
}
inline fun <reified T: Content> ServerContentStorage<T>.wrap() = ServerContentStorageWrapper(
this,
T::class
)

View File

@@ -3,4 +3,4 @@ package dev.inmo.postssystem.features.content.server
import dev.inmo.micro_utils.repos.WriteCRUDRepo
import dev.inmo.postssystem.features.content.common.*
interface ServerWriteContentStorage : WriteCRUDRepo<RegisteredContent, ContentId, Content>
interface ServerWriteContentStorage<T: Content> : WriteCRUDRepo<RegisteredContent, ContentId, T>

View File

@@ -11,6 +11,7 @@ kotlin {
dependencies {
api project(":postssystem.features.content.text.common")
api project(":postssystem.features.common.server")
api project(":postssystem.features.content.server")
}
}
}

View File

@@ -0,0 +1,53 @@
package dev.inmo.postssystem.features.content.text.server
import com.benasher44.uuid.uuid4
import dev.inmo.micro_utils.repos.exposed.AbstractExposedCRUDRepo
import dev.inmo.micro_utils.repos.exposed.initTable
import dev.inmo.postssystem.features.content.common.ContentId
import dev.inmo.postssystem.features.content.common.RegisteredContent
import dev.inmo.postssystem.features.content.server.ServerContentStorage
import dev.inmo.postssystem.features.content.text.common.TextContent
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.InsertStatement
import org.jetbrains.exposed.sql.statements.UpdateStatement
class TextServerContentStorage(
override val database: Database
) : ServerContentStorage<TextContent>,
AbstractExposedCRUDRepo<RegisteredContent, ContentId, TextContent>(tableName = "TextContent") {
val idColumn = text("id")
private val textColumn = text("text")
override val selectByIds: SqlExpressionBuilder.(List<ContentId>) -> Op<Boolean> = {
idColumn.inList(it.map { it.string })
}
override val selectById: SqlExpressionBuilder.(ContentId) -> Op<Boolean> = {
idColumn.eq(it.string)
}
override val ResultRow.asObject: RegisteredContent
get() = RegisteredContent(
ContentId(get(idColumn)),
TextContent(get(textColumn))
)
init {
initTable()
}
override fun insert(value: TextContent, it: InsertStatement<Number>) {
it[idColumn] = uuid4().toString()
it[textColumn] = value.text
}
override fun update(id: ContentId, value: TextContent, it: UpdateStatement) {
it[textColumn] = value.text
}
override fun InsertStatement<Number>.asObject(value: TextContent): RegisteredContent {
return RegisteredContent(
ContentId(get(idColumn)),
TextContent(get(textColumn))
)
}
}