Prefix maven modules
This commit is contained in:
@@ -1,77 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>persistance</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<artifactId>domain</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<artifactId>shared</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<artifactId>shared</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mariadb.jdbc</groupId>
|
||||
<artifactId>mariadb-java-client</artifactId>
|
||||
<version>2.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>1.4.200</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-core</artifactId>
|
||||
<version>6.5.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
<version>3.4.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>me.liuwj.ktorm</groupId>
|
||||
<artifactId>ktorm-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>me.liuwj.ktorm</groupId>
|
||||
<artifactId>ktorm-support-mysql</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -1,28 +0,0 @@
|
||||
package be.simplenotes.persistance
|
||||
|
||||
import be.simplenotes.persistance.utils.DbType
|
||||
import be.simplenotes.persistance.utils.type
|
||||
import be.simplenotes.shared.config.DataSourceConfig
|
||||
import me.liuwj.ktorm.database.Database
|
||||
import me.liuwj.ktorm.database.asIterable
|
||||
import java.sql.SQLTransientException
|
||||
|
||||
interface DbHealthCheck {
|
||||
fun isOk(): Boolean
|
||||
}
|
||||
|
||||
internal class DbHealthCheckImpl(
|
||||
private val db: Database,
|
||||
private val dataSourceConfig: DataSourceConfig,
|
||||
) : DbHealthCheck {
|
||||
override fun isOk() = if (dataSourceConfig.type() == DbType.H2) true
|
||||
else try {
|
||||
db.useConnection { connection ->
|
||||
connection.prepareStatement("""SHOW DATABASES""").use {
|
||||
it.executeQuery().asIterable().map { it.getString(1) }
|
||||
}
|
||||
}.any { it in dataSourceConfig.jdbcUrl }
|
||||
} catch (e: SQLTransientException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package be.simplenotes.persistance
|
||||
|
||||
import be.simplenotes.persistance.utils.DbType
|
||||
import be.simplenotes.persistance.utils.type
|
||||
import be.simplenotes.shared.config.DataSourceConfig
|
||||
import org.flywaydb.core.Flyway
|
||||
import javax.sql.DataSource
|
||||
|
||||
interface DbMigrations {
|
||||
fun migrate()
|
||||
}
|
||||
|
||||
internal class DbMigrationsImpl(
|
||||
private val dataSource: DataSource,
|
||||
private val dataSourceConfig: DataSourceConfig,
|
||||
) : DbMigrations {
|
||||
override fun migrate() {
|
||||
|
||||
val migrationDir = when (dataSourceConfig.type()) {
|
||||
DbType.H2 -> "db/migration/other"
|
||||
DbType.MariaDb -> "db/migration/mariadb"
|
||||
}
|
||||
|
||||
Flyway.configure()
|
||||
.dataSource(dataSource)
|
||||
.locations(migrationDir)
|
||||
.load()
|
||||
.migrate()
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package be.simplenotes.persistance
|
||||
|
||||
import be.simplenotes.domain.usecases.repositories.NoteRepository
|
||||
import be.simplenotes.domain.usecases.repositories.UserRepository
|
||||
import be.simplenotes.persistance.converters.NoteConverter
|
||||
import be.simplenotes.persistance.converters.NoteConverterImpl
|
||||
import be.simplenotes.persistance.converters.UserConverter
|
||||
import be.simplenotes.persistance.converters.UserConverterImpl
|
||||
import be.simplenotes.persistance.notes.NoteRepositoryImpl
|
||||
import be.simplenotes.persistance.users.UserRepositoryImpl
|
||||
import be.simplenotes.shared.config.DataSourceConfig
|
||||
import com.zaxxer.hikari.HikariConfig
|
||||
import com.zaxxer.hikari.HikariDataSource
|
||||
import me.liuwj.ktorm.database.Database
|
||||
import org.koin.dsl.bind
|
||||
import org.koin.dsl.module
|
||||
import org.koin.dsl.onClose
|
||||
import javax.sql.DataSource
|
||||
|
||||
private fun hikariDataSource(conf: DataSourceConfig): HikariDataSource {
|
||||
val hikariConfig = HikariConfig().also {
|
||||
it.jdbcUrl = conf.jdbcUrl
|
||||
it.driverClassName = conf.driverClassName
|
||||
it.username = conf.username
|
||||
it.password = conf.password
|
||||
it.maximumPoolSize = conf.maximumPoolSize
|
||||
it.connectionTimeout = conf.connectionTimeout
|
||||
}
|
||||
return HikariDataSource(hikariConfig)
|
||||
}
|
||||
|
||||
val migrationModule = module {
|
||||
single<DbMigrations> { DbMigrationsImpl(get(), get()) }
|
||||
}
|
||||
|
||||
val persistanceModule = module {
|
||||
single<NoteConverter> { NoteConverterImpl() }
|
||||
single<UserConverter> { UserConverterImpl() }
|
||||
single<UserRepository> { UserRepositoryImpl(get(), get()) }
|
||||
single<NoteRepository> { NoteRepositoryImpl(get(), get()) }
|
||||
single { hikariDataSource(get()) } bind DataSource::class onClose { it?.close() }
|
||||
single {
|
||||
get<DbMigrations>().migrate()
|
||||
Database.connect(get<DataSource>())
|
||||
}
|
||||
single<DbHealthCheck> { DbHealthCheckImpl(get(), get()) }
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package be.simplenotes.persistance.converters
|
||||
|
||||
import be.simplenotes.domain.model.*
|
||||
import be.simplenotes.persistance.notes.NoteEntity
|
||||
import me.liuwj.ktorm.entity.Entity
|
||||
import org.mapstruct.Mapper
|
||||
import org.mapstruct.Mapping
|
||||
import org.mapstruct.Mappings
|
||||
import java.time.LocalDateTime
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* This is an abstract class because kotlin default methods in interface are not seen as default in kapt
|
||||
* @see [KT-25960](https://youtrack.jetbrains.com/issue/KT-25960)
|
||||
*/
|
||||
@Mapper(uses = [NoteEntityFactory::class, UserEntityFactory::class])
|
||||
internal abstract class NoteConverter {
|
||||
|
||||
fun toNote(entity: NoteEntity, tags: Tags) =
|
||||
Note(NoteMetadata(title = entity.title, tags = tags), entity.markdown, entity.html)
|
||||
|
||||
@Mappings(
|
||||
Mapping(target = ".", source = "entity"),
|
||||
Mapping(target = "tags", source = "tags"),
|
||||
)
|
||||
abstract fun toNoteMetadata(entity: NoteEntity, tags: Tags): NoteMetadata
|
||||
|
||||
@Mappings(
|
||||
Mapping(target = ".", source = "entity"),
|
||||
Mapping(target = "tags", source = "tags"),
|
||||
)
|
||||
abstract fun toPersistedNoteMetadata(entity: NoteEntity, tags: Tags): PersistedNoteMetadata
|
||||
|
||||
fun toPersistedNote(entity: NoteEntity, tags: Tags) = PersistedNote(
|
||||
NoteMetadata(title = entity.title, tags = tags),
|
||||
entity.markdown, entity.html, entity.updatedAt, entity.uuid, entity.public
|
||||
)
|
||||
|
||||
@Mappings(
|
||||
Mapping(target = ".", source = "entity"),
|
||||
Mapping(target = "trash", source = "entity.deleted"),
|
||||
Mapping(target = "tags", source = "tags"),
|
||||
)
|
||||
abstract fun toExportedNote(entity: NoteEntity, tags: Tags): ExportedNote
|
||||
|
||||
fun toEntity(note: Note, uuid: UUID, userId: Int, updatedAt: LocalDateTime) = NoteEntity {
|
||||
this.title = note.meta.title
|
||||
this.markdown = note.markdown
|
||||
this.html = note.html
|
||||
this.uuid = uuid
|
||||
this.deleted = false
|
||||
this.public = false
|
||||
this.user.id = userId
|
||||
this.updatedAt = updatedAt
|
||||
}
|
||||
|
||||
@Mappings(
|
||||
Mapping(target = ".", source = "note"),
|
||||
Mapping(target = "updatedAt", source = "updatedAt"),
|
||||
Mapping(target = "uuid", source = "uuid"),
|
||||
Mapping(target = "public", constant = "false"),
|
||||
)
|
||||
abstract fun toPersistedNote(note: Note, updatedAt: LocalDateTime, uuid: UUID): PersistedNote
|
||||
|
||||
abstract fun toEntity(persistedNoteMetadata: PersistedNoteMetadata): NoteEntity
|
||||
|
||||
abstract fun toEntity(noteMetadata: NoteMetadata): NoteEntity
|
||||
|
||||
@Mapping(target = "title", source = "meta.title")
|
||||
abstract fun toEntity(persistedNote: PersistedNote): NoteEntity
|
||||
|
||||
@Mapping(target = "deleted", source = "trash")
|
||||
abstract fun toEntity(exportedNote: ExportedNote): NoteEntity
|
||||
|
||||
}
|
||||
|
||||
typealias Tags = List<String>
|
||||
|
||||
internal class NoteEntityFactory : Entity.Factory<NoteEntity>()
|
||||
@@ -1,17 +0,0 @@
|
||||
package be.simplenotes.persistance.converters
|
||||
|
||||
import be.simplenotes.domain.model.PersistedUser
|
||||
import be.simplenotes.domain.model.User
|
||||
import be.simplenotes.persistance.users.UserEntity
|
||||
import me.liuwj.ktorm.entity.Entity
|
||||
import org.mapstruct.Mapper
|
||||
|
||||
@Mapper(uses = [UserEntityFactory::class])
|
||||
internal interface UserConverter {
|
||||
fun toUser(userEntity: UserEntity): User
|
||||
fun toPersistedUser(userEntity: UserEntity): PersistedUser
|
||||
fun toEntity(user: User): UserEntity
|
||||
fun toEntity(user: PersistedUser): UserEntity
|
||||
}
|
||||
|
||||
internal class UserEntityFactory : Entity.Factory<UserEntity>()
|
||||
@@ -1,25 +0,0 @@
|
||||
package be.simplenotes.persistance.extensions
|
||||
|
||||
import me.liuwj.ktorm.schema.*
|
||||
import java.nio.ByteBuffer
|
||||
import java.sql.PreparedStatement
|
||||
import java.sql.ResultSet
|
||||
import java.sql.Types
|
||||
import java.util.*
|
||||
|
||||
internal class UuidBinarySqlType : SqlType<UUID>(Types.BINARY, typeName = "uuidBinary") {
|
||||
override fun doGetResult(rs: ResultSet, index: Int): UUID? {
|
||||
val value = rs.getBytes(index) ?: return null
|
||||
return ByteBuffer.wrap(value).let { b -> UUID(b.long, b.long) }
|
||||
}
|
||||
|
||||
override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: UUID) {
|
||||
val bytes = ByteBuffer.allocate(16)
|
||||
.putLong(parameter.mostSignificantBits)
|
||||
.putLong(parameter.leastSignificantBits)
|
||||
.array()
|
||||
ps.setBytes(index, bytes)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun <E : Any> BaseTable<E>.uuidBinary(name: String) = registerColumn(name, UuidBinarySqlType())
|
||||
@@ -1,215 +0,0 @@
|
||||
package be.simplenotes.persistance.notes
|
||||
|
||||
import be.simplenotes.domain.model.ExportedNote
|
||||
import be.simplenotes.domain.model.Note
|
||||
import be.simplenotes.domain.model.PersistedNote
|
||||
import be.simplenotes.domain.model.PersistedNoteMetadata
|
||||
import be.simplenotes.domain.usecases.repositories.NoteRepository
|
||||
import be.simplenotes.persistance.converters.NoteConverter
|
||||
import me.liuwj.ktorm.database.Database
|
||||
import me.liuwj.ktorm.dsl.*
|
||||
import me.liuwj.ktorm.entity.*
|
||||
import java.time.LocalDateTime
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
internal class NoteRepositoryImpl(private val db: Database, private val converter: NoteConverter) : NoteRepository {
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
override fun findAll(
|
||||
userId: Int,
|
||||
limit: Int,
|
||||
offset: Int,
|
||||
tag: String?,
|
||||
deleted: Boolean,
|
||||
): List<PersistedNoteMetadata> {
|
||||
require(limit > 0) { "limit should be positive" }
|
||||
require(offset >= 0) { "offset should not be negative" }
|
||||
|
||||
val uuids1: List<UUID>? = if (tag != null) {
|
||||
db.from(Tags)
|
||||
.leftJoin(Notes, on = Notes.uuid eq Tags.noteUuid)
|
||||
.select(Notes.uuid)
|
||||
.where { (Notes.userId eq userId) and (Tags.name eq tag) and (Notes.deleted eq deleted) }
|
||||
.map { it[Notes.uuid]!! }
|
||||
} else null
|
||||
|
||||
var query = db.notes
|
||||
.filterColumns { listOf(it.uuid, it.title, it.updatedAt) }
|
||||
.filter { (it.userId eq userId) and (it.deleted eq deleted) }
|
||||
|
||||
if (uuids1 != null) query = query.filter { it.uuid inList uuids1 }
|
||||
|
||||
val notes = query
|
||||
.sortedByDescending { it.updatedAt }
|
||||
.take(limit)
|
||||
.drop(offset)
|
||||
.toList()
|
||||
|
||||
val tagsByUuid = notes.tagsByUuid()
|
||||
|
||||
return notes.map { note ->
|
||||
val tags = tagsByUuid[note.uuid] ?: emptyList()
|
||||
converter.toPersistedNoteMetadata(note, tags)
|
||||
}
|
||||
}
|
||||
|
||||
override fun exists(userId: Int, uuid: UUID): Boolean {
|
||||
return db.notes.any { (it.userId eq userId) and (it.uuid eq uuid) and (it.deleted eq false) }
|
||||
}
|
||||
|
||||
override fun create(userId: Int, note: Note): PersistedNote {
|
||||
val uuid = UUID.randomUUID()
|
||||
val entity = converter.toEntity(note, uuid, userId, LocalDateTime.now())
|
||||
db.notes.add(entity)
|
||||
db.batchInsert(Tags) {
|
||||
note.meta.tags.forEach { tagName ->
|
||||
item {
|
||||
it.noteUuid to uuid
|
||||
it.name to tagName
|
||||
}
|
||||
}
|
||||
}
|
||||
return converter.toPersistedNote(entity, note.meta.tags)
|
||||
}
|
||||
|
||||
override fun find(userId: Int, uuid: UUID): PersistedNote? {
|
||||
val note = db.notes
|
||||
.filterColumns { it.columns - it.userId }
|
||||
.filter { it.uuid eq uuid }
|
||||
.find { (it.userId eq userId) and (it.deleted eq false) }
|
||||
?: return null
|
||||
|
||||
val tags = db.from(Tags)
|
||||
.select(Tags.name)
|
||||
.where { Tags.noteUuid eq uuid }
|
||||
.map { it[Tags.name]!! }
|
||||
|
||||
return converter.toPersistedNote(note, tags)
|
||||
}
|
||||
|
||||
override fun update(userId: Int, uuid: UUID, note: Note): PersistedNote? {
|
||||
val now = LocalDateTime.now()
|
||||
val count = db.update(Notes) {
|
||||
it.title to note.meta.title
|
||||
it.markdown to note.markdown
|
||||
it.html to note.html
|
||||
it.updatedAt to now
|
||||
where { (it.uuid eq uuid) and (it.userId eq userId) and (it.deleted eq false) }
|
||||
}
|
||||
|
||||
if (count == 0) return null
|
||||
|
||||
// delete all tags
|
||||
db.delete(Tags) {
|
||||
it.noteUuid eq uuid
|
||||
}
|
||||
|
||||
// put new ones
|
||||
note.meta.tags.forEach { tagName ->
|
||||
db.insert(Tags) {
|
||||
it.name to tagName
|
||||
it.noteUuid to uuid
|
||||
}
|
||||
}
|
||||
|
||||
return converter.toPersistedNote(note, now, uuid)
|
||||
}
|
||||
|
||||
override fun delete(userId: Int, uuid: UUID, permanent: Boolean): Boolean {
|
||||
return if (!permanent) {
|
||||
db.update(Notes) {
|
||||
it.deleted to true
|
||||
it.updatedAt to LocalDateTime.now()
|
||||
where { it.userId eq userId and (it.uuid eq uuid) }
|
||||
} == 1
|
||||
} else
|
||||
db.delete(Notes) { it.uuid eq uuid and (it.userId eq userId) } == 1
|
||||
}
|
||||
|
||||
override fun restore(userId: Int, uuid: UUID): Boolean {
|
||||
return db.update(Notes) {
|
||||
it.deleted to false
|
||||
where { (it.userId eq userId) and (it.uuid eq uuid) }
|
||||
} == 1
|
||||
}
|
||||
|
||||
override fun getTags(userId: Int): List<String> =
|
||||
db.from(Tags)
|
||||
.leftJoin(Notes, on = Notes.uuid eq Tags.noteUuid)
|
||||
.selectDistinct(Tags.name)
|
||||
.where { (Notes.userId eq userId) and (Notes.deleted eq false) }
|
||||
.map { it[Tags.name]!! }
|
||||
|
||||
override fun count(userId: Int, tag: String?, deleted: Boolean): Int {
|
||||
return if (tag == null) db.notes.count { (it.userId eq userId) and (Notes.deleted eq deleted) }
|
||||
else db.sequenceOf(Tags).count {
|
||||
(it.name eq tag) and (it.note.userId eq userId) and (it.note.deleted eq deleted)
|
||||
}
|
||||
}
|
||||
|
||||
override fun export(userId: Int): List<ExportedNote> {
|
||||
|
||||
val notes = db.notes
|
||||
.filterColumns { it.columns - it.userId }
|
||||
.filter { it.userId eq userId }
|
||||
.sortedByDescending { it.updatedAt }
|
||||
.toList()
|
||||
|
||||
val tagsByUuid = notes.tagsByUuid()
|
||||
|
||||
return notes.map { note ->
|
||||
val tags = tagsByUuid[note.uuid] ?: emptyList()
|
||||
converter.toExportedNote(note, tags)
|
||||
}
|
||||
}
|
||||
|
||||
override fun findAllDetails(userId: Int): List<PersistedNote> {
|
||||
val notes = db.notes
|
||||
.filterColumns { it.columns - it.deleted }
|
||||
.filter { (it.userId eq userId) and (it.deleted eq false) }
|
||||
.toList()
|
||||
|
||||
val tagsByUuid = notes.tagsByUuid()
|
||||
|
||||
return notes.map { note ->
|
||||
val tags = tagsByUuid[note.uuid] ?: emptyList()
|
||||
converter.toPersistedNote(note, tags)
|
||||
}
|
||||
}
|
||||
|
||||
override fun makePublic(userId: Int, uuid: UUID) = db.update(Notes) {
|
||||
it.public to true
|
||||
where { Notes.userId eq userId and (Notes.uuid eq uuid) and (it.deleted eq false) }
|
||||
} == 1
|
||||
|
||||
override fun makePrivate(userId: Int, uuid: UUID) = db.update(Notes) {
|
||||
it.public to false
|
||||
where { Notes.userId eq userId and (Notes.uuid eq uuid) and (it.deleted eq false) }
|
||||
} == 1
|
||||
|
||||
override fun findPublic(uuid: UUID): PersistedNote? {
|
||||
val note = db.notes
|
||||
.filterColumns { it.columns - it.userId }
|
||||
.filter { it.uuid eq uuid }
|
||||
.filter { it.public eq true }
|
||||
.find { it.deleted eq false }
|
||||
?: return null
|
||||
|
||||
val tags = db.from(Tags)
|
||||
.select(Tags.name)
|
||||
.where { Tags.noteUuid eq uuid }
|
||||
.map { it[Tags.name]!! }
|
||||
|
||||
return converter.toPersistedNote(note, tags)
|
||||
}
|
||||
|
||||
private fun List<NoteEntity>.tagsByUuid(): Map<UUID, List<String>> {
|
||||
return if (isEmpty()) emptyMap()
|
||||
else db.tags
|
||||
.filterColumns { listOf(it.noteUuid, it.name) }
|
||||
.filter { it.noteUuid inList map { note -> note.uuid } }
|
||||
.groupByTo(HashMap(), { it.note.uuid }, { it.name })
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
@file:Suppress("unused")
|
||||
|
||||
package be.simplenotes.persistance.notes
|
||||
|
||||
import be.simplenotes.persistance.extensions.uuidBinary
|
||||
import be.simplenotes.persistance.users.UserEntity
|
||||
import be.simplenotes.persistance.users.Users
|
||||
import me.liuwj.ktorm.database.Database
|
||||
import me.liuwj.ktorm.entity.Entity
|
||||
import me.liuwj.ktorm.entity.sequenceOf
|
||||
import me.liuwj.ktorm.schema.*
|
||||
import java.time.LocalDateTime
|
||||
import java.util.*
|
||||
|
||||
internal open class Notes(alias: String?) : Table<NoteEntity>("Notes", alias) {
|
||||
companion object : Notes(null)
|
||||
|
||||
override fun aliased(alias: String) = Notes(alias)
|
||||
|
||||
val uuid = uuidBinary("uuid").primaryKey().bindTo { it.uuid }
|
||||
val title = varchar("title").bindTo { it.title }
|
||||
val markdown = text("markdown").bindTo { it.markdown }
|
||||
val html = text("html").bindTo { it.html }
|
||||
val userId = int("user_id").references(Users) { it.user }
|
||||
val updatedAt = datetime("updated_at").bindTo { it.updatedAt }
|
||||
val deleted = boolean("deleted").bindTo { it.deleted }
|
||||
val public = boolean("public").bindTo { it.public }
|
||||
val user get() = userId.referenceTable as Users
|
||||
}
|
||||
|
||||
internal val Database.notes get() = this.sequenceOf(Notes, withReferences = false)
|
||||
|
||||
internal interface NoteEntity : Entity<NoteEntity> {
|
||||
companion object : Entity.Factory<NoteEntity>()
|
||||
|
||||
var uuid: UUID
|
||||
var title: String
|
||||
var markdown: String
|
||||
var html: String
|
||||
var updatedAt: LocalDateTime
|
||||
var deleted: Boolean
|
||||
var public: Boolean
|
||||
|
||||
var user: UserEntity
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package be.simplenotes.persistance.notes
|
||||
|
||||
import be.simplenotes.persistance.extensions.uuidBinary
|
||||
import me.liuwj.ktorm.database.*
|
||||
import me.liuwj.ktorm.entity.*
|
||||
import me.liuwj.ktorm.schema.*
|
||||
|
||||
internal open class Tags(alias: String?) : Table<TagEntity>("Tags", alias) {
|
||||
companion object : Tags(null)
|
||||
|
||||
override fun aliased(alias: String) = Tags(alias)
|
||||
|
||||
val id = int("id").primaryKey().bindTo { it.id }
|
||||
val name = varchar("name").bindTo { it.name }
|
||||
val noteUuid = uuidBinary("note_uuid").references(Notes) { it.note }
|
||||
val note get() = noteUuid.referenceTable as Notes
|
||||
}
|
||||
|
||||
internal val Database.tags get() = this.sequenceOf(Tags, withReferences = false)
|
||||
|
||||
internal interface TagEntity : Entity<TagEntity> {
|
||||
companion object : Entity.Factory<TagEntity>()
|
||||
|
||||
val id: Int
|
||||
var name: String
|
||||
var note: NoteEntity
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package be.simplenotes.persistance.users
|
||||
|
||||
import be.simplenotes.domain.model.PersistedUser
|
||||
import be.simplenotes.domain.model.User
|
||||
import be.simplenotes.domain.usecases.repositories.UserRepository
|
||||
import be.simplenotes.persistance.converters.UserConverter
|
||||
import me.liuwj.ktorm.database.Database
|
||||
import me.liuwj.ktorm.dsl.*
|
||||
import me.liuwj.ktorm.entity.any
|
||||
import me.liuwj.ktorm.entity.find
|
||||
import java.sql.SQLIntegrityConstraintViolationException
|
||||
|
||||
internal class UserRepositoryImpl(private val db: Database, private val converter: UserConverter) : UserRepository {
|
||||
override fun create(user: User): PersistedUser? {
|
||||
return try {
|
||||
val id = db.insertAndGenerateKey(Users) {
|
||||
it.username to user.username
|
||||
it.password to user.password
|
||||
} as Int
|
||||
PersistedUser(user.username, user.password, id)
|
||||
} catch (e: SQLIntegrityConstraintViolationException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun find(username: String) = db.users.find { it.username eq username }
|
||||
?.let { converter.toPersistedUser(it) }
|
||||
|
||||
override fun find(id: Int) = db.users.find { it.id eq id }?.let {
|
||||
converter.toPersistedUser(it)
|
||||
}
|
||||
|
||||
override fun exists(username: String) = db.users.any { it.username eq username }
|
||||
override fun exists(id: Int) = db.users.any { it.id eq id }
|
||||
override fun delete(id: Int) = db.delete(Users) { it.id eq id } == 1
|
||||
override fun findAll() = db.from(Users).select(Users.id).map { it[Users.id]!! }
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package be.simplenotes.persistance.users
|
||||
|
||||
import me.liuwj.ktorm.database.Database
|
||||
import me.liuwj.ktorm.entity.Entity
|
||||
import me.liuwj.ktorm.entity.sequenceOf
|
||||
import me.liuwj.ktorm.schema.Table
|
||||
import me.liuwj.ktorm.schema.int
|
||||
import me.liuwj.ktorm.schema.varchar
|
||||
|
||||
internal open class Users(alias: String?) : Table<UserEntity>("Users", alias) {
|
||||
companion object : Users(null)
|
||||
|
||||
override fun aliased(alias: String) = Users(alias)
|
||||
|
||||
val id = int("id").primaryKey().bindTo { it.id }
|
||||
val username = varchar("username").bindTo { it.username }
|
||||
val password = varchar("password").bindTo { it.password }
|
||||
}
|
||||
|
||||
internal interface UserEntity : Entity<UserEntity> {
|
||||
companion object : Entity.Factory<UserEntity>()
|
||||
|
||||
var id: Int
|
||||
var username: String
|
||||
var password: String
|
||||
}
|
||||
|
||||
internal val Database.users get() = this.sequenceOf(Users, withReferences = false)
|
||||
@@ -1,8 +0,0 @@
|
||||
package be.simplenotes.persistance.utils
|
||||
|
||||
import be.simplenotes.shared.config.DataSourceConfig
|
||||
|
||||
enum class DbType { H2, MariaDb }
|
||||
|
||||
fun DataSourceConfig.type(): DbType = if (jdbcUrl.contains("mariadb")) DbType.MariaDb
|
||||
else DbType.H2
|
||||
@@ -1,36 +0,0 @@
|
||||
create table Users
|
||||
(
|
||||
id int auto_increment primary key,
|
||||
username varchar(50) not null,
|
||||
password varchar(255) not null,
|
||||
|
||||
constraint username unique (username)
|
||||
) character set 'utf8mb4'
|
||||
collate 'utf8mb4_general_ci';
|
||||
|
||||
create table Notes
|
||||
(
|
||||
uuid binary(16) not null primary key,
|
||||
title varchar(50) not null,
|
||||
markdown mediumtext not null,
|
||||
html mediumtext not null,
|
||||
user_id int not null,
|
||||
updated_at datetime null,
|
||||
|
||||
constraint Notes_fk_user foreign key (user_id) references Users (id) on delete cascade
|
||||
|
||||
) character set 'utf8mb4'
|
||||
collate 'utf8mb4_general_ci';
|
||||
|
||||
create index user_id on Notes (user_id);
|
||||
|
||||
create table Tags
|
||||
(
|
||||
id int auto_increment primary key,
|
||||
name varchar(50) not null,
|
||||
note_uuid binary(16) not null,
|
||||
constraint Tags_fk_note foreign key (note_uuid) references Notes (uuid) on delete cascade
|
||||
) character set 'utf8mb4'
|
||||
collate 'utf8mb4_general_ci';
|
||||
|
||||
create index note_uuid on Tags (note_uuid);
|
||||
@@ -1,2 +0,0 @@
|
||||
alter table Notes
|
||||
add column deleted bool not null default false
|
||||
@@ -1,2 +0,0 @@
|
||||
alter table Notes
|
||||
add column public bool not null default false
|
||||
@@ -1,33 +0,0 @@
|
||||
create table Users
|
||||
(
|
||||
id int auto_increment primary key,
|
||||
username varchar(50) not null,
|
||||
password varchar(255) not null,
|
||||
|
||||
constraint username unique (username)
|
||||
);
|
||||
|
||||
create table Notes
|
||||
(
|
||||
uuid binary(16) not null primary key,
|
||||
title varchar(50) not null,
|
||||
markdown mediumtext not null,
|
||||
html mediumtext not null,
|
||||
user_id int not null,
|
||||
updated_at datetime null,
|
||||
|
||||
constraint Notes_fk_user foreign key (user_id) references Users (id) on delete cascade
|
||||
|
||||
);
|
||||
|
||||
create index user_id on Notes (user_id);
|
||||
|
||||
create table Tags
|
||||
(
|
||||
id int auto_increment primary key,
|
||||
name varchar(50) not null,
|
||||
note_uuid binary(16) not null,
|
||||
constraint Tags_fk_note foreign key (note_uuid) references Notes (uuid) on delete cascade
|
||||
);
|
||||
|
||||
create index note_uuid on Tags (note_uuid);
|
||||
@@ -1,2 +0,0 @@
|
||||
alter table Notes
|
||||
add column deleted bool not null default false
|
||||
@@ -1,2 +0,0 @@
|
||||
alter table Notes
|
||||
add column public bool not null default false
|
||||
@@ -1,16 +0,0 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<withJansi>true</withJansi>
|
||||
<encoder>
|
||||
<pattern>%cyan(%d{YYYY-MM-dd HH:mm:ss.SSS}) [%thread] %highlight(%-5level) %green(%logger{36}) - %msg%n
|
||||
</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
<logger name="org.eclipse.jetty" level="INFO"/>
|
||||
<logger name="me.liuwj.ktorm.database" level="INFO"/>
|
||||
<logger name="com.zaxxer.hikari" level="INFO"/>
|
||||
<logger name="org.flywaydb.core" level="INFO"/>
|
||||
</configuration>
|
||||
@@ -1 +0,0 @@
|
||||
package be.simplenotes.persistance
|
||||
@@ -1,168 +0,0 @@
|
||||
package be.simplenotes.persistance.converters
|
||||
|
||||
import be.simplenotes.domain.model.*
|
||||
import be.simplenotes.persistance.notes.NoteEntity
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.mapstruct.factory.Mappers
|
||||
import java.time.LocalDateTime
|
||||
import java.util.*
|
||||
|
||||
internal class NoteConverterTest {
|
||||
|
||||
@Nested
|
||||
@DisplayName("Entity -> Models")
|
||||
inner class EntityToModels {
|
||||
|
||||
@Test
|
||||
fun `convert NoteEntity to Note`() {
|
||||
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||
val entity = NoteEntity {
|
||||
title = "title"
|
||||
markdown = "md"
|
||||
html = "html"
|
||||
}
|
||||
val tags = listOf("a", "b")
|
||||
val note = converter.toNote(entity, tags)
|
||||
val expectedNote = Note(NoteMetadata(
|
||||
title = "title",
|
||||
tags = tags,
|
||||
), markdown = "md", html = "html")
|
||||
assertThat(note).isEqualTo(expectedNote)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convert NoteEntity to ExportedNote`() {
|
||||
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||
val entity = NoteEntity {
|
||||
title = "title"
|
||||
markdown = "md"
|
||||
html = "html"
|
||||
updatedAt = LocalDateTime.MIN
|
||||
deleted = true
|
||||
}
|
||||
val tags = listOf("a", "b")
|
||||
val note = converter.toExportedNote(entity, tags)
|
||||
val expectedNote = ExportedNote(
|
||||
title = "title",
|
||||
tags = tags,
|
||||
markdown = "md",
|
||||
html = "html",
|
||||
updatedAt = LocalDateTime.MIN,
|
||||
trash = true
|
||||
)
|
||||
assertThat(note).isEqualTo(expectedNote)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convert NoteEntity to PersistedNoteMetadata`() {
|
||||
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||
val entity = NoteEntity {
|
||||
uuid = UUID.randomUUID()
|
||||
title = "title"
|
||||
markdown = "md"
|
||||
html = "html"
|
||||
updatedAt = LocalDateTime.MIN
|
||||
deleted = true
|
||||
}
|
||||
val tags = listOf("a", "b")
|
||||
val note = converter.toPersistedNoteMetadata(entity, tags)
|
||||
val expectedNote = PersistedNoteMetadata(
|
||||
title = "title",
|
||||
tags = tags,
|
||||
updatedAt = LocalDateTime.MIN,
|
||||
uuid = entity.uuid
|
||||
)
|
||||
assertThat(note).isEqualTo(expectedNote)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Models -> Entity")
|
||||
inner class ModelsToEntity {
|
||||
|
||||
@Test
|
||||
fun `convert Note to NoteEntity`() {
|
||||
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||
val note = Note(NoteMetadata("title", emptyList()), "md", "html")
|
||||
val entity = converter.toEntity(note, UUID.randomUUID(), 2, LocalDateTime.MIN)
|
||||
|
||||
assertThat(entity)
|
||||
.hasFieldOrPropertyWithValue("markdown", "md")
|
||||
.hasFieldOrPropertyWithValue("html", "html")
|
||||
.hasFieldOrPropertyWithValue("title", "title")
|
||||
.hasFieldOrPropertyWithValue("uuid", entity.uuid)
|
||||
.hasFieldOrPropertyWithValue("updatedAt", LocalDateTime.MIN)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convert PersistedNoteMetadata to NoteEntity`() {
|
||||
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||
val persistedNoteMetadata =
|
||||
PersistedNoteMetadata("title", emptyList(), LocalDateTime.MIN, UUID.randomUUID())
|
||||
val entity = converter.toEntity(persistedNoteMetadata)
|
||||
|
||||
assertThat(entity)
|
||||
.hasFieldOrPropertyWithValue("uuid", persistedNoteMetadata.uuid)
|
||||
.hasFieldOrPropertyWithValue("updatedAt", persistedNoteMetadata.updatedAt)
|
||||
.hasFieldOrPropertyWithValue("title", "title")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convert NoteMetadata to NoteEntity`() {
|
||||
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||
val noteMetadata = NoteMetadata("title", emptyList())
|
||||
val entity = converter.toEntity(noteMetadata)
|
||||
|
||||
assertThat(entity)
|
||||
.hasFieldOrPropertyWithValue("title", "title")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convert PersistedNote to NoteEntity`() {
|
||||
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||
val persistedNote = PersistedNote(
|
||||
NoteMetadata("title", emptyList()),
|
||||
markdown = "md",
|
||||
html = "html",
|
||||
updatedAt = LocalDateTime.MIN,
|
||||
uuid = UUID.randomUUID(),
|
||||
public = true
|
||||
)
|
||||
val entity = converter.toEntity(persistedNote)
|
||||
|
||||
assertThat(entity)
|
||||
.hasFieldOrPropertyWithValue("title", "title")
|
||||
.hasFieldOrPropertyWithValue("markdown", "md")
|
||||
.hasFieldOrPropertyWithValue("uuid", persistedNote.uuid)
|
||||
.hasFieldOrPropertyWithValue("updatedAt", persistedNote.updatedAt)
|
||||
.hasFieldOrPropertyWithValue("deleted", false)
|
||||
.hasFieldOrPropertyWithValue("public", true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convert ExportedNote to NoteEntity`() {
|
||||
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||
val exportedNote = ExportedNote(
|
||||
"title",
|
||||
emptyList(),
|
||||
markdown = "md",
|
||||
html = "html",
|
||||
updatedAt = LocalDateTime.MIN,
|
||||
trash = true
|
||||
)
|
||||
val entity = converter.toEntity(exportedNote)
|
||||
|
||||
assertThat(entity)
|
||||
.hasFieldOrPropertyWithValue("title", "title")
|
||||
.hasFieldOrPropertyWithValue("markdown", "md")
|
||||
.hasFieldOrPropertyWithValue("updatedAt", exportedNote.updatedAt)
|
||||
.hasFieldOrPropertyWithValue("deleted", true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package be.simplenotes.persistance.converters
|
||||
|
||||
import be.simplenotes.domain.model.PersistedUser
|
||||
import be.simplenotes.domain.model.User
|
||||
import be.simplenotes.persistance.users.UserEntity
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.mapstruct.factory.Mappers
|
||||
|
||||
internal class UserConverterTest {
|
||||
|
||||
@Test
|
||||
fun `convert UserEntity to User`() {
|
||||
val converter = Mappers.getMapper(UserConverter::class.java)
|
||||
val entity = UserEntity {
|
||||
username = "test"
|
||||
password = "test2"
|
||||
}.apply {
|
||||
this["id"] = 2
|
||||
}
|
||||
val user = converter.toUser(entity)
|
||||
assertThat(user).isEqualTo(User("test", "test2"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convert UserEntity to PersistedUser`() {
|
||||
val converter = Mappers.getMapper(UserConverter::class.java)
|
||||
val entity = UserEntity {
|
||||
username = "test"
|
||||
password = "test2"
|
||||
}.apply {
|
||||
this["id"] = 2
|
||||
}
|
||||
val user = converter.toPersistedUser(entity)
|
||||
assertThat(user).isEqualTo(PersistedUser("test", "test2", 2))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convert User to UserEntity`() {
|
||||
val converter = Mappers.getMapper(UserConverter::class.java)
|
||||
val user = User("test", "test2")
|
||||
val entity = converter.toEntity(user)
|
||||
|
||||
assertThat(entity)
|
||||
.hasFieldOrPropertyWithValue("username", "test")
|
||||
.hasFieldOrPropertyWithValue("password", "test2")
|
||||
}
|
||||
}
|
||||
@@ -1,331 +0,0 @@
|
||||
package be.simplenotes.persistance.notes
|
||||
|
||||
import be.simplenotes.domain.model.*
|
||||
import be.simplenotes.domain.usecases.repositories.NoteRepository
|
||||
import be.simplenotes.domain.usecases.repositories.UserRepository
|
||||
import be.simplenotes.persistance.DbMigrations
|
||||
import be.simplenotes.persistance.converters.NoteConverter
|
||||
import be.simplenotes.persistance.migrationModule
|
||||
import be.simplenotes.persistance.persistanceModule
|
||||
import be.simplenotes.shared.config.DataSourceConfig
|
||||
import me.liuwj.ktorm.database.Database
|
||||
import me.liuwj.ktorm.dsl.eq
|
||||
import me.liuwj.ktorm.entity.filter
|
||||
import me.liuwj.ktorm.entity.find
|
||||
import me.liuwj.ktorm.entity.mapColumns
|
||||
import me.liuwj.ktorm.entity.toList
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.flywaydb.core.Flyway
|
||||
import org.junit.jupiter.api.*
|
||||
import org.junit.jupiter.api.parallel.ResourceLock
|
||||
import org.koin.dsl.koinApplication
|
||||
import org.koin.dsl.module
|
||||
import org.mapstruct.factory.Mappers
|
||||
import java.sql.SQLIntegrityConstraintViolationException
|
||||
import java.util.*
|
||||
import javax.sql.DataSource
|
||||
|
||||
@ResourceLock("h2")
|
||||
internal class NoteRepositoryImplTest {
|
||||
private val testModule = module {
|
||||
single { dataSourceConfig() }
|
||||
}
|
||||
|
||||
private val koinApp = koinApplication {
|
||||
modules(persistanceModule, migrationModule, testModule)
|
||||
}
|
||||
|
||||
private fun dataSourceConfig() = DataSourceConfig(
|
||||
jdbcUrl = "jdbc:h2:mem:regular;DB_CLOSE_DELAY=-1;",
|
||||
driverClassName = "org.h2.Driver",
|
||||
username = "h2",
|
||||
password = "",
|
||||
maximumPoolSize = 2,
|
||||
connectionTimeout = 3000
|
||||
)
|
||||
|
||||
private val koin = koinApp.koin
|
||||
|
||||
@AfterAll
|
||||
fun afterAll() = koinApp.close()
|
||||
|
||||
private val migration = koin.get<DbMigrations>()
|
||||
private val dataSource = koin.get<DataSource>()
|
||||
private val noteRepo = koin.get<NoteRepository>()
|
||||
private val userRepo = koin.get<UserRepository>()
|
||||
private val db = koin.get<Database>()
|
||||
|
||||
private lateinit var user1: PersistedUser
|
||||
private lateinit var user2: PersistedUser
|
||||
|
||||
@BeforeEach
|
||||
fun beforeEach() {
|
||||
Flyway.configure()
|
||||
.dataSource(dataSource)
|
||||
.load()
|
||||
.clean()
|
||||
|
||||
migration.migrate()
|
||||
|
||||
user1 = userRepo.create(User("1", "1"))!!
|
||||
user2 = userRepo.create(User("2", "2"))!!
|
||||
}
|
||||
|
||||
private fun createNote(
|
||||
userId: Int,
|
||||
title: String,
|
||||
tags: List<String> = emptyList(),
|
||||
md: String = "md",
|
||||
html: String = "html",
|
||||
): PersistedNote = noteRepo.create(userId, Note(NoteMetadata(title, tags), md, html))
|
||||
|
||||
private fun PersistedNote.toPersistedMeta() = PersistedNoteMetadata(meta.title, meta.tags, updatedAt, uuid)
|
||||
|
||||
@Nested
|
||||
@DisplayName("create()")
|
||||
inner class Create {
|
||||
|
||||
@Test
|
||||
fun `create note for non existing user`() {
|
||||
val note = Note(NoteMetadata("title", emptyList()), "md", "html")
|
||||
|
||||
assertThatThrownBy {
|
||||
noteRepo.create(1000, note)
|
||||
}.isInstanceOf(SQLIntegrityConstraintViolationException::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create note for existing user`() {
|
||||
val note = Note(NoteMetadata("title", emptyList()), "md", "html")
|
||||
|
||||
assertThat(noteRepo.create(user1.id, note))
|
||||
.isEqualToIgnoringGivenFields(note, "uuid", "updatedAt", "public")
|
||||
.hasNoNullFieldsOrProperties()
|
||||
|
||||
assertThat(db.notes.toList())
|
||||
.hasSize(1)
|
||||
.first()
|
||||
.isEqualToIgnoringGivenFields(note, "uuid", "updatedAt", "public")
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("findAll()")
|
||||
inner class FindAll {
|
||||
|
||||
@Test
|
||||
fun `find all notes`() {
|
||||
val notes1 = listOf(
|
||||
createNote(user1.id, "1", listOf("a", "b")),
|
||||
createNote(user1.id, "2"),
|
||||
createNote(user1.id, "3", listOf("c"))
|
||||
)
|
||||
|
||||
val notes2 = listOf(
|
||||
createNote(user2.id, "4")
|
||||
)
|
||||
|
||||
assertThat(noteRepo.findAll(user1.id))
|
||||
.hasSize(3)
|
||||
.usingElementComparatorIgnoringFields("updatedAt")
|
||||
.containsExactlyInAnyOrderElementsOf(
|
||||
notes1.map { it.toPersistedMeta() }
|
||||
)
|
||||
|
||||
assertThat(noteRepo.findAll(user2.id))
|
||||
.hasSize(1)
|
||||
.usingElementComparatorIgnoringFields("updatedAt")
|
||||
.containsExactlyInAnyOrderElementsOf(
|
||||
notes2.map { it.toPersistedMeta() }
|
||||
)
|
||||
|
||||
assertThat(noteRepo.findAll(1000)).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun pagination() {
|
||||
(50 downTo 1).forEach {
|
||||
createNote(user1.id, "$it")
|
||||
}
|
||||
|
||||
assertThat(noteRepo.findAll(user1.id, limit = 20, offset = 0))
|
||||
.hasSize(20)
|
||||
.allMatch { it.title.toInt() in 1..20 }
|
||||
|
||||
assertThat(noteRepo.findAll(user1.id, limit = 20, offset = 20))
|
||||
.hasSize(20)
|
||||
.allMatch { it.title.toInt() in 21..40 }
|
||||
|
||||
assertThat(noteRepo.findAll(user1.id, limit = 20, offset = 40))
|
||||
.hasSize(10)
|
||||
.allMatch { it.title.toInt() in 41..50 }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `find all notes with tag`() {
|
||||
createNote(user1.id, "1", listOf("a", "b"))
|
||||
createNote(user1.id, "2")
|
||||
createNote(user1.id, "3", listOf("c"))
|
||||
createNote(user1.id, "4", listOf("c"))
|
||||
createNote(user2.id, "5", listOf("c"))
|
||||
|
||||
assertThat(noteRepo.findAll(user1.id, tag = "a"))
|
||||
.hasSize(1)
|
||||
.first()
|
||||
.hasFieldOrPropertyWithValue("title", "1")
|
||||
|
||||
assertThat(noteRepo.findAll(user1.id, tag = "c"))
|
||||
.hasSize(2)
|
||||
|
||||
assertThat(noteRepo.findAll(user2.id, tag = "c"))
|
||||
.hasSize(1)
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("find() | exists()")
|
||||
inner class FindExists {
|
||||
@Test
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun `find an existing note`() {
|
||||
createNote(user1.id, "1", listOf("a", "b"))
|
||||
|
||||
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||
|
||||
val note = db.notes.find { it.title eq "1" }!!
|
||||
.let { entity ->
|
||||
val tags = db.tags.filter { it.noteUuid eq entity.uuid }.mapColumns { it.name } as List<String>
|
||||
converter.toPersistedNote(entity, tags)
|
||||
}
|
||||
|
||||
assertThat(noteRepo.find(user1.id, note.uuid))
|
||||
.isEqualTo(note)
|
||||
|
||||
assertThat(noteRepo.exists(user1.id, note.uuid))
|
||||
.isTrue
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `find an existing note from the wrong user`() {
|
||||
val note = createNote(user1.id, "1", listOf("a", "b"))
|
||||
assertThat(noteRepo.find(user2.id, note.uuid)).isNull()
|
||||
assertThat(noteRepo.exists(user2.id, note.uuid)).isFalse
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `find a non existing note`() {
|
||||
createNote(user1.id, "1", listOf("a", "b"))
|
||||
val uuid = UUID.randomUUID()
|
||||
assertThat(noteRepo.find(user1.id, uuid)).isNull()
|
||||
assertThat(noteRepo.exists(user2.id, uuid)).isFalse
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("delete()")
|
||||
inner class Delete {
|
||||
|
||||
@Test
|
||||
fun `delete an existing note for a user should succeed and then fail`() {
|
||||
val note = createNote(user1.id, "1", listOf("a", "b"))
|
||||
assertThat(noteRepo.delete(user1.id, note.uuid))
|
||||
.isTrue
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `delete an existing note for the wrong user`() {
|
||||
val note = createNote(user1.id, "1", listOf("a", "b"))
|
||||
assertThat(noteRepo.delete(1000, note.uuid))
|
||||
.isFalse
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("getTags()")
|
||||
inner class Tags {
|
||||
|
||||
@Test
|
||||
fun getTags() {
|
||||
val notes1 = listOf(
|
||||
createNote(user1.id, "1", listOf("a", "b")),
|
||||
createNote(user1.id, "2"),
|
||||
createNote(user1.id, "3", listOf("c", "a"))
|
||||
)
|
||||
|
||||
val notes2 = listOf(
|
||||
createNote(user2.id, "4", listOf("a"))
|
||||
)
|
||||
|
||||
val user1Tags = notes1.flatMap { it.meta.tags }.toSet()
|
||||
assertThat(noteRepo.getTags(user1.id))
|
||||
.containsExactlyInAnyOrderElementsOf(user1Tags)
|
||||
|
||||
val user2Tags = notes2.flatMap { it.meta.tags }.toSet()
|
||||
assertThat(noteRepo.getTags(user2.id))
|
||||
.containsExactlyInAnyOrderElementsOf(user2Tags)
|
||||
|
||||
assertThat(noteRepo.getTags(1000))
|
||||
.isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("update()")
|
||||
inner class Update {
|
||||
|
||||
@Test
|
||||
fun getTags() {
|
||||
val note1 = createNote(user1.id, "1", listOf("a", "b"))
|
||||
val newNote1 = Note(meta = note1.meta, markdown = "new", "new")
|
||||
assertThat(noteRepo.update(user1.id, note1.uuid, newNote1))
|
||||
.isNotNull
|
||||
|
||||
assertThat(noteRepo.find(user1.id, note1.uuid))
|
||||
.isEqualToComparingOnlyGivenFields(newNote1, "meta", "markdown", "html")
|
||||
|
||||
val note2 = createNote(user1.id, "2")
|
||||
val newNote2 = Note(meta = note1.meta.copy(tags = listOf("a")), markdown = "new", "new")
|
||||
assertThat(noteRepo.update(user1.id, note2.uuid, newNote2))
|
||||
.isNotNull
|
||||
|
||||
assertThat(noteRepo.find(user1.id, note2.uuid))
|
||||
.isEqualToComparingOnlyGivenFields(newNote2, "meta", "markdown", "html")
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class Trash {
|
||||
|
||||
@Test
|
||||
fun `trashed noted should be restored`() {
|
||||
val note1 = createNote(user1.id, "1", listOf("a", "b"))
|
||||
|
||||
assertThat(noteRepo.delete(user1.id, note1.uuid, permanent = false))
|
||||
.isTrue
|
||||
|
||||
val isDeleted = db.notes
|
||||
.find { it.uuid eq note1.uuid }
|
||||
?.deleted
|
||||
|
||||
assertThat(isDeleted).`as`("Check that Notes.deleted is true").isTrue
|
||||
|
||||
assertThat(noteRepo.restore(user1.id, note1.uuid)).isTrue
|
||||
|
||||
val isDeleted2 = db.notes
|
||||
.find { it.uuid eq note1.uuid }
|
||||
?.deleted
|
||||
|
||||
assertThat(isDeleted2).`as`("Check that Notes.deleted is false after restore()").isFalse
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `permanent delete`() {
|
||||
val note1 = createNote(user1.id, "1", listOf("a", "b"))
|
||||
assertThat(noteRepo.delete(user1.id, note1.uuid, permanent = true))
|
||||
.isTrue
|
||||
|
||||
assertThat(noteRepo.restore(user1.id, note1.uuid)).isFalse
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
package be.simplenotes.persistance.users
|
||||
|
||||
import be.simplenotes.domain.model.User
|
||||
import be.simplenotes.domain.usecases.repositories.UserRepository
|
||||
import be.simplenotes.persistance.DbMigrations
|
||||
import be.simplenotes.persistance.migrationModule
|
||||
import be.simplenotes.persistance.persistanceModule
|
||||
import be.simplenotes.shared.config.DataSourceConfig
|
||||
import me.liuwj.ktorm.database.*
|
||||
import me.liuwj.ktorm.dsl.*
|
||||
import me.liuwj.ktorm.entity.*
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.flywaydb.core.Flyway
|
||||
import org.junit.jupiter.api.AfterAll
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.parallel.ResourceLock
|
||||
import org.koin.dsl.koinApplication
|
||||
import org.koin.dsl.module
|
||||
import javax.sql.DataSource
|
||||
|
||||
@ResourceLock("h2")
|
||||
internal class UserRepositoryImplTest {
|
||||
|
||||
// region setup
|
||||
private val testModule = module {
|
||||
single { dataSourceConfig() }
|
||||
}
|
||||
|
||||
private val koinApp = koinApplication {
|
||||
modules(persistanceModule, migrationModule, testModule)
|
||||
}
|
||||
|
||||
private fun dataSourceConfig() = DataSourceConfig(
|
||||
jdbcUrl = "jdbc:h2:mem:regular;DB_CLOSE_DELAY=-1;",
|
||||
driverClassName = "org.h2.Driver",
|
||||
username = "h2",
|
||||
password = "",
|
||||
maximumPoolSize = 2,
|
||||
connectionTimeout = 3000
|
||||
)
|
||||
|
||||
private val koin = koinApp.koin
|
||||
|
||||
@AfterAll
|
||||
fun afterAll() = koinApp.close()
|
||||
|
||||
private val migration = koin.get<DbMigrations>()
|
||||
private val dataSource = koin.get<DataSource>()
|
||||
private val userRepo = koin.get<UserRepository>()
|
||||
private val db = koin.get<Database>()
|
||||
|
||||
@BeforeEach
|
||||
fun beforeEach() {
|
||||
Flyway.configure()
|
||||
.dataSource(dataSource)
|
||||
.load()
|
||||
.clean()
|
||||
migration.migrate()
|
||||
}
|
||||
// endregion setup
|
||||
|
||||
@Test
|
||||
fun `insert user`() {
|
||||
val user = User("username", "test")
|
||||
assertThat(userRepo.create(user))
|
||||
.isNotNull
|
||||
.hasFieldOrPropertyWithValue("username", user.username)
|
||||
.hasFieldOrPropertyWithValue("password", user.password)
|
||||
|
||||
assertThat(db.users.find { it.username eq user.username }).isNotNull
|
||||
assertThat(db.users.toList()).hasSize(1)
|
||||
assertThat(userRepo.create(user)).isNull()
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class Query {
|
||||
|
||||
@Test
|
||||
fun `query existing user`() {
|
||||
val user = User("username", "test")
|
||||
userRepo.create(user)
|
||||
|
||||
val foundUserMaybe = userRepo.find(user.username)
|
||||
assertThat(foundUserMaybe).isNotNull
|
||||
val foundUser = foundUserMaybe!!
|
||||
assertThat(foundUser).isEqualToIgnoringGivenFields(user, "id")
|
||||
assertThat(userRepo.exists(user.username)).isTrue
|
||||
assertThat(userRepo.find(foundUser.id)).isEqualTo(foundUser)
|
||||
assertThat(userRepo.exists(foundUser.id)).isTrue
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `query non existing user`() {
|
||||
assertThat(userRepo.find("I don't exist")).isNull()
|
||||
assertThat(userRepo.find(1)).isNull()
|
||||
assertThat(userRepo.exists(1)).isFalse
|
||||
assertThat(userRepo.exists("I don't exist")).isFalse
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class Delete {
|
||||
|
||||
@Test
|
||||
fun `delete existing user`() {
|
||||
val user = User("username", "test")
|
||||
userRepo.create(user)
|
||||
|
||||
val foundUser = userRepo.find(user.username)!!
|
||||
assertThat(userRepo.delete(foundUser.id)).isTrue
|
||||
assertThat(db.users.toList()).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `delete non existing user`() {
|
||||
assertThat(userRepo.delete(1)).isFalse
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user