Merge branch 'feature/uuid'
This commit is contained in:
commit
081e0fca26
@ -19,7 +19,43 @@ GET http://localhost:8081/notes
|
|||||||
Authorization: Bearer {{token}}
|
Authorization: Bearer {{token}}
|
||||||
|
|
||||||
> {%
|
> {%
|
||||||
|
client.global.set("uuid", response.body[0].uuid);
|
||||||
client.test("Request executed successfully", function() {
|
client.test("Request executed successfully", function() {
|
||||||
client.assert(response.status === 200, "Response status is not 200");
|
client.assert(response.status === 200, "Response status is not 200");
|
||||||
});
|
});
|
||||||
%}
|
%}
|
||||||
|
|
||||||
|
### Create note
|
||||||
|
POST http://localhost:8081/notes
|
||||||
|
Authorization: Bearer {{token}}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"title": "test",
|
||||||
|
"tags": [
|
||||||
|
"Some",
|
||||||
|
"Tags"
|
||||||
|
],
|
||||||
|
"chapters": [
|
||||||
|
{
|
||||||
|
"title": "Chapter 1",
|
||||||
|
"content": "# This is some content"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
> {%
|
||||||
|
client.test("Request executed successfully", function() {
|
||||||
|
client.assert(response.status === 201, "Response status is not 201");
|
||||||
|
});
|
||||||
|
%}
|
||||||
|
|
||||||
|
### Get a note
|
||||||
|
GET http://localhost:8081/notes/{{uuid}}
|
||||||
|
Authorization: Bearer {{token}}
|
||||||
|
|
||||||
|
> {%
|
||||||
|
client.test("Request executed successfully", function() {
|
||||||
|
client.assert(response.status === 200, "Response status is not 200");
|
||||||
|
});
|
||||||
|
%}
|
||||||
37
api/resources/db/migration/V8__Notes_uuid.sql
Normal file
37
api/resources/db/migration/V8__Notes_uuid.sql
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
-- no need to migrate existing data yet
|
||||||
|
drop table if exists Chapters;
|
||||||
|
drop table if exists Tags;
|
||||||
|
drop table if exists Notes;
|
||||||
|
|
||||||
|
CREATE TABLE `Notes`
|
||||||
|
(
|
||||||
|
`uuid` binary(16) PRIMARY KEY,
|
||||||
|
`title` varchar(50) NOT NULL,
|
||||||
|
`user_id` int NOT NULL,
|
||||||
|
`updated_at` datetime
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE `Notes`
|
||||||
|
ADD FOREIGN KEY (`user_id`) REFERENCES `Users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT;
|
||||||
|
|
||||||
|
CREATE TABLE `Tags`
|
||||||
|
(
|
||||||
|
`id` int PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
`name` varchar(50) NOT NULL,
|
||||||
|
`note_uuid` binary(16) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE `Tags`
|
||||||
|
ADD FOREIGN KEY (`note_uuid`) REFERENCES `Notes` (`uuid`) ON DELETE CASCADE ON UPDATE RESTRICT;
|
||||||
|
|
||||||
|
CREATE TABLE `Chapters`
|
||||||
|
(
|
||||||
|
`id` int PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
`number` int NOT NULL,
|
||||||
|
`title` varchar(50) NOT NULL,
|
||||||
|
`content` text NOT NULL,
|
||||||
|
`note_uuid` binary(16) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE `Chapters`
|
||||||
|
ADD FOREIGN KEY (`note_uuid`) REFERENCES `Notes` (`uuid`) ON DELETE CASCADE ON UPDATE RESTRICT;
|
||||||
@ -4,7 +4,7 @@
|
|||||||
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
<root level="INFO">
|
<root level="TRACE">
|
||||||
<appender-ref ref="STDOUT"/>
|
<appender-ref ref="STDOUT"/>
|
||||||
</root>
|
</root>
|
||||||
<logger name="org.eclipse.jetty" level="INFO"/>
|
<logger name="org.eclipse.jetty" level="INFO"/>
|
||||||
|
|||||||
@ -2,11 +2,12 @@ package be.vandewalleh.entities
|
|||||||
|
|
||||||
import me.liuwj.ktorm.entity.*
|
import me.liuwj.ktorm.entity.*
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
interface Note : Entity<Note> {
|
interface Note : Entity<Note> {
|
||||||
companion object : Entity.Factory<Note>()
|
companion object : Entity.Factory<Note>()
|
||||||
|
|
||||||
val id: Int
|
var uuid: UUID
|
||||||
var title: String
|
var title: String
|
||||||
var user: User
|
var user: User
|
||||||
var updatedAt: LocalDateTime
|
var updatedAt: LocalDateTime
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
package be.vandewalleh.extensions
|
package be.vandewalleh.extensions
|
||||||
|
|
||||||
import be.vandewalleh.kodein
|
import be.vandewalleh.kodein
|
||||||
|
import be.vandewalleh.services.FullNoteCreateDTO
|
||||||
|
import be.vandewalleh.services.FullNotePatchDTO
|
||||||
import be.vandewalleh.services.UserService
|
import be.vandewalleh.services.UserService
|
||||||
import io.ktor.application.*
|
import io.ktor.application.*
|
||||||
import io.ktor.auth.*
|
import io.ktor.auth.*
|
||||||
@ -23,12 +25,8 @@ fun ApplicationCall.userId(): Int {
|
|||||||
return userService.getUserId(email)!!
|
return userService.getUserId(email)!!
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Tags(val tags: List<String>)
|
class NoteCreate(val title: String, val tags: List<String>)
|
||||||
|
|
||||||
suspend fun ApplicationCall.receiveTags(): List<String> {
|
suspend fun ApplicationCall.receiveNoteCreate(): FullNoteCreateDTO = receive()
|
||||||
return receive<Tags>().tags
|
|
||||||
}
|
|
||||||
|
|
||||||
data class NotePatch(val tags: List<String>?, val title: String?)
|
suspend fun ApplicationCall.receiveNotePatch() : FullNotePatchDTO = receive()
|
||||||
|
|
||||||
suspend fun ApplicationCall.receiveNotePatch() = receive<NotePatch>()
|
|
||||||
27
api/src/extensions/KtormExtensions.kt
Normal file
27
api/src/extensions/KtormExtensions.kt
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package be.vandewalleh.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.*
|
||||||
|
|
||||||
|
class UuidBinarySqlType : SqlType<java.util.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <E : Any> BaseTable<E>.uuidBinary(name: String): BaseTable<E>.ColumnRegistration<java.util.UUID> {
|
||||||
|
return registerColumn(name, UuidBinarySqlType())
|
||||||
|
}
|
||||||
@ -1,22 +1,12 @@
|
|||||||
package be.vandewalleh.extensions
|
package be.vandewalleh.extensions
|
||||||
|
|
||||||
import be.vandewalleh.kodein
|
|
||||||
import be.vandewalleh.services.NotesService
|
|
||||||
import be.vandewalleh.tables.Notes
|
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import org.kodein.di.generic.instance
|
import java.util.*
|
||||||
|
|
||||||
private val notesService by kodein.instance<NotesService>()
|
|
||||||
|
|
||||||
fun Parameters.noteTitle(): String {
|
fun Parameters.noteTitle(): String {
|
||||||
return this["noteTitle"]!!
|
return this["noteTitle"]!!
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun Parameters.noteUuid(): UUID {
|
||||||
* Method that returns a [Notes] ID from it's title and the currently logged in user.
|
return UUID.fromString(this["noteUuid"])
|
||||||
* returns null if none found
|
|
||||||
*/
|
|
||||||
fun Parameters.noteId(userId: Int): Int? {
|
|
||||||
val title = noteTitle()
|
|
||||||
return notesService.getNoteIdFromUserIdAndTitle(userId, title)
|
|
||||||
}
|
}
|
||||||
@ -9,5 +9,6 @@ fun Application.corsFeature() {
|
|||||||
anyHost()
|
anyHost()
|
||||||
header(HttpHeaders.ContentType)
|
header(HttpHeaders.ContentType)
|
||||||
header(HttpHeaders.Authorization)
|
header(HttpHeaders.Authorization)
|
||||||
|
methods.add(HttpMethod.Delete)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,13 +0,0 @@
|
|||||||
package be.vandewalleh.routing
|
|
||||||
|
|
||||||
import io.ktor.auth.*
|
|
||||||
import io.ktor.routing.*
|
|
||||||
import org.kodein.di.Kodein
|
|
||||||
|
|
||||||
fun Routing.chapters(kodein: Kodein) {
|
|
||||||
authenticate {
|
|
||||||
route("/notes/{noteTitle}/chapters/{chapterNumber}") {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,9 +1,11 @@
|
|||||||
package be.vandewalleh.routing
|
package be.vandewalleh.routing
|
||||||
|
|
||||||
|
import be.vandewalleh.extensions.receiveNoteCreate
|
||||||
import be.vandewalleh.extensions.userId
|
import be.vandewalleh.extensions.userId
|
||||||
import be.vandewalleh.services.NotesService
|
import be.vandewalleh.services.NotesService
|
||||||
import io.ktor.application.*
|
import io.ktor.application.*
|
||||||
import io.ktor.auth.*
|
import io.ktor.auth.*
|
||||||
|
import io.ktor.http.*
|
||||||
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
|
||||||
@ -18,5 +20,12 @@ fun Routing.notes(kodein: Kodein) {
|
|||||||
val notes = notesService.getNotes(userId)
|
val notes = notesService.getNotes(userId)
|
||||||
call.respond(notes)
|
call.respond(notes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
post("/notes") {
|
||||||
|
val userId = call.userId()
|
||||||
|
val note = call.receiveNoteCreate()
|
||||||
|
val uuid = notesService.createNote(userId, note)
|
||||||
|
call.respond(HttpStatusCode.Created, mapOf("uuid" to uuid))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,5 @@ fun Routing.registerRoutes(kodein: Kodein) {
|
|||||||
login(kodein)
|
login(kodein)
|
||||||
notes(kodein)
|
notes(kodein)
|
||||||
title(kodein)
|
title(kodein)
|
||||||
chapters(kodein)
|
|
||||||
tags(kodein)
|
tags(kodein)
|
||||||
}
|
}
|
||||||
@ -1,6 +1,9 @@
|
|||||||
package be.vandewalleh.routing
|
package be.vandewalleh.routing
|
||||||
|
|
||||||
import be.vandewalleh.extensions.*
|
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 be.vandewalleh.services.NotesService
|
||||||
import io.ktor.application.*
|
import io.ktor.application.*
|
||||||
import io.ktor.auth.*
|
import io.ktor.auth.*
|
||||||
@ -14,49 +17,39 @@ fun Routing.title(kodein: Kodein) {
|
|||||||
val notesService by kodein.instance<NotesService>()
|
val notesService by kodein.instance<NotesService>()
|
||||||
|
|
||||||
authenticate {
|
authenticate {
|
||||||
route("/notes/{noteTitle}") {
|
route("/notes/{noteUuid}") {
|
||||||
post {
|
|
||||||
val userId = call.userId()
|
|
||||||
val title = call.parameters.noteTitle()
|
|
||||||
val tags = call.receiveTags()
|
|
||||||
val noteId = call.parameters.noteId(userId)
|
|
||||||
|
|
||||||
if (noteId != null) {
|
|
||||||
return@post call.respondStatus(HttpStatusCode.Conflict)
|
|
||||||
}
|
|
||||||
|
|
||||||
notesService.createNote(userId, title, tags)
|
|
||||||
call.respondStatus(HttpStatusCode.Created)
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
get {
|
||||||
val userId = call.userId()
|
val userId = call.userId()
|
||||||
val noteId = call.parameters.noteId(userId)
|
val noteUuid = call.parameters.noteUuid()
|
||||||
?: return@get call.respondStatus(HttpStatusCode.NotFound)
|
|
||||||
|
|
||||||
val response = notesService.getTagsAndChapters(noteId)
|
val exists = notesService.noteExists(userId, noteUuid)
|
||||||
|
if (!exists) return@get call.respondStatus(HttpStatusCode.NotFound)
|
||||||
|
|
||||||
|
val response = notesService.getNote(noteUuid)
|
||||||
call.respond(response)
|
call.respond(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
patch {
|
patch {
|
||||||
val notePatch = call.receiveNotePatch()
|
|
||||||
if (notePatch.tags == null && notePatch.title == null)
|
|
||||||
return@patch call.respondStatus(HttpStatusCode.BadRequest)
|
|
||||||
|
|
||||||
val userId = call.userId()
|
val userId = call.userId()
|
||||||
val noteId = call.parameters.noteId(userId)
|
val noteUuid = call.parameters.noteUuid()
|
||||||
?: return@patch call.respondStatus(HttpStatusCode.NotFound)
|
|
||||||
|
|
||||||
notesService.updateNote(noteId, notePatch.tags, notePatch.title)
|
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)
|
call.respondStatus(HttpStatusCode.OK)
|
||||||
}
|
}
|
||||||
|
|
||||||
delete {
|
delete {
|
||||||
val userId = call.userId()
|
val userId = call.userId()
|
||||||
val noteId = call.parameters.noteId(userId)
|
val noteUuid = call.parameters.noteUuid()
|
||||||
?: return@delete call.respondStatus(HttpStatusCode.NotFound)
|
|
||||||
|
|
||||||
notesService.deleteNote(noteId)
|
val exists = notesService.noteExists(userId, noteUuid)
|
||||||
|
if (!exists) return@delete call.respondStatus(HttpStatusCode.NotFound)
|
||||||
|
|
||||||
|
notesService.deleteNote(noteUuid)
|
||||||
call.respondStatus(HttpStatusCode.OK)
|
call.respondStatus(HttpStatusCode.OK)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,11 +5,13 @@ import be.vandewalleh.tables.Notes
|
|||||||
import be.vandewalleh.tables.Tags
|
import be.vandewalleh.tables.Tags
|
||||||
import me.liuwj.ktorm.database.*
|
import me.liuwj.ktorm.database.*
|
||||||
import me.liuwj.ktorm.dsl.*
|
import me.liuwj.ktorm.dsl.*
|
||||||
|
import me.liuwj.ktorm.entity.*
|
||||||
import org.kodein.di.Kodein
|
import org.kodein.di.Kodein
|
||||||
import org.kodein.di.KodeinAware
|
import org.kodein.di.KodeinAware
|
||||||
import org.kodein.di.generic.instance
|
import org.kodein.di.generic.instance
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* service to handle database queries at the Notes level.
|
* service to handle database queries at the Notes level.
|
||||||
@ -18,102 +20,175 @@ class NotesService(override val kodein: Kodein) : KodeinAware {
|
|||||||
private val db by instance<Database>()
|
private val db by instance<Database>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns a list of [NotesDTO] associated with the userId
|
* returns a list of [BasicNoteDTO] associated with the userId
|
||||||
*/
|
*/
|
||||||
fun getNotes(userId: Int): List<NotesDTO> = db.from(Notes)
|
fun getNotes(userId: Int): List<BasicNoteDTO> {
|
||||||
.select(Notes.id, Notes.title, Notes.updatedAt)
|
val notes = db.sequenceOf(Notes)
|
||||||
.where { Notes.userId eq userId }
|
.filterColumns { listOf(it.uuid, it.title, it.updatedAt) }
|
||||||
.orderBy(Notes.updatedAt.desc())
|
.filter { it.userId eq userId }
|
||||||
.map { row ->
|
.sortedByDescending { it.updatedAt }
|
||||||
val tags = db.from(Tags)
|
.toList()
|
||||||
.select(Tags.name)
|
|
||||||
.where { Tags.noteId eq row[Notes.id]!! }
|
|
||||||
.map { it[Tags.name]!! }
|
|
||||||
|
|
||||||
val updatedAt = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(row[Notes.updatedAt]!!)
|
if (notes.isEmpty()) return emptyList()
|
||||||
|
|
||||||
NotesDTO(row[Notes.title]!!, tags, updatedAt)
|
val tags = db.sequenceOf(Tags)
|
||||||
|
.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, DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(note.updatedAt))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getNoteIdFromUserIdAndTitle(userId: Int, noteTitle: String): Int? = db.from(Notes)
|
fun noteExists(userId: Int, uuid: UUID): Boolean {
|
||||||
.select(Notes.id)
|
return db.from(Notes)
|
||||||
.where { Notes.userId eq userId and (Notes.title eq noteTitle) }
|
.select(Notes.uuid)
|
||||||
.limit(0, 1)
|
.where { Notes.userId eq userId }
|
||||||
.map { it[Notes.id]!! }
|
.where { Notes.uuid eq uuid }
|
||||||
.firstOrNull()
|
.limit(0, 1)
|
||||||
|
.toList().size == 1
|
||||||
|
}
|
||||||
|
|
||||||
fun createNote(userId: Int, title: String, tags: List<String>) {
|
fun createNote(userId: Int, note: FullNoteCreateDTO): UUID {
|
||||||
|
val uuid = UUID.randomUUID()
|
||||||
db.useTransaction {
|
db.useTransaction {
|
||||||
val noteId = db.insertAndGenerateKey(Notes) {
|
db.insert(Notes) {
|
||||||
it.title to title
|
it.uuid to uuid
|
||||||
|
it.title to note.title
|
||||||
it.userId to userId
|
it.userId to userId
|
||||||
it.updatedAt to LocalDateTime.now()
|
it.updatedAt to LocalDateTime.now()
|
||||||
}
|
}
|
||||||
|
|
||||||
tags.forEach { tagName ->
|
db.batchInsert(Tags) {
|
||||||
db.insert(Tags) {
|
note.tags.forEach { tagName ->
|
||||||
it.name to tagName
|
item {
|
||||||
it.noteId to noteId
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getTagsAndChapters(noteId: Int): TagsChaptersDTO {
|
fun getNote(noteUuid: UUID): FullNoteDTO {
|
||||||
|
val note = db.sequenceOf(Notes)
|
||||||
|
.filterColumns { listOf(it.title, it.updatedAt) }
|
||||||
|
.find { it.uuid eq noteUuid } ?: error("Note not found")
|
||||||
|
|
||||||
val tags = db.from(Tags)
|
val tags = db.from(Tags)
|
||||||
.select(Tags.name)
|
.select(Tags.name)
|
||||||
.where { Tags.noteId eq noteId }
|
.where { Tags.noteUuid eq noteUuid }
|
||||||
.map { it[Tags.name]!! }
|
.map { it[Tags.name]!! }
|
||||||
.toList()
|
.toList()
|
||||||
|
|
||||||
val chapters = db.from(Chapters)
|
val chapters = db.from(Chapters)
|
||||||
.select(Chapters.title, Chapters.content)
|
.select(Chapters.title, Chapters.content)
|
||||||
.where { Chapters.noteId eq noteId }
|
.where { Chapters.noteUuid eq noteUuid }
|
||||||
.orderBy(Chapters.number.asc())
|
.orderBy(Chapters.number.asc())
|
||||||
.map { ChaptersDTO(it[Chapters.title]!!, it[Chapters.content]!!) }
|
.map { ChapterDTO(it[Chapters.title]!!, it[Chapters.content]!!) }
|
||||||
.toList()
|
.toList()
|
||||||
|
|
||||||
return TagsChaptersDTO(tags, chapters)
|
val updatedAtFormatted = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(note.updatedAt)
|
||||||
|
return FullNoteDTO(
|
||||||
|
uuid = noteUuid,
|
||||||
|
title = note.title,
|
||||||
|
updatedAt = updatedAtFormatted,
|
||||||
|
tags = tags,
|
||||||
|
chapters = chapters
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateNote(noteId: Int, tags: List<String>?, title: String?): Unit =
|
fun updateNote(patch: FullNotePatchDTO) {
|
||||||
|
if (patch.uuid == null) return
|
||||||
db.useTransaction {
|
db.useTransaction {
|
||||||
if (title != null) {
|
if (patch.title != null) {
|
||||||
db.update(Notes) {
|
db.update(Notes) {
|
||||||
it.title to title
|
it.title to patch.title
|
||||||
it.updatedAt to LocalDateTime.now()
|
it.updatedAt to LocalDateTime.now()
|
||||||
where { it.id eq noteId }
|
where { it.uuid eq patch.uuid }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tags != null) {
|
if (patch.tags != null) {
|
||||||
// delete all tags
|
// delete all tags
|
||||||
db.delete(Tags) {
|
db.delete(Tags) {
|
||||||
it.noteId eq noteId
|
it.noteUuid eq patch.uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
// put new ones
|
// put new ones
|
||||||
tags.forEach { tagName ->
|
patch.tags.forEach { tagName ->
|
||||||
db.insert(Tags) {
|
db.insert(Tags) {
|
||||||
it.name to tagName
|
it.name to tagName
|
||||||
it.noteId to noteId
|
it.noteUuid to patch.uuid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteNote(noteId: Int): Unit =
|
TODO("get chapters")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteNote(noteUuid: UUID): Unit =
|
||||||
db.useTransaction {
|
db.useTransaction {
|
||||||
db.delete(Notes) { it.id eq noteId }
|
db.delete(Notes) { it.uuid eq noteUuid }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getTags(userId: Int): List<String> = db.from(Tags)
|
fun getTags(userId: Int): List<String> = db.from(Tags)
|
||||||
.leftJoin(Notes, on = Tags.noteId eq Notes.id)
|
.leftJoin(Notes, on = Tags.noteUuid eq Notes.uuid)
|
||||||
.select(Tags.name)
|
.select(Tags.name)
|
||||||
.where { Notes.userId eq userId }
|
.where { Notes.userId eq userId }
|
||||||
.map { it[Tags.name]!! }
|
.map { it[Tags.name]!! }
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ChaptersDTO(val title: String, val content: String)
|
data class ChapterDTO(
|
||||||
data class TagsChaptersDTO(val tags: List<String>, val chapters: List<ChaptersDTO>)
|
val title: String,
|
||||||
data class NotesDTO(val title: String, val tags: List<String>, val updatedAt: String)
|
val content: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class FullNoteDTO(
|
||||||
|
val uuid: UUID,
|
||||||
|
val title: String,
|
||||||
|
val updatedAt: String,
|
||||||
|
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: String? = 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: String
|
||||||
|
)
|
||||||
@ -1,6 +1,7 @@
|
|||||||
package be.vandewalleh.tables
|
package be.vandewalleh.tables
|
||||||
|
|
||||||
import be.vandewalleh.entities.Chapter
|
import be.vandewalleh.entities.Chapter
|
||||||
|
import be.vandewalleh.extensions.uuidBinary
|
||||||
import me.liuwj.ktorm.schema.*
|
import me.liuwj.ktorm.schema.*
|
||||||
|
|
||||||
object Chapters : Table<Chapter>("Chapters") {
|
object Chapters : Table<Chapter>("Chapters") {
|
||||||
@ -8,6 +9,6 @@ object Chapters : Table<Chapter>("Chapters") {
|
|||||||
val number by int("number").bindTo { it.number }
|
val number by int("number").bindTo { it.number }
|
||||||
val content by text("content").bindTo { it.content }
|
val content by text("content").bindTo { it.content }
|
||||||
val title by varchar("title").bindTo { it.title }
|
val title by varchar("title").bindTo { it.title }
|
||||||
val noteId by int("note_id").references(Notes) { it.note }
|
val noteUuid by uuidBinary("note_uuid").references(Notes) { it.note }
|
||||||
val note get() = noteId.referenceTable as Notes
|
val note get() = noteUuid.referenceTable as Notes
|
||||||
}
|
}
|
||||||
@ -1,10 +1,11 @@
|
|||||||
package be.vandewalleh.tables
|
package be.vandewalleh.tables
|
||||||
|
|
||||||
import be.vandewalleh.entities.Note
|
import be.vandewalleh.entities.Note
|
||||||
|
import be.vandewalleh.extensions.uuidBinary
|
||||||
import me.liuwj.ktorm.schema.*
|
import me.liuwj.ktorm.schema.*
|
||||||
|
|
||||||
object Notes : Table<Note>("Notes") {
|
object Notes : Table<Note>("Notes") {
|
||||||
val id by int("id").primaryKey().bindTo { it.id }
|
val uuid by uuidBinary("uuid").primaryKey().bindTo { it.uuid }
|
||||||
val title by varchar("title").bindTo { it.title }
|
val title by varchar("title").bindTo { it.title }
|
||||||
val userId by int("user_id").references(Users) { it.user }
|
val userId by int("user_id").references(Users) { it.user }
|
||||||
val updatedAt by datetime("updated_at").bindTo { it.updatedAt }
|
val updatedAt by datetime("updated_at").bindTo { it.updatedAt }
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
package be.vandewalleh.tables
|
package be.vandewalleh.tables
|
||||||
|
|
||||||
import be.vandewalleh.entities.Tag
|
import be.vandewalleh.entities.Tag
|
||||||
|
import be.vandewalleh.extensions.uuidBinary
|
||||||
import me.liuwj.ktorm.schema.*
|
import me.liuwj.ktorm.schema.*
|
||||||
|
|
||||||
object Tags : Table<Tag>("Tags") {
|
object Tags : Table<Tag>("Tags") {
|
||||||
val id by int("id").primaryKey().bindTo { it.id }
|
val id by int("id").primaryKey().bindTo { it.id }
|
||||||
val name by varchar("name").bindTo { it.name }
|
val name by varchar("name").bindTo { it.name }
|
||||||
val noteId by int("note_id").references(Notes) { it.note }
|
val noteUuid by uuidBinary("note_uuid").references(Notes) { it.note }
|
||||||
val note get() = noteId.referenceTable as Notes
|
val note get() = noteUuid.referenceTable as Notes
|
||||||
}
|
}
|
||||||
@ -70,14 +70,19 @@ export default {
|
|||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$axios.get('/notes').then((e) => {
|
this.loadNotes()
|
||||||
this.notes = e.data
|
|
||||||
this.loading = false
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async loadNotes() {
|
||||||
|
await this.$axios.get('/notes').then((e) => {
|
||||||
|
this.notes = e.data
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
editItem(item) {},
|
editItem(item) {},
|
||||||
deleteItem(item) {},
|
async deleteItem(item) {
|
||||||
|
await this.$axios.delete(`/notes/${item.uuid}`).then(this.loadNotes)
|
||||||
|
},
|
||||||
format,
|
format,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user