add realization of PostsAPI in Exposed
This commit is contained in:
parent
35c2de527b
commit
bf5da036b6
@ -29,9 +29,9 @@ interface ReadPostsAPI {
|
|||||||
suspend fun getPostsByContent(id: ContentId): List<RegisteredPost>
|
suspend fun getPostsByContent(id: ContentId): List<RegisteredPost>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return all [RegisteredPost]s between [from] and [to]. Range will be used INCLUSIVE, line \[[from], [to]\]
|
* @return all [RegisteredPost]s which was registered between [from] and [to]. Range will be used INCLUSIVE, line \[[from], [to]\]
|
||||||
*/
|
*/
|
||||||
suspend fun getPostsByDates(from: DateTime = MIN_DATE, to: DateTime = MAX_DATE): List<RegisteredPost>
|
suspend fun getPostsByCreatingDates(from: DateTime = MIN_DATE, to: DateTime = MAX_DATE): List<RegisteredPost>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return all posts by pages basing on their creation date
|
* @return all posts by pages basing on their creation date
|
||||||
@ -39,10 +39,10 @@ interface ReadPostsAPI {
|
|||||||
suspend fun getPostsByPagination(pagination: Pagination): PaginationResult<out RegisteredPost>
|
suspend fun getPostsByPagination(pagination: Pagination): PaginationResult<out RegisteredPost>
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun ReadPostsAPI.getPostsByDates(
|
suspend fun ReadPostsAPI.getPostsByCreatingDates(
|
||||||
from: DateTime? = null,
|
from: DateTime? = null,
|
||||||
to: DateTime? = null
|
to: DateTime? = null
|
||||||
) = getPostsByDates(
|
) = getPostsByCreatingDates(
|
||||||
from ?: MIN_DATE,
|
from ?: MIN_DATE,
|
||||||
to ?: MAX_DATE
|
to ?: MAX_DATE
|
||||||
)
|
)
|
||||||
|
@ -68,7 +68,7 @@ class InMemoryPostsAPI(
|
|||||||
override suspend fun getPostsByContent(id: ContentId): List<RegisteredPost> =
|
override suspend fun getPostsByContent(id: ContentId): List<RegisteredPost> =
|
||||||
posts.values.filter { post -> id in post.content }
|
posts.values.filter { post -> id in post.content }
|
||||||
|
|
||||||
override suspend fun getPostsByDates(from: DateTime, to: DateTime): List<RegisteredPost> =
|
override suspend fun getPostsByCreatingDates(from: DateTime, to: DateTime): List<RegisteredPost> =
|
||||||
(from .. to).let { range ->
|
(from .. to).let { range ->
|
||||||
posts.values.filter {
|
posts.values.filter {
|
||||||
it.creationDate in range
|
it.creationDate in range
|
||||||
|
@ -18,7 +18,7 @@ private class ContentAPIDatabaseTable(
|
|||||||
internal val idColumn = text("_id")
|
internal val idColumn = text("_id")
|
||||||
internal val dataColumn = text("data")
|
internal val dataColumn = text("data")
|
||||||
|
|
||||||
private inline fun <T> transaction(noinline body: Transaction.() -> T): T = transaction(database, body)
|
private inline fun <T> transaction(noinline body: Transaction.() -> T): T = database.transaction(body)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
transaction {
|
transaction {
|
||||||
@ -84,5 +84,5 @@ private class ContentAPIDatabaseTable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ExposedContentAPI (
|
class ExposedContentAPI (
|
||||||
private val database: Database
|
database: Database
|
||||||
) : ContentAPI by ContentAPIDatabaseTable(database)
|
) : ContentAPI by ContentAPIDatabaseTable(database)
|
||||||
|
@ -0,0 +1,177 @@
|
|||||||
|
package com.insanusmokrassar.postssystem.core.exposed
|
||||||
|
|
||||||
|
import com.insanusmokrassar.postssystem.core.content.ContentId
|
||||||
|
import com.insanusmokrassar.postssystem.core.post.*
|
||||||
|
import com.insanusmokrassar.postssystem.core.post.api.PostsAPI
|
||||||
|
import com.insanusmokrassar.postssystem.core.utils.generatePostId
|
||||||
|
import com.insanusmokrassar.postssystem.core.utils.pagination.*
|
||||||
|
import com.soywiz.klock.DateTime
|
||||||
|
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.*
|
||||||
|
|
||||||
|
private class PostsAPIContentRelations(
|
||||||
|
private val database: Database
|
||||||
|
) : Table() {
|
||||||
|
private val postIdColumn = text("postId")
|
||||||
|
private val contentIdColumn = text("contentId")
|
||||||
|
|
||||||
|
private inline fun <T> transaction(noinline body: Transaction.() -> T): T = database.transaction(body)
|
||||||
|
|
||||||
|
init {
|
||||||
|
transaction {
|
||||||
|
SchemaUtils.createMissingTablesAndColumns(this@PostsAPIContentRelations)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPostContents(postId: PostId): List<ContentId> {
|
||||||
|
return transaction {
|
||||||
|
select { postIdColumn.eq(postId) }.map { it[contentIdColumn] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getContentPosts(contentId: ContentId): List<PostId> {
|
||||||
|
return transaction {
|
||||||
|
select { contentIdColumn.eq(contentId) }.map { it[postIdColumn] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun linkPostAndContents(postId: PostId, vararg contentIds: ContentId) {
|
||||||
|
transaction {
|
||||||
|
val leftToPut = contentIds.toSet() - getPostContents(postId)
|
||||||
|
leftToPut.forEach { contentId ->
|
||||||
|
insert {
|
||||||
|
it[postIdColumn] = postId
|
||||||
|
it[contentIdColumn] = contentId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun unlinkPostAndContents(postId: PostId, vararg contentIds: ContentId): Boolean {
|
||||||
|
return transaction {
|
||||||
|
deleteWhere {
|
||||||
|
postIdColumn.eq(postId).and(contentIdColumn.inList(contentIds.toList()))
|
||||||
|
} > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PostsAPIDatabaseTable(
|
||||||
|
private val database: Database
|
||||||
|
) : PostsAPI, Table() {
|
||||||
|
private val contentsTable = PostsAPIContentRelations(database)
|
||||||
|
|
||||||
|
private val idColumn = text("postId")
|
||||||
|
private val creationDateColumn = datetime("creationDate").default(org.joda.time.DateTime.now())
|
||||||
|
private val modificationDateColumn = datetime("modificationDate").default(org.joda.time.DateTime.now())
|
||||||
|
|
||||||
|
|
||||||
|
private val postCreatedBroadcastChannel = BroadcastChannel<RegisteredPost>(Channel.BUFFERED)
|
||||||
|
override val postCreatedFlow: Flow<RegisteredPost> = postCreatedBroadcastChannel.asFlow()
|
||||||
|
|
||||||
|
private val postDeletedBroadcastChannel = BroadcastChannel<RegisteredPost>(Channel.BUFFERED)
|
||||||
|
override val postDeletedFlow: Flow<RegisteredPost> = postDeletedBroadcastChannel.asFlow()
|
||||||
|
|
||||||
|
private val postUpdatedBroadcastChannel = BroadcastChannel<RegisteredPost>(Channel.BUFFERED)
|
||||||
|
override val postUpdatedFlow: Flow<RegisteredPost> = postUpdatedBroadcastChannel.asFlow()
|
||||||
|
|
||||||
|
private inline fun <T> transaction(noinline body: Transaction.() -> T): T = database.transaction(body)
|
||||||
|
|
||||||
|
init {
|
||||||
|
transaction {
|
||||||
|
SchemaUtils.createMissingTablesAndColumns(this@PostsAPIDatabaseTable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ResultRow.toRegisteredPost(): RegisteredPost = get(idColumn).let { id ->
|
||||||
|
SimpleRegisteredPost(
|
||||||
|
id,
|
||||||
|
contentsTable.getPostContents(id),
|
||||||
|
DateTime(get(creationDateColumn).millis),
|
||||||
|
DateTime(get(modificationDateColumn).millis)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun createPost(post: Post): RegisteredPost? {
|
||||||
|
val id = generatePostId()
|
||||||
|
return transaction {
|
||||||
|
insert {
|
||||||
|
it[idColumn] = id
|
||||||
|
}
|
||||||
|
contentsTable.linkPostAndContents(id, *post.content.toTypedArray())
|
||||||
|
select { idColumn.eq(id) }.firstOrNull() ?.toRegisteredPost()
|
||||||
|
} ?.also {
|
||||||
|
postCreatedBroadcastChannel.send(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deletePost(id: PostId): Boolean {
|
||||||
|
val post = getPostById(id) ?: return false
|
||||||
|
return (transaction {
|
||||||
|
deleteWhere { idColumn.eq(id) }
|
||||||
|
} > 0).also {
|
||||||
|
if (it) {
|
||||||
|
postDeletedBroadcastChannel.send(post)
|
||||||
|
contentsTable.unlinkPostAndContents(id, *post.content.toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updatePostContent(postId: PostId, post: Post): Boolean {
|
||||||
|
return transaction {
|
||||||
|
val alreadyLinked = contentsTable.getPostContents(postId)
|
||||||
|
val toRemove = alreadyLinked - post.content
|
||||||
|
val toInsert = post.content - alreadyLinked
|
||||||
|
val updated = (toRemove.isNotEmpty() && contentsTable.unlinkPostAndContents(postId, *toRemove.toTypedArray())) || toInsert.isNotEmpty()
|
||||||
|
if (toInsert.isNotEmpty()) {
|
||||||
|
contentsTable.linkPostAndContents(postId, *toInsert.toTypedArray())
|
||||||
|
}
|
||||||
|
updated
|
||||||
|
}.also {
|
||||||
|
if (it) {
|
||||||
|
getPostById(postId) ?.also { updatedPost -> postUpdatedBroadcastChannel.send(updatedPost) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override suspend fun getPostsIds(): Set<PostId> {
|
||||||
|
return transaction {
|
||||||
|
selectAll().map { it[idColumn] }.toSet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getPostById(id: PostId): RegisteredPost? {
|
||||||
|
return transaction {
|
||||||
|
select { idColumn.eq(id) }.firstOrNull() ?.toRegisteredPost()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getPostsByContent(id: ContentId): List<RegisteredPost> {
|
||||||
|
return transaction {
|
||||||
|
val postsIds = contentsTable.getContentPosts(id)
|
||||||
|
select { idColumn.inList(postsIds) }.map { it.toRegisteredPost() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getPostsByCreatingDates(from: DateTime, to: DateTime): List<RegisteredPost> {
|
||||||
|
return transaction {
|
||||||
|
select { creationDateColumn.between(from, to) }.map { it.toRegisteredPost() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getPostsByPagination(pagination: Pagination): PaginationResult<out RegisteredPost> {
|
||||||
|
return transaction {
|
||||||
|
val posts = selectAll().limit(pagination.size, pagination.firstIndex).orderBy(creationDateColumn).map {
|
||||||
|
it.toRegisteredPost()
|
||||||
|
}
|
||||||
|
val postsNumber = selectAll().count()
|
||||||
|
pagination.createResult(postsNumber, posts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExposedPostsAPI (
|
||||||
|
database: Database
|
||||||
|
) : PostsAPI by PostsAPIDatabaseTable(database)
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.insanusmokrassar.postssystem.core.exposed
|
||||||
|
|
||||||
|
import org.jetbrains.exposed.sql.Database
|
||||||
|
import org.jetbrains.exposed.sql.Transaction
|
||||||
|
|
||||||
|
inline fun <T> Database.transaction(noinline body: Transaction.() -> T): T = org.jetbrains.exposed.sql.transactions.transaction(this, body)
|
Loading…
Reference in New Issue
Block a user