Merge branch 'fix/notfound' into refactor/services
This commit is contained in:
commit
5850541ad8
@ -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]
|
||||
|
||||
|
||||
@ -1,14 +1,20 @@
|
||||
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.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
|
||||
@ -26,7 +32,6 @@ fun Application.module() {
|
||||
configurationFeature()
|
||||
|
||||
kodein = Kodein {
|
||||
import(controllerModule)
|
||||
import(configurationModule)
|
||||
import(serviceModule)
|
||||
|
||||
@ -41,11 +46,18 @@ fun Application.module() {
|
||||
val migration by kodein.instance<Migration>()
|
||||
migration.migrate()
|
||||
|
||||
val controllers by kodein.instance<Set<KodeinController>>()
|
||||
|
||||
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<Route> {
|
||||
return listOf(root) + root.children.flatMap { allRoutes(it) }
|
||||
}
|
||||
@ -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<SimpleJWT>()
|
||||
verifier(simpleJwt.verifier)
|
||||
validate {
|
||||
UserIdPrincipal(it.payload.getClaim("name").asString())
|
||||
|
||||
@ -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<Database>()
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
@ -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<KodeinController>()
|
||||
|
||||
bind<KodeinController>().inSet() with singleton { RegisterController(this.kodein) }
|
||||
bind<KodeinController>().inSet() with singleton { LoginController(this.kodein) }
|
||||
bind<KodeinController>().inSet() with singleton { NotesController(this.kodein) }
|
||||
bind<KodeinController>().inSet() with singleton { TitleController(this.kodein) }
|
||||
}
|
||||
@ -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<UserService>()
|
||||
|
||||
override fun Routing.routes() {
|
||||
post {
|
||||
val user = call.receive<UserRegistrationDto>()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<Database>()
|
||||
|
||||
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<String>)
|
||||
|
||||
private class ChapterDto(val title: String, val content: String)
|
||||
private class GetResponseBody(val tags: List<String>, val chapters: List<ChapterDto>)
|
||||
|
||||
private class PatchRequestBody(val title: String? = null, val tags: List<String>? = null)
|
||||
|
||||
override fun Routing.routes() {
|
||||
post {
|
||||
val title = call.noteTitle() ?: error("")
|
||||
val tags = call.receive<PostRequestBody>().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<PatchRequestBody>()
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<UserService>()
|
||||
|
||||
/**
|
||||
* retrieves the user email from the JWT token
|
||||
*/
|
||||
fun ApplicationCall.userEmail(): String =
|
||||
this.principal<UserIdPrincipal>()!!.name
|
||||
|
||||
fun ApplicationCall.userId(): Int =
|
||||
userService.getUserId(userEmail())!!
|
||||
|
||||
}
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
35
api/src/extensions/ApplicationCallExtensions.kt
Normal file
35
api/src/extensions/ApplicationCallExtensions.kt
Normal file
@ -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<UserService>()
|
||||
|
||||
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<UserIdPrincipal>()!!.name
|
||||
return userService.getUserId(email)!!
|
||||
}
|
||||
|
||||
private class Tags(val tags: List<String>)
|
||||
|
||||
suspend fun ApplicationCall.receiveTags(): List<String> {
|
||||
return receive<Tags>().tags
|
||||
}
|
||||
|
||||
data class NotePatch(val tags: List<String>?, val title: String?)
|
||||
|
||||
suspend fun ApplicationCall.receiveNotePatch() = receive<NotePatch>()
|
||||
22
api/src/extensions/ParametersExtensions.kt
Normal file
22
api/src/extensions/ParametersExtensions.kt
Normal file
@ -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<NotesService>()
|
||||
|
||||
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)
|
||||
}
|
||||
14
api/src/routing/ChaptersController.kt
Normal file
14
api/src/routing/ChaptersController.kt
Normal file
@ -0,0 +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}") {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,7 @@
|
||||
package be.vandewalleh.controllers
|
||||
package be.vandewalleh.routing
|
||||
|
||||
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
|
||||
@ -10,30 +9,31 @@ 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
|
||||
|
||||
class LoginController(kodein: Kodein) : KodeinController("/login", kodein) {
|
||||
private val simpleJwt by instance<SimpleJWT>()
|
||||
private val userService by instance<UserService>()
|
||||
fun Routing.login(kodein: Kodein) {
|
||||
val simpleJwt by kodein.instance<SimpleJWT>()
|
||||
val userService by kodein.instance<UserService>()
|
||||
|
||||
data class TokenResponse(val token: String)
|
||||
|
||||
override fun Routing.routes() {
|
||||
post {
|
||||
|
||||
route("/login"){
|
||||
post {
|
||||
val credential = call.receive<UsernamePasswordCredential>()
|
||||
|
||||
val (email, password) = userService.getEmailAndPasswordFromUsername(credential.username)
|
||||
?: return@post call.respondStatus(HttpStatusCode.Unauthorized)
|
||||
|
||||
?: return@post call.respond(HttpStatusCode.Unauthorized)
|
||||
|
||||
if (!BCrypt.checkpw(credential.password, password)) {
|
||||
return@post call.respondStatus(HttpStatusCode.Unauthorized)
|
||||
return@post call.respond(HttpStatusCode.Unauthorized)
|
||||
}
|
||||
|
||||
return@post call.respond(TokenResponse(simpleJwt.sign(email)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,19 +1,20 @@
|
||||
package be.vandewalleh.controllers
|
||||
package be.vandewalleh.routing
|
||||
|
||||
import be.vandewalleh.controllers.base.AuthCrudController
|
||||
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
|
||||
import org.kodein.di.Kodein
|
||||
import org.kodein.di.generic.instance
|
||||
|
||||
class NotesController(kodein: Kodein) : AuthCrudController("/notes", kodein) {
|
||||
private val notesService by kodein.instance<NotesService>()
|
||||
fun Routing.notes(kodein: Kodein) {
|
||||
val notesService by kodein.instance<NotesService>()
|
||||
|
||||
override fun Routing.routes() {
|
||||
get {
|
||||
authenticate {
|
||||
get("/notes") {
|
||||
val userId = call.userId()
|
||||
val notes = notesService.getNotes(userId)
|
||||
call.respond(notes)
|
||||
33
api/src/routing/RegisterController.kt
Normal file
33
api/src/routing/RegisterController.kt
Normal file
@ -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<UserService>()
|
||||
|
||||
post("/register") {
|
||||
val user = call.receive<UserRegistrationDto>()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
12
api/src/routing/Routes.kt
Normal file
12
api/src/routing/Routes.kt
Normal file
@ -0,0 +1,12 @@
|
||||
package be.vandewalleh.routing
|
||||
|
||||
import io.ktor.routing.Routing
|
||||
import org.kodein.di.Kodein
|
||||
|
||||
fun Routing.registerRoutes(kodein: Kodein) {
|
||||
login(kodein)
|
||||
register(kodein)
|
||||
notes(kodein)
|
||||
title(kodein)
|
||||
chapters(kodein)
|
||||
}
|
||||
64
api/src/routing/TitleController.kt
Normal file
64
api/src/routing/TitleController.kt
Normal file
@ -0,0 +1,64 @@
|
||||
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.*
|
||||
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/{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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<Database>()
|
||||
private val db by instance<Database>()
|
||||
|
||||
/**
|
||||
* returns a list of [NotesDTO] associated with the userId
|
||||
*/
|
||||
fun getNotes(userId: Int): List<NotesDTO> {
|
||||
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<NotesDTO> = 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<String>) {
|
||||
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<String>?, 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<String>, val chapters: List<ChaptersDTO>)
|
||||
data class NotesDTO(val title: String, val tags: List<String>, val updatedAt: String)
|
||||
@ -10,4 +10,5 @@ import org.kodein.di.generic.singleton
|
||||
*/
|
||||
val serviceModule = Kodein.Module(name = "Services") {
|
||||
bind<NotesService>() with singleton { NotesService(this.kodein) }
|
||||
bind<UserService>() with singleton { UserService(this.kodein) }
|
||||
}
|
||||
@ -36,7 +36,8 @@
|
||||
"<span class="hljs-attribute">type</span>": <span class="hljs-value"><span class="hljs-string">"string"</span>
|
||||
</span>}
|
||||
</span>}
|
||||
</span>}</code></pre><div style="height: 1px;"></div></div></div></div></div></div></div></div><div class="middle"><div id="accounts-create-an-account-post" class="action post"><h4 class="action-heading"><div class="name">Register a new user</div><a href="#accounts-create-an-account-post" class="method post">POST</a><code class="uri">/register</code></h4></div></div><hr class="split"><div class="middle"><div id="accounts-authenticate-user" class="resource"><h3 class="resource-heading">Authenticate user <a href="#accounts-authenticate-user" class="permalink">¶</a></h3><p>Authenticate one user to access protected routes.</p>
|
||||
</span>}</code></pre><div style="height: 1px;"></div></div></div></div></div></div></div></div><div class="middle"><div id="accounts-create-an-account-post" class="action post"><h4 class="action-heading"><div class="name">Register a new user</div><a href="#accounts-create-an-account-post" class="method post">POST</a><code class="uri">/register</code></h4></div></div><hr class="split"><div class="middle"><div id="accounts-authenticate-user" class="resource"><h3 class="resource-heading">Authenticate user <a href="#accounts-authenticate-user" class="permalink">¶</a></h3><p>Authenticate
|
||||
one user to access protected routing.</p>
|
||||
</div></div><div class="right"><div class="definition"><span class="method post">POST</span> <span class="uri"><span class="hostname">http://localhost:5000</span>/login</span></div><div class="tabs"><div class="example-names"><span>Requests</span><span class="tab-button">example 1</span></div><div class="tab"><div><div class="inner"><h5>Headers</h5><pre><code><span class="hljs-attribute">Content-Type</span>: <span class="hljs-string">application/json</span></code></pre><div style="height: 1px;"></div><h5>Body</h5><pre><code>{
|
||||
"<span class="hljs-attribute">username</span>": <span class="hljs-value"><span class="hljs-string">"babar"</span></span>,
|
||||
"<span class="hljs-attribute">password</span>": <span class="hljs-value"><span class="hljs-string">"tortue"</span>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user