Performance improvements
This commit is contained in:
parent
8a9e878d5f
commit
f40349ea98
@ -12,7 +12,7 @@ fun Application.authenticationModule() {
|
||||
val simpleJwt by kodein.instance<SimpleJWT>(tag = "auth")
|
||||
verifier(simpleJwt.verifier)
|
||||
validate {
|
||||
UserIdPrincipal(it.payload.getClaim("email").asString())
|
||||
UserDbIdPrincipal(it.payload.getClaim("id").asInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,8 +11,8 @@ class SimpleJWT(secret: String, validity: Long, unit: TimeUnit) {
|
||||
private val algorithm = Algorithm.HMAC256(secret)
|
||||
|
||||
val verifier: JWTVerifier = JWT.require(algorithm).build()
|
||||
fun sign(email: String): String = JWT.create()
|
||||
.withClaim("email", email)
|
||||
fun sign(id: Int): String = JWT.create()
|
||||
.withClaim("id", id)
|
||||
.withExpiresAt(getExpiration())
|
||||
.sign(algorithm)
|
||||
|
||||
|
||||
5
api/src/auth/UserDbIdPrincipal.kt
Normal file
5
api/src/auth/UserDbIdPrincipal.kt
Normal file
@ -0,0 +1,5 @@
|
||||
package be.vandewalleh.auth
|
||||
|
||||
import io.ktor.auth.Principal
|
||||
|
||||
data class UserDbIdPrincipal(val id: Int) : Principal
|
||||
@ -1,5 +1,6 @@
|
||||
package be.vandewalleh.extensions
|
||||
|
||||
import be.vandewalleh.auth.UserDbIdPrincipal
|
||||
import be.vandewalleh.kodein
|
||||
import be.vandewalleh.services.FullNoteCreateDTO
|
||||
import be.vandewalleh.services.FullNotePatchDTO
|
||||
@ -11,21 +12,14 @@ import io.ktor.request.*
|
||||
import io.ktor.response.*
|
||||
import org.kodein.di.generic.instance
|
||||
|
||||
val userService by kodein.instance<UserService>()
|
||||
|
||||
suspend fun ApplicationCall.respondStatus(status: HttpStatusCode) {
|
||||
respond(status, status.description)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the user email for the currently authenticated user
|
||||
*/
|
||||
fun ApplicationCall.userEmail() = principal<UserIdPrincipal>()!!.name
|
||||
|
||||
/**
|
||||
* @return the userId for the currently authenticated user
|
||||
*/
|
||||
fun ApplicationCall.userId() = userService.getUserId(userEmail())!!
|
||||
fun ApplicationCall.userId() = principal<UserDbIdPrincipal>()!!.id
|
||||
|
||||
class NoteCreate(val title: String, val tags: List<String>)
|
||||
|
||||
|
||||
8
api/src/extensions/Coroutines.kt
Normal file
8
api/src/extensions/Coroutines.kt
Normal file
@ -0,0 +1,8 @@
|
||||
package be.vandewalleh.extensions
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
|
||||
fun <T> ioAsync(block: suspend CoroutineScope.() -> T): Deferred<T> {
|
||||
return CoroutineScope(Dispatchers.IO).async(block = block)
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
package be.vandewalleh.routing
|
||||
|
||||
import be.vandewalleh.auth.SimpleJWT
|
||||
import be.vandewalleh.auth.UserDbIdPrincipal
|
||||
import be.vandewalleh.auth.UsernamePasswordCredential
|
||||
import be.vandewalleh.extensions.respondStatus
|
||||
import be.vandewalleh.services.UserService
|
||||
@ -26,16 +27,16 @@ fun Routing.auth(kodein: Kodein) {
|
||||
post("/user/login") {
|
||||
val credential = call.receive<UsernamePasswordCredential>()
|
||||
|
||||
val (email, password) = userService.getEmailAndPasswordFromUsername(credential.username)
|
||||
val user = userService.getFromUsername(credential.username)
|
||||
?: return@post call.respondStatus(HttpStatusCode.Unauthorized)
|
||||
|
||||
if (!BCrypt.checkpw(credential.password, password)) {
|
||||
if (!BCrypt.checkpw(credential.password, user.password)) {
|
||||
return@post call.respondStatus(HttpStatusCode.Unauthorized)
|
||||
}
|
||||
|
||||
val response = DualToken(
|
||||
token = authSimpleJwt.sign(email),
|
||||
refreshToken = refreshSimpleJwt.sign(email)
|
||||
token = authSimpleJwt.sign(user.id),
|
||||
refreshToken = refreshSimpleJwt.sign(user.id)
|
||||
)
|
||||
return@post call.respond(response)
|
||||
}
|
||||
@ -43,16 +44,16 @@ fun Routing.auth(kodein: Kodein) {
|
||||
post("/user/refresh_token") {
|
||||
val token = call.receive<RefreshToken>().refreshToken
|
||||
|
||||
val email = try {
|
||||
val id = try {
|
||||
val decodedJWT = refreshSimpleJwt.verifier.verify(token)
|
||||
decodedJWT.getClaim("email").asString()
|
||||
decodedJWT.getClaim("id").asInt()
|
||||
} catch (e: JWTVerificationException) {
|
||||
return@post call.respondStatus(HttpStatusCode.Unauthorized)
|
||||
}
|
||||
|
||||
val response = DualToken(
|
||||
token = authSimpleJwt.sign(email),
|
||||
refreshToken = refreshSimpleJwt.sign(email)
|
||||
token = authSimpleJwt.sign(id),
|
||||
refreshToken = refreshSimpleJwt.sign(id)
|
||||
)
|
||||
return@post call.respond(response)
|
||||
}
|
||||
@ -60,8 +61,8 @@ fun Routing.auth(kodein: Kodein) {
|
||||
authenticate {
|
||||
get("/user/me") {
|
||||
// retrieve email from token
|
||||
val email = call.principal<UserIdPrincipal>()!!.name
|
||||
val info = userService.getUserInfo(email)
|
||||
val id = call.principal<UserDbIdPrincipal>()!!.id
|
||||
val info = userService.getUserInfo(id)
|
||||
if (info != null) call.respond(mapOf("user" to info))
|
||||
else call.respondStatus(HttpStatusCode.Unauthorized)
|
||||
}
|
||||
|
||||
@ -5,10 +5,10 @@ import be.vandewalleh.extensions.receiveNotePatch
|
||||
import be.vandewalleh.extensions.respondStatus
|
||||
import be.vandewalleh.extensions.userId
|
||||
import be.vandewalleh.services.NotesService
|
||||
import io.ktor.application.*
|
||||
import io.ktor.auth.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.response.*
|
||||
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
|
||||
@ -22,10 +22,8 @@ fun Routing.title(kodein: Kodein) {
|
||||
val userId = call.userId()
|
||||
val noteUuid = call.parameters.noteUuid()
|
||||
|
||||
val exists = notesService.noteExists(userId, noteUuid)
|
||||
if (!exists) return@get call.respondStatus(HttpStatusCode.NotFound)
|
||||
|
||||
val response = notesService.getNote(noteUuid)
|
||||
val response =
|
||||
notesService.getNote(userId, noteUuid) ?: return@get call.respondStatus(HttpStatusCode.NotFound)
|
||||
call.respond(response)
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
package be.vandewalleh.services
|
||||
|
||||
import be.vandewalleh.extensions.ioAsync
|
||||
import be.vandewalleh.tables.Chapters
|
||||
import be.vandewalleh.tables.Notes
|
||||
import be.vandewalleh.tables.Tags
|
||||
import me.liuwj.ktorm.database.*
|
||||
import me.liuwj.ktorm.database.Database
|
||||
import me.liuwj.ktorm.dsl.*
|
||||
import me.liuwj.ktorm.entity.*
|
||||
import org.kodein.di.Kodein
|
||||
@ -23,7 +24,7 @@ class NotesService(override val kodein: Kodein) : KodeinAware {
|
||||
* returns a list of [BasicNoteDTO] associated with the userId
|
||||
*/
|
||||
fun getNotes(userId: Int): List<BasicNoteDTO> {
|
||||
val notes = db.sequenceOf(Notes)
|
||||
val notes = db.sequenceOf(Notes, withReferences = false)
|
||||
.filterColumns { listOf(it.uuid, it.title, it.updatedAt) }
|
||||
.filter { it.userId eq userId }
|
||||
.sortedByDescending { it.updatedAt }
|
||||
@ -31,7 +32,7 @@ class NotesService(override val kodein: Kodein) : KodeinAware {
|
||||
|
||||
if (notes.isEmpty()) return emptyList()
|
||||
|
||||
val tags = db.sequenceOf(Tags)
|
||||
val tags = db.sequenceOf(Tags, withReferences = false)
|
||||
.filterColumns { listOf(it.noteUuid, it.name) }
|
||||
.filter { it.noteUuid inList notes.map { it.uuid } }
|
||||
.toList()
|
||||
@ -89,25 +90,39 @@ class NotesService(override val kodein: Kodein) : KodeinAware {
|
||||
return uuid
|
||||
}
|
||||
|
||||
fun getNote(noteUuid: UUID): FullNoteDTO {
|
||||
val note = db.sequenceOf(Notes)
|
||||
.filterColumns { listOf(it.title, it.updatedAt) }
|
||||
.find { it.uuid eq noteUuid } ?: error("Note not found")
|
||||
|
||||
val tags = db.from(Tags)
|
||||
|
||||
|
||||
suspend fun getNote(userId: Int, noteUuid: UUID): FullNoteDTO? {
|
||||
val deferredNote = ioAsync {
|
||||
db.sequenceOf(Notes, withReferences = false)
|
||||
.filterColumns { listOf(it.title, it.updatedAt) }
|
||||
.filter { it.uuid eq noteUuid }
|
||||
.find { it.userId eq userId }
|
||||
}
|
||||
|
||||
val deferredTags = ioAsync {
|
||||
db.from(Tags)
|
||||
.select(Tags.name)
|
||||
.where { Tags.noteUuid eq noteUuid }
|
||||
.map { it[Tags.name]!! }
|
||||
.toList()
|
||||
}
|
||||
|
||||
val chapters = db.from(Chapters)
|
||||
val deferredChapters = ioAsync {
|
||||
db.from(Chapters)
|
||||
.select(Chapters.title, Chapters.content)
|
||||
.where { Chapters.noteUuid eq noteUuid }
|
||||
.orderBy(Chapters.number.asc())
|
||||
.map { ChapterDTO(it[Chapters.title]!!, it[Chapters.content]!!) }
|
||||
.toList()
|
||||
}
|
||||
|
||||
val note = deferredNote.await() ?: return null
|
||||
|
||||
val updatedAtFormatted = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(note.updatedAt)
|
||||
val tags = deferredTags.await()
|
||||
val chapters = deferredChapters.await()
|
||||
return FullNoteDTO(
|
||||
uuid = noteUuid,
|
||||
title = note.title,
|
||||
|
||||
@ -30,11 +30,18 @@ class UserService(override val kodein: Kodein) : KodeinAware {
|
||||
/**
|
||||
* returns a user email and password from it's email if found or null
|
||||
*/
|
||||
fun getEmailAndPasswordFromUsername(username: String): Pair<String, String>? {
|
||||
fun getFromUsername(username: String): UserSchema? {
|
||||
return db.from(Users)
|
||||
.select(Users.email, Users.password)
|
||||
.select(Users.email, Users.password, Users.id)
|
||||
.where { Users.username eq username }
|
||||
.map { row -> row[Users.email]!! to row[Users.password]!! }
|
||||
.map { row ->
|
||||
UserSchema(
|
||||
row[Users.id]!!,
|
||||
username,
|
||||
row[Users.email]!!,
|
||||
row[Users.password]!!
|
||||
)
|
||||
}
|
||||
.firstOrNull()
|
||||
}
|
||||
|
||||
@ -52,11 +59,10 @@ class UserService(override val kodein: Kodein) : KodeinAware {
|
||||
.firstOrNull() != null
|
||||
}
|
||||
|
||||
fun getUserInfo(email: String): UserInfoDto? {
|
||||
fun getUserInfo(id: Int): UserInfoDto? {
|
||||
return db.from(Users)
|
||||
.select(Users.email, Users.username)
|
||||
.where { Users.email eq email }
|
||||
.limit(0, 1)
|
||||
.where { Users.id eq id }
|
||||
.map { UserInfoDto(it[Users.username]!!, it[Users.email]!!) }
|
||||
.firstOrNull()
|
||||
}
|
||||
@ -98,5 +104,6 @@ class UserService(override val kodein: Kodein) : KodeinAware {
|
||||
}
|
||||
}
|
||||
|
||||
data class UserSchema(val id: Int, val username: String, val email: String, val password: String)
|
||||
data class UserDto(val username: String, val email: String, val password: String)
|
||||
data class UserInfoDto(val username: String, val email: String)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user