Clean routes

This commit is contained in:
2020-07-01 16:35:13 +02:00
parent a7c832236c
commit 02c6b2a0c5
8 changed files with 181 additions and 108 deletions
+66 -45
View File
@@ -1,5 +1,6 @@
package be.vandewalleh.routing
import be.vandewalleh.extensions.RoutingBuilder
import be.vandewalleh.extensions.authenticatedUserId
import be.vandewalleh.extensions.respondStatus
import be.vandewalleh.features.ValidationException
@@ -7,69 +8,89 @@ import be.vandewalleh.services.NoteService
import be.vandewalleh.validation.noteValidator
import be.vandewalleh.validation.receiveValidated
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
import org.kodein.di.generic.instance
import java.util.*
fun Route.noteRoutes(kodein: Kodein) {
val noteService by kodein.instance<NoteService>()
class NoteRoutes(noteService: NoteService) : RoutingBuilder({
authenticate {
route("/notes") {
createNote(noteService)
getAllNotes(noteService)
route("/{uuid}") {
getNote(noteService)
updateNote(noteService)
deleteNote(noteService)
}
}
}
})
private fun Route.createNote(noteService: NoteService) {
post {
val userId = call.authenticatedUserId()
val note = call.receiveValidated(noteValidator)
val createdNote = noteService.create(userId, note)
call.respond(HttpStatusCode.Created, createdNote)
}
}
private fun Route.getAllNotes(noteService: NoteService) {
get {
val userId = call.authenticatedUserId()
val notes = noteService.findAll(userId)
val limit = call.parameters["limit"]?.toInt() ?: 20// FIXME validate
val after = call.parameters["after"]?.let { UUID.fromString(it) } // FIXME validate
val notes = noteService.findAll(userId, limit, after)
call.respond(notes)
}
}
route("/{uuid}") {
fun ApplicationCall.userIdNoteIdPair(): Pair<Int, UUID> {
val userId = authenticatedUserId()
val uuid = parameters["uuid"]
val noteUuid = try {
UUID.fromString(uuid)
} catch (e: IllegalArgumentException) {
throw ValidationException("`$uuid` is not a valid UUID")
}
return userId to noteUuid
}
get {
val (userId, noteUuid) = call.userIdNoteIdPair()
val response = noteService.find(userId, noteUuid)
?: return@get call.respondStatus(HttpStatusCode.NotFound)
call.respond(response)
}
put {
val (userId, noteUuid) = call.userIdNoteIdPair()
val note = call.receiveValidated(noteValidator)
note.uuid = noteUuid
if (noteService.updateNote(userId, note))
call.respondStatus(HttpStatusCode.OK)
else call.respondStatus(HttpStatusCode.NotFound)
}
delete {
val (userId, noteUuid) = call.userIdNoteIdPair()
if (noteService.delete(userId, noteUuid))
call.respondStatus(HttpStatusCode.OK)
else
call.respondStatus(HttpStatusCode.NotFound)
}
private fun Route.getNote(noteService: NoteService) {
get {
val userId = call.authenticatedUserId()
val noteUuid = call.noteUuid()
val response = noteService.find(userId, noteUuid)
?: return@get call.respondStatus(HttpStatusCode.NotFound)
call.respond(response)
}
}
private fun Route.updateNote(noteService: NoteService) {
put {
val userId = call.authenticatedUserId()
val noteUuid = call.noteUuid()
val note = call.receiveValidated(noteValidator)
note.uuid = noteUuid
if (noteService.updateNote(userId, note))
call.respondStatus(HttpStatusCode.OK)
else call.respondStatus(HttpStatusCode.NotFound)
}
}
private fun Route.deleteNote(noteService: NoteService) {
delete {
val userId = call.authenticatedUserId()
val noteUuid = call.noteUuid()
if (noteService.delete(userId, noteUuid))
call.respondStatus(HttpStatusCode.OK)
else
call.respondStatus(HttpStatusCode.NotFound)
}
}
private fun ApplicationCall.noteUuid(): UUID {
val uuid = parameters["uuid"]
return try {
UUID.fromString(uuid)
} catch (e: IllegalArgumentException) {
throw ValidationException("`$uuid` is not a valid UUID")
}
}
+8 -6
View File
@@ -1,17 +1,19 @@
package be.vandewalleh.routing
import be.vandewalleh.extensions.RoutingBuilder
import be.vandewalleh.extensions.authenticatedUserId
import be.vandewalleh.services.NoteService
import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.response.*
import io.ktor.routing.*
import org.kodein.di.Kodein
import org.kodein.di.generic.instance
fun Route.tagsRoute(kodein: Kodein) {
val noteService by kodein.instance<NoteService>()
get {
call.respond(noteService.getTags(call.authenticatedUserId()))
class TagRoutes(noteService: NoteService) : RoutingBuilder({
authenticate {
get("/tags") {
call.respond(noteService.getTags(call.authenticatedUserId()))
}
}
}
})
+64 -34
View File
@@ -1,8 +1,8 @@
package be.vandewalleh.routing
import be.vandewalleh.auth.SimpleJWT
import be.vandewalleh.auth.UserIdPrincipal
import be.vandewalleh.auth.UsernamePasswordCredential
import be.vandewalleh.extensions.RoutingBuilder
import be.vandewalleh.extensions.authenticatedUserId
import be.vandewalleh.extensions.respondStatus
import be.vandewalleh.features.PasswordHash
@@ -16,18 +16,51 @@ import io.ktor.http.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import org.kodein.di.Kodein
import org.kodein.di.generic.instance
data class RefreshToken(val refreshToken: String)
data class DualToken(val token: String, val refreshToken: String)
class UserRoutes(
authJWT: SimpleJWT,
refreshJWT: SimpleJWT,
userService: UserService,
passwordHash: PasswordHash
) : RoutingBuilder({
route("/user") {
createUser(userService)
route("/login") {
login(userService, passwordHash, authJWT, refreshJWT)
}
route("/refresh_token") {
refreshToken(userService, authJWT, refreshJWT)
}
authenticate {
deleteUser(userService)
route("/me") {
userInfo(userService)
}
}
}
})
fun Route.userRoutes(kodein: Kodein) {
val authSimpleJwt by kodein.instance<SimpleJWT>("auth")
val refreshSimpleJwt by kodein.instance<SimpleJWT>("refresh")
val userService by kodein.instance<UserService>()
val passwordHash by kodein.instance<PasswordHash>()
private fun Route.userInfo(userService: UserService) {
get {
val id = call.authenticatedUserId()
val info = userService.find(id)
if (info != null) call.respond(mapOf("user" to info))
else call.respondStatus(HttpStatusCode.Unauthorized)
}
}
private fun Route.deleteUser(userService: UserService) {
delete {
val userId = call.authenticatedUserId()
call.respondStatus(
if (userService.delete(userId)) HttpStatusCode.OK
else HttpStatusCode.NotFound
)
}
}
private fun Route.createUser(userService: UserService) {
post {
val user = call.receiveValidated(registerValidator)
@@ -39,8 +72,16 @@ fun Route.userRoutes(kodein: Kodein) {
call.respond(HttpStatusCode.Created, newUser)
}
}
post("/login") {
private fun Route.login(
userService: UserService,
passwordHash: PasswordHash,
authJWT: SimpleJWT,
refreshJWT: SimpleJWT
) {
post {
val credential = call.receive<UsernamePasswordCredential>()
val user = userService.find(credential.username)
@@ -51,17 +92,20 @@ fun Route.userRoutes(kodein: Kodein) {
}
val response = DualToken(
token = authSimpleJwt.sign(user.id),
refreshToken = refreshSimpleJwt.sign(user.id)
token = authJWT.sign(user.id),
refreshToken = refreshJWT.sign(user.id)
)
return@post call.respond(response)
}
}
post("/refresh_token") {
private fun Route.refreshToken(userService: UserService, authJWT: SimpleJWT, refreshJWT: SimpleJWT) {
post {
val token = call.receive<RefreshToken>().refreshToken
val id = try {
val decodedJWT = refreshSimpleJwt.verifier.verify(token)
val decodedJWT = refreshJWT.verifier.verify(token)
decodedJWT.getClaim("id").asInt()
} catch (e: JWTVerificationException) {
return@post call.respondStatus(HttpStatusCode.Unauthorized)
@@ -71,26 +115,12 @@ fun Route.userRoutes(kodein: Kodein) {
return@post call.respondStatus(HttpStatusCode.Unauthorized)
val response = DualToken(
token = authSimpleJwt.sign(id),
refreshToken = refreshSimpleJwt.sign(id)
token = authJWT.sign(id),
refreshToken = refreshJWT.sign(id)
)
return@post call.respond(response)
}
authenticate {
delete {
val userId = call.authenticatedUserId()
call.respondStatus(
if (userService.delete(userId)) HttpStatusCode.OK
else HttpStatusCode.NotFound
)
}
get("/me") {
val id = call.principal<UserIdPrincipal>()!!.id
val info = userService.find(id)
if (info != null) call.respond(mapOf("user" to info))
else call.respondStatus(HttpStatusCode.Unauthorized)
}
}
}
private data class RefreshToken(val refreshToken: String)
private data class DualToken(val token: String, val refreshToken: String)