replace subprojects and add publishing of postssystem.core

This commit is contained in:
2019-11-15 14:12:01 +06:00
parent 9f4990b442
commit a7de0817f8
30 changed files with 100 additions and 6 deletions

@ -1,35 +0,0 @@
project.version = "$project_public_version"
project.group = "$project_public_group"
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"
}
}
apply plugin: 'kotlinx-serialization'
apply plugin: 'kotlin'
repositories {
mavenLocal()
jcenter()
mavenCentral()
maven { url "https://kotlin.bintray.com/kotlinx" }
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation project(":core")
api "org.jetbrains.exposed:exposed:$exposed_version"
testImplementation "org.xerial:sqlite-jdbc:$test_sqlite_version"
testImplementation "org.junit.jupiter:junit-jupiter-api:$test_junit_version"
}

@ -1,3 +0,0 @@
exposed_version=0.17.7
test_sqlite_version=3.28.0
test_junit_version=5.5.2

@ -1 +0,0 @@
rootProject.name="postssystem.core-exposed"

@ -1,88 +0,0 @@
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.utils.generateContentId
import com.insanusmokrassar.postssystem.core.utils.pagination.*
import kotlinx.coroutines.channels.BroadcastChannel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.serialization.json.Json
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
private class ContentAPIDatabaseTable(
private val database: Database
) : Table("ContentAPI"), ContentAPI {
internal val idColumn = text("_id")
internal val dataColumn = text("data")
private inline fun <T> transaction(noinline body: Transaction.() -> T): T = database.transaction(body)
init {
transaction {
SchemaUtils.createMissingTablesAndColumns(this@ContentAPIDatabaseTable)
}
}
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()
override suspend fun createContent(content: Content): RegisteredContent? {
return transaction {
insert {
it[idColumn] = generateContentId()
it[dataColumn] = Json.plain.stringify(Content.serializer(), content)
}.getOrNull(idColumn) ?.let { id ->
RegisteredContent(
id,
content
)
}
} ?.also {
contentCreatedBroadcastChannel.send(it)
}
}
override suspend fun deleteContent(id: ContentId): Boolean {
val content = getContentById(id) ?: return false
return transaction {
deleteWhere {
idColumn.eq(id)
} > 0
}.also {
if (it) {
contentDeletedBroadcastChannel.send(content)
}
}
}
private fun ResultRow.asRegisteredContent(): RegisteredContent = RegisteredContent(
get(idColumn),
Json.plain.parse(Content.serializer(), get(dataColumn))
)
override suspend fun getContentsIds(): Set<ContentId> {
return transaction {
selectAll().map { it[idColumn] }
}.toSet()
}
override suspend fun getContentById(id: ContentId): RegisteredContent? {
return transaction {
select { idColumn.eq(id) }.firstOrNull() ?.asRegisteredContent()
}
}
override suspend fun getContentByPagination(pagination: Pagination): PaginationResult<out RegisteredContent> {
return transaction {
selectAll().count() to selectAll().limit(n = pagination.size, offset = pagination.firstIndex).map { it.asRegisteredContent() }
}.let { (count, results) ->
pagination.createResult(count, results)
}
}
}
class ExposedContentAPI (
database: Database
) : ContentAPI by ContentAPIDatabaseTable(database)

@ -1,177 +0,0 @@
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)

@ -1,6 +0,0 @@
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)

@ -1,56 +0,0 @@
package com.insanusmokrassar.postssystem.core.exposed
import com.insanusmokrassar.postssystem.core.content.SimpleTextContent
import kotlinx.coroutines.runBlocking
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.transactions.transactionManager
import org.junit.jupiter.api.Test
import java.io.File
import java.sql.Connection
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 {
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.createContent(SimpleTextContent(i.toString())) }
assert(content != null)
assert(runBlocking { api.getContentsIds().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()
}
}
}

@ -1,64 +0,0 @@
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 org.junit.jupiter.api.*
import java.io.File
import java.sql.Connection
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
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>
@BeforeAll
fun prepare() {
databaseFiles = (0 until numberOfDatabases).map {
File("$tempFolder/ExposedPostsAPICommonTestsDB$it.db")
}
apis = databaseFiles.map {
ExposedPostsAPI(
Database.Companion.connect("jdbc:sqlite:${it.absolutePath}", driver = "org.sqlite.JDBC").also {
it.transactionManager.defaultIsolationLevel = Connection.TRANSACTION_SERIALIZABLE
}
)
}
}
@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)
}
)
}
}
}
@AfterAll
fun `Close and delete databases`() {
databaseFiles.forEach {
it.delete()
}
}
}