Clean routes
This commit is contained in:
parent
a7c832236c
commit
02c6b2a0c5
@ -9,7 +9,7 @@
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
<logger name="me.liuwj.ktorm.database" level="INFO"/>
|
||||
<logger name="me.liuwj.ktorm.database" level="DEBUG"/>
|
||||
<logger name="com.zaxxer.hikari" level="INFO"/>
|
||||
<logger name="org.eclipse.jetty" level="INFO"/>
|
||||
<logger name="io.netty" level="INFO"/>
|
||||
|
||||
@ -3,11 +3,15 @@ package be.vandewalleh
|
||||
import be.vandewalleh.auth.AuthenticationModule
|
||||
import be.vandewalleh.auth.SimpleJWT
|
||||
import be.vandewalleh.extensions.ApplicationBuilder
|
||||
import be.vandewalleh.extensions.RoutingBuilder
|
||||
import be.vandewalleh.factories.configurationFactory
|
||||
import be.vandewalleh.factories.dataSourceFactory
|
||||
import be.vandewalleh.factories.databaseFactory
|
||||
import be.vandewalleh.factories.simpleJwtFactory
|
||||
import be.vandewalleh.features.*
|
||||
import be.vandewalleh.routing.NoteRoutes
|
||||
import be.vandewalleh.routing.TagRoutes
|
||||
import be.vandewalleh.routing.UserRoutes
|
||||
import be.vandewalleh.services.NoteService
|
||||
import be.vandewalleh.services.UserService
|
||||
import org.kodein.di.Kodein
|
||||
@ -28,6 +32,19 @@ val mainModule = Kodein.Module("main") {
|
||||
bind<ApplicationBuilder>().inSet() with singleton { MigrationHook(instance()) }
|
||||
bind<ApplicationBuilder>().inSet() with singleton { ShutdownDatabaseConnection(instance()) }
|
||||
|
||||
bind() from setBinding<RoutingBuilder>()
|
||||
bind<RoutingBuilder>().inSet() with singleton { NoteRoutes(instance()) }
|
||||
bind<RoutingBuilder>().inSet() with singleton { TagRoutes(instance()) }
|
||||
bind<RoutingBuilder>().inSet() with singleton {
|
||||
UserRoutes(
|
||||
instance(tag = "auth"),
|
||||
instance(tag = "refresh"),
|
||||
instance(),
|
||||
instance()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
bind<SimpleJWT>(tag = "auth") with singleton { simpleJwtFactory(instance<Config>().jwt.auth) }
|
||||
bind<SimpleJWT>(tag = "refresh") with singleton { simpleJwtFactory(instance<Config>().jwt.refresh) }
|
||||
|
||||
|
||||
@ -1,11 +1,8 @@
|
||||
package be.vandewalleh
|
||||
|
||||
import be.vandewalleh.extensions.ApplicationBuilder
|
||||
import be.vandewalleh.routing.noteRoutes
|
||||
import be.vandewalleh.routing.tagsRoute
|
||||
import be.vandewalleh.routing.userRoutes
|
||||
import be.vandewalleh.extensions.RoutingBuilder
|
||||
import io.ktor.application.*
|
||||
import io.ktor.auth.*
|
||||
import io.ktor.routing.*
|
||||
import io.ktor.server.engine.*
|
||||
import io.ktor.server.netty.*
|
||||
@ -43,7 +40,7 @@ fun serve(kodein: Kodein) {
|
||||
}
|
||||
}
|
||||
with(embeddedServer(Netty, env)) {
|
||||
addShutdownHook { stop(3, 5, TimeUnit.SECONDS) }
|
||||
addShutdownHook { stop(1, 5, TimeUnit.SECONDS) }
|
||||
start(wait = true)
|
||||
}
|
||||
}
|
||||
@ -56,18 +53,8 @@ fun Application.module(kodein: Kodein) {
|
||||
it.builder(this)
|
||||
}
|
||||
|
||||
routing {
|
||||
route("/user") {
|
||||
userRoutes(kodein)
|
||||
}
|
||||
authenticate {
|
||||
route("/notes") {
|
||||
noteRoutes(kodein)
|
||||
}
|
||||
route("/tags") {
|
||||
tagsRoute(kodein)
|
||||
val routingBuilders: Set<RoutingBuilder> by kodein.instance()
|
||||
routingBuilders.forEach {
|
||||
routing(it.builder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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,52 +8,62 @@ 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
|
||||
}
|
||||
|
||||
private fun Route.getNote(noteService: NoteService) {
|
||||
get {
|
||||
val (userId, noteUuid) = call.userIdNoteIdPair()
|
||||
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, noteUuid) = call.userIdNoteIdPair()
|
||||
val userId = call.authenticatedUserId()
|
||||
val noteUuid = call.noteUuid()
|
||||
|
||||
val note = call.receiveValidated(noteValidator)
|
||||
note.uuid = noteUuid
|
||||
@ -61,15 +72,25 @@ fun Route.noteRoutes(kodein: Kodein) {
|
||||
call.respondStatus(HttpStatusCode.OK)
|
||||
else call.respondStatus(HttpStatusCode.NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Route.deleteNote(noteService: NoteService) {
|
||||
delete {
|
||||
val (userId, noteUuid) = call.userIdNoteIdPair()
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
class TagRoutes(noteService: NoteService) : RoutingBuilder({
|
||||
authenticate {
|
||||
get("/tags") {
|
||||
call.respond(noteService.getTags(call.authenticatedUserId()))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -18,12 +18,25 @@ class NoteService(private val db: Database) {
|
||||
/**
|
||||
* returns a list of [Note] associated with the userId
|
||||
*/
|
||||
suspend fun findAll(userId: Int, limit: Int = 20, offset: Int = 0): List<Note> = launchIo {
|
||||
suspend fun findAll(userId: Int, limit: Int = 20, after: UUID? = null): List<Note> = launchIo {
|
||||
|
||||
var previous: LocalDateTime? = null
|
||||
|
||||
if (after != null) {
|
||||
previous = db.sequenceOf(Notes, withReferences = false)
|
||||
.filter { it.userId eq userId and (it.uuid eq after) }
|
||||
.mapColumns { it.updatedAt }
|
||||
.firstOrNull() ?: return@launchIo emptyList()
|
||||
}
|
||||
|
||||
val notes = db.sequenceOf(Notes, withReferences = false)
|
||||
.filterColumns { it.columns - it.userId }
|
||||
.filter { it.userId eq userId }
|
||||
.filter {
|
||||
if (previous == null) it.userId eq userId
|
||||
else (it.userId eq userId) and (it.updatedAt less previous)
|
||||
}
|
||||
.sortedByDescending { it.updatedAt }
|
||||
.take(limit).drop(offset)
|
||||
.take(limit)
|
||||
.toList()
|
||||
|
||||
if (notes.isEmpty()) return@launchIo emptyList()
|
||||
|
||||
@ -10,6 +10,9 @@ val noteValidator: Validator<Note> = ValidatorBuilder.of<Note>()
|
||||
notNull().notBlank().lessThanOrEqual(50)
|
||||
}
|
||||
.konstraint(Note::tags) {
|
||||
this.lessThanOrEqual(10)
|
||||
lessThanOrEqual(10)
|
||||
}
|
||||
.konstraint(Note::content) {
|
||||
notNull().notBlank()
|
||||
}
|
||||
.build()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user