Lots of things
This commit is contained in:
parent
f134a83604
commit
3306b16f91
@ -5,29 +5,21 @@ create table Users
|
|||||||
password varchar(255) not null,
|
password varchar(255) not null,
|
||||||
|
|
||||||
constraint username unique (username)
|
constraint username unique (username)
|
||||||
);
|
) character set 'utf8mb4'
|
||||||
|
collate 'utf8mb4_general_ci';
|
||||||
|
|
||||||
create table Notes
|
create table Notes
|
||||||
(
|
(
|
||||||
uuid binary(16) not null primary key,
|
uuid binary(16) not null primary key,
|
||||||
title varchar(50) not null,
|
title varchar(50) not null,
|
||||||
|
content text not null,
|
||||||
user_id int not null,
|
user_id int not null,
|
||||||
updated_at datetime null,
|
updated_at datetime null,
|
||||||
|
|
||||||
constraint Notes_fk_user foreign key (user_id) references Users (id) on delete cascade
|
constraint Notes_fk_user foreign key (user_id) references Users (id) on delete cascade
|
||||||
);
|
|
||||||
|
|
||||||
create table Chapters
|
) character set 'utf8mb4'
|
||||||
(
|
collate 'utf8mb4_general_ci';
|
||||||
id int auto_increment primary key,
|
|
||||||
number int not null,
|
|
||||||
title varchar(50) not null,
|
|
||||||
content text not null,
|
|
||||||
note_uuid binary(16) not null,
|
|
||||||
constraint Chapters_fk_note foreign key (note_uuid) references Notes (uuid) on delete cascade
|
|
||||||
);
|
|
||||||
|
|
||||||
create index note_uuid on Chapters (note_uuid);
|
|
||||||
|
|
||||||
create index user_id on Notes (user_id);
|
create index user_id on Notes (user_id);
|
||||||
|
|
||||||
@ -37,6 +29,7 @@ create table Tags
|
|||||||
name varchar(50) not null,
|
name varchar(50) not null,
|
||||||
note_uuid binary(16) not null,
|
note_uuid binary(16) not null,
|
||||||
constraint Tags_fk_note foreign key (note_uuid) references Notes (uuid) on delete cascade
|
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);
|
create index note_uuid on Tags (note_uuid);
|
||||||
@ -1,24 +1,17 @@
|
|||||||
package be.vandewalleh
|
package be.vandewalleh
|
||||||
|
|
||||||
import be.vandewalleh.features.Config
|
import be.vandewalleh.features.Config
|
||||||
import be.vandewalleh.features.configurationModule
|
|
||||||
import be.vandewalleh.features.loadFeatures
|
import be.vandewalleh.features.loadFeatures
|
||||||
import be.vandewalleh.migrations.Migration
|
import be.vandewalleh.migrations.Migration
|
||||||
import be.vandewalleh.routing.registerRoutes
|
import be.vandewalleh.routing.registerRoutes
|
||||||
import be.vandewalleh.services.serviceModule
|
|
||||||
import io.ktor.application.*
|
import io.ktor.application.*
|
||||||
import io.ktor.routing.*
|
import io.ktor.routing.*
|
||||||
import io.ktor.server.engine.*
|
import io.ktor.server.engine.*
|
||||||
import io.ktor.server.netty.*
|
import io.ktor.server.netty.*
|
||||||
import me.liuwj.ktorm.database.*
|
|
||||||
import org.kodein.di.Kodein
|
import org.kodein.di.Kodein
|
||||||
import org.kodein.di.description
|
import org.kodein.di.description
|
||||||
import org.kodein.di.generic.bind
|
|
||||||
import org.kodein.di.generic.instance
|
import org.kodein.di.generic.instance
|
||||||
import org.kodein.di.generic.singleton
|
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import javax.sql.DataSource
|
|
||||||
|
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
package be.vandewalleh.entities
|
|
||||||
|
|
||||||
import me.liuwj.ktorm.entity.*
|
|
||||||
|
|
||||||
interface Chapter : Entity<Chapter> {
|
|
||||||
companion object : Entity.Factory<Chapter>()
|
|
||||||
|
|
||||||
val id: Int
|
|
||||||
var number: Int
|
|
||||||
var title: String
|
|
||||||
var content: String
|
|
||||||
var note: Note
|
|
||||||
}
|
|
||||||
@ -1,5 +1,6 @@
|
|||||||
package be.vandewalleh.entities
|
package be.vandewalleh.entities
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
import me.liuwj.ktorm.entity.*
|
import me.liuwj.ktorm.entity.*
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -9,6 +10,12 @@ interface Note : Entity<Note> {
|
|||||||
|
|
||||||
var uuid: UUID
|
var uuid: UUID
|
||||||
var title: String
|
var title: String
|
||||||
var user: User
|
var content: String
|
||||||
var updatedAt: LocalDateTime
|
var updatedAt: LocalDateTime
|
||||||
}
|
|
||||||
|
@get:JsonIgnore
|
||||||
|
var user: User
|
||||||
|
|
||||||
|
// Not part of the Notes table
|
||||||
|
var tags: List<String>
|
||||||
|
}
|
||||||
|
|||||||
@ -1,12 +1,9 @@
|
|||||||
package be.vandewalleh.extensions
|
package be.vandewalleh.extensions
|
||||||
|
|
||||||
import be.vandewalleh.auth.UserDbIdPrincipal
|
import be.vandewalleh.auth.UserDbIdPrincipal
|
||||||
import be.vandewalleh.services.FullNoteCreateDTO
|
|
||||||
import be.vandewalleh.services.FullNotePatchDTO
|
|
||||||
import io.ktor.application.*
|
import io.ktor.application.*
|
||||||
import io.ktor.auth.*
|
import io.ktor.auth.*
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import io.ktor.request.*
|
|
||||||
import io.ktor.response.*
|
import io.ktor.response.*
|
||||||
|
|
||||||
suspend fun ApplicationCall.respondStatus(status: HttpStatusCode) {
|
suspend fun ApplicationCall.respondStatus(status: HttpStatusCode) {
|
||||||
@ -17,9 +14,3 @@ suspend fun ApplicationCall.respondStatus(status: HttpStatusCode) {
|
|||||||
* @return the userId for the currently authenticated user
|
* @return the userId for the currently authenticated user
|
||||||
*/
|
*/
|
||||||
fun ApplicationCall.userId() = principal<UserDbIdPrincipal>()!!.id
|
fun ApplicationCall.userId() = principal<UserDbIdPrincipal>()!!.id
|
||||||
|
|
||||||
class NoteCreate(val title: String, val tags: List<String>)
|
|
||||||
|
|
||||||
suspend fun ApplicationCall.receiveNoteCreate(): FullNoteCreateDTO = receive()
|
|
||||||
|
|
||||||
suspend fun ApplicationCall.receiveNotePatch(): FullNotePatchDTO = receive()
|
|
||||||
|
|||||||
@ -3,10 +3,6 @@ package be.vandewalleh.extensions
|
|||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
fun Parameters.noteTitle(): String {
|
|
||||||
return this["noteTitle"]!!
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Parameters.noteUuid(): UUID {
|
fun Parameters.noteUuid(): UUID {
|
||||||
return UUID.fromString(this["noteUuid"])
|
return UUID.fromString(this["noteUuid"])
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
package be.vandewalleh.routing
|
package be.vandewalleh.routing
|
||||||
|
|
||||||
|
import be.vandewalleh.routing.notes.notes
|
||||||
|
import be.vandewalleh.routing.notes.tags
|
||||||
|
import be.vandewalleh.routing.notes.title
|
||||||
|
import be.vandewalleh.routing.user.auth
|
||||||
|
import be.vandewalleh.routing.user.user
|
||||||
import io.ktor.routing.*
|
import io.ktor.routing.*
|
||||||
import org.kodein.di.Kodein
|
import org.kodein.di.Kodein
|
||||||
|
|
||||||
@ -9,4 +14,4 @@ fun Routing.registerRoutes(kodein: Kodein) {
|
|||||||
notes(kodein)
|
notes(kodein)
|
||||||
title(kodein)
|
title(kodein)
|
||||||
tags(kodein)
|
tags(kodein)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,55 +0,0 @@
|
|||||||
package be.vandewalleh.routing
|
|
||||||
|
|
||||||
import be.vandewalleh.extensions.noteUuid
|
|
||||||
import be.vandewalleh.extensions.receiveNotePatch
|
|
||||||
import be.vandewalleh.extensions.respondStatus
|
|
||||||
import be.vandewalleh.extensions.userId
|
|
||||||
import be.vandewalleh.services.NotesService
|
|
||||||
import io.ktor.application.call
|
|
||||||
import io.ktor.auth.authenticate
|
|
||||||
import io.ktor.http.HttpStatusCode
|
|
||||||
import io.ktor.response.respond
|
|
||||||
import io.ktor.routing.*
|
|
||||||
import org.kodein.di.Kodein
|
|
||||||
import org.kodein.di.generic.instance
|
|
||||||
|
|
||||||
fun Routing.title(kodein: Kodein) {
|
|
||||||
val notesService by kodein.instance<NotesService>()
|
|
||||||
|
|
||||||
authenticate {
|
|
||||||
route("/notes/{noteUuid}") {
|
|
||||||
get {
|
|
||||||
val userId = call.userId()
|
|
||||||
val noteUuid = call.parameters.noteUuid()
|
|
||||||
|
|
||||||
val response =
|
|
||||||
notesService.getNote(userId, noteUuid) ?: return@get call.respondStatus(HttpStatusCode.NotFound)
|
|
||||||
call.respond(response)
|
|
||||||
}
|
|
||||||
|
|
||||||
patch {
|
|
||||||
val userId = call.userId()
|
|
||||||
val noteUuid = call.parameters.noteUuid()
|
|
||||||
|
|
||||||
val exists = notesService.noteExists(userId, noteUuid)
|
|
||||||
if (!exists) return@patch call.respondStatus(HttpStatusCode.NotFound)
|
|
||||||
|
|
||||||
val notePatch = call.receiveNotePatch().copy(uuid = noteUuid)
|
|
||||||
|
|
||||||
notesService.updateNote(notePatch)
|
|
||||||
call.respondStatus(HttpStatusCode.OK)
|
|
||||||
}
|
|
||||||
|
|
||||||
delete {
|
|
||||||
val userId = call.userId()
|
|
||||||
val noteUuid = call.parameters.noteUuid()
|
|
||||||
|
|
||||||
val exists = notesService.noteExists(userId, noteUuid)
|
|
||||||
if (!exists) return@delete call.respondStatus(HttpStatusCode.NotFound)
|
|
||||||
|
|
||||||
notesService.deleteNote(noteUuid)
|
|
||||||
call.respondStatus(HttpStatusCode.OK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
53
api/src/routing/notes/NoteController.kt
Normal file
53
api/src/routing/notes/NoteController.kt
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package be.vandewalleh.routing.notes
|
||||||
|
|
||||||
|
import be.vandewalleh.entities.Note
|
||||||
|
import be.vandewalleh.extensions.noteUuid
|
||||||
|
import be.vandewalleh.extensions.respondStatus
|
||||||
|
import be.vandewalleh.extensions.userId
|
||||||
|
import be.vandewalleh.services.NoteService
|
||||||
|
import io.ktor.application.call
|
||||||
|
import io.ktor.auth.authenticate
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.request.*
|
||||||
|
import io.ktor.response.respond
|
||||||
|
import io.ktor.routing.*
|
||||||
|
import org.kodein.di.Kodein
|
||||||
|
import org.kodein.di.generic.instance
|
||||||
|
|
||||||
|
fun Routing.title(kodein: Kodein) {
|
||||||
|
val noteService by kodein.instance<NoteService>()
|
||||||
|
|
||||||
|
authenticate {
|
||||||
|
route("/notes/{noteUuid}") {
|
||||||
|
get {
|
||||||
|
val userId = call.userId()
|
||||||
|
val noteUuid = call.parameters.noteUuid()
|
||||||
|
|
||||||
|
val response = noteService.find(userId, noteUuid)
|
||||||
|
?: return@get call.respondStatus(HttpStatusCode.NotFound)
|
||||||
|
call.respond(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
put {
|
||||||
|
val userId = call.userId()
|
||||||
|
val noteUuid = call.parameters.noteUuid()
|
||||||
|
val note = call.receive<Note>()
|
||||||
|
note.uuid = noteUuid
|
||||||
|
|
||||||
|
if (noteService.updateNote(userId, note))
|
||||||
|
call.respondStatus(HttpStatusCode.OK)
|
||||||
|
else call.respondStatus(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete {
|
||||||
|
val userId = call.userId()
|
||||||
|
val noteUuid = call.parameters.noteUuid()
|
||||||
|
|
||||||
|
if (noteService.delete(userId, noteUuid))
|
||||||
|
call.respondStatus(HttpStatusCode.OK)
|
||||||
|
else
|
||||||
|
call.respondStatus(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,31 +1,32 @@
|
|||||||
package be.vandewalleh.routing
|
package be.vandewalleh.routing.notes
|
||||||
|
|
||||||
import be.vandewalleh.extensions.receiveNoteCreate
|
import be.vandewalleh.entities.Note
|
||||||
import be.vandewalleh.extensions.userId
|
import be.vandewalleh.extensions.userId
|
||||||
import be.vandewalleh.services.NotesService
|
import be.vandewalleh.services.NoteService
|
||||||
import io.ktor.application.*
|
import io.ktor.application.*
|
||||||
import io.ktor.auth.*
|
import io.ktor.auth.*
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
|
import io.ktor.request.*
|
||||||
import io.ktor.response.*
|
import io.ktor.response.*
|
||||||
import io.ktor.routing.*
|
import io.ktor.routing.*
|
||||||
import org.kodein.di.Kodein
|
import org.kodein.di.Kodein
|
||||||
import org.kodein.di.generic.instance
|
import org.kodein.di.generic.instance
|
||||||
|
|
||||||
fun Routing.notes(kodein: Kodein) {
|
fun Routing.notes(kodein: Kodein) {
|
||||||
val notesService by kodein.instance<NotesService>()
|
val noteService by kodein.instance<NoteService>()
|
||||||
|
|
||||||
authenticate {
|
authenticate {
|
||||||
get("/notes") {
|
get("/notes") {
|
||||||
val userId = call.userId()
|
val userId = call.userId()
|
||||||
val notes = notesService.getNotes(userId)
|
val notes = noteService.findAll(userId)
|
||||||
call.respond(notes)
|
call.respond(notes)
|
||||||
}
|
}
|
||||||
|
|
||||||
post("/notes") {
|
post("/notes") {
|
||||||
val userId = call.userId()
|
val userId = call.userId()
|
||||||
val note = call.receiveNoteCreate()
|
val note = call.receive<Note>()
|
||||||
val uuid = notesService.createNote(userId, note)
|
val createdNote = noteService.create(userId, note)
|
||||||
call.respond(HttpStatusCode.Created, mapOf("uuid" to uuid))
|
call.respond(HttpStatusCode.Created, createdNote)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
package be.vandewalleh.routing
|
package be.vandewalleh.routing.notes
|
||||||
|
|
||||||
import be.vandewalleh.extensions.userId
|
import be.vandewalleh.extensions.userId
|
||||||
import be.vandewalleh.services.NotesService
|
import be.vandewalleh.services.NoteService
|
||||||
import io.ktor.application.*
|
import io.ktor.application.*
|
||||||
import io.ktor.auth.*
|
import io.ktor.auth.*
|
||||||
import io.ktor.response.*
|
import io.ktor.response.*
|
||||||
@ -10,11 +10,11 @@ import org.kodein.di.Kodein
|
|||||||
import org.kodein.di.generic.instance
|
import org.kodein.di.generic.instance
|
||||||
|
|
||||||
fun Routing.tags(kodein: Kodein) {
|
fun Routing.tags(kodein: Kodein) {
|
||||||
val notesService by kodein.instance<NotesService>()
|
val noteService by kodein.instance<NoteService>()
|
||||||
|
|
||||||
authenticate {
|
authenticate {
|
||||||
get("/tags") {
|
get("/tags") {
|
||||||
call.respond(notesService.getTags(call.userId()))
|
call.respond(noteService.getTags(call.userId()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package be.vandewalleh.routing
|
package be.vandewalleh.routing.user
|
||||||
|
|
||||||
import be.vandewalleh.auth.SimpleJWT
|
import be.vandewalleh.auth.SimpleJWT
|
||||||
import be.vandewalleh.auth.UserDbIdPrincipal
|
import be.vandewalleh.auth.UserDbIdPrincipal
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package be.vandewalleh.routing
|
package be.vandewalleh.routing.user
|
||||||
|
|
||||||
import be.vandewalleh.extensions.respondStatus
|
import be.vandewalleh.extensions.respondStatus
|
||||||
import be.vandewalleh.extensions.userId
|
import be.vandewalleh.extensions.userId
|
||||||
144
api/src/services/NoteService.kt
Normal file
144
api/src/services/NoteService.kt
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package be.vandewalleh.services
|
||||||
|
|
||||||
|
import be.vandewalleh.entities.Note
|
||||||
|
import be.vandewalleh.extensions.ioAsync
|
||||||
|
import be.vandewalleh.extensions.launchIo
|
||||||
|
import be.vandewalleh.tables.Notes
|
||||||
|
import be.vandewalleh.tables.Tags
|
||||||
|
import me.liuwj.ktorm.database.*
|
||||||
|
import me.liuwj.ktorm.dsl.*
|
||||||
|
import me.liuwj.ktorm.entity.*
|
||||||
|
import org.kodein.di.Kodein
|
||||||
|
import org.kodein.di.KodeinAware
|
||||||
|
import org.kodein.di.generic.instance
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* service to handle database queries at the Notes level.
|
||||||
|
*/
|
||||||
|
class NoteService(override val kodein: Kodein) : KodeinAware {
|
||||||
|
private val db by instance<Database>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns a list of [Note] associated with the userId
|
||||||
|
*/
|
||||||
|
suspend fun findAll(userId: Int): List<Note> {
|
||||||
|
val notes = launchIo {
|
||||||
|
db.sequenceOf(Notes, withReferences = false)
|
||||||
|
.filterColumns { it.columns - it.userId - it.content }
|
||||||
|
.filter { it.userId eq userId }
|
||||||
|
.sortedByDescending { it.updatedAt }
|
||||||
|
.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notes.isEmpty()) return emptyList()
|
||||||
|
|
||||||
|
val allTags = launchIo {
|
||||||
|
db.sequenceOf(Tags, withReferences = false)
|
||||||
|
.filterColumns { listOf(it.noteUuid, it.name) }
|
||||||
|
.filter { it.noteUuid inList notes.map { note -> note.uuid } }
|
||||||
|
.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
val tagsByUuid = allTags.groupBy({ it.note.uuid }, { it.name })
|
||||||
|
|
||||||
|
notes.forEach {
|
||||||
|
val tags = tagsByUuid[it.uuid]
|
||||||
|
if (tags != null) it.tags = tags
|
||||||
|
}
|
||||||
|
|
||||||
|
return notes
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun exists(userId: Int, uuid: UUID) = launchIo {
|
||||||
|
db.sequenceOf(Notes, withReferences = false).any { it.userId eq userId and (it.uuid eq uuid) }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
suspend fun create(userId: Int, note: Note): Note = launchIo {
|
||||||
|
val uuid = UUID.randomUUID()
|
||||||
|
val newNote = note.copy().apply {
|
||||||
|
this["uuid"] = uuid
|
||||||
|
this.user["id"] = userId
|
||||||
|
this.updatedAt = LocalDateTime.now()
|
||||||
|
}
|
||||||
|
db.useTransaction {
|
||||||
|
db.sequenceOf(Notes).add(newNote)
|
||||||
|
db.batchInsert(Tags) {
|
||||||
|
note.tags.forEach { tagName ->
|
||||||
|
item {
|
||||||
|
it.noteUuid to uuid
|
||||||
|
it.name to tagName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
newNote
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
suspend fun find(userId: Int, noteUuid: UUID): Note? {
|
||||||
|
val deferredNote = ioAsync {
|
||||||
|
db.sequenceOf(Notes, withReferences = false)
|
||||||
|
.filterColumns { it.columns - it.userId }
|
||||||
|
.filter { it.uuid eq noteUuid }
|
||||||
|
.find { it.userId eq userId }
|
||||||
|
}
|
||||||
|
|
||||||
|
val deferredTags = ioAsync {
|
||||||
|
db.sequenceOf(Tags, withReferences = false)
|
||||||
|
.filter { it.noteUuid eq noteUuid }
|
||||||
|
.mapColumns { it.name } as List<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
val note = deferredNote.await() ?: return null
|
||||||
|
val tags = deferredTags.await()
|
||||||
|
|
||||||
|
return note.also { it.tags = tags }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateNote(userId: Int, note: Note): Boolean = launchIo {
|
||||||
|
if (note["uuid"] == null) error("UUID is required")
|
||||||
|
|
||||||
|
db.useTransaction {
|
||||||
|
val currentNote = db.sequenceOf(Notes, withReferences = false)
|
||||||
|
.find { it.uuid eq note.uuid and (it.userId eq userId) }
|
||||||
|
?: return@launchIo false
|
||||||
|
|
||||||
|
currentNote.title = note.title
|
||||||
|
currentNote.content = note.content
|
||||||
|
currentNote.updatedAt = LocalDateTime.now()
|
||||||
|
currentNote.flushChanges()
|
||||||
|
|
||||||
|
// delete all tags
|
||||||
|
db.delete(Tags) {
|
||||||
|
it.noteUuid eq note.uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
// put new ones
|
||||||
|
note.tags.forEach { tagName ->
|
||||||
|
db.insert(Tags) {
|
||||||
|
it.name to tagName
|
||||||
|
it.noteUuid to note.uuid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun delete(userId: Int, noteUuid: UUID): Boolean = launchIo {
|
||||||
|
db.useTransaction {
|
||||||
|
db.delete(Notes) { it.uuid eq noteUuid and (it.userId eq userId) } == 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
suspend fun getTags(userId: Int): List<String> = launchIo {
|
||||||
|
db.sequenceOf(Tags)
|
||||||
|
.filter { it.note.userId eq userId }
|
||||||
|
.mapColumns(isDistinct = true) { it.name } as List<String>
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,204 +0,0 @@
|
|||||||
package be.vandewalleh.services
|
|
||||||
|
|
||||||
import be.vandewalleh.extensions.ioAsync
|
|
||||||
import be.vandewalleh.tables.Chapters
|
|
||||||
import be.vandewalleh.tables.Notes
|
|
||||||
import be.vandewalleh.tables.Tags
|
|
||||||
import me.liuwj.ktorm.database.*
|
|
||||||
import me.liuwj.ktorm.dsl.*
|
|
||||||
import me.liuwj.ktorm.entity.*
|
|
||||||
import org.kodein.di.Kodein
|
|
||||||
import org.kodein.di.KodeinAware
|
|
||||||
import org.kodein.di.generic.instance
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* service to handle database queries at the Notes level.
|
|
||||||
*/
|
|
||||||
class NotesService(override val kodein: Kodein) : KodeinAware {
|
|
||||||
private val db by instance<Database>()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns a list of [BasicNoteDTO] associated with the userId
|
|
||||||
*/
|
|
||||||
fun getNotes(userId: Int): List<BasicNoteDTO> {
|
|
||||||
val notes = db.sequenceOf(Notes, withReferences = false)
|
|
||||||
.filterColumns { listOf(it.uuid, it.title, it.updatedAt) }
|
|
||||||
.filter { it.userId eq userId }
|
|
||||||
.sortedByDescending { it.updatedAt }
|
|
||||||
.toList()
|
|
||||||
|
|
||||||
if (notes.isEmpty()) return emptyList()
|
|
||||||
|
|
||||||
val tags = db.sequenceOf(Tags, withReferences = false)
|
|
||||||
.filterColumns { listOf(it.noteUuid, it.name) }
|
|
||||||
.filter { it.noteUuid inList notes.map { it.uuid } }
|
|
||||||
.toList()
|
|
||||||
|
|
||||||
return notes.map { note ->
|
|
||||||
val noteTags = tags.asSequence()
|
|
||||||
.filter { it.note.uuid == note.uuid }
|
|
||||||
.map { it.name }
|
|
||||||
.toList()
|
|
||||||
|
|
||||||
BasicNoteDTO(note.uuid, note.title, noteTags, note.updatedAt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun noteExists(userId: Int, uuid: UUID): Boolean {
|
|
||||||
return db.sequenceOf(Notes)
|
|
||||||
.filterColumns { listOf(it.uuid) }
|
|
||||||
.find {
|
|
||||||
it.userId eq userId and (it.uuid eq uuid)
|
|
||||||
} == null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createNote(userId: Int, note: FullNoteCreateDTO): UUID {
|
|
||||||
val uuid = UUID.randomUUID()
|
|
||||||
db.useTransaction {
|
|
||||||
db.insert(Notes) {
|
|
||||||
it.uuid to uuid
|
|
||||||
it.title to note.title
|
|
||||||
it.userId to userId
|
|
||||||
it.updatedAt to LocalDateTime.now()
|
|
||||||
}
|
|
||||||
|
|
||||||
db.batchInsert(Tags) {
|
|
||||||
note.tags.forEach { tagName ->
|
|
||||||
item {
|
|
||||||
it.noteUuid to uuid
|
|
||||||
it.name to tagName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
db.batchInsert(Chapters) {
|
|
||||||
note.chapters.forEachIndexed { index, chapter ->
|
|
||||||
item {
|
|
||||||
it.noteUuid to uuid
|
|
||||||
it.title to chapter.title
|
|
||||||
it.number to index
|
|
||||||
it.content to chapter.content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
suspend fun getNote(userId: Int, noteUuid: UUID): FullNoteDTO? {
|
|
||||||
val deferredNote = ioAsync {
|
|
||||||
db.sequenceOf(Notes, withReferences = false)
|
|
||||||
.filterColumns { listOf(it.title, it.updatedAt) }
|
|
||||||
.filter { it.uuid eq noteUuid }
|
|
||||||
.find { it.userId eq userId }
|
|
||||||
}
|
|
||||||
|
|
||||||
val deferredTags = ioAsync {
|
|
||||||
db.from(Tags)
|
|
||||||
.select(Tags.name)
|
|
||||||
.where { Tags.noteUuid eq noteUuid }
|
|
||||||
.map { it[Tags.name]!! }
|
|
||||||
.toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
val deferredChapters = ioAsync {
|
|
||||||
db.from(Chapters)
|
|
||||||
.select(Chapters.title, Chapters.content)
|
|
||||||
.where { Chapters.noteUuid eq noteUuid }
|
|
||||||
.orderBy(Chapters.number.asc())
|
|
||||||
.map { ChapterDTO(it[Chapters.title]!!, it[Chapters.content]!!) }
|
|
||||||
.toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
val note = deferredNote.await() ?: return null
|
|
||||||
|
|
||||||
val tags = deferredTags.await()
|
|
||||||
val chapters = deferredChapters.await()
|
|
||||||
return FullNoteDTO(
|
|
||||||
uuid = noteUuid,
|
|
||||||
title = note.title,
|
|
||||||
updatedAt = note.updatedAt,
|
|
||||||
tags = tags,
|
|
||||||
chapters = chapters
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateNote(patch: FullNotePatchDTO) {
|
|
||||||
if (patch.uuid == null) return
|
|
||||||
db.useTransaction {
|
|
||||||
if (patch.title != null) {
|
|
||||||
db.update(Notes) {
|
|
||||||
it.title to patch.title
|
|
||||||
it.updatedAt to LocalDateTime.now()
|
|
||||||
where { it.uuid eq patch.uuid }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (patch.tags != null) {
|
|
||||||
// delete all tags
|
|
||||||
db.delete(Tags) {
|
|
||||||
it.noteUuid eq patch.uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
// put new ones
|
|
||||||
patch.tags.forEach { tagName ->
|
|
||||||
db.insert(Tags) {
|
|
||||||
it.name to tagName
|
|
||||||
it.noteUuid to patch.uuid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TODO("get chapters")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun deleteNote(noteUuid: UUID): Unit =
|
|
||||||
db.useTransaction {
|
|
||||||
db.delete(Notes) { it.uuid eq noteUuid }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getTags(userId: Int): List<String> = db.from(Tags)
|
|
||||||
.leftJoin(Notes, on = Tags.noteUuid eq Notes.uuid)
|
|
||||||
.select(Tags.name)
|
|
||||||
.where { Notes.userId eq userId }
|
|
||||||
.map { it[Tags.name]!! }
|
|
||||||
}
|
|
||||||
|
|
||||||
data class ChapterDTO(
|
|
||||||
val title: String,
|
|
||||||
val content: String
|
|
||||||
)
|
|
||||||
|
|
||||||
data class FullNoteDTO(
|
|
||||||
val uuid: UUID,
|
|
||||||
val title: String,
|
|
||||||
val updatedAt: LocalDateTime,
|
|
||||||
val tags: List<String>,
|
|
||||||
val chapters: List<ChapterDTO>
|
|
||||||
)
|
|
||||||
|
|
||||||
data class FullNoteCreateDTO(
|
|
||||||
val title: String,
|
|
||||||
val tags: List<String>,
|
|
||||||
val chapters: List<ChapterDTO>
|
|
||||||
)
|
|
||||||
|
|
||||||
data class FullNotePatchDTO(
|
|
||||||
val uuid: UUID? = null,
|
|
||||||
val title: String? = null,
|
|
||||||
val updatedAt: LocalDateTime? = null,
|
|
||||||
val tags: List<String>? = null,
|
|
||||||
val chapters: List<ChapterDTO>? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
data class BasicNoteDTO(
|
|
||||||
val uuid: UUID,
|
|
||||||
val title: String,
|
|
||||||
val tags: List<String>,
|
|
||||||
val updatedAt: LocalDateTime
|
|
||||||
)
|
|
||||||
@ -9,6 +9,6 @@ import org.kodein.di.generic.singleton
|
|||||||
* [Kodein] controller module containing the app services
|
* [Kodein] controller module containing the app services
|
||||||
*/
|
*/
|
||||||
val serviceModule = Kodein.Module(name = "Services") {
|
val serviceModule = Kodein.Module(name = "Services") {
|
||||||
bind<NotesService>() with singleton { NotesService(this.kodein) }
|
bind<NoteService>() with singleton { NoteService(this.kodein) }
|
||||||
bind<UserService>() with singleton { UserService(this.kodein) }
|
bind<UserService>() with singleton { UserService(this.kodein) }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +0,0 @@
|
|||||||
package be.vandewalleh.tables
|
|
||||||
|
|
||||||
import be.vandewalleh.entities.Chapter
|
|
||||||
import be.vandewalleh.extensions.uuidBinary
|
|
||||||
import me.liuwj.ktorm.schema.*
|
|
||||||
|
|
||||||
object Chapters : Table<Chapter>("Chapters") {
|
|
||||||
val id = int("id").primaryKey().bindTo { it.id }
|
|
||||||
val number = int("number").bindTo { it.number }
|
|
||||||
val content = text("content").bindTo { it.content }
|
|
||||||
val title = varchar("title").bindTo { it.title }
|
|
||||||
val noteUuid = uuidBinary("note_uuid").references(Notes) { it.note }
|
|
||||||
val note get() = noteUuid.referenceTable as Notes
|
|
||||||
}
|
|
||||||
@ -4,9 +4,14 @@ import be.vandewalleh.entities.Note
|
|||||||
import be.vandewalleh.extensions.uuidBinary
|
import be.vandewalleh.extensions.uuidBinary
|
||||||
import me.liuwj.ktorm.schema.*
|
import me.liuwj.ktorm.schema.*
|
||||||
|
|
||||||
object Notes : Table<Note>("Notes") {
|
open class Notes(alias: String?) : Table<Note>("Notes", alias) {
|
||||||
|
companion object : Notes(null)
|
||||||
|
|
||||||
|
override fun aliased(alias: String) = Notes(alias)
|
||||||
|
|
||||||
val uuid = uuidBinary("uuid").primaryKey().bindTo { it.uuid }
|
val uuid = uuidBinary("uuid").primaryKey().bindTo { it.uuid }
|
||||||
val title = varchar("title").bindTo { it.title }
|
val title = varchar("title").bindTo { it.title }
|
||||||
|
val content = varchar("content").bindTo { it.content }
|
||||||
val userId = int("user_id").references(Users) { it.user }
|
val userId = int("user_id").references(Users) { it.user }
|
||||||
val updatedAt = datetime("updated_at").bindTo { it.updatedAt }
|
val updatedAt = datetime("updated_at").bindTo { it.updatedAt }
|
||||||
val user get() = userId.referenceTable as Users
|
val user get() = userId.referenceTable as Users
|
||||||
|
|||||||
@ -4,7 +4,11 @@ import be.vandewalleh.entities.Tag
|
|||||||
import be.vandewalleh.extensions.uuidBinary
|
import be.vandewalleh.extensions.uuidBinary
|
||||||
import me.liuwj.ktorm.schema.*
|
import me.liuwj.ktorm.schema.*
|
||||||
|
|
||||||
object Tags : Table<Tag>("Tags") {
|
open class Tags(alias: String?) : Table<Tag>("Tags", alias) {
|
||||||
|
companion object : Tags(null)
|
||||||
|
|
||||||
|
override fun aliased(alias: String) = Tags(alias)
|
||||||
|
|
||||||
val id = int("id").primaryKey().bindTo { it.id }
|
val id = int("id").primaryKey().bindTo { it.id }
|
||||||
val name = varchar("name").bindTo { it.name }
|
val name = varchar("name").bindTo { it.name }
|
||||||
val noteUuid = uuidBinary("note_uuid").references(Notes) { it.note }
|
val noteUuid = uuidBinary("note_uuid").references(Notes) { it.note }
|
||||||
|
|||||||
@ -3,7 +3,11 @@ package be.vandewalleh.tables
|
|||||||
import be.vandewalleh.entities.User
|
import be.vandewalleh.entities.User
|
||||||
import me.liuwj.ktorm.schema.*
|
import me.liuwj.ktorm.schema.*
|
||||||
|
|
||||||
object Users : Table<User>("Users") {
|
open class Users(alias: String?) : Table<User>("Users", alias) {
|
||||||
|
companion object : Users(null)
|
||||||
|
|
||||||
|
override fun aliased(alias: String) = Users(alias)
|
||||||
|
|
||||||
val id = int("id").primaryKey().bindTo { it.id }
|
val id = int("id").primaryKey().bindTo { it.id }
|
||||||
val username = varchar("username").bindTo { it.username }
|
val username = varchar("username").bindTo { it.username }
|
||||||
val password = varchar("password").bindTo { it.password }
|
val password = varchar("password").bindTo { it.password }
|
||||||
|
|||||||
228
api/test/integration/services/NoteServiceTest.kt
Normal file
228
api/test/integration/services/NoteServiceTest.kt
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
package integration.services
|
||||||
|
|
||||||
|
import am.ik.yavi.builder.ValidatorBuilder
|
||||||
|
import am.ik.yavi.core.CustomConstraint
|
||||||
|
import am.ik.yavi.core.Validator
|
||||||
|
import be.vandewalleh.entities.Note
|
||||||
|
import be.vandewalleh.mainModule
|
||||||
|
import be.vandewalleh.migrations.Migration
|
||||||
|
import be.vandewalleh.services.NoteService
|
||||||
|
import be.vandewalleh.services.UserService
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.databind.util.StdDateFormat
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||||
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import com.github.javafaker.Faker
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import me.liuwj.ktorm.jackson.*
|
||||||
|
import org.amshove.kluent.*
|
||||||
|
import org.junit.jupiter.api.*
|
||||||
|
import org.kodein.di.Kodein
|
||||||
|
import org.kodein.di.generic.bind
|
||||||
|
import org.kodein.di.generic.instance
|
||||||
|
import org.kodein.di.generic.singleton
|
||||||
|
import utils.KMariadbContainer
|
||||||
|
import javax.sql.DataSource
|
||||||
|
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
|
class NoteServiceTest {
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
inner class DB {
|
||||||
|
private val mariadb = KMariadbContainer().apply { start() }
|
||||||
|
|
||||||
|
private val kodein = Kodein {
|
||||||
|
import(mainModule, allowOverride = true)
|
||||||
|
bind<DataSource>(overrides = true) with singleton { mariadb.datasource() }
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
val migration by kodein.instance<Migration>()
|
||||||
|
migration.migrate()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun run() {
|
||||||
|
val userService by kodein.instance<UserService>()
|
||||||
|
val user = runBlocking { userService.create("test", "test")!! }
|
||||||
|
val noteService by kodein.instance<NoteService>()
|
||||||
|
val note = runBlocking {
|
||||||
|
noteService.create(user.id, Note {
|
||||||
|
this.title = "a note"
|
||||||
|
this.content = """# Title
|
||||||
|
|
|
||||||
|
|😝😝😝😝
|
||||||
|
|another line
|
||||||
|
""".trimMargin()
|
||||||
|
this.tags = listOf("a", "tag")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
println(note)
|
||||||
|
|
||||||
|
val objectMapper = ObjectMapper().apply {
|
||||||
|
registerModule(JavaTimeModule())
|
||||||
|
registerModule(KtormModule())
|
||||||
|
disable(DeserializationFeature.ACCEPT_FLOAT_AS_INT)
|
||||||
|
dateFormat = StdDateFormat()
|
||||||
|
}
|
||||||
|
val json = objectMapper.writeValueAsString(note)
|
||||||
|
println(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test tag list`() {
|
||||||
|
val userService by kodein.instance<UserService>()
|
||||||
|
val user = runBlocking { userService.create("test", "test")!! }
|
||||||
|
val user2 = runBlocking { userService.create("user2", "test")!! }
|
||||||
|
|
||||||
|
val noteService by kodein.instance<NoteService>()
|
||||||
|
runBlocking {
|
||||||
|
noteService.create(user.id, Note {
|
||||||
|
title = "test"
|
||||||
|
content = ""
|
||||||
|
tags = listOf("same")
|
||||||
|
})
|
||||||
|
noteService.create(user.id, Note {
|
||||||
|
title = "test2"
|
||||||
|
content = ""
|
||||||
|
tags = listOf("same")
|
||||||
|
})
|
||||||
|
noteService.create(user.id, Note {
|
||||||
|
title = "test3"
|
||||||
|
content = ""
|
||||||
|
tags = listOf("another")
|
||||||
|
})
|
||||||
|
noteService.create(user2.id, Note {
|
||||||
|
title = "test"
|
||||||
|
content = ""
|
||||||
|
tags = listOf("user2")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
val user1Tags = runBlocking { noteService.getTags(user.id) }
|
||||||
|
user1Tags `should be equal to` listOf("same", "another")
|
||||||
|
val user2Tags = runBlocking { noteService.getTags(user2.id) }
|
||||||
|
user2Tags `should be equal to` listOf("user2")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test patch note`() {
|
||||||
|
val noteService by kodein.instance<NoteService>()
|
||||||
|
val userService by kodein.instance<UserService>()
|
||||||
|
val user = runBlocking { userService.create(Faker().name().username(), "test") }!!
|
||||||
|
val note = runBlocking {
|
||||||
|
noteService.create(user.id, Note {
|
||||||
|
this.title = "title"
|
||||||
|
this.content = "old content"
|
||||||
|
this.tags = emptyList()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
val get = runBlocking { noteService.find(user.id, note.uuid) }
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
noteService.updateNote(user.id, Note {
|
||||||
|
uuid = note.uuid
|
||||||
|
title = "new title"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
val updated = runBlocking { noteService.find(user.id, note.uuid) }
|
||||||
|
println("updated: $updated")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
inner class NoteValidation {
|
||||||
|
@Test
|
||||||
|
fun `test update constraints`() {
|
||||||
|
|
||||||
|
val fieldPresentConstraint = object : CustomConstraint<Note> {
|
||||||
|
override fun defaultMessageFormat() = "fmt {0} {1} {2}"
|
||||||
|
|
||||||
|
override fun messageKey() = "title|content|tags"
|
||||||
|
|
||||||
|
override fun test(note: Note): Boolean {
|
||||||
|
val hasTitle = note["title"] != null
|
||||||
|
val hasContent = note["content"] != null
|
||||||
|
val hasTags = note["tags"] != null && note.tags.isNotEmpty()
|
||||||
|
return hasTitle || hasContent || hasTags
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
val userValidator: Validator<Note> = ValidatorBuilder<Note>()
|
||||||
|
.constraintOnTarget(fieldPresentConstraint, "present")
|
||||||
|
.build()
|
||||||
|
|
||||||
|
userValidator.validate(Note {
|
||||||
|
title = "this is a title"
|
||||||
|
}).isValid `should be equal to` true
|
||||||
|
|
||||||
|
userValidator.validate(Note {
|
||||||
|
content = "this is a title"
|
||||||
|
}).isValid `should be equal to` true
|
||||||
|
|
||||||
|
userValidator.validate(Note {
|
||||||
|
tags = emptyList()
|
||||||
|
}).isValid `should be equal to` false
|
||||||
|
|
||||||
|
userValidator.validate(Note {
|
||||||
|
tags = listOf("tags")
|
||||||
|
}).isValid `should be equal to` true
|
||||||
|
|
||||||
|
userValidator.validate(Note {
|
||||||
|
tags = listOf("tags")
|
||||||
|
title = "This is a title"
|
||||||
|
}).isValid `should be equal to` true
|
||||||
|
|
||||||
|
userValidator.validate(Note {
|
||||||
|
tags = listOf("tags")
|
||||||
|
title = "This is a title"
|
||||||
|
content = """
|
||||||
|
# This is
|
||||||
|
|
||||||
|
some markdown content
|
||||||
|
""".trimIndent()
|
||||||
|
}).isValid `should be equal to` true
|
||||||
|
|
||||||
|
userValidator.validate(Note {
|
||||||
|
tags = listOf("tags")
|
||||||
|
title = "This is a title"
|
||||||
|
content = """
|
||||||
|
# This is
|
||||||
|
|
||||||
|
some markdown content
|
||||||
|
""".trimIndent()
|
||||||
|
}).isValid `should be equal to` true
|
||||||
|
|
||||||
|
userValidator.validate(Note()).isValid `should be equal to` false
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
inner class NoteEntity {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test entity`() {
|
||||||
|
val objectMapper = ObjectMapper().apply {
|
||||||
|
registerModule(JavaTimeModule())
|
||||||
|
registerModule(KtormModule())
|
||||||
|
disable(DeserializationFeature.ACCEPT_FLOAT_AS_INT)
|
||||||
|
dateFormat = StdDateFormat()
|
||||||
|
}
|
||||||
|
val note: Note = objectMapper.readValue("""{"uuid": "c6d80-5fe6-4a30-b034-da63f6663c2c"}""")
|
||||||
|
println(note.uuid)
|
||||||
|
println(note.uuid::class.qualifiedName)
|
||||||
|
println(note.uuid.leastSignificantBits)
|
||||||
|
println(note.uuid.mostSignificantBits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user