From 1f9923dc82a2a93f36a8fce8cfa3717d36753893 Mon Sep 17 00:00:00 2001 From: Hubert Van De Walle Date: Mon, 20 Apr 2020 19:21:52 +0200 Subject: [PATCH 1/2] WIP --- api-doc/users/index.apib | 2 +- api/src/NotesApplication.kt | 25 ++- api/src/controllers/ChaptersController.kt | 24 --- api/src/controllers/Controllers.kt | 20 -- api/src/controllers/LoginController.kt | 39 ---- api/src/controllers/NotesController.kt | 22 --- api/src/controllers/RegisterController.kt | 35 ---- api/src/controllers/TitleController.kt | 171 ------------------ .../controllers/base/AuthCrudController.kt | 27 --- api/src/controllers/base/KodeinController.kt | 41 ----- .../extensions/ApplicationCallExtensions.kt | 35 ++++ api/src/extensions/ParametersExtensions.kt | 22 +++ api/src/routing/ChaptersController.kt | 8 + api/src/routing/LoginController.kt | 35 ++++ api/src/routing/NotesController.kt | 20 ++ api/src/routing/RegisterController.kt | 33 ++++ api/src/routing/Routes.kt | 30 +++ api/src/routing/TitleController.kt | 59 ++++++ api/src/services/NotesService.kt | 82 +++++++-- api/src/services/Services.kt | 1 + public/index.html | 3 +- 21 files changed, 333 insertions(+), 401 deletions(-) delete mode 100644 api/src/controllers/ChaptersController.kt delete mode 100644 api/src/controllers/Controllers.kt delete mode 100644 api/src/controllers/LoginController.kt delete mode 100644 api/src/controllers/NotesController.kt delete mode 100644 api/src/controllers/RegisterController.kt delete mode 100644 api/src/controllers/TitleController.kt delete mode 100644 api/src/controllers/base/AuthCrudController.kt delete mode 100644 api/src/controllers/base/KodeinController.kt create mode 100644 api/src/extensions/ApplicationCallExtensions.kt create mode 100644 api/src/extensions/ParametersExtensions.kt create mode 100644 api/src/routing/ChaptersController.kt create mode 100644 api/src/routing/LoginController.kt create mode 100644 api/src/routing/NotesController.kt create mode 100644 api/src/routing/RegisterController.kt create mode 100644 api/src/routing/Routes.kt create mode 100644 api/src/routing/TitleController.kt diff --git a/api-doc/users/index.apib b/api-doc/users/index.apib index 126ce93..e4fd27c 100644 --- a/api-doc/users/index.apib +++ b/api-doc/users/index.apib @@ -34,7 +34,7 @@ ## Authenticate user [/login] -Authenticate one user to access protected routes. +Authenticate one user to access protected routing. ### Authenticate a user [POST] diff --git a/api/src/NotesApplication.kt b/api/src/NotesApplication.kt index c9d1d57..125c687 100644 --- a/api/src/NotesApplication.kt +++ b/api/src/NotesApplication.kt @@ -1,14 +1,17 @@ package be.vandewalleh -import be.vandewalleh.controllers.base.KodeinController -import be.vandewalleh.controllers.controllerModule import be.vandewalleh.features.configurationFeature import be.vandewalleh.features.configurationModule import be.vandewalleh.features.features import be.vandewalleh.migrations.Migration +import be.vandewalleh.routing.registerRoutes import be.vandewalleh.services.serviceModule import io.ktor.application.Application +import io.ktor.application.feature import io.ktor.application.log +import io.ktor.routing.Route +import io.ktor.routing.Routing +import io.ktor.routing.RoutingPath.Companion.root import io.ktor.routing.routing import me.liuwj.ktorm.database.Database import org.kodein.di.Kodein @@ -26,7 +29,6 @@ fun Application.module() { configurationFeature() kodein = Kodein { - import(controllerModule) import(configurationModule) import(serviceModule) @@ -41,11 +43,18 @@ fun Application.module() { val migration by kodein.instance() migration.migrate() - val controllers by kodein.instance>() - routing { - controllers.forEach { - it.apply { registerRoutes() } - } + registerRoutes(kodein) } + + val root = feature(Routing) + val allRoutes = allRoutes(root) + allRoutes.forEach { + println(it.toString()) + } + +} + +fun allRoutes(root: Route): List { + return listOf(root) + root.children.flatMap { allRoutes(it) } } \ No newline at end of file diff --git a/api/src/controllers/ChaptersController.kt b/api/src/controllers/ChaptersController.kt deleted file mode 100644 index c42f2eb..0000000 --- a/api/src/controllers/ChaptersController.kt +++ /dev/null @@ -1,24 +0,0 @@ -package be.vandewalleh.controllers - -import be.vandewalleh.controllers.base.AuthCrudController -import io.ktor.application.ApplicationCall -import io.ktor.routing.Routing -import me.liuwj.ktorm.database.Database -import org.kodein.di.Kodein -import org.kodein.di.generic.instance - -class ChaptersController(kodein: Kodein) : AuthCrudController("/notes/{noteTitle}/chapters/{chapterNumber}", kodein) { - private val db by kodein.instance() - - private fun ApplicationCall.noteTitle(): String? { - return this.parameters["noteTitle"]!! - } - - private fun ApplicationCall.chapterNumber(): Int? { - return this.parameters["chapterNumber"]?.toIntOrNull() - } - - override fun Routing.routes() { - TODO("Not yet implemented") - } -} diff --git a/api/src/controllers/Controllers.kt b/api/src/controllers/Controllers.kt deleted file mode 100644 index 0ed5c15..0000000 --- a/api/src/controllers/Controllers.kt +++ /dev/null @@ -1,20 +0,0 @@ -package be.vandewalleh.controllers - -import be.vandewalleh.controllers.base.KodeinController -import org.kodein.di.Kodein -import org.kodein.di.generic.bind -import org.kodein.di.generic.inSet -import org.kodein.di.generic.setBinding -import org.kodein.di.generic.singleton - -/** - * [Kodein] controller module containing the app controllers - */ -val controllerModule = Kodein.Module(name = "Controller") { - bind() from setBinding() - - bind().inSet() with singleton { RegisterController(this.kodein) } - bind().inSet() with singleton { LoginController(this.kodein) } - bind().inSet() with singleton { NotesController(this.kodein) } - bind().inSet() with singleton { TitleController(this.kodein) } -} \ No newline at end of file diff --git a/api/src/controllers/LoginController.kt b/api/src/controllers/LoginController.kt deleted file mode 100644 index 176faf9..0000000 --- a/api/src/controllers/LoginController.kt +++ /dev/null @@ -1,39 +0,0 @@ -package be.vandewalleh.controllers - -import be.vandewalleh.auth.SimpleJWT -import be.vandewalleh.auth.UsernamePasswordCredential -import be.vandewalleh.controllers.base.KodeinController -import be.vandewalleh.services.UserService -import io.ktor.application.call -import io.ktor.http.HttpStatusCode -import io.ktor.request.receive -import io.ktor.response.respond -import io.ktor.routing.Routing -import io.ktor.routing.post -import org.kodein.di.Kodein -import org.kodein.di.generic.instance -import org.mindrot.jbcrypt.BCrypt - -class LoginController(kodein: Kodein) : KodeinController("/login", kodein) { - private val simpleJwt by instance() - private val userService by instance() - - data class TokenResponse(val token: String) - - override fun Routing.routes() { - post { - - val credential = call.receive() - - val (email, password) = userService.getEmailAndPasswordFromUsername(credential.username) - ?: return@post call.respondStatus(HttpStatusCode.Unauthorized) - - - if (!BCrypt.checkpw(credential.password, password)) { - return@post call.respondStatus(HttpStatusCode.Unauthorized) - } - - return@post call.respond(TokenResponse(simpleJwt.sign(email))) - } - } -} \ No newline at end of file diff --git a/api/src/controllers/NotesController.kt b/api/src/controllers/NotesController.kt deleted file mode 100644 index 5fdacb9..0000000 --- a/api/src/controllers/NotesController.kt +++ /dev/null @@ -1,22 +0,0 @@ -package be.vandewalleh.controllers - -import be.vandewalleh.controllers.base.AuthCrudController -import be.vandewalleh.services.NotesService -import io.ktor.application.call -import io.ktor.response.respond -import io.ktor.routing.Routing -import io.ktor.routing.get -import org.kodein.di.Kodein -import org.kodein.di.generic.instance - -class NotesController(kodein: Kodein) : AuthCrudController("/notes", kodein) { - private val notesService by kodein.instance() - - override fun Routing.routes() { - get { - val userId = call.userId() - val notes = notesService.getNotes(userId) - call.respond(notes) - } - } -} diff --git a/api/src/controllers/RegisterController.kt b/api/src/controllers/RegisterController.kt deleted file mode 100644 index 93aba9e..0000000 --- a/api/src/controllers/RegisterController.kt +++ /dev/null @@ -1,35 +0,0 @@ -package be.vandewalleh.controllers - -import be.vandewalleh.controllers.base.KodeinController -import be.vandewalleh.services.UserRegistrationDto -import be.vandewalleh.services.UserService -import io.ktor.application.call -import io.ktor.http.HttpStatusCode -import io.ktor.request.receive -import io.ktor.response.respond -import io.ktor.routing.Routing -import io.ktor.routing.post -import org.kodein.di.Kodein -import org.kodein.di.generic.instance -import org.mindrot.jbcrypt.BCrypt - -class RegisterController(kodein: Kodein) : KodeinController("/register", kodein) { - private val userService by instance() - - override fun Routing.routes() { - post { - val user = call.receive() - - if (userService.userExists(user.username, user.email)) - return@post call.respond(HttpStatusCode.Conflict) - - val hashedPassword = BCrypt.hashpw(user.password, BCrypt.gensalt()) - - userService.createUser( - UserRegistrationDto(user.username, user.email, hashedPassword) - ) - - return@post call.respondStatus(HttpStatusCode.Created) - } - } -} \ No newline at end of file diff --git a/api/src/controllers/TitleController.kt b/api/src/controllers/TitleController.kt deleted file mode 100644 index 0e7d432..0000000 --- a/api/src/controllers/TitleController.kt +++ /dev/null @@ -1,171 +0,0 @@ -package be.vandewalleh.controllers - -import be.vandewalleh.controllers.base.AuthCrudController -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 -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.* -import me.liuwj.ktorm.database.Database -import me.liuwj.ktorm.dsl.* -import me.liuwj.ktorm.entity.add -import me.liuwj.ktorm.entity.find -import me.liuwj.ktorm.entity.sequenceOf -import org.kodein.di.Kodein -import org.kodein.di.generic.instance -import java.time.LocalDateTime - -class TitleController(kodein: Kodein) : AuthCrudController("/notes/{noteTitle}", kodein) { - private val db by kodein.instance() - - private fun ApplicationCall.noteTitle(): String? { - return this.parameters["noteTitle"]!! - } - - private fun ApplicationCall.user(): User { - return db.sequenceOf(Users) - .find { it.email eq this.userEmail() } - ?: error("") - } - - /** - * Method that returns a [Notes] ID from it's title and the currently logged in user. - * returns null if none found - */ - private fun ApplicationCall.requestedNoteId(): Int? { - val user = user() - val title = noteTitle() ?: error("title missing") - - return 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() - } - - private class PostRequestBody(val tags: List) - - private class ChapterDto(val title: String, val content: String) - private class GetResponseBody(val tags: List, val chapters: List) - - private class PatchRequestBody(val title: String? = null, val tags: List? = null) - - override fun Routing.routes() { - post { - val title = call.noteTitle() ?: error("") - val tags = call.receive().tags - - val user = call.user() - - val exists = call.requestedNoteId() != 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) - } - - get { - val noteId = call.requestedNoteId() - ?: 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) - } - - patch { - val requestedChanges = call.receive() - - // This means no changes have been requested.. - if (requestedChanges.tags == null && requestedChanges.title == null) { - return@patch call.respondStatus(HttpStatusCode.BadRequest) - } - - val noteId = call.requestedNoteId() - ?: return@patch call.respondStatus(HttpStatusCode.NotFound) - - db.useTransaction { - if (requestedChanges.title != null) { - db.update(Notes) { - it.title to requestedChanges.title - where { it.id eq noteId } - } - } - - if (requestedChanges.tags != null) { - // delete all tags - db.delete(Tags) { - it.noteId eq noteId - } - - // put new ones - requestedChanges.tags.forEach { tagName -> - db.insert(Tags) { - it.name to tagName - it.noteId to noteId - } - } - } - - } - - call.respondStatus(HttpStatusCode.OK) - } - - delete { - val noteId = call.requestedNoteId() - ?: return@delete call.respondStatus(HttpStatusCode.NotFound) - - db.useTransaction { - db.delete(Tags) { it.noteId eq noteId } - db.delete(Chapters) { it.noteId eq noteId } - db.delete(Notes) { it.id eq noteId } - } - - call.respondStatus(HttpStatusCode.OK) - } - } -} \ No newline at end of file diff --git a/api/src/controllers/base/AuthCrudController.kt b/api/src/controllers/base/AuthCrudController.kt deleted file mode 100644 index b778f88..0000000 --- a/api/src/controllers/base/AuthCrudController.kt +++ /dev/null @@ -1,27 +0,0 @@ -package be.vandewalleh.controllers.base - -import be.vandewalleh.services.UserService -import io.ktor.application.ApplicationCall -import io.ktor.auth.UserIdPrincipal -import io.ktor.auth.principal -import org.kodein.di.Kodein -import org.kodein.di.generic.instance - -abstract class AuthCrudController( - path: String, - override val kodein: Kodein -) : - KodeinController(path, kodein, auth = true) { - - private val userService by instance() - - /** - * retrieves the user email from the JWT token - */ - fun ApplicationCall.userEmail(): String = - this.principal()!!.name - - fun ApplicationCall.userId(): Int = - userService.getUserId(userEmail())!! - -} diff --git a/api/src/controllers/base/KodeinController.kt b/api/src/controllers/base/KodeinController.kt deleted file mode 100644 index c287205..0000000 --- a/api/src/controllers/base/KodeinController.kt +++ /dev/null @@ -1,41 +0,0 @@ -package be.vandewalleh.controllers.base - -import io.ktor.application.ApplicationCall -import io.ktor.auth.authenticate -import io.ktor.http.HttpStatusCode -import io.ktor.response.respond -import io.ktor.routing.Routing -import io.ktor.routing.route -import org.kodein.di.Kodein -import org.kodein.di.KodeinAware - -abstract class KodeinController( - private val path: String, - override val kodein: Kodein, - private val auth: Boolean = false -) : KodeinAware { - - /** - * Method that subtypes must override to declare their [Routing] routes. - */ - abstract fun Routing.routes() - - - fun Routing.registerRoutes() { - if (auth) { - authenticate { - route(path) { - this@registerRoutes.routes() - } - } - } else { - route(path) { - this@registerRoutes.routes() - } - } - } - - suspend fun ApplicationCall.respondStatus(status: HttpStatusCode) { - this.respond(status, mapOf("message" to status.description)) - } -} diff --git a/api/src/extensions/ApplicationCallExtensions.kt b/api/src/extensions/ApplicationCallExtensions.kt new file mode 100644 index 0000000..de8ee00 --- /dev/null +++ b/api/src/extensions/ApplicationCallExtensions.kt @@ -0,0 +1,35 @@ +package be.vandewalleh.extensions + +import be.vandewalleh.kodein +import be.vandewalleh.services.UserService +import io.ktor.application.ApplicationCall +import io.ktor.auth.UserIdPrincipal +import io.ktor.auth.principal +import io.ktor.http.HttpStatusCode +import io.ktor.request.receive +import io.ktor.response.respond +import org.kodein.di.generic.instance + +val userService by kodein.instance() + +suspend fun ApplicationCall.respondStatus(status: HttpStatusCode) { + respond(status, status.description) +} + +/** + * @return the userId for the currently authenticated user + */ +fun ApplicationCall.userId(): Int { + val email = principal()!!.name + return userService.getUserId(email)!! +} + +private class Tags(val tags: List) + +suspend fun ApplicationCall.receiveTags(): List { + return receive().tags +} + +data class NotePatch(val tags: List?, val title: String?) + +suspend fun ApplicationCall.receiveNotePatch() = receive() \ No newline at end of file diff --git a/api/src/extensions/ParametersExtensions.kt b/api/src/extensions/ParametersExtensions.kt new file mode 100644 index 0000000..9b0b208 --- /dev/null +++ b/api/src/extensions/ParametersExtensions.kt @@ -0,0 +1,22 @@ +package be.vandewalleh.extensions + +import be.vandewalleh.kodein +import be.vandewalleh.services.NotesService +import be.vandewalleh.tables.Notes +import io.ktor.http.Parameters +import org.kodein.di.generic.instance + +val notesService by kodein.instance() + +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) +} \ No newline at end of file diff --git a/api/src/routing/ChaptersController.kt b/api/src/routing/ChaptersController.kt new file mode 100644 index 0000000..1fafeda --- /dev/null +++ b/api/src/routing/ChaptersController.kt @@ -0,0 +1,8 @@ +package be.vandewalleh.routing + +import io.ktor.routing.Routing +import org.kodein.di.Kodein + +fun Routing.chapters(kodein: Kodein) { + +} diff --git a/api/src/routing/LoginController.kt b/api/src/routing/LoginController.kt new file mode 100644 index 0000000..b51b755 --- /dev/null +++ b/api/src/routing/LoginController.kt @@ -0,0 +1,35 @@ +package be.vandewalleh.routing + +import be.vandewalleh.auth.SimpleJWT +import be.vandewalleh.auth.UsernamePasswordCredential +import be.vandewalleh.services.UserService +import io.ktor.application.call +import io.ktor.http.HttpStatusCode +import io.ktor.request.receive +import io.ktor.response.respond +import io.ktor.routing.Routing +import io.ktor.routing.post +import org.kodein.di.Kodein +import org.kodein.di.generic.instance +import org.mindrot.jbcrypt.BCrypt + +fun Routing.login(kodein: Kodein) { + val simpleJwt by kodein.instance() + val userService by kodein.instance() + + data class TokenResponse(val token: String) + + post { + val credential = call.receive() + + val (email, password) = userService.getEmailAndPasswordFromUsername(credential.username) + ?: return@post call.respond(HttpStatusCode.Unauthorized) + + if (!BCrypt.checkpw(credential.password, password)) { + return@post call.respond(HttpStatusCode.Unauthorized) + } + + return@post call.respond(TokenResponse(simpleJwt.sign(email))) + } + +} \ No newline at end of file diff --git a/api/src/routing/NotesController.kt b/api/src/routing/NotesController.kt new file mode 100644 index 0000000..bec6086 --- /dev/null +++ b/api/src/routing/NotesController.kt @@ -0,0 +1,20 @@ +package be.vandewalleh.routing + +import be.vandewalleh.extensions.userId +import be.vandewalleh.services.NotesService +import io.ktor.application.call +import io.ktor.response.respond +import io.ktor.routing.Routing +import io.ktor.routing.get +import org.kodein.di.Kodein +import org.kodein.di.generic.instance + +fun Routing.notes(kodein: Kodein) { + val notesService by kodein.instance() + + get { + val userId = call.userId() + val notes = notesService.getNotes(userId) + call.respond(notes) + } +} diff --git a/api/src/routing/RegisterController.kt b/api/src/routing/RegisterController.kt new file mode 100644 index 0000000..f36039f --- /dev/null +++ b/api/src/routing/RegisterController.kt @@ -0,0 +1,33 @@ +package be.vandewalleh.routing + +import be.vandewalleh.extensions.respondStatus +import be.vandewalleh.services.UserRegistrationDto +import be.vandewalleh.services.UserService +import io.ktor.application.call +import io.ktor.http.HttpStatusCode +import io.ktor.request.receive +import io.ktor.response.respond +import io.ktor.routing.Routing +import io.ktor.routing.post +import org.kodein.di.Kodein +import org.kodein.di.generic.instance +import org.mindrot.jbcrypt.BCrypt + +fun Routing.register(kodein: Kodein) { + val userService by kodein.instance() + + post { + val user = call.receive() + + if (userService.userExists(user.username, user.email)) + return@post call.respond(HttpStatusCode.Conflict) + + val hashedPassword = BCrypt.hashpw(user.password, BCrypt.gensalt()) + + userService.createUser( + UserRegistrationDto(user.username, user.email, hashedPassword) + ) + + return@post call.respondStatus(HttpStatusCode.Created) + } +} \ No newline at end of file diff --git a/api/src/routing/Routes.kt b/api/src/routing/Routes.kt new file mode 100644 index 0000000..24341d4 --- /dev/null +++ b/api/src/routing/Routes.kt @@ -0,0 +1,30 @@ +package be.vandewalleh.routing + +import io.ktor.auth.authenticate +import io.ktor.routing.Routing +import io.ktor.routing.route +import org.kodein.di.Kodein + +fun Routing.registerRoutes(kodein: Kodein) { + route("/login") { + this@registerRoutes.login(kodein) + } + + route("/register") { + this@registerRoutes.register(kodein) + } + + authenticate { + route("/notes") { + this@registerRoutes.notes(kodein) + + route("/{noteTitle}") { + this@registerRoutes.title(kodein) + + route("/chapters/{chapterNumber}") { + this@registerRoutes.chapters(kodein) + } + } + } + } +} \ No newline at end of file diff --git a/api/src/routing/TitleController.kt b/api/src/routing/TitleController.kt new file mode 100644 index 0000000..0e994cb --- /dev/null +++ b/api/src/routing/TitleController.kt @@ -0,0 +1,59 @@ +package be.vandewalleh.routing + +import be.vandewalleh.extensions.* +import be.vandewalleh.services.NotesService +import io.ktor.application.call +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() + + 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 { + val userId = call.userId() + val noteId = call.parameters.noteId(userId) + ?: return@get call.respondStatus(HttpStatusCode.NotFound) + + val response = notesService.getTagsAndChapters(noteId) + 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) + + notesService.updateNote(noteId, notePatch.tags, notePatch.title) + call.respondStatus(HttpStatusCode.OK) + } + + delete { + val userId = call.userId() + val noteId = call.parameters.noteId(userId) + ?: return@delete call.respondStatus(HttpStatusCode.NotFound) + + notesService.deleteNote(noteId) + call.respondStatus(HttpStatusCode.OK) + } +} diff --git a/api/src/services/NotesService.kt b/api/src/services/NotesService.kt index 43eed75..c88bef2 100644 --- a/api/src/services/NotesService.kt +++ b/api/src/services/NotesService.kt @@ -1,5 +1,6 @@ package be.vandewalleh.services +import be.vandewalleh.tables.Chapters import be.vandewalleh.tables.Notes import be.vandewalleh.tables.Tags import me.liuwj.ktorm.database.Database @@ -13,22 +14,20 @@ import java.time.format.DateTimeFormatter * service to handle database queries at the Notes level. */ class NotesService(override val kodein: Kodein) : KodeinAware { - val db by instance() + private val db by instance() /** * returns a list of [NotesDTO] associated with the userId */ - fun getNotes(userId: Int): List { - val notes = db.from(Notes) - .select(Notes.id, Notes.title, Notes.updatedAt) - .where { Notes.userId eq userId } - .orderBy(Notes.updatedAt.desc()) - .map { row -> - Notes.createEntity(row) - } - .toList() - - return notes.map { note -> + 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 -> + Notes.createEntity(row) + } + .toList() + .map { note -> val tags = db.from(Tags) .select(Tags.name) .where { Tags.noteId eq note.id } @@ -40,8 +39,67 @@ class NotesService(override val kodein: Kodein) : KodeinAware { NotesDTO(note.title, tags, 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 createNote(userId: Int, title: String, tags: List) { + TODO() } + fun getTagsAndChapters(noteId: Int): TagsChaptersDTO { + 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 { ChaptersDTO(it[Chapters.title]!!, it[Chapters.content]!!) } + .toList() + + return TagsChaptersDTO(tags, chapters) + } + + fun updateNote(noteId: Int, tags: List?, title: String?): Unit = + db.useTransaction { + if (title != null) { + db.update(Notes) { + it.title to title + where { it.id eq noteId } + } + } + + if (tags != null) { + // delete all tags + db.delete(Tags) { + it.noteId eq noteId + } + + // put new ones + tags.forEach { tagName -> + db.insert(Tags) { + it.name to tagName + it.noteId to noteId + } + } + } + } + + fun deleteNote(noteId: Int): Unit = + db.useTransaction { + db.delete(Tags) { it.noteId eq noteId } + db.delete(Chapters) { it.noteId eq noteId } + db.delete(Notes) { it.id eq noteId } + } } +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 diff --git a/api/src/services/Services.kt b/api/src/services/Services.kt index fdb698c..6db6891 100644 --- a/api/src/services/Services.kt +++ b/api/src/services/Services.kt @@ -10,4 +10,5 @@ import org.kodein.di.generic.singleton */ val serviceModule = Kodein.Module(name = "Services") { bind() with singleton { NotesService(this.kodein) } + bind() with singleton { UserService(this.kodein) } } \ No newline at end of file diff --git a/public/index.html b/public/index.html index c840ea6..1e970fc 100644 --- a/public/index.html +++ b/public/index.html @@ -36,7 +36,8 @@ "type": "string" } } -}

Register a new user
POST/register


Authenticate user

Authenticate one user to access protected routes.

+}

Register a new user
POST/register


Authenticate user

Authenticate + one user to access protected routing.

POST http://localhost:5000/login
Requestsexample 1
Headers
Content-Type: application/json
Body
{
   "username": "babar",
   "password": "tortue"

From 69c35d0732e9cf237ac8cba731b561c595ae0cd2 Mon Sep 17 00:00:00 2001
From: Hubert Van De Walle 
Date: Tue, 21 Apr 2020 16:06:53 +0200
Subject: [PATCH 2/2] This is ugly..

---
 api/src/NotesApplication.kt           |  3 +
 api/src/auth/AuthenticationModule.kt  |  2 +-
 api/src/routing/ChaptersController.kt |  6 ++
 api/src/routing/LoginController.kt    | 20 +++---
 api/src/routing/NotesController.kt    | 11 ++--
 api/src/routing/RegisterController.kt |  2 +-
 api/src/routing/Routes.kt             | 28 ++-------
 api/src/routing/TitleController.kt    | 87 ++++++++++++++-------------
 8 files changed, 81 insertions(+), 78 deletions(-)

diff --git a/api/src/NotesApplication.kt b/api/src/NotesApplication.kt
index 125c687..c1c3cff 100644
--- a/api/src/NotesApplication.kt
+++ b/api/src/NotesApplication.kt
@@ -7,11 +7,14 @@ import be.vandewalleh.migrations.Migration
 import be.vandewalleh.routing.registerRoutes
 import be.vandewalleh.services.serviceModule
 import io.ktor.application.Application
+import io.ktor.application.call
 import io.ktor.application.feature
 import io.ktor.application.log
+import io.ktor.response.respond
 import io.ktor.routing.Route
 import io.ktor.routing.Routing
 import io.ktor.routing.RoutingPath.Companion.root
+import io.ktor.routing.get
 import io.ktor.routing.routing
 import me.liuwj.ktorm.database.Database
 import org.kodein.di.Kodein
diff --git a/api/src/auth/AuthenticationModule.kt b/api/src/auth/AuthenticationModule.kt
index 1a9de0f..f57ad63 100644
--- a/api/src/auth/AuthenticationModule.kt
+++ b/api/src/auth/AuthenticationModule.kt
@@ -11,7 +11,7 @@ import org.kodein.di.generic.instance
 fun Application.authenticationModule() {
     install(Authentication) {
         jwt {
-            val simpleJwt: SimpleJWT by kodein.instance()
+            val simpleJwt by kodein.instance()
             verifier(simpleJwt.verifier)
             validate {
                 UserIdPrincipal(it.payload.getClaim("name").asString())
diff --git a/api/src/routing/ChaptersController.kt b/api/src/routing/ChaptersController.kt
index 1fafeda..76850b7 100644
--- a/api/src/routing/ChaptersController.kt
+++ b/api/src/routing/ChaptersController.kt
@@ -1,8 +1,14 @@
 package be.vandewalleh.routing
 
+import io.ktor.auth.authenticate
 import io.ktor.routing.Routing
+import io.ktor.routing.route
 import org.kodein.di.Kodein
 
 fun Routing.chapters(kodein: Kodein) {
+    authenticate {
+        route("/notes/{noteTitle}/chapters/{chapterNumber}") {
 
+        }
+    }
 }
diff --git a/api/src/routing/LoginController.kt b/api/src/routing/LoginController.kt
index b51b755..d776de1 100644
--- a/api/src/routing/LoginController.kt
+++ b/api/src/routing/LoginController.kt
@@ -9,6 +9,7 @@ import io.ktor.request.receive
 import io.ktor.response.respond
 import io.ktor.routing.Routing
 import io.ktor.routing.post
+import io.ktor.routing.route
 import org.kodein.di.Kodein
 import org.kodein.di.generic.instance
 import org.mindrot.jbcrypt.BCrypt
@@ -19,17 +20,20 @@ fun Routing.login(kodein: Kodein) {
 
     data class TokenResponse(val token: String)
 
-    post {
-        val credential = call.receive()
+    route("/login"){
+        post {
+            val credential = call.receive()
 
-        val (email, password) = userService.getEmailAndPasswordFromUsername(credential.username)
-            ?: return@post call.respond(HttpStatusCode.Unauthorized)
+            val (email, password) = userService.getEmailAndPasswordFromUsername(credential.username)
+                ?: return@post call.respond(HttpStatusCode.Unauthorized)
 
-        if (!BCrypt.checkpw(credential.password, password)) {
-            return@post call.respond(HttpStatusCode.Unauthorized)
+            if (!BCrypt.checkpw(credential.password, password)) {
+                return@post call.respond(HttpStatusCode.Unauthorized)
+            }
+
+            return@post call.respond(TokenResponse(simpleJwt.sign(email)))
         }
-
-        return@post call.respond(TokenResponse(simpleJwt.sign(email)))
     }
 
+
 }
\ No newline at end of file
diff --git a/api/src/routing/NotesController.kt b/api/src/routing/NotesController.kt
index bec6086..f4108ea 100644
--- a/api/src/routing/NotesController.kt
+++ b/api/src/routing/NotesController.kt
@@ -3,6 +3,7 @@ package be.vandewalleh.routing
 import be.vandewalleh.extensions.userId
 import be.vandewalleh.services.NotesService
 import io.ktor.application.call
+import io.ktor.auth.authenticate
 import io.ktor.response.respond
 import io.ktor.routing.Routing
 import io.ktor.routing.get
@@ -12,9 +13,11 @@ import org.kodein.di.generic.instance
 fun Routing.notes(kodein: Kodein) {
     val notesService by kodein.instance()
 
-    get {
-        val userId = call.userId()
-        val notes = notesService.getNotes(userId)
-        call.respond(notes)
+    authenticate {
+        get("/notes") {
+            val userId = call.userId()
+            val notes = notesService.getNotes(userId)
+            call.respond(notes)
+        }
     }
 }
diff --git a/api/src/routing/RegisterController.kt b/api/src/routing/RegisterController.kt
index f36039f..8f32ae0 100644
--- a/api/src/routing/RegisterController.kt
+++ b/api/src/routing/RegisterController.kt
@@ -16,7 +16,7 @@ import org.mindrot.jbcrypt.BCrypt
 fun Routing.register(kodein: Kodein) {
     val userService by kodein.instance()
 
-    post {
+    post("/register") {
         val user = call.receive()
 
         if (userService.userExists(user.username, user.email))
diff --git a/api/src/routing/Routes.kt b/api/src/routing/Routes.kt
index 24341d4..31e6401 100644
--- a/api/src/routing/Routes.kt
+++ b/api/src/routing/Routes.kt
@@ -1,30 +1,12 @@
 package be.vandewalleh.routing
 
-import io.ktor.auth.authenticate
 import io.ktor.routing.Routing
-import io.ktor.routing.route
 import org.kodein.di.Kodein
 
 fun Routing.registerRoutes(kodein: Kodein) {
-    route("/login") {
-        this@registerRoutes.login(kodein)
-    }
-
-    route("/register") {
-        this@registerRoutes.register(kodein)
-    }
-
-    authenticate {
-        route("/notes") {
-            this@registerRoutes.notes(kodein)
-
-            route("/{noteTitle}") {
-                this@registerRoutes.title(kodein)
-
-                route("/chapters/{chapterNumber}") {
-                    this@registerRoutes.chapters(kodein)
-                }
-            }
-        }
-    }
+    login(kodein)
+    register(kodein)
+    notes(kodein)
+    title(kodein)
+    chapters(kodein)
 }
\ No newline at end of file
diff --git a/api/src/routing/TitleController.kt b/api/src/routing/TitleController.kt
index 0e994cb..4fb4e95 100644
--- a/api/src/routing/TitleController.kt
+++ b/api/src/routing/TitleController.kt
@@ -3,6 +3,7 @@ package be.vandewalleh.routing
 import be.vandewalleh.extensions.*
 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.*
@@ -12,48 +13,52 @@ import org.kodein.di.generic.instance
 fun Routing.title(kodein: Kodein) {
     val notesService by kodein.instance()
 
-    post {
-        val userId = call.userId()
-        val title = call.parameters.noteTitle()
-        val tags = call.receiveTags()
-        val noteId = call.parameters.noteId(userId)
+    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)
+                if (noteId != null) {
+                    return@post call.respondStatus(HttpStatusCode.Conflict)
+                }
+
+                notesService.createNote(userId, title, tags)
+                call.respondStatus(HttpStatusCode.Created)
+            }
+
+            get {
+                val userId = call.userId()
+                val noteId = call.parameters.noteId(userId)
+                    ?: return@get call.respondStatus(HttpStatusCode.NotFound)
+
+                val response = notesService.getTagsAndChapters(noteId)
+                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)
+
+                notesService.updateNote(noteId, notePatch.tags, notePatch.title)
+                call.respondStatus(HttpStatusCode.OK)
+            }
+
+            delete {
+                val userId = call.userId()
+                val noteId = call.parameters.noteId(userId)
+                    ?: return@delete call.respondStatus(HttpStatusCode.NotFound)
+
+                notesService.deleteNote(noteId)
+                call.respondStatus(HttpStatusCode.OK)
+            }
         }
-
-        notesService.createNote(userId, title, tags)
-        call.respondStatus(HttpStatusCode.Created)
-    }
-
-    get {
-        val userId = call.userId()
-        val noteId = call.parameters.noteId(userId)
-            ?: return@get call.respondStatus(HttpStatusCode.NotFound)
-
-        val response = notesService.getTagsAndChapters(noteId)
-        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)
-
-        notesService.updateNote(noteId, notePatch.tags, notePatch.title)
-        call.respondStatus(HttpStatusCode.OK)
-    }
-
-    delete {
-        val userId = call.userId()
-        val noteId = call.parameters.noteId(userId)
-            ?: return@delete call.respondStatus(HttpStatusCode.NotFound)
-
-        notesService.deleteNote(noteId)
-        call.respondStatus(HttpStatusCode.OK)
     }
 }