From 0335c1eb08db9b95206cacb663f19029a689ac5b Mon Sep 17 00:00:00 2001 From: Hubert Van De Walle Date: Sun, 19 Apr 2020 23:27:43 +0200 Subject: [PATCH 1/9] Add create note --- api/http/test.http | 14 ++++- api/src/controllers/Controllers.kt | 1 + api/src/controllers/KodeinController.kt | 7 +++ api/src/controllers/NotesTitleController.kt | 70 +++++++++++++++++++++ 4 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 api/src/controllers/NotesTitleController.kt diff --git a/api/http/test.http b/api/http/test.http index c75f3f6..287d50c 100644 --- a/api/http/test.http +++ b/api/http/test.http @@ -11,4 +11,16 @@ Content-Type: application/json ### Get notes GET http://localhost:8081/notes -Authorization: Bearer {{token}} \ No newline at end of file +Authorization: Bearer {{token}} + +### Create a note +POST http://localhost:8081/notes/babar +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "tags": [ + "Test", + "Dev" + ] +} \ No newline at end of file diff --git a/api/src/controllers/Controllers.kt b/api/src/controllers/Controllers.kt index f4fc0b1..799361c 100644 --- a/api/src/controllers/Controllers.kt +++ b/api/src/controllers/Controllers.kt @@ -15,4 +15,5 @@ val controllerModule = Kodein.Module(name = "Controller") { bind().inSet() with singleton { UserController(this.kodein) } bind().inSet() with singleton { HealthCheckController(this.kodein) } bind().inSet() with singleton { NotesController(this.kodein) } + bind().inSet() with singleton { NotesTitleController(this.kodein) } } \ No newline at end of file diff --git a/api/src/controllers/KodeinController.kt b/api/src/controllers/KodeinController.kt index 332feec..5231c49 100644 --- a/api/src/controllers/KodeinController.kt +++ b/api/src/controllers/KodeinController.kt @@ -1,5 +1,8 @@ package be.vandewalleh.controllers +import io.ktor.application.ApplicationCall +import io.ktor.http.HttpStatusCode +import io.ktor.response.respond import io.ktor.routing.Routing import org.kodein.di.Kodein import org.kodein.di.KodeinAware @@ -11,4 +14,8 @@ abstract class KodeinController(override val kodein: Kodein) : KodeinAware { * Method that subtypes must override to register the handled [Routing] routes. */ abstract fun Routing.registerRoutes() + + suspend fun ApplicationCall.respondStatus(status: HttpStatusCode) { + this.respond(status, mapOf("message" to status.description)) + } } diff --git a/api/src/controllers/NotesTitleController.kt b/api/src/controllers/NotesTitleController.kt new file mode 100644 index 0000000..f91155a --- /dev/null +++ b/api/src/controllers/NotesTitleController.kt @@ -0,0 +1,70 @@ +package be.vandewalleh.controllers + +import be.vandewalleh.entities.Note +import be.vandewalleh.entities.Tag +import be.vandewalleh.tables.Notes +import be.vandewalleh.tables.Tags +import be.vandewalleh.tables.Users +import io.ktor.application.ApplicationCall +import io.ktor.application.call +import io.ktor.http.HttpStatusCode +import io.ktor.request.receive +import io.ktor.response.respond +import io.ktor.routing.Route +import io.ktor.routing.post +import me.liuwj.ktorm.database.Database +import me.liuwj.ktorm.dsl.eq +import me.liuwj.ktorm.entity.* +import org.kodein.di.Kodein +import org.kodein.di.generic.instance +import java.time.LocalDateTime + +class NotesTitleController(kodein: Kodein) : AuthCrudController("/notes/{noteTitle}", kodein) { + private val db by kodein.instance() + + private fun ApplicationCall.noteTitle(): String? { + return this.parameters["noteTitle"]!! + } + + private class PostRequestBody(val tags: List) + + override val route: Route.() -> Unit = { + post { + val title = call.noteTitle() ?: error("") + val tags = call.receive().tags + + val user = db.sequenceOf(Users) + .find { it.email eq call.userEmail() } ?: error("") + + val exists = db.sequenceOf(Notes) + .filter { it.userId eq user.id } + .filter { it.title eq title } + .firstOrNull() != null + + if (exists) { + return@post call.respondStatus(HttpStatusCode.Conflict) + } + + db.useTransaction { + val note = Note { + this.title = title + this.user = user + this.updatedAt = LocalDateTime.now() + } + + db.sequenceOf(Notes).add(note) + + tags.forEach { tagName -> + val tag = Tag { + this.note = note + this.name = tagName + } + + db.sequenceOf(Tags).add(tag) + } + } + + call.respondStatus(HttpStatusCode.Created) + } + } +} \ No newline at end of file From 142b94722e7a3d769ae50122ac8296b1af529a1c Mon Sep 17 00:00:00 2001 From: Hubert Van De Walle Date: Mon, 20 Apr 2020 00:11:47 +0200 Subject: [PATCH 2/9] Add title column to chapters --- api/resources/db/migration/V6__Update_chapter_table.sql | 2 ++ api/src/entities/Chapter.kt | 1 + api/src/tables/Chapters.kt | 1 + 3 files changed, 4 insertions(+) create mode 100644 api/resources/db/migration/V6__Update_chapter_table.sql diff --git a/api/resources/db/migration/V6__Update_chapter_table.sql b/api/resources/db/migration/V6__Update_chapter_table.sql new file mode 100644 index 0000000..1478385 --- /dev/null +++ b/api/resources/db/migration/V6__Update_chapter_table.sql @@ -0,0 +1,2 @@ +ALTER TABLE `Chapters` + ADD COLUMN `title` varchar(50); diff --git a/api/src/entities/Chapter.kt b/api/src/entities/Chapter.kt index 6ca869d..0498139 100644 --- a/api/src/entities/Chapter.kt +++ b/api/src/entities/Chapter.kt @@ -7,6 +7,7 @@ interface Chapter : Entity { val id: Int var number: Int + var title: String var content: String var note: Note } \ No newline at end of file diff --git a/api/src/tables/Chapters.kt b/api/src/tables/Chapters.kt index a8d7c8f..4651fda 100644 --- a/api/src/tables/Chapters.kt +++ b/api/src/tables/Chapters.kt @@ -11,6 +11,7 @@ object Chapters : Table("Chapters") { val id by int("id").primaryKey().bindTo { it.id } val number by int("number").bindTo { it.number } val content by text("content").bindTo { it.content } + val title by varchar("title").bindTo { it.title } val noteId by int("note_id").references(Notes) { it.note } val note get() = noteId.referenceTable as Notes } \ No newline at end of file From e6bfe37b359df9e1cdfffc0db58c3c413873c670 Mon Sep 17 00:00:00 2001 From: Hubert Van De Walle Date: Mon, 20 Apr 2020 00:12:15 +0200 Subject: [PATCH 3/9] Add Read note --- api/src/controllers/NotesTitleController.kt | 47 +++++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/api/src/controllers/NotesTitleController.kt b/api/src/controllers/NotesTitleController.kt index f91155a..4fc4b46 100644 --- a/api/src/controllers/NotesTitleController.kt +++ b/api/src/controllers/NotesTitleController.kt @@ -2,6 +2,8 @@ package be.vandewalleh.controllers import be.vandewalleh.entities.Note import be.vandewalleh.entities.Tag +import be.vandewalleh.entities.User +import be.vandewalleh.tables.Chapters import be.vandewalleh.tables.Notes import be.vandewalleh.tables.Tags import be.vandewalleh.tables.Users @@ -11,9 +13,10 @@ import io.ktor.http.HttpStatusCode import io.ktor.request.receive import io.ktor.response.respond import io.ktor.routing.Route +import io.ktor.routing.get import io.ktor.routing.post import me.liuwj.ktorm.database.Database -import me.liuwj.ktorm.dsl.eq +import me.liuwj.ktorm.dsl.* import me.liuwj.ktorm.entity.* import org.kodein.di.Kodein import org.kodein.di.generic.instance @@ -26,15 +29,23 @@ class NotesTitleController(kodein: Kodein) : AuthCrudController("/notes/{noteTit return this.parameters["noteTitle"]!! } + private fun ApplicationCall.user(): User { + return db.sequenceOf(Users) + .find { it.email eq this.userEmail() } + ?: error("") + } + private class PostRequestBody(val tags: List) + private class ChapterDto(val title: String, val content: String) + private class GetResponseBody(val tags: List, val chapters: List) + override val route: Route.() -> Unit = { post { val title = call.noteTitle() ?: error("") val tags = call.receive().tags - val user = db.sequenceOf(Users) - .find { it.email eq call.userEmail() } ?: error("") + val user = call.user() val exists = db.sequenceOf(Notes) .filter { it.userId eq user.id } @@ -66,5 +77,35 @@ class NotesTitleController(kodein: Kodein) : AuthCrudController("/notes/{noteTit call.respondStatus(HttpStatusCode.Created) } + + get { + val user = call.user() + val title = call.noteTitle() ?: error("title missing") + + val noteId = db.from(Notes) + .select(Notes.id) + .where { Notes.userId eq user.id and (Notes.title eq title) } + .limit(0, 1) + .map { it[Notes.id]!! } + .firstOrNull() + ?: return@get call.respondStatus(HttpStatusCode.NotFound) + + val tags = db.from(Tags) + .select(Tags.name) + .where { Tags.noteId eq noteId } + .map { it[Tags.name]!! } + .toList() + + val chapters = db.from(Chapters) + .select(Chapters.title, Chapters.content) + .where { Chapters.noteId eq noteId } + .orderBy(Chapters.number.asc()) + .map { ChapterDto(it[Chapters.title]!!, it[Chapters.content]!!) } + .toList() + + val response = GetResponseBody(tags, chapters) + + call.respond(response) + } } } \ No newline at end of file From e064124e722e5b5428aecec0e8a78eb684e19d82 Mon Sep 17 00:00:00 2001 From: Hubert Van De Walle Date: Mon, 20 Apr 2020 00:12:21 +0200 Subject: [PATCH 4/9] Update tests --- api/http/test.http | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/api/http/test.http b/api/http/test.http index 287d50c..6af1fc7 100644 --- a/api/http/test.http +++ b/api/http/test.http @@ -23,4 +23,9 @@ Authorization: Bearer {{token}} "Test", "Dev" ] -} \ No newline at end of file +} + +### Create a note +GET http://localhost:8081/notes/babar +Content-Type: application/json +Authorization: Bearer {{token}} From 35ebaaa79f4a0283256ec21918328f2dda1b8cc5 Mon Sep 17 00:00:00 2001 From: Hubert Van De Walle Date: Mon, 20 Apr 2020 00:13:32 +0200 Subject: [PATCH 5/9] Update api docs --- api-doc/notes/index.apib | 1 - docs/index.html | 6 +----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/api-doc/notes/index.apib b/api-doc/notes/index.apib index 95a9892..2ca1111 100644 --- a/api-doc/notes/index.apib +++ b/api-doc/notes/index.apib @@ -54,7 +54,6 @@ + Response 200 (application/json) + Attributes (object) - + title: Kotlin (string) + tags: Dev, Server (array[string]) + chapters (array) + (Chapter) diff --git a/docs/index.html b/docs/index.html index 295d5b7..c840ea6 100644 --- a/docs/index.html +++ b/docs/index.html @@ -113,7 +113,6 @@ } }
Responses201409
This response has no content.
This response has no content.

Create a Note
POST/notes/{noteTitle}

URI Parameters
HideShow
noteTitle
string (required) Example: Kotlin

The title of the Note.


GET http://localhost:5000/notes/Kotlin
Requestsexample 1
Headers
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Responses200404
Headers
Content-Type: application/json
Body
{
-  "title": "Kotlin",
   "tags": [
     "Dev",
     "Server"
@@ -132,9 +131,6 @@
   "$schema": "http://json-schema.org/draft-04/schema#",
   "type": "object",
   "properties": {
-    "title": {
-      "type": "string"
-    },
     "tags": {
       "type": "array"
     },
@@ -221,7 +217,7 @@
       "type": "array"
     }
   }
-}

Get all tags
GET/tags


Generated by aglio on 19 Apr 2020