split into controllers
This commit is contained in:
parent
4fb57ef61b
commit
470f882b29
@ -1,210 +1,31 @@
|
|||||||
|
import dev.mduchene.bolts.entity.EntityDefinitionRepository
|
||||||
import dev.mduchene.bolts.persistence.Database
|
import dev.mduchene.bolts.persistence.Database
|
||||||
import dev.mduchene.bolts.persistence.DatabaseConfig
|
import dev.mduchene.bolts.persistence.DatabaseConfig
|
||||||
import dev.mduchene.bolts.entity.EntityDefinition
|
|
||||||
import dev.mduchene.bolts.entity.EntityDefinitionRepository
|
|
||||||
import dev.mduchene.bolts.entity.EntityField
|
|
||||||
import dev.mduchene.bolts.entity.FieldType
|
|
||||||
import dev.mduchene.bolts.entity.RelationshipType
|
|
||||||
import dev.mduchene.bolts.user.LoginService
|
import dev.mduchene.bolts.user.LoginService
|
||||||
import dev.mduchene.bolts.user.SessionRepository
|
import dev.mduchene.bolts.user.SessionRepository
|
||||||
import dev.mduchene.bolts.user.UserRepository
|
import dev.mduchene.bolts.user.UserRepository
|
||||||
|
import dev.mduchene.bolts.web.AuthController
|
||||||
|
import dev.mduchene.bolts.web.EntityDefinitionController
|
||||||
|
import dev.mduchene.bolts.web.HomeController
|
||||||
|
import dev.mduchene.bolts.web.UserController
|
||||||
import io.javalin.Javalin
|
import io.javalin.Javalin
|
||||||
import io.javalin.http.HttpStatus
|
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
val database = Database(DatabaseConfig.fromEnvironment())
|
val database = Database(DatabaseConfig.fromEnvironment())
|
||||||
database.initialize(reinitialize = System.getenv("DB_REINITIALIZE").toBoolean())
|
database.initialize(reinitialize = System.getenv("DB_REINITIALIZE").toBoolean())
|
||||||
|
|
||||||
val users = UserRepository(database)
|
val users = UserRepository(database)
|
||||||
val loginService = LoginService(users, SessionRepository(database))
|
val loginService = LoginService(users, SessionRepository(database))
|
||||||
val entities = EntityDefinitionRepository(database)
|
|
||||||
loginService.initializeAdmin()
|
loginService.initializeAdmin()
|
||||||
|
|
||||||
|
val controllers = listOf(
|
||||||
|
HomeController(),
|
||||||
|
AuthController(loginService),
|
||||||
|
UserController(users, loginService),
|
||||||
|
EntityDefinitionController(EntityDefinitionRepository(database), loginService),
|
||||||
|
)
|
||||||
|
|
||||||
Javalin.create { config ->
|
Javalin.create { config ->
|
||||||
config.routes.apply {
|
controllers.forEach { it.register(config.routes) }
|
||||||
get("/") { ctx -> ctx.result("Hello World") }
|
|
||||||
post("/api/login") { ctx ->
|
|
||||||
val username = ctx.formParam("username").orEmpty()
|
|
||||||
val password = ctx.formParam("password").orEmpty()
|
|
||||||
|
|
||||||
val session = loginService.authenticate(username, password)
|
|
||||||
if (session != null) {
|
|
||||||
ctx.contentType("application/json")
|
|
||||||
.result(
|
|
||||||
"""{"username":"${session.user.username.toJsonString()}","role":"${session.user.role.toJsonString()}","token":"${session.token}"}""",
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
ctx.status(HttpStatus.UNAUTHORIZED)
|
|
||||||
.contentType("application/json")
|
|
||||||
.result("""{"message":"Invalid username or password"}""")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
get("/api/users") { ctx ->
|
|
||||||
val token = ctx.header("Authorization")?.removePrefix("Bearer ")
|
|
||||||
val currentUser = loginService.userForToken(token)
|
|
||||||
if (currentUser?.role != "admin") {
|
|
||||||
ctx.status(HttpStatus.FORBIDDEN)
|
|
||||||
.contentType("application/json")
|
|
||||||
.result("""{"message":"Admin access required"}""")
|
|
||||||
return@get
|
|
||||||
}
|
|
||||||
|
|
||||||
val result = users.findAll().joinToString(prefix = "[", postfix = "]") { user ->
|
|
||||||
"""{"id":${user.id},"username":"${user.username.toJsonString()}","role":"${user.role.toJsonString()}"}"""
|
|
||||||
}
|
|
||||||
ctx.contentType("application/json").result(result)
|
|
||||||
}
|
|
||||||
get("/api/entity-definitions") { ctx ->
|
|
||||||
if (!ctx.requireAdmin(loginService)) return@get
|
|
||||||
ctx.contentType("application/json").result(entities.findAll().toJson())
|
|
||||||
}
|
|
||||||
post("/api/entity-definitions") { ctx ->
|
|
||||||
if (!ctx.requireAdmin(loginService)) return@post
|
|
||||||
val name = ctx.requiredFormParam("name") ?: return@post
|
|
||||||
val identifier = ctx.requiredFormParam("identifier") ?: return@post
|
|
||||||
ctx.status(HttpStatus.CREATED).contentType("application/json")
|
|
||||||
.result(entities.create(name, identifier).toJson())
|
|
||||||
}
|
|
||||||
patch("/api/entity-definitions/{entityId}") { ctx ->
|
|
||||||
if (!ctx.requireAdmin(loginService)) return@patch
|
|
||||||
val id = ctx.pathParam("entityId").toLongOrNull()
|
|
||||||
val name = ctx.requiredFormParam("name")
|
|
||||||
val identifier = ctx.requiredFormParam("identifier")
|
|
||||||
if (id == null || name == null || identifier == null) return@patch
|
|
||||||
val entity = entities.update(id, name, identifier)
|
|
||||||
if (entity == null) ctx.notFound() else ctx.contentType("application/json").result(entity.toJson())
|
|
||||||
}
|
|
||||||
delete("/api/entity-definitions/{entityId}") { ctx ->
|
|
||||||
if (!ctx.requireAdmin(loginService)) return@delete
|
|
||||||
val id = ctx.pathParam("entityId").toLongOrNull()
|
|
||||||
if (id == null || !entities.delete(id)) ctx.notFound() else ctx.status(HttpStatus.NO_CONTENT)
|
|
||||||
}
|
|
||||||
post("/api/entity-definitions/{entityId}/fields") { ctx ->
|
|
||||||
if (!ctx.requireAdmin(loginService)) return@post
|
|
||||||
val entityId = ctx.pathParam("entityId").toLongOrNull()
|
|
||||||
val name = ctx.requiredFormParam("name")
|
|
||||||
val identifier = ctx.requiredFormParam("identifier")
|
|
||||||
val type = ctx.formParam("type")?.let(FieldType::from)
|
|
||||||
val relationship = ctx.relationshipDetails(type, entities)
|
|
||||||
if (entityId == null || name == null || identifier == null || type == null || relationship == null) {
|
|
||||||
if (type == null) ctx.badRequest("A valid field type is required")
|
|
||||||
return@post
|
|
||||||
}
|
|
||||||
val field = entities.createField(
|
|
||||||
entityId,
|
|
||||||
name,
|
|
||||||
identifier,
|
|
||||||
type,
|
|
||||||
relationship.targetEntityId,
|
|
||||||
relationship.type,
|
|
||||||
)
|
|
||||||
if (field == null) ctx.notFound() else {
|
|
||||||
ctx.status(HttpStatus.CREATED).contentType("application/json").result(field.toJson())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
patch("/api/entity-definitions/{entityId}/fields/{fieldId}") { ctx ->
|
|
||||||
if (!ctx.requireAdmin(loginService)) return@patch
|
|
||||||
val entityId = ctx.pathParam("entityId").toLongOrNull()
|
|
||||||
val fieldId = ctx.pathParam("fieldId").toLongOrNull()
|
|
||||||
val name = ctx.requiredFormParam("name")
|
|
||||||
val identifier = ctx.requiredFormParam("identifier")
|
|
||||||
val type = ctx.formParam("type")?.let(FieldType::from)
|
|
||||||
val relationship = ctx.relationshipDetails(type, entities)
|
|
||||||
if (entityId == null || fieldId == null || name == null || identifier == null || type == null || relationship == null) {
|
|
||||||
if (type == null) ctx.badRequest("A valid field type is required")
|
|
||||||
return@patch
|
|
||||||
}
|
|
||||||
val field = entities.updateField(
|
|
||||||
entityId,
|
|
||||||
fieldId,
|
|
||||||
name,
|
|
||||||
identifier,
|
|
||||||
type,
|
|
||||||
relationship.targetEntityId,
|
|
||||||
relationship.type,
|
|
||||||
)
|
|
||||||
if (field == null) ctx.notFound() else ctx.contentType("application/json").result(field.toJson())
|
|
||||||
}
|
|
||||||
delete("/api/entity-definitions/{entityId}/fields/{fieldId}") { ctx ->
|
|
||||||
if (!ctx.requireAdmin(loginService)) return@delete
|
|
||||||
val entityId = ctx.pathParam("entityId").toLongOrNull()
|
|
||||||
val fieldId = ctx.pathParam("fieldId").toLongOrNull()
|
|
||||||
if (entityId == null || fieldId == null || !entities.deleteField(entityId, fieldId)) {
|
|
||||||
ctx.notFound()
|
|
||||||
} else {
|
|
||||||
ctx.status(HttpStatus.NO_CONTENT)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.start(System.getenv("SERVER_PORT")?.toIntOrNull() ?: 7070)
|
}.start(System.getenv("SERVER_PORT")?.toIntOrNull() ?: 7070)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun io.javalin.http.Context.requireAdmin(loginService: LoginService): Boolean {
|
|
||||||
val token = header("Authorization")?.removePrefix("Bearer ")
|
|
||||||
if (loginService.userForToken(token)?.role == "admin") return true
|
|
||||||
status(HttpStatus.FORBIDDEN).contentType("application/json").result("""{"message":"Admin access required"}""")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun io.javalin.http.Context.requiredFormParam(name: String): String? {
|
|
||||||
val value = formParam(name)?.trim()?.takeIf(String::isNotEmpty)
|
|
||||||
if (value == null) badRequest("$name is required")
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun io.javalin.http.Context.badRequest(message: String) {
|
|
||||||
status(HttpStatus.BAD_REQUEST).contentType("application/json")
|
|
||||||
.result("""{"message":"${message.toJsonString()}"}""")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun io.javalin.http.Context.notFound() {
|
|
||||||
status(HttpStatus.NOT_FOUND).contentType("application/json").result("""{"message":"Not found"}""")
|
|
||||||
}
|
|
||||||
|
|
||||||
private data class RelationshipDetails(
|
|
||||||
val targetEntityId: Long?,
|
|
||||||
val type: RelationshipType?,
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun io.javalin.http.Context.relationshipDetails(
|
|
||||||
fieldType: FieldType?,
|
|
||||||
entities: EntityDefinitionRepository,
|
|
||||||
): RelationshipDetails? {
|
|
||||||
if (fieldType != FieldType.RELATIONSHIP) return RelationshipDetails(null, null)
|
|
||||||
|
|
||||||
val targetEntityId = formParam("targetEntityId")?.toLongOrNull()
|
|
||||||
val relationshipType = formParam("relationshipType")?.let(RelationshipType::from)
|
|
||||||
if (targetEntityId == null || entities.findAll().none { it.id == targetEntityId }) {
|
|
||||||
badRequest("A valid target entity is required")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
if (relationshipType == null) {
|
|
||||||
badRequest("A valid relationship type is required")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return RelationshipDetails(targetEntityId, relationshipType)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun List<EntityDefinition>.toJson() = joinToString(prefix = "[", postfix = "]") { it.toJson() }
|
|
||||||
private fun EntityDefinition.toJson() =
|
|
||||||
"""{"id":$id,"name":"${name.toJsonString()}","identifier":"${identifier.toJsonString()}","fields":${fields.joinToString(prefix = "[", postfix = "]") { it.toJson() }}}"""
|
|
||||||
private fun EntityField.toJson() =
|
|
||||||
"""{"id":$id,"name":"${name.toJsonString()}","identifier":"${identifier.toJsonString()}","type":"$type","targetEntityId":${targetEntityId ?: "null"},"targetEntityName":${targetEntityName?.let { "\"${it.toJsonString()}\"" } ?: "null"},"relationshipType":${relationshipType?.let { "\"$it\"" } ?: "null"}}"""
|
|
||||||
|
|
||||||
private fun String.toJsonString() = buildString {
|
|
||||||
for (character in this@toJsonString) {
|
|
||||||
when (character) {
|
|
||||||
'\\' -> append("\\\\")
|
|
||||||
'"' -> append("\\\"")
|
|
||||||
'\b' -> append("\\b")
|
|
||||||
'\u000C' -> append("\\f")
|
|
||||||
'\n' -> append("\\n")
|
|
||||||
'\r' -> append("\\r")
|
|
||||||
'\t' -> append("\\t")
|
|
||||||
else -> if (character.code < 0x20) {
|
|
||||||
append("\\u%04x".format(character.code))
|
|
||||||
} else {
|
|
||||||
append(character)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
24
src/main/kotlin/dev/mduchene/bolts/web/AuthController.kt
Normal file
24
src/main/kotlin/dev/mduchene/bolts/web/AuthController.kt
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package dev.mduchene.bolts.web
|
||||||
|
|
||||||
|
import dev.mduchene.bolts.user.LoginService
|
||||||
|
import io.javalin.http.HttpStatus
|
||||||
|
import io.javalin.router.JavalinDefaultRoutingApi
|
||||||
|
|
||||||
|
class AuthController(private val loginService: LoginService) : Controller {
|
||||||
|
override fun register(routes: JavalinDefaultRoutingApi) {
|
||||||
|
routes.post("/api/login") { ctx ->
|
||||||
|
val username = ctx.formParam("username").orEmpty()
|
||||||
|
val password = ctx.formParam("password").orEmpty()
|
||||||
|
val session = loginService.authenticate(username, password)
|
||||||
|
|
||||||
|
if (session != null) {
|
||||||
|
ctx.jsonResult(
|
||||||
|
"""{"username":"${session.user.username.toJsonString()}","role":"${session.user.role.toJsonString()}","token":"${session.token}"}""",
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ctx.status(HttpStatus.UNAUTHORIZED)
|
||||||
|
.jsonResult("""{"message":"Invalid username or password"}""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/main/kotlin/dev/mduchene/bolts/web/Controller.kt
Normal file
7
src/main/kotlin/dev/mduchene/bolts/web/Controller.kt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package dev.mduchene.bolts.web
|
||||||
|
|
||||||
|
import io.javalin.router.JavalinDefaultRoutingApi
|
||||||
|
|
||||||
|
fun interface Controller {
|
||||||
|
fun register(routes: JavalinDefaultRoutingApi)
|
||||||
|
}
|
||||||
@ -0,0 +1,131 @@
|
|||||||
|
package dev.mduchene.bolts.web
|
||||||
|
|
||||||
|
import dev.mduchene.bolts.entity.EntityDefinition
|
||||||
|
import dev.mduchene.bolts.entity.EntityDefinitionRepository
|
||||||
|
import dev.mduchene.bolts.entity.EntityField
|
||||||
|
import dev.mduchene.bolts.entity.FieldType
|
||||||
|
import dev.mduchene.bolts.entity.RelationshipType
|
||||||
|
import dev.mduchene.bolts.user.LoginService
|
||||||
|
import io.javalin.http.Context
|
||||||
|
import io.javalin.http.HttpStatus
|
||||||
|
import io.javalin.router.JavalinDefaultRoutingApi
|
||||||
|
|
||||||
|
class EntityDefinitionController(
|
||||||
|
private val entities: EntityDefinitionRepository,
|
||||||
|
private val loginService: LoginService,
|
||||||
|
) : Controller {
|
||||||
|
override fun register(routes: JavalinDefaultRoutingApi) {
|
||||||
|
routes.get("/api/entity-definitions") { ctx ->
|
||||||
|
if (!ctx.requireAdmin(loginService)) return@get
|
||||||
|
ctx.jsonResult(entities.findAll().toJson())
|
||||||
|
}
|
||||||
|
routes.post("/api/entity-definitions") { ctx ->
|
||||||
|
if (!ctx.requireAdmin(loginService)) return@post
|
||||||
|
val name = ctx.requiredFormParam("name") ?: return@post
|
||||||
|
val identifier = ctx.requiredFormParam("identifier") ?: return@post
|
||||||
|
ctx.status(HttpStatus.CREATED).jsonResult(entities.create(name, identifier).toJson())
|
||||||
|
}
|
||||||
|
routes.patch("/api/entity-definitions/{entityId}") { ctx ->
|
||||||
|
if (!ctx.requireAdmin(loginService)) return@patch
|
||||||
|
val id = ctx.pathParam("entityId").toLongOrNull()
|
||||||
|
val name = ctx.requiredFormParam("name")
|
||||||
|
val identifier = ctx.requiredFormParam("identifier")
|
||||||
|
if (id == null || name == null || identifier == null) return@patch
|
||||||
|
val entity = entities.update(id, name, identifier)
|
||||||
|
if (entity == null) ctx.notFound() else ctx.jsonResult(entity.toJson())
|
||||||
|
}
|
||||||
|
routes.delete("/api/entity-definitions/{entityId}") { ctx ->
|
||||||
|
if (!ctx.requireAdmin(loginService)) return@delete
|
||||||
|
val id = ctx.pathParam("entityId").toLongOrNull()
|
||||||
|
if (id == null || !entities.delete(id)) ctx.notFound() else ctx.status(HttpStatus.NO_CONTENT)
|
||||||
|
}
|
||||||
|
routes.post("/api/entity-definitions/{entityId}/fields") { ctx ->
|
||||||
|
if (!ctx.requireAdmin(loginService)) return@post
|
||||||
|
val entityId = ctx.pathParam("entityId").toLongOrNull()
|
||||||
|
val name = ctx.requiredFormParam("name")
|
||||||
|
val identifier = ctx.requiredFormParam("identifier")
|
||||||
|
val type = ctx.formParam("type")?.let(FieldType::from)
|
||||||
|
val relationship = ctx.relationshipDetails(type)
|
||||||
|
if (entityId == null || name == null || identifier == null || type == null || relationship == null) {
|
||||||
|
if (type == null) ctx.badRequest("A valid field type is required")
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
val field = entities.createField(
|
||||||
|
entityId,
|
||||||
|
name,
|
||||||
|
identifier,
|
||||||
|
type,
|
||||||
|
relationship.targetEntityId,
|
||||||
|
relationship.type,
|
||||||
|
)
|
||||||
|
if (field == null) ctx.notFound() else {
|
||||||
|
ctx.status(HttpStatus.CREATED).jsonResult(field.toJson())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
routes.patch("/api/entity-definitions/{entityId}/fields/{fieldId}") { ctx ->
|
||||||
|
if (!ctx.requireAdmin(loginService)) return@patch
|
||||||
|
val entityId = ctx.pathParam("entityId").toLongOrNull()
|
||||||
|
val fieldId = ctx.pathParam("fieldId").toLongOrNull()
|
||||||
|
val name = ctx.requiredFormParam("name")
|
||||||
|
val identifier = ctx.requiredFormParam("identifier")
|
||||||
|
val type = ctx.formParam("type")?.let(FieldType::from)
|
||||||
|
val relationship = ctx.relationshipDetails(type)
|
||||||
|
if (
|
||||||
|
entityId == null || fieldId == null || name == null || identifier == null ||
|
||||||
|
type == null || relationship == null
|
||||||
|
) {
|
||||||
|
if (type == null) ctx.badRequest("A valid field type is required")
|
||||||
|
return@patch
|
||||||
|
}
|
||||||
|
val field = entities.updateField(
|
||||||
|
entityId,
|
||||||
|
fieldId,
|
||||||
|
name,
|
||||||
|
identifier,
|
||||||
|
type,
|
||||||
|
relationship.targetEntityId,
|
||||||
|
relationship.type,
|
||||||
|
)
|
||||||
|
if (field == null) ctx.notFound() else ctx.jsonResult(field.toJson())
|
||||||
|
}
|
||||||
|
routes.delete("/api/entity-definitions/{entityId}/fields/{fieldId}") { ctx ->
|
||||||
|
if (!ctx.requireAdmin(loginService)) return@delete
|
||||||
|
val entityId = ctx.pathParam("entityId").toLongOrNull()
|
||||||
|
val fieldId = ctx.pathParam("fieldId").toLongOrNull()
|
||||||
|
if (entityId == null || fieldId == null || !entities.deleteField(entityId, fieldId)) {
|
||||||
|
ctx.notFound()
|
||||||
|
} else {
|
||||||
|
ctx.status(HttpStatus.NO_CONTENT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Context.relationshipDetails(fieldType: FieldType?): RelationshipDetails? {
|
||||||
|
if (fieldType != FieldType.RELATIONSHIP) return RelationshipDetails(null, null)
|
||||||
|
|
||||||
|
val targetEntityId = formParam("targetEntityId")?.toLongOrNull()
|
||||||
|
val relationshipType = formParam("relationshipType")?.let(RelationshipType::from)
|
||||||
|
if (targetEntityId == null || entities.findAll().none { it.id == targetEntityId }) {
|
||||||
|
badRequest("A valid target entity is required")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (relationshipType == null) {
|
||||||
|
badRequest("A valid relationship type is required")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return RelationshipDetails(targetEntityId, relationshipType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class RelationshipDetails(
|
||||||
|
val targetEntityId: Long?,
|
||||||
|
val type: RelationshipType?,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<EntityDefinition>.toJson() = joinToString(prefix = "[", postfix = "]") { it.toJson() }
|
||||||
|
|
||||||
|
private fun EntityDefinition.toJson() =
|
||||||
|
"""{"id":$id,"name":"${name.toJsonString()}","identifier":"${identifier.toJsonString()}","fields":${fields.joinToString(prefix = "[", postfix = "]") { it.toJson() }}}"""
|
||||||
|
|
||||||
|
private fun EntityField.toJson() =
|
||||||
|
"""{"id":$id,"name":"${name.toJsonString()}","identifier":"${identifier.toJsonString()}","type":"$type","targetEntityId":${targetEntityId ?: "null"},"targetEntityName":${targetEntityName?.let { "\"${it.toJsonString()}\"" } ?: "null"},"relationshipType":${relationshipType?.let { "\"$it\"" } ?: "null"}}"""
|
||||||
9
src/main/kotlin/dev/mduchene/bolts/web/HomeController.kt
Normal file
9
src/main/kotlin/dev/mduchene/bolts/web/HomeController.kt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package dev.mduchene.bolts.web
|
||||||
|
|
||||||
|
import io.javalin.router.JavalinDefaultRoutingApi
|
||||||
|
|
||||||
|
class HomeController : Controller {
|
||||||
|
override fun register(routes: JavalinDefaultRoutingApi) {
|
||||||
|
routes.get("/") { ctx -> ctx.result("Hello World") }
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/main/kotlin/dev/mduchene/bolts/web/HttpSupport.kt
Normal file
48
src/main/kotlin/dev/mduchene/bolts/web/HttpSupport.kt
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package dev.mduchene.bolts.web
|
||||||
|
|
||||||
|
import dev.mduchene.bolts.user.LoginService
|
||||||
|
import io.javalin.http.Context
|
||||||
|
import io.javalin.http.HttpStatus
|
||||||
|
|
||||||
|
internal fun Context.requireAdmin(loginService: LoginService): Boolean {
|
||||||
|
val token = header("Authorization")?.removePrefix("Bearer ")
|
||||||
|
if (loginService.userForToken(token)?.role == "admin") return true
|
||||||
|
status(HttpStatus.FORBIDDEN).jsonResult("""{"message":"Admin access required"}""")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Context.requiredFormParam(name: String): String? {
|
||||||
|
val value = formParam(name)?.trim()?.takeIf(String::isNotEmpty)
|
||||||
|
if (value == null) badRequest("$name is required")
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Context.badRequest(message: String) {
|
||||||
|
status(HttpStatus.BAD_REQUEST).jsonResult("""{"message":"${message.toJsonString()}"}""")
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Context.notFound() {
|
||||||
|
status(HttpStatus.NOT_FOUND).jsonResult("""{"message":"Not found"}""")
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Context.jsonResult(value: String): Context =
|
||||||
|
contentType("application/json").result(value)
|
||||||
|
|
||||||
|
internal fun String.toJsonString() = buildString {
|
||||||
|
for (character in this@toJsonString) {
|
||||||
|
when (character) {
|
||||||
|
'\\' -> append("\\\\")
|
||||||
|
'"' -> append("\\\"")
|
||||||
|
'\b' -> append("\\b")
|
||||||
|
'\u000C' -> append("\\f")
|
||||||
|
'\n' -> append("\\n")
|
||||||
|
'\r' -> append("\\r")
|
||||||
|
'\t' -> append("\\t")
|
||||||
|
else -> if (character.code < 0x20) {
|
||||||
|
append("\\u%04x".format(character.code))
|
||||||
|
} else {
|
||||||
|
append(character)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/main/kotlin/dev/mduchene/bolts/web/UserController.kt
Normal file
21
src/main/kotlin/dev/mduchene/bolts/web/UserController.kt
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package dev.mduchene.bolts.web
|
||||||
|
|
||||||
|
import dev.mduchene.bolts.user.LoginService
|
||||||
|
import dev.mduchene.bolts.user.UserRepository
|
||||||
|
import io.javalin.router.JavalinDefaultRoutingApi
|
||||||
|
|
||||||
|
class UserController(
|
||||||
|
private val users: UserRepository,
|
||||||
|
private val loginService: LoginService,
|
||||||
|
) : Controller {
|
||||||
|
override fun register(routes: JavalinDefaultRoutingApi) {
|
||||||
|
routes.get("/api/users") { ctx ->
|
||||||
|
if (!ctx.requireAdmin(loginService)) return@get
|
||||||
|
|
||||||
|
val result = users.findAll().joinToString(prefix = "[", postfix = "]") { user ->
|
||||||
|
"""{"id":${user.id},"username":"${user.username.toJsonString()}","role":"${user.role.toJsonString()}"}"""
|
||||||
|
}
|
||||||
|
ctx.jsonResult(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user