From 52aae6773fdd683acb0c44e762087dc908d34e34 Mon Sep 17 00:00:00 2001 From: Hubert Van De Walle Date: Sat, 25 Apr 2020 17:24:43 +0200 Subject: [PATCH] Start migration of notesServices + controllers --- api/src/entities/Note.kt | 2 +- .../extensions/ApplicationCallExtensions.kt | 11 +- api/src/extensions/ParametersExtensions.kt | 16 +-- api/src/routing/ChaptersController.kt | 13 -- api/src/routing/NotesController.kt | 18 +++ api/src/routing/Routes.kt | 1 - api/src/routing/TitleController.kt | 51 ++++--- api/src/services/NotesService.kt | 124 ++++++++++++------ 8 files changed, 130 insertions(+), 106 deletions(-) delete mode 100644 api/src/routing/ChaptersController.kt diff --git a/api/src/entities/Note.kt b/api/src/entities/Note.kt index 9f657b0..09ec45c 100644 --- a/api/src/entities/Note.kt +++ b/api/src/entities/Note.kt @@ -7,7 +7,7 @@ import java.util.* interface Note : Entity { companion object : Entity.Factory() - val uuid: UUID + var uuid: UUID var title: String var user: User var updatedAt: LocalDateTime diff --git a/api/src/extensions/ApplicationCallExtensions.kt b/api/src/extensions/ApplicationCallExtensions.kt index 3a241ce..a09a900 100644 --- a/api/src/extensions/ApplicationCallExtensions.kt +++ b/api/src/extensions/ApplicationCallExtensions.kt @@ -1,6 +1,7 @@ package be.vandewalleh.extensions import be.vandewalleh.kodein +import be.vandewalleh.services.FullNoteDTOPatch import be.vandewalleh.services.UserService import io.ktor.application.* import io.ktor.auth.* @@ -23,12 +24,8 @@ fun ApplicationCall.userId(): Int { return userService.getUserId(email)!! } -private class Tags(val tags: List) +class NoteCreate(val title: String, val tags: List) -suspend fun ApplicationCall.receiveTags(): List { - return receive().tags -} +suspend fun ApplicationCall.receiveNoteCreate(): NoteCreate = receive() -data class NotePatch(val tags: List?, val title: String?) - -suspend fun ApplicationCall.receiveNotePatch() = receive() \ No newline at end of file +suspend fun ApplicationCall.receiveNotePatch() : FullNoteDTOPatch = receive() \ No newline at end of file diff --git a/api/src/extensions/ParametersExtensions.kt b/api/src/extensions/ParametersExtensions.kt index 5e7863b..af7dcd0 100644 --- a/api/src/extensions/ParametersExtensions.kt +++ b/api/src/extensions/ParametersExtensions.kt @@ -1,22 +1,12 @@ package be.vandewalleh.extensions -import be.vandewalleh.kodein -import be.vandewalleh.services.NotesService -import be.vandewalleh.tables.Notes import io.ktor.http.* -import org.kodein.di.generic.instance - -private val notesService by kodein.instance() +import java.util.* fun Parameters.noteTitle(): String { return this["noteTitle"]!! } -/** - * Method that returns a [Notes] ID from it's title and the currently logged in user. - * returns null if none found - */ -fun Parameters.noteId(userId: Int): Int? { - val title = noteTitle() - return notesService.getNoteIdFromUserIdAndTitle(userId, title) +fun Parameters.noteUuid(): UUID { + return UUID.fromString(this["noteUuid"]) } \ No newline at end of file diff --git a/api/src/routing/ChaptersController.kt b/api/src/routing/ChaptersController.kt deleted file mode 100644 index 4236948..0000000 --- a/api/src/routing/ChaptersController.kt +++ /dev/null @@ -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}") { - - } - } -} diff --git a/api/src/routing/NotesController.kt b/api/src/routing/NotesController.kt index ac2e587..e5fc693 100644 --- a/api/src/routing/NotesController.kt +++ b/api/src/routing/NotesController.kt @@ -1,9 +1,13 @@ package be.vandewalleh.routing +import be.vandewalleh.extensions.noteTitle +import be.vandewalleh.extensions.receiveNoteCreate +import be.vandewalleh.extensions.respondStatus import be.vandewalleh.extensions.userId import be.vandewalleh.services.NotesService import io.ktor.application.* import io.ktor.auth.* +import io.ktor.http.* import io.ktor.response.* import io.ktor.routing.* import org.kodein.di.Kodein @@ -18,5 +22,19 @@ fun Routing.notes(kodein: Kodein) { val notes = notesService.getNotes(userId) call.respond(notes) } + + post("/notes") { + val userId = call.userId() + val noteUuid = call.parameters.noteTitle() + val note = call.receiveNoteCreate() + val exists = notesService.noteExistsWithTitle(userId, note.title) + + if (exists) { + return@post call.respondStatus(HttpStatusCode.Conflict) + } + + notesService.createNote(userId, note.title, note.tags) + call.respondStatus(HttpStatusCode.Created) + } } } diff --git a/api/src/routing/Routes.kt b/api/src/routing/Routes.kt index da82d16..4ed1e22 100644 --- a/api/src/routing/Routes.kt +++ b/api/src/routing/Routes.kt @@ -8,6 +8,5 @@ fun Routing.registerRoutes(kodein: Kodein) { login(kodein) notes(kodein) title(kodein) - chapters(kodein) tags(kodein) } \ No newline at end of file diff --git a/api/src/routing/TitleController.kt b/api/src/routing/TitleController.kt index 292e368..fda2058 100644 --- a/api/src/routing/TitleController.kt +++ b/api/src/routing/TitleController.kt @@ -1,6 +1,9 @@ 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 io.ktor.application.* import io.ktor.auth.* @@ -14,49 +17,39 @@ fun Routing.title(kodein: Kodein) { val notesService by kodein.instance() authenticate { - route("/notes/{noteTitle}") { - 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) - } - + route("/notes/{noteUuid}") { get { val userId = call.userId() - val noteId = call.parameters.noteId(userId) - ?: return@get call.respondStatus(HttpStatusCode.NotFound) + val noteUuid = call.parameters.noteUuid() - 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) } patch { - val notePatch = call.receiveNotePatch() - if (notePatch.tags == null && notePatch.title == null) - return@patch call.respondStatus(HttpStatusCode.BadRequest) - val userId = call.userId() - val noteId = call.parameters.noteId(userId) - ?: return@patch call.respondStatus(HttpStatusCode.NotFound) + val noteUuid = call.parameters.noteUuid() - 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) } delete { val userId = call.userId() - val noteId = call.parameters.noteId(userId) - ?: return@delete call.respondStatus(HttpStatusCode.NotFound) + val noteUuid = call.parameters.noteUuid() - notesService.deleteNote(noteId) + val exists = notesService.noteExists(userId, noteUuid) + if (exists) return@delete call.respondStatus(HttpStatusCode.NotFound) + + notesService.deleteNote(noteUuid) call.respondStatus(HttpStatusCode.OK) } } diff --git a/api/src/services/NotesService.kt b/api/src/services/NotesService.kt index f89c829..9feeda5 100644 --- a/api/src/services/NotesService.kt +++ b/api/src/services/NotesService.kt @@ -5,11 +5,13 @@ 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.time.format.DateTimeFormatter +import java.util.* /** * service to handle database queries at the Notes level. @@ -18,102 +20,140 @@ class NotesService(override val kodein: Kodein) : KodeinAware { private val db by instance() /** - * returns a list of [NotesDTO] associated with the userId + * returns a list of [BasicNoteDTO] associated with the userId */ - fun getNotes(userId: Int): List = db.from(Notes) - .select(Notes.id, Notes.title, Notes.updatedAt) - .where { Notes.userId eq userId } - .orderBy(Notes.updatedAt.desc()) - .map { row -> - val tags = db.from(Tags) - .select(Tags.name) - .where { Tags.noteId eq row[Notes.id]!! } - .map { it[Tags.name]!! } + fun getNotes(userId: Int): List { + val notes = db.sequenceOf(Notes) + .filterColumns { listOf(it.uuid, it.title, it.updatedAt) } + .filter { it.userId eq userId } + .sortedByDescending { it.updatedAt } + .toList() - 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) - .select(Notes.id) - .where { Notes.userId eq userId and (Notes.title eq noteTitle) } - .limit(0, 1) - .map { it[Notes.id]!! } - .firstOrNull() + fun noteExistsWithTitle(userId: Int, title: String): Boolean { + TODO() + } + + fun noteExists(userId: Int, uuid: UUID): Boolean { + TODO() + } fun createNote(userId: Int, title: String, tags: List) { db.useTransaction { - val noteId = db.insertAndGenerateKey(Notes) { + val uuid = UUID.randomUUID() + db.insert(Notes) { + it.uuid to uuid it.title to title it.userId to userId it.updatedAt to LocalDateTime.now() } - tags.forEach { tagName -> - db.insert(Tags) { - it.name to tagName - it.noteId to noteId + db.batchInsert(Tags) { + tags.forEach { tagName -> + item { + it.noteUuid to uuid + it.name to tagName + } } } } } - fun getTagsAndChapters(noteId: Int): TagsChaptersDTO { + fun getNote(noteUuid: UUID): FullNoteDTO { + TODO() val tags = db.from(Tags) .select(Tags.name) - .where { Tags.noteId eq noteId } + .where { Tags.noteUuid eq noteUuid } .map { it[Tags.name]!! } .toList() val chapters = db.from(Chapters) .select(Chapters.title, Chapters.content) - .where { Chapters.noteId eq noteId } + .where { Chapters.noteUuid eq noteUuid } .orderBy(Chapters.number.asc()) - .map { ChaptersDTO(it[Chapters.title]!!, it[Chapters.content]!!) } + .map { ChapterDTO(it[Chapters.title]!!, it[Chapters.content]!!) } .toList() - return TagsChaptersDTO(tags, chapters) } - fun updateNote(noteId: Int, tags: List?, title: String?): Unit = + fun updateNote(patch: FullNoteDTOPatch) { + if(patch.uuid == null) return db.useTransaction { - if (title != null) { + if (patch.title != null) { db.update(Notes) { - it.title to title + it.title to patch.title 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 db.delete(Tags) { - it.noteId eq noteId + it.noteUuid eq patch.uuid } // put new ones - tags.forEach { tagName -> + patch.tags.forEach { tagName -> db.insert(Tags) { 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.delete(Notes) { it.id eq noteId } + db.delete(Notes) { it.uuid eq noteUuid } } fun getTags(userId: Int): List = db.from(Tags) - .leftJoin(Notes, on = Tags.noteId eq Notes.id) + .leftJoin(Notes, on = Tags.noteUuid eq Notes.uuid) .select(Tags.name) .where { Notes.userId eq userId } .map { it[Tags.name]!! } } -data class ChaptersDTO(val title: String, val content: String) -data class TagsChaptersDTO(val tags: List, val chapters: List) -data class NotesDTO(val title: String, val tags: List, val updatedAt: String) \ No newline at end of file +data class ChapterDTO(val title: String, val content: String) +data class FullNoteDTO( + val uuid: UUID, + val title: String, + val updatedAt: String, + val tags: List, + val chapters: List +) + +data class FullNoteDTOPatch( + val uuid: UUID? = null, + val title: String? = null, + val updatedAt: String? = null, + val tags: List? = null, + val chapters: List? = null +) + +data class BasicNoteDTO( + val uuid: UUID, + val title: String, + val tags: List, + val updatedAt: String +) \ No newline at end of file