start including of publishing subproject
This commit is contained in:
47
postssystem.core.exposed/build.gradle
Normal file
47
postssystem.core.exposed/build.gradle
Normal file
@@ -0,0 +1,47 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||
classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:$gradle_bintray_plugin_version"
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.plugin.serialization" version "$kotlin_version"
|
||||
}
|
||||
|
||||
project.version = "$core_version"
|
||||
project.group = "com.insanusmokrassar"
|
||||
|
||||
apply plugin: "java-library"
|
||||
apply plugin: "kotlin"
|
||||
apply from: "./publish.gradle"
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven { url "https://kotlin.bintray.com/kotlinx" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
api "org.jetbrains.exposed:exposed-core:$exposed_version"
|
||||
api "org.jetbrains.exposed:exposed-jdbc:$exposed_version"
|
||||
|
||||
if ((project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true") {
|
||||
api "com.insanusmokrassar:postssystem.core:$core_version"
|
||||
} else {
|
||||
implementation project(":postssystem.core")
|
||||
}
|
||||
|
||||
testImplementation "org.xerial:sqlite-jdbc:$test_sqlite_version"
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test"
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit"
|
||||
}
|
2
postssystem.core.exposed/gradle.properties
Normal file
2
postssystem.core.exposed/gradle.properties
Normal file
@@ -0,0 +1,2 @@
|
||||
exposed_version=0.23.1
|
||||
test_sqlite_version=3.28.0
|
56
postssystem.core.exposed/maven.publish.gradle
Normal file
56
postssystem.core.exposed/maven.publish.gradle
Normal file
@@ -0,0 +1,56 @@
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
task sourcesJar(type: Jar) {
|
||||
from sourceSets.main.allSource
|
||||
classifier = 'sources'
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar) {
|
||||
from javadoc
|
||||
classifier = 'javadoc'
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
maven(MavenPublication) {
|
||||
from components.java
|
||||
|
||||
artifact sourcesJar
|
||||
artifact javadocJar
|
||||
|
||||
pom.withXml {
|
||||
asNode().children().last() + {
|
||||
resolveStrategy = Closure.DELEGATE_FIRST
|
||||
|
||||
description "Exposed realisation for PostsSystem Core"
|
||||
name "PostsSystem Core Exposed realization"
|
||||
url "https://git.insanusmokrassar.com/PostsSystem/Core/"
|
||||
|
||||
scm {
|
||||
developerConnection "scm:git:[fetch=]https://git.insanusmokrassar.com/PostsSystem/Core/.git[push=]https://git.insanusmokrassar.com/PostsSystem/Core/.git"
|
||||
url "https://git.insanusmokrassar.com/PostsSystem/Core/.git"
|
||||
}
|
||||
|
||||
developers {
|
||||
|
||||
developer {
|
||||
id "InsanusMokrassar"
|
||||
name "Ovsiannikov Aleksei"
|
||||
email "ovsyannikov.alexey95@gmail.com"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
licenses {
|
||||
|
||||
license {
|
||||
name "Apache Software License 2.0"
|
||||
url "https://git.insanusmokrassar.com/PostsSystem/Core/src/master/LICENSE"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
39
postssystem.core.exposed/publish.gradle
Normal file
39
postssystem.core.exposed/publish.gradle
Normal file
@@ -0,0 +1,39 @@
|
||||
apply plugin: 'com.jfrog.bintray'
|
||||
|
||||
ext {
|
||||
projectBintrayDir = "${project.group}/".replace(".", "/") + "${project.name}/${project.version}"
|
||||
}
|
||||
|
||||
bintray {
|
||||
user = project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')
|
||||
key = project.hasProperty('BINTRAY_KEY') ? project.property('BINTRAY_KEY') : System.getenv('BINTRAY_KEY')
|
||||
publications = ["maven"]
|
||||
filesSpec {
|
||||
into "$projectBintrayDir"
|
||||
from("build/libs") {
|
||||
include "**/*.asc"
|
||||
}
|
||||
from("build/publications/maven") {
|
||||
rename 'pom-default.xml(.*)', "${project.name}-${project.version}.pom\$1"
|
||||
}
|
||||
}
|
||||
pkg {
|
||||
repo = "InsanusMokrassar"
|
||||
name = "${project.name}"
|
||||
vcsUrl = "https://github.com/PostsSystem/PostsSystemCore"
|
||||
licenses = ["Apache-2.0"]
|
||||
version {
|
||||
name = "${project.version}"
|
||||
released = new Date()
|
||||
vcsTag = "${project.version}"
|
||||
gpg {
|
||||
sign = true
|
||||
passphrase = project.hasProperty('signing.gnupg.passphrase') ? project.property('signing.gnupg.passphrase') : System.getenv('signing.gnupg.passphrase')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "maven.publish.gradle"
|
||||
|
||||
bintrayUpload.dependsOn publishToMavenLocal
|
1
postssystem.core.exposed/publish_config.json
Normal file
1
postssystem.core.exposed/publish_config.json
Normal file
@@ -0,0 +1 @@
|
||||
{"bintrayConfig":{"repo":"InsanusMokrassar","packageName":"${project.name}","packageVcs":"https://github.com/PostsSystem/PostsSystemCore"},"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://git.insanusmokrassar.com/PostsSystem/Core/src/master/LICENSE"}],"mavenConfig":{"name":"PostsSystem Core Exposed realization","description":"Exposed realisation for PostsSystem Core","url":"https://git.insanusmokrassar.com/PostsSystem/Core/","vcsUrl":"https://git.insanusmokrassar.com/PostsSystem/Core/.git","developers":[{"id":"InsanusMokrassar","name":"Ovsiannikov Aleksei","eMail":"ovsyannikov.alexey95@gmail.com"}]},"type":"JVM"}
|
@@ -0,0 +1,3 @@
|
||||
package com.insanusmokrassar.postssystem.core.exposed
|
||||
|
||||
internal const val ChannelDefaultSize = 64
|
@@ -0,0 +1,134 @@
|
||||
package com.insanusmokrassar.postssystem.core.exposed
|
||||
|
||||
import com.insanusmokrassar.postssystem.core.content.*
|
||||
import com.insanusmokrassar.postssystem.core.content.api.ContentAPI
|
||||
import com.insanusmokrassar.postssystem.core.exposed.content.*
|
||||
import com.insanusmokrassar.postssystem.core.utils.generateContentId
|
||||
import com.insanusmokrassar.postssystem.core.utils.pagination.*
|
||||
import kotlinx.coroutines.channels.BroadcastChannel
|
||||
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 ContentAPIDatabaseTable(
|
||||
private val database: Database,
|
||||
private val textHolder: ContentHolderRepo<TextContent>,
|
||||
private val binaryHolder: ContentHolderRepo<BinaryContent>,
|
||||
private val specialHolder: ContentHolderRepo<SpecialContent>
|
||||
) : Table("ContentAPI"), ContentAPI, ContentHolderRepo<Content> {
|
||||
internal val idColumn = text("_id")
|
||||
internal val typeColumn = text("type")
|
||||
|
||||
init {
|
||||
transaction(database) {
|
||||
SchemaUtils.createMissingTablesAndColumns(this@ContentAPIDatabaseTable)
|
||||
}
|
||||
}
|
||||
|
||||
private val contentCreatedBroadcastChannel = BroadcastChannel<RegisteredContent>(ChannelDefaultSize)
|
||||
private val contentDeletedBroadcastChannel = BroadcastChannel<RegisteredContent>(ChannelDefaultSize)
|
||||
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(database) {
|
||||
select { idColumn.eq(id) }.limit(1).firstOrNull() ?.get(typeColumn)
|
||||
} ?.holder ?.getContent(id)
|
||||
override suspend fun removeContent(id: ContentId) {
|
||||
transaction(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(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(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(database) {
|
||||
selectAll().map { it[idColumn] }
|
||||
}.toSet()
|
||||
}
|
||||
override suspend fun getContentById(id: ContentId): RegisteredContent? {
|
||||
val content = getContent(id) ?: return null
|
||||
return transaction(database) {
|
||||
select { idColumn.eq(id) }.limit(1).firstOrNull() ?.asRegisteredContent(content)
|
||||
}
|
||||
}
|
||||
override suspend fun getContentByPagination(pagination: Pagination): PaginationResult<out RegisteredContent> {
|
||||
return transaction(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 ExposedContentAPI (
|
||||
database: Database,
|
||||
textHolder: ContentHolderRepo<TextContent> = TextContentHolderRepo(database),
|
||||
binaryHolder: ContentHolderRepo<BinaryContent> = BinaryContentHolderRepo(database),
|
||||
specialHolder: ContentHolderRepo<SpecialContent> = SpecialContentHolderRepo(database)
|
||||
) : ContentAPI by ContentAPIDatabaseTable(database, textHolder, binaryHolder, specialHolder)
|
@@ -0,0 +1,175 @@
|
||||
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.*
|
||||
import kotlinx.coroutines.channels.BroadcastChannel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
private class PostsAPIContentRelations(
|
||||
private val database: Database
|
||||
) : Table() {
|
||||
private val postIdColumn = text("postId")
|
||||
private val contentIdColumn = text("contentId")
|
||||
|
||||
init {
|
||||
transaction(database) {
|
||||
SchemaUtils.createMissingTablesAndColumns(this@PostsAPIContentRelations)
|
||||
}
|
||||
}
|
||||
|
||||
fun getPostContents(postId: PostId): List<ContentId> {
|
||||
return transaction(database) {
|
||||
select { postIdColumn.eq(postId) }.map { it[contentIdColumn] }
|
||||
}
|
||||
}
|
||||
|
||||
fun getContentPosts(contentId: ContentId): List<PostId> {
|
||||
return transaction(database) {
|
||||
select { contentIdColumn.eq(contentId) }.map { it[postIdColumn] }
|
||||
}
|
||||
}
|
||||
|
||||
fun linkPostAndContents(postId: PostId, vararg contentIds: ContentId) {
|
||||
transaction(database) {
|
||||
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(database) {
|
||||
deleteWhere {
|
||||
postIdColumn.eq(postId).and(contentIdColumn.inList(contentIds.toList()))
|
||||
} > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val dateTimeFormat = DateFormat("EEE, dd MMM yyyy HH:mm:ss z")
|
||||
|
||||
private class PostsAPIDatabaseTable(
|
||||
private val database: Database
|
||||
) : PostsAPI, Table() {
|
||||
private val contentsTable = PostsAPIContentRelations(database)
|
||||
|
||||
private val idColumn = text("postId")
|
||||
private val creationDateColumn = text("creationDate").default(
|
||||
DateTime.now().toString(dateTimeFormat)
|
||||
)
|
||||
|
||||
|
||||
private val postCreatedBroadcastChannel = BroadcastChannel<RegisteredPost>(ChannelDefaultSize)
|
||||
override val postCreatedFlow: Flow<RegisteredPost> = postCreatedBroadcastChannel.asFlow()
|
||||
|
||||
private val postDeletedBroadcastChannel = BroadcastChannel<RegisteredPost>(ChannelDefaultSize)
|
||||
override val postDeletedFlow: Flow<RegisteredPost> = postDeletedBroadcastChannel.asFlow()
|
||||
|
||||
private val postUpdatedBroadcastChannel = BroadcastChannel<RegisteredPost>(ChannelDefaultSize)
|
||||
override val postUpdatedFlow: Flow<RegisteredPost> = postUpdatedBroadcastChannel.asFlow()
|
||||
|
||||
init {
|
||||
transaction(database) {
|
||||
SchemaUtils.createMissingTablesAndColumns(this@PostsAPIDatabaseTable)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ResultRow.toRegisteredPost(): RegisteredPost = get(idColumn).let { id ->
|
||||
SimpleRegisteredPost(
|
||||
id,
|
||||
contentsTable.getPostContents(id),
|
||||
dateTimeFormat.parse(get(creationDateColumn)).local
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun createPost(post: Post): RegisteredPost? {
|
||||
val id = generatePostId()
|
||||
return transaction(database) {
|
||||
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(database) {
|
||||
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(database) {
|
||||
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(database) {
|
||||
selectAll().map { it[idColumn] }.toSet()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPostById(id: PostId): RegisteredPost? {
|
||||
return transaction(database) {
|
||||
select { idColumn.eq(id) }.firstOrNull() ?.toRegisteredPost()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPostsByContent(id: ContentId): List<RegisteredPost> {
|
||||
return transaction(database) {
|
||||
val postsIds = contentsTable.getContentPosts(id)
|
||||
select { idColumn.inList(postsIds) }.map { it.toRegisteredPost() }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPostsByCreatingDates(from: DateTime, to: DateTime): List<RegisteredPost> {
|
||||
return transaction(database) {
|
||||
select { creationDateColumn.between(from, to) }.map { it.toRegisteredPost() }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPostsByPagination(pagination: Pagination): PaginationResult<RegisteredPost> {
|
||||
return transaction(database) {
|
||||
val posts = selectAll().paginate(pagination).orderBy(creationDateColumn).map {
|
||||
it.toRegisteredPost()
|
||||
}
|
||||
val postsNumber = selectAll().count()
|
||||
posts.createPaginationResult(pagination, postsNumber)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ExposedPostsAPI (
|
||||
database: Database
|
||||
) : PostsAPI by PostsAPIDatabaseTable(database)
|
@@ -0,0 +1,7 @@
|
||||
package com.insanusmokrassar.postssystem.core.exposed
|
||||
|
||||
import com.insanusmokrassar.postssystem.core.utils.pagination.Pagination
|
||||
import com.insanusmokrassar.postssystem.core.utils.pagination.firstIndex
|
||||
import org.jetbrains.exposed.sql.Query
|
||||
|
||||
fun Query.paginate(pagination: Pagination) = limit(pagination.size, pagination.firstIndex.toLong())
|
@@ -0,0 +1,58 @@
|
||||
package com.insanusmokrassar.postssystem.core.exposed.content
|
||||
|
||||
import com.insanusmokrassar.postssystem.core.content.BinaryContent
|
||||
import com.insanusmokrassar.postssystem.core.content.ContentId
|
||||
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(database) {
|
||||
SchemaUtils.createMissingTablesAndColumns(this@BinaryContentHolderRepoTable)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getContent(id: ContentId): BinaryContent? = transaction(database) {
|
||||
select {
|
||||
idColumn.eq(id)
|
||||
}.limit(1).firstOrNull() ?.let {
|
||||
val bytes = it[dataColumn].bytes
|
||||
BinaryContent(
|
||||
it[mimeColumn],
|
||||
it[originalFileNameColumn]
|
||||
) {
|
||||
bytes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun removeContent(id: ContentId) {
|
||||
transaction(database) {
|
||||
deleteWhere { idColumn.eq(id) }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun putContent(id: ContentId, content: BinaryContent) {
|
||||
transaction(database) {
|
||||
insert {
|
||||
it[idColumn] = id
|
||||
it[originalFileNameColumn] = content.originalFileName
|
||||
it[mimeColumn] = content.mimeType
|
||||
it[dataColumn] = ExposedBlob(content.dataAllocator())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BinaryContentHolderRepo(
|
||||
database: Database
|
||||
) : ContentHolderRepo<BinaryContent> by BinaryContentHolderRepoTable(database)
|
@@ -0,0 +1,10 @@
|
||||
package com.insanusmokrassar.postssystem.core.exposed.content
|
||||
|
||||
import com.insanusmokrassar.postssystem.core.content.Content
|
||||
import com.insanusmokrassar.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)
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
package com.insanusmokrassar.postssystem.core.exposed.content
|
||||
|
||||
import com.insanusmokrassar.postssystem.core.content.ContentId
|
||||
import com.insanusmokrassar.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(database) {
|
||||
SchemaUtils.createMissingTablesAndColumns(this@SpecialContentHolderRepoTable)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getContent(id: ContentId): SpecialContent? = transaction(database) {
|
||||
select {
|
||||
idColumn.eq(id)
|
||||
}.limit(1).firstOrNull() ?.get(internalIdColumn) ?.let {
|
||||
SpecialContent(it)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun removeContent(id: ContentId) {
|
||||
transaction(database) {
|
||||
deleteWhere { idColumn.eq(id) }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun putContent(id: ContentId, content: SpecialContent) {
|
||||
transaction(database) {
|
||||
insert {
|
||||
it[idColumn] = id
|
||||
it[internalIdColumn] = content.internalId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SpecialContentHolderRepo(
|
||||
database: Database
|
||||
) : ContentHolderRepo<SpecialContent> by SpecialContentHolderRepoTable(database)
|
@@ -0,0 +1,47 @@
|
||||
package com.insanusmokrassar.postssystem.core.exposed.content
|
||||
|
||||
import com.insanusmokrassar.postssystem.core.content.ContentId
|
||||
import com.insanusmokrassar.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(database) {
|
||||
SchemaUtils.createMissingTablesAndColumns(this@TextContentHolderRepoTable)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getContent(id: ContentId): TextContent? = transaction(database) {
|
||||
select {
|
||||
idColumn.eq(id)
|
||||
}.limit(1).firstOrNull() ?.get(textColumn) ?.let {
|
||||
TextContent(it)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun removeContent(id: ContentId) {
|
||||
transaction(database) {
|
||||
deleteWhere { idColumn.eq(id) }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun putContent(id: ContentId, content: TextContent) {
|
||||
transaction(database) {
|
||||
insert {
|
||||
it[idColumn] = id
|
||||
it[textColumn] = content.text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TextContentHolderRepo(
|
||||
database: Database
|
||||
) : ContentHolderRepo<TextContent> by TextContentHolderRepoTable(database)
|
@@ -0,0 +1,62 @@
|
||||
package com.insanusmokrassar.postssystem.core.exposed
|
||||
|
||||
import com.insanusmokrassar.postssystem.core.content.TextContent
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import org.jetbrains.exposed.sql.transactions.transactionManager
|
||||
import java.io.File
|
||||
import java.sql.Connection
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class ExposedContentAPICommonTests {
|
||||
private val tempFolder = System.getProperty("java.io.tmpdir")!!
|
||||
|
||||
@Test
|
||||
fun `Test that it is possible to use several different databases at one time`() {
|
||||
val numberOfDatabases = 8
|
||||
|
||||
val databaseFiles = (0 until numberOfDatabases).map {
|
||||
"$tempFolder/ExposedContentAPICommonTestsDB$it.db"
|
||||
}
|
||||
|
||||
val apis = databaseFiles.map {
|
||||
File(it).also {
|
||||
it.delete()
|
||||
it.deleteOnExit()
|
||||
}
|
||||
ExposedContentAPI(
|
||||
Database.Companion.connect("jdbc:sqlite:$it", driver = "org.sqlite.JDBC").also {
|
||||
it.transactionManager.defaultIsolationLevel = Connection.TRANSACTION_SERIALIZABLE
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val results = apis.mapIndexed { i, api ->
|
||||
val content = runBlocking { api.registerContent(TextContent(i.toString())) }
|
||||
assert(content != null)
|
||||
val ids = runBlocking { api.getContentsIds() }
|
||||
assertEquals(ids.size, 1)
|
||||
content!!
|
||||
}
|
||||
|
||||
results.forEachIndexed { i, content ->
|
||||
apis.forEachIndexed { j, api ->
|
||||
assert(
|
||||
runBlocking {
|
||||
api.getContentById(content.id) == (if (i != j) null else content)
|
||||
}
|
||||
)
|
||||
assert(
|
||||
runBlocking {
|
||||
api.deleteContent(content.id) == (i == j)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
databaseFiles.forEach {
|
||||
File(it).delete()
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
package com.insanusmokrassar.postssystem.core.exposed
|
||||
|
||||
import com.insanusmokrassar.postssystem.core.post.SimplePost
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import org.jetbrains.exposed.sql.transactions.transactionManager
|
||||
import java.io.File
|
||||
import java.sql.Connection
|
||||
import kotlin.test.*
|
||||
|
||||
class ExposedPostsAPICommonTests {
|
||||
private val tempFolder = System.getProperty("java.io.tmpdir")!!
|
||||
|
||||
private val numberOfDatabases = 8
|
||||
private lateinit var databaseFiles: List<File>
|
||||
private lateinit var apis: List<ExposedPostsAPI>
|
||||
|
||||
@BeforeTest
|
||||
fun prepare() {
|
||||
databaseFiles = (0 until numberOfDatabases).map {
|
||||
File("$tempFolder/ExposedPostsAPICommonTestsDB$it.db")
|
||||
}
|
||||
apis = databaseFiles.map {
|
||||
val database = Database.connect("jdbc:sqlite:${it.absolutePath}").also {
|
||||
it.transactionManager.defaultIsolationLevel = Connection.TRANSACTION_SERIALIZABLE
|
||||
}
|
||||
ExposedPostsAPI(
|
||||
database
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Test that it is possible to use several different databases at one time`() {
|
||||
val posts = apis.mapIndexed { i, api ->
|
||||
val content = runBlocking { api.createPost(SimplePost(listOf(i.toString()))) }
|
||||
assert(content != null)
|
||||
assert(runBlocking { api.getPostsIds().size == 1 })
|
||||
content!!
|
||||
}
|
||||
|
||||
posts.forEachIndexed { i, post ->
|
||||
apis.forEachIndexed { j, api ->
|
||||
assert(
|
||||
runBlocking {
|
||||
api.getPostById(post.id) == (if (i != j) null else post)
|
||||
}
|
||||
)
|
||||
assert(
|
||||
runBlocking {
|
||||
api.deletePost(post.id) == (i == j)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTest
|
||||
fun `Close and delete databases`() {
|
||||
databaseFiles.forEach {
|
||||
it.delete()
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user