core/core/exposed/src/jvmMain/kotlin/dev/inmo/postssystem/core/exposed/ExposedContentRepo.kt

155 lines
5.3 KiB
Kotlin

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)