📮 Adds action routes

This commit is contained in:
Daniel Svitan 2025-05-11 18:20:52 +02:00
parent de3bc8897e
commit 2fa523b814
6 changed files with 132 additions and 28 deletions

View File

@ -0,0 +1,38 @@
package dev.svitan.plugins
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.plugins.NotFoundException
import io.ktor.server.plugins.statuspages.StatusPages
import io.ktor.server.response.respond
class AuthorizationException(message: String) : Exception(message)
fun Application.configureErrors() {
install(StatusPages) {
exception<Throwable> { call, cause ->
when (cause) {
is IllegalArgumentException -> call.respond(
status = HttpStatusCode.BadRequest,
message = cause.message!!
)
is NotFoundException -> call.respond(
status = HttpStatusCode.NotFound,
message = cause.message ?: ""
)
is AuthorizationException -> call.respond(
status = HttpStatusCode.Unauthorized,
message = cause.message ?: ""
)
else -> call.respond(
status = HttpStatusCode.InternalServerError,
message = cause.message ?: ""
)
}
}
}
}

View File

@ -1,14 +1,11 @@
package dev.svitan.plugins package dev.svitan.plugins
import dev.svitan.routes.routeAction
import dev.svitan.routes.routeAuth import dev.svitan.routes.routeAuth
import io.ktor.http.*
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.plugins.NotFoundException
import io.ktor.server.plugins.requestvalidation.* import io.ktor.server.plugins.requestvalidation.*
import io.ktor.server.plugins.statuspages.*
import io.ktor.server.response.* import io.ktor.server.response.*
import io.ktor.server.routing.get import io.ktor.server.routing.*
import io.ktor.server.routing.routing
fun Application.configureRouting() { fun Application.configureRouting() {
install(RequestValidation) { install(RequestValidation) {
@ -19,27 +16,6 @@ fun Application.configureRouting() {
} }
} }
install(StatusPages) {
exception<Throwable> { call, cause ->
when (cause) {
is IllegalArgumentException -> call.respond(
status = HttpStatusCode.BadRequest,
message = cause.message!!
)
is NotFoundException -> call.respond(
status = HttpStatusCode.NotFound,
message = cause.message ?: ""
)
else -> call.respond(
status = HttpStatusCode.InternalServerError,
message = cause.message ?: ""
)
}
}
}
routing { routing {
get("/") { get("/") {
call.respond("Hello World!") call.respond("Hello World!")
@ -47,4 +23,5 @@ fun Application.configureRouting() {
} }
routeAuth() routeAuth()
routeAction()
} }

View File

@ -0,0 +1,67 @@
package dev.svitan.routes
import dev.svitan.plugins.AuthorizationException
import dev.svitan.schemas.ActionService
import dev.svitan.schemas.AuthService
import dev.svitan.schemas.NewActionDTO
import dev.svitan.schemas.RunActionDTO
import dev.svitan.schemas.sha256
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.plugins.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import java.util.*
fun Application.routeAction() {
routing {
authentication {
get("/action") {
call.respond(ActionService.readAll())
}
post("/action") {
val action = call.receive<NewActionDTO>()
call.respond(HttpStatusCode.Created, ActionService.create(action))
}
get("/action/{id}") {
val idRaw = call.parameters["id"] ?: throw BadRequestException("Invalid id")
val id = UUID.fromString(idRaw)
val action = ActionService.read(id) ?: throw NotFoundException()
call.respond(action)
}
post("/action/{id}") {
val idRaw = call.parameters["id"] ?: throw BadRequestException("Invalid id")
val id = UUID.fromString(idRaw)
}
put("/action/{id}") {
val idRaw = call.parameters["id"] ?: throw BadRequestException("Invalid id")
val id = UUID.fromString(idRaw)
val action = ActionService.read(id) ?: throw NotFoundException()
val run = call.receive<RunActionDTO>()
val auth = AuthService.validate(
UUID.fromString(action.authId),
run.pin,
run.key
) ?: throw AuthorizationException("Invalid pin or key")
// TODO: run the action
call.respond(HttpStatusCode.NoContent)
}
delete("/action/{id}") {
val idRaw = call.parameters["id"] ?: throw BadRequestException("Invalid id")
val id = UUID.fromString(idRaw)
ActionService.delete(id)
call.respond(HttpStatusCode.NoContent)
}
}
}
}

View File

@ -19,7 +19,7 @@ fun Application.routeAuth() {
call.respond(AuthService.readAll()) call.respond(AuthService.readAll())
} }
put("/auth") { post("/auth") {
val auth = call.receive<NewAuthDTO>() val auth = call.receive<NewAuthDTO>()
call.respond(HttpStatusCode.Created, AuthService.create(auth)) call.respond(HttpStatusCode.Created, AuthService.create(auth))
} }

View File

@ -40,7 +40,16 @@ class ActionDTO(
@Serializable @Serializable
class NewActionDTO( class NewActionDTO(
val name: String, val kind: String, val source: String, val authId: String val name: String,
val kind: String,
val source: String,
val authId: String
)
@Serializable
class RunActionDTO(
val pin: String,
val key: String
) )
class ActionService { class ActionService {

View File

@ -73,6 +73,19 @@ class AuthService {
} }
} }
fun validate(id: UUID, pin: String, key: String): AuthDTO? = transaction {
Auths.selectAll()
.where { (Auths.id eq id) and (Auths.pin eq sha256(pin)) and (Auths.key eq sha256(key)) }
.map {
AuthDTO(
it[Auths.id].toString(),
it[Auths.name],
it[Auths.createdAt]
)
}
.singleOrNull()
}
fun delete(id: UUID) { fun delete(id: UUID) {
transaction { transaction {
Auths.deleteWhere { Auths.id eq id } Auths.deleteWhere { Auths.id eq id }