4 Commits

Author SHA1 Message Date
hubert 4ff97044f0 Change public/private button css 2020-10-23 08:26:03 +02:00
hubert ead1932d48 Fix some css 2020-10-23 07:16:07 +02:00
hubert 4a7dcec363 Use Json lenses 2020-10-23 06:22:44 +02:00
hubert cb76a3253d Use mapstruct 2020-10-21 22:55:36 +02:00
19 changed files with 471 additions and 135 deletions
+21 -24
View File
@@ -1,6 +1,6 @@
package be.simplenotes.app.api
import be.simplenotes.app.extensions.json
import be.simplenotes.app.extensions.auto
import be.simplenotes.app.utils.parseSearchTerms
import be.simplenotes.domain.model.PersistedNote
import be.simplenotes.domain.model.PersistedNoteMetadata
@@ -8,48 +8,39 @@ import be.simplenotes.domain.security.JwtPayload
import be.simplenotes.domain.usecases.NoteService
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.json.Json
import org.http4k.core.Request
import org.http4k.core.Response
import org.http4k.core.Status.Companion.BAD_REQUEST
import org.http4k.core.Status.Companion.NOT_FOUND
import org.http4k.core.Status.Companion.OK
import org.http4k.routing.path
import org.http4k.lens.Path
import org.http4k.lens.uuid
import java.util.*
class ApiNoteController(private val noteService: NoteService, private val json: Json) {
fun createNote(request: Request, jwtPayload: JwtPayload): Response {
val content = json.decodeFromString(NoteContent.serializer(), request.bodyString()).content
val content = noteContentLens(request)
return noteService.create(jwtPayload.userId, content).fold(
{
Response(BAD_REQUEST)
},
{
Response(OK).json(json.encodeToString(UuidContent.serializer(), UuidContent(it.uuid)))
}
{ Response(BAD_REQUEST) },
{ uuidContentLens(UuidContent(it.uuid), Response(OK)) }
)
}
fun notes(request: Request, jwtPayload: JwtPayload): Response {
val notes = noteService.paginatedNotes(jwtPayload.userId, page = 1).notes
val json = json.encodeToString(ListSerializer(PersistedNoteMetadata.serializer()), notes)
return Response(OK).json(json)
return persistedNotesMetadataLens(notes, Response(OK))
}
fun note(request: Request, jwtPayload: JwtPayload): Response {
val uuid = request.path("uuid")!!
return noteService.find(jwtPayload.userId, UUID.fromString(uuid))
?.let { Response(OK).json(json.encodeToString(PersistedNote.serializer(), it)) }
fun note(request: Request, jwtPayload: JwtPayload): Response =
noteService.find(jwtPayload.userId, uuidLens(request))
?.let { persistedNoteLens(it, Response(OK)) }
?: Response(NOT_FOUND)
}
fun update(request: Request, jwtPayload: JwtPayload): Response {
val uuid = UUID.fromString(request.path("uuid")!!)
val content = json.decodeFromString(NoteContent.serializer(), request.bodyString()).content
return noteService.update(jwtPayload.userId, uuid, content).fold({
val content = noteContentLens(request)
return noteService.update(jwtPayload.userId, uuidLens(request), content).fold({
Response(BAD_REQUEST)
}, {
if (it == null) Response(NOT_FOUND)
@@ -58,13 +49,19 @@ class ApiNoteController(private val noteService: NoteService, private val json:
}
fun search(request: Request, jwtPayload: JwtPayload): Response {
val query = json.decodeFromString(SearchContent.serializer(), request.bodyString()).query
val query = searchContentLens(request)
val terms = parseSearchTerms(query)
val notes = noteService.search(jwtPayload.userId, terms)
val json = json.encodeToString(ListSerializer(PersistedNoteMetadata.serializer()), notes)
return Response(OK).json(json)
return persistedNotesMetadataLens(notes, Response(OK))
}
private val uuidContentLens = json.auto<UuidContent>().toLens()
private val noteContentLens = json.auto<NoteContent>().map { it.content }.toLens()
private val searchContentLens = json.auto<SearchContent>().map { it.query }.toLens()
private val persistedNotesMetadataLens = json.auto<List<PersistedNoteMetadata>>().toLens()
private val persistedNoteLens = json.auto<PersistedNote>().toLens()
private val uuidLens = Path.uuid().of("uuid")
}
@Serializable
+11 -11
View File
@@ -1,25 +1,25 @@
package be.simplenotes.app.api
import be.simplenotes.app.extensions.json
import be.simplenotes.app.extensions.auto
import be.simplenotes.domain.usecases.UserService
import be.simplenotes.domain.usecases.users.login.LoginForm
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import org.http4k.core.Request
import org.http4k.core.Response
import org.http4k.core.Status
import org.http4k.core.Status.Companion.BAD_REQUEST
import org.http4k.core.Status.Companion.OK
class ApiUserController(private val userService: UserService, private val json: Json) {
private val tokenLens = json.auto<Token>().toLens()
private val loginFormLens = json.auto<LoginForm>().toLens()
fun login(request: Request): Response {
val form = json.decodeFromString(LoginForm.serializer(), request.bodyString())
val result = userService.login(form)
return result.fold({
Response(Status.BAD_REQUEST)
}, {
Response(Status.OK).json(json.encodeToString(Token.serializer(), Token(it)))
})
}
fun login(request: Request) = userService
.login(loginFormLens(request))
.fold(
{ Response(BAD_REQUEST) },
{ tokenLens(Token(it), Response(OK)) }
)
}
@Serializable
@@ -1,17 +1,35 @@
package be.simplenotes.app.extensions
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.http4k.asString
import org.http4k.core.Body
import org.http4k.core.ContentType
import org.http4k.core.Request
import org.http4k.core.Response
import org.http4k.core.Status.Companion.FOUND
import org.http4k.core.Status.Companion.MOVED_PERMANENTLY
import org.http4k.lens.*
fun Response.html(html: String) = body(html)
.header("Content-Type", "text/html; charset=utf-8")
.header("Cache-Control", "no-cache")
fun Response.json(json: String) = body(json).header("Content-Type", "application/json")
fun Response.Companion.redirect(url: String, permanent: Boolean = false) =
Response(if (permanent) MOVED_PERMANENTLY else FOUND).header("Location", url)
fun Request.isSecure() = header("X-Forwarded-Proto")?.contains("https") ?: false
val bodyLens = httpBodyRoot(
listOf(Meta(true, "body", ParamMeta.ObjectParam, "body")),
ContentType.APPLICATION_JSON.withNoDirectives(), ContentNegotiation.StrictNoDirective
).map(
{ it.payload.asString() },
{ Body(it) }
)
inline fun <reified T> Json.auto(): BiDiBodyLensSpec<T> = bodyLens.map(
{ decodeFromString(it) },
{ encodeToString(it) }
)
+1 -1
View File
@@ -13,7 +13,7 @@ class BaseView(staticFileResolver: StaticFileResolver) : View(staticFileResolver
) {
section("text-center my-2 p-2") {
h1("text-5xl casual") {
span("text-teal-300") { +"Simplenotes " }
span("text-teal-300") { +"SimpleNotes " }
+"- access your notes anywhere"
}
}
+20 -23
View File
@@ -143,7 +143,6 @@ class NoteView(staticFileResolver: StaticFileResolver) : View(staticFileResolver
}
if (!shared) {
noteActionForm(note)
publicPrivateForm(note)
if (note.public) {
p("my-4") {
@@ -166,12 +165,29 @@ class NoteView(staticFileResolver: StaticFileResolver) : View(staticFileResolver
}
private fun DIV.noteActionForm(note: PersistedNote) {
span("flex space-x-2 justify-end mb-4") {
form(method = FormMethod.post, classes = "inline flex space-x-2 justify-end mb-4") {
a(
href = "/notes/${note.uuid}/edit",
classes = "btn btn-teal"
classes = "btn btn-green"
) { +"Edit" }
form(method = FormMethod.post, classes = "inline") {
span {
button(
type = ButtonType.submit,
name = if (note.public) "private" else "public",
classes = "font-semibold border-b-4 ${if (note.public) "border-teal-200" else "border-green-500"}" +
" p-2 rounded-l bg-teal-200 text-gray-800"
) {
+"Private"
}
button(
type = ButtonType.submit,
name = if (note.public) "private" else "public",
classes = "font-semibold border-b-4 ${if (!note.public) "border-teal-200" else "border-green-500"}" +
" p-2 rounded-r bg-teal-200 text-gray-800"
) {
+"Public"
}
}
button(
type = ButtonType.submit,
name = "delete",
@@ -179,23 +195,4 @@ class NoteView(staticFileResolver: StaticFileResolver) : View(staticFileResolver
) { +"Delete" }
}
}
}
private fun DIV.publicPrivateForm(note: PersistedNote) {
span("flex space-x-2 justify-end mb-4") {
form(method = FormMethod.post, classes = "ml-auto ") {
button(
type = ButtonType.submit,
name = if (note.public) "private" else "public",
classes = "btn btn-teal"
) {
if (note.public)
+"This note is public, do you want to make it private ?"
else
+"This note is private, do you want to make it public ?"
}
}
}
}
}
+6 -5
View File
@@ -26,22 +26,23 @@ class SettingView(staticFileResolver: StaticFileResolver) : View(staticFileResol
}
}
section("m-4 p-4 bg-gray-800 rounded flex justify-around") {
section("m-4 p-2 bg-gray-800 rounded flex flex-wrap justify-around items-end") {
form(method = FormMethod.post, action = "/export") {
form(classes = "m-2", method = FormMethod.post, action = "/export") {
button(name = "display",
classes = "inline btn btn-teal block",
type = submit) { +"Display my data" }
}
form(method = FormMethod.post, action = "/export") {
form(classes = "m-2", method = FormMethod.post, action = "/export") {
listOf("json", "zip").forEach { format ->
div {
listOf("json", "zip").forEach { format ->
radioInput(name = "format") {
id = format
attributes["value"] = format
if(format == "json") attributes["checked"] = ""
if (format == "json") attributes["checked"] = ""
else attributes["class"] = "ml-4"
}
label(classes = "ml-2") {
attributes["for"] = format
@@ -25,8 +25,8 @@ fun FlowContent.deletedNoteTable(notes: List<PersistedNoteMetadata>) = div("over
td("text-center") { +updatedAt.toTimeAgo() }
td { tags(tags) }
td("text-center") {
form(classes = "inline", method = post, action = "/notes/deleted/$uuid") {
button(classes = "btn btn-red", type = submit, name = "delete") {
form(method = post, action = "/notes/deleted/$uuid") {
button(classes = "btn btn-red mb-2", type = submit, name = "delete") {
+"Delete permanently"
}
button(classes = "ml-2 btn btn-green", type = submit, name = "restore") {
+4
View File
@@ -11,6 +11,10 @@
<artifactId>persistance</artifactId>
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
</dependency>
<dependency>
<groupId>be.simplenotes</groupId>
<artifactId>domain</artifactId>
@@ -2,6 +2,10 @@ 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
@@ -30,8 +34,10 @@ val migrationModule = module {
}
val persistanceModule = module {
single<UserRepository> { UserRepositoryImpl(get()) }
single<NoteRepository> { NoteRepositoryImpl(get()) }
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()
@@ -0,0 +1,80 @@
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>()
@@ -0,0 +1,17 @@
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>()
@@ -5,6 +5,7 @@ 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.*
@@ -12,7 +13,7 @@ import java.time.LocalDateTime
import java.util.*
import kotlin.collections.HashMap
internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
internal class NoteRepositoryImpl(private val db: Database, private val converter: NoteConverter) : NoteRepository {
@Throws(IllegalArgumentException::class)
override fun findAll(
@@ -20,7 +21,7 @@ internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
limit: Int,
offset: Int,
tag: String?,
deleted: Boolean
deleted: Boolean,
): List<PersistedNoteMetadata> {
require(limit > 0) { "limit should be positive" }
require(offset >= 0) { "offset should not be negative" }
@@ -49,7 +50,7 @@ internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
return notes.map { note ->
val tags = tagsByUuid[note.uuid] ?: emptyList()
note.toPersistedMetadata(tags)
converter.toPersistedNoteMetadata(note, tags)
}
}
@@ -59,9 +60,7 @@ internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
override fun create(userId: Int, note: Note): PersistedNote {
val uuid = UUID.randomUUID()
val entity = note.toEntity(uuid, userId).apply {
this.updatedAt = LocalDateTime.now()
}
val entity = converter.toEntity(note, uuid, userId, LocalDateTime.now())
db.notes.add(entity)
db.batchInsert(Tags) {
note.meta.tags.forEach { tagName ->
@@ -71,8 +70,7 @@ internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
}
}
}
return entity.toPersistedNote(note.meta.tags)
return converter.toPersistedNote(entity, note.meta.tags)
}
override fun find(userId: Int, uuid: UUID): PersistedNote? {
@@ -87,7 +85,7 @@ internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
.where { Tags.noteUuid eq uuid }
.map { it[Tags.name]!! }
return note.toPersistedNote(tags)
return converter.toPersistedNote(note, tags)
}
override fun update(userId: Int, uuid: UUID, note: Note): PersistedNote? {
@@ -115,14 +113,7 @@ internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
}
}
return PersistedNote(
meta = note.meta,
markdown = note.markdown,
html = note.html,
updatedAt = now,
uuid = uuid,
public = false, // TODO
)
return converter.toPersistedNote(note, now, uuid)
}
override fun delete(userId: Int, uuid: UUID, permanent: Boolean): Boolean {
@@ -168,14 +159,8 @@ internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
val tagsByUuid = notes.tagsByUuid()
return notes.map { note ->
ExportedNote(
title = note.title,
tags = tagsByUuid[note.uuid] ?: emptyList(),
markdown = note.markdown,
html = note.html,
updatedAt = note.updatedAt,
trash = note.deleted,
)
val tags = tagsByUuid[note.uuid] ?: emptyList()
converter.toExportedNote(note, tags)
}
}
@@ -189,7 +174,7 @@ internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
return notes.map { note ->
val tags = tagsByUuid[note.uuid] ?: emptyList()
note.toPersistedNote(tags)
converter.toPersistedNote(note, tags)
}
}
@@ -216,7 +201,7 @@ internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
.where { Tags.noteUuid eq uuid }
.map { it[Tags.name]!! }
return note.toPersistedNote(tags)
return converter.toPersistedNote(note, tags)
}
private fun List<NoteEntity>.tagsByUuid(): Map<UUID, List<String>> {
+3 -24
View File
@@ -2,15 +2,12 @@
package be.simplenotes.persistance.notes
import be.simplenotes.domain.model.Note
import be.simplenotes.domain.model.NoteMetadata
import be.simplenotes.domain.model.PersistedNote
import be.simplenotes.domain.model.PersistedNoteMetadata
import be.simplenotes.persistance.extensions.uuidBinary
import be.simplenotes.persistance.users.UserEntity
import be.simplenotes.persistance.users.Users
import me.liuwj.ktorm.database.*
import me.liuwj.ktorm.entity.*
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.*
@@ -46,21 +43,3 @@ internal interface NoteEntity : Entity<NoteEntity> {
var user: UserEntity
}
internal fun NoteEntity.toPersistedMetadata(tags: List<String>) = PersistedNoteMetadata(title, tags, updatedAt, uuid)
internal fun NoteEntity.toPersistedNote(tags: List<String>) =
PersistedNote(NoteMetadata(title, tags), markdown, html, updatedAt, uuid, public)
internal fun Note.toEntity(uuid: UUID, userId: Int): NoteEntity {
val note = this
return 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
}
}
@@ -3,13 +3,14 @@ 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) : UserRepository {
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) {
@@ -22,8 +23,13 @@ internal class UserRepositoryImpl(private val db: Database) : UserRepository {
}
}
override fun find(username: String) = db.users.find { it.username eq username }?.toPersistedUser()
override fun find(id: Int) = db.users.find { it.id eq id }?.toPersistedUser()
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
+7 -7
View File
@@ -1,9 +1,11 @@
package be.simplenotes.persistance.users
import be.simplenotes.domain.model.PersistedUser
import me.liuwj.ktorm.database.*
import me.liuwj.ktorm.entity.*
import me.liuwj.ktorm.schema.*
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)
@@ -18,11 +20,9 @@ internal open class Users(alias: String?) : Table<UserEntity>("Users", alias) {
internal interface UserEntity : Entity<UserEntity> {
companion object : Entity.Factory<UserEntity>()
val id: Int
var id: Int
var username: String
var password: String
}
internal fun UserEntity.toPersistedUser() = PersistedUser(username, password, id)
internal val Database.users get() = this.sequenceOf(Users, withReferences = false)
@@ -0,0 +1,168 @@
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)
}
}
}
@@ -0,0 +1,48 @@
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")
}
}
@@ -4,12 +4,16 @@ 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.*
import me.liuwj.ktorm.dsl.*
import me.liuwj.ktorm.entity.*
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
@@ -17,6 +21,7 @@ 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
@@ -186,10 +191,12 @@ internal class NoteRepositoryImplTest {
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>
entity.toPersistedNote(tags)
converter.toPersistedNote(entity, tags)
}
assertThat(noteRepo.find(user1.id, note.uuid))
+23
View File
@@ -29,6 +29,8 @@
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<kotlin.compiler.jvmTarget>${java.version}</kotlin.compiler.jvmTarget>
<org.mapstruct.version>1.4.1.Final</org.mapstruct.version>
</properties>
<dependencies>
@@ -99,6 +101,21 @@
<groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>kapt</id>
<goals>
<goal>kapt</goal>
</goals>
<configuration>
<annotationProcessorPaths>
<annotationProcessorPath>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</annotationProcessorPath>
</annotationProcessorPaths>
</configuration>
</execution>
<execution>
<id>compile</id>
<phase>process-sources</phase>
@@ -181,6 +198,12 @@
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<!-- region tests -->
<dependency>
<groupId>org.junit.jupiter</groupId>