more stuff
This commit is contained in:
@@ -4,26 +4,24 @@ import AuthTokenResponse
|
||||
import com.fasterxml.jackson.databind.JsonMappingException
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.restapi.config.AppConfig.Companion.appConfig
|
||||
import com.restapi.config.Auth
|
||||
import com.restapi.config.Auth.getAuthEndpoint
|
||||
import com.restapi.config.AuthEndpoint
|
||||
import com.restapi.domain.DataModel
|
||||
import com.restapi.config.Auth.parseAuthToken
|
||||
import com.restapi.controllers.Entities
|
||||
import com.restapi.domain.DataNotFoundException
|
||||
import com.restapi.domain.Session
|
||||
import com.restapi.domain.Session.creatSeq
|
||||
import com.restapi.domain.Session.database
|
||||
import com.restapi.domain.Session.findByEntityAndId
|
||||
import com.restapi.domain.Session.nextUniqId
|
||||
import com.restapi.domain.Session.objectMapper
|
||||
import com.restapi.domain.Session.redis
|
||||
import com.restapi.domain.Session.setAuthorizedUser
|
||||
import io.ebean.CallableSql
|
||||
import io.ebean.DuplicateKeyException
|
||||
import io.ebean.RawSqlBuilder
|
||||
import io.javalin.Javalin
|
||||
import io.javalin.apibuilder.ApiBuilder.*
|
||||
import io.javalin.http.*
|
||||
import io.javalin.http.util.NaiveRateLimit
|
||||
import io.javalin.http.util.RateLimitUtil
|
||||
import io.javalin.json.JavalinJackson
|
||||
import io.javalin.security.AccessManager
|
||||
import io.javalin.security.RouteRole
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.net.URI
|
||||
import java.net.URLEncoder
|
||||
@@ -32,11 +30,14 @@ import java.net.http.HttpRequest
|
||||
import java.net.http.HttpRequest.BodyPublishers
|
||||
import java.net.http.HttpResponse.BodyHandlers
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.time.LocalDateTime
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.jvm.optionals.getOrDefault
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val logger = LoggerFactory.getLogger("api")
|
||||
|
||||
//ratelimit based on IP Only
|
||||
RateLimitUtil.keyFunction = { ctx -> ctx.header("X-Forwarded-For")?.split(",")?.get(0) ?: ctx.ip() }
|
||||
Javalin
|
||||
.create { cfg ->
|
||||
cfg.http.generateEtags = true
|
||||
@@ -52,7 +53,8 @@ fun main(args: Array<String>) {
|
||||
}
|
||||
cfg.http.defaultContentType = ContentType.JSON
|
||||
cfg.compression.gzipOnly()
|
||||
cfg.jsonMapper(JavalinJackson(Session.objectMapper))
|
||||
cfg.jsonMapper(JavalinJackson(objectMapper))
|
||||
cfg.accessManager(AppAccessManager())
|
||||
}
|
||||
.routes {
|
||||
|
||||
@@ -95,110 +97,49 @@ fun main(args: Array<String>) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
before("/api/*") { ctx ->
|
||||
//validate, auth token
|
||||
|
||||
NaiveRateLimit.requestPerTimeUnit(ctx, appConfig.rateLimit().getOrDefault(30), TimeUnit.MINUTES) // throws if rate limit is exceeded
|
||||
|
||||
//allow only alpha, numeric, hypen, underscore, dot in paths
|
||||
val regex = Regex("^[a-zA-Z0-9\\-_\\.]+$")
|
||||
|
||||
ctx.path().split("/").dropWhile { it.isEmpty() }
|
||||
ctx.path().split("/")
|
||||
.dropWhile { it.isEmpty() }
|
||||
.forEach {
|
||||
if (!it.matches(regex)) {
|
||||
throw IllegalArgumentException()
|
||||
}
|
||||
}
|
||||
|
||||
val at = ctx.header("Authorization")?.replace("Bearer ", "")?.replace("Bearer: ", "")?.trim()
|
||||
?: throw UnauthorizedResponse()
|
||||
val pt = Auth.parseAuthToken(authToken = at)
|
||||
val authToken = ctx.header("Authorization")?.replace("Bearer ", "")
|
||||
?.replace("Bearer: ", "")
|
||||
?.trim() ?: throw UnauthorizedResponse()
|
||||
|
||||
setAuthorizedUser(pt)
|
||||
logger.warn("authToken = $authToken")
|
||||
|
||||
setAuthorizedUser(parseAuthToken(authToken = authToken))
|
||||
}
|
||||
path("/api") {
|
||||
post("/execute/{name}") {
|
||||
val name = it.pathParam("name")
|
||||
val params = it.bodyAsClass<Map<String, Any>>()
|
||||
val placeholders = (0..params.entries.size).joinToString(",") { "?" }
|
||||
val sql = "{call $name($placeholders)}"
|
||||
val cs: CallableSql = database.createCallableSql(sql)
|
||||
params.entries.forEachIndexed { index, entry ->
|
||||
cs.setParameter(index + 1, entry.value)
|
||||
}
|
||||
database.execute(cs)
|
||||
}
|
||||
get("/{entity}/{id}") {
|
||||
it.json(
|
||||
database.findByEntityAndId(it.pathParam("entity"), it.pathParam("id"))
|
||||
)
|
||||
}
|
||||
post("/{entity}/query/{id}") {
|
||||
val sql = it.bodyAsClass<Query>()
|
||||
val query = database.findByEntityAndId(it.pathParam("entity"), it.pathParam("id"))
|
||||
post("/execute/{name}", Entities::executeStoredProcedure, Roles(Role.DbOps))
|
||||
|
||||
val querySql = query.data["sql"] as String? ?: throw NotFoundResponse()
|
||||
get("/{entity}/{id}", Entities::view, Roles(Role.Standard(Action.VIEW)))
|
||||
post("/{entity}/query/{id}", Entities::sqlQueryId, Roles(Role.Standard(Action.VIEW)))
|
||||
post("/{entity}/query", Entities::sqlQueryRaw, Roles(Role.Standard(Action.VIEW)))
|
||||
post("/{entity}", Entities::create, Roles(Role.Standard(Action.CREATE)))
|
||||
|
||||
it.json(
|
||||
database.find(DataModel::class.java)
|
||||
.setRawSql(
|
||||
RawSqlBuilder.parse(querySql).create()
|
||||
).apply {
|
||||
sql.params.forEach { (t, u) ->
|
||||
setParameter(t, u)
|
||||
}
|
||||
}
|
||||
.findList()
|
||||
)
|
||||
put("/{entity}/approve/{id}", Entities::approve, Roles(Role.Standard(Action.APPROVE)))
|
||||
put("/{entity}/reject/{id}", Entities::reject, Roles(Role.Standard(Action.APPROVE)))
|
||||
put("/{entity}/{action}/{id}", Entities::action, Roles(Role.Entity))
|
||||
|
||||
}
|
||||
post("/{entity}/query") {
|
||||
val sql = it.bodyAsClass<Query>()
|
||||
it.json(
|
||||
database.find(DataModel::class.java)
|
||||
.setRawSql(
|
||||
RawSqlBuilder.parse(sql.sql).create()
|
||||
).apply {
|
||||
sql.params.forEach { (t, u) ->
|
||||
setParameter(t, u)
|
||||
}
|
||||
}
|
||||
.findList()
|
||||
)
|
||||
|
||||
}
|
||||
post("/{entity}") {
|
||||
val entity = it.pathParam("entity")
|
||||
val seqCreated = creatSeq(entity)
|
||||
logger.debug("sequence created for $entity? = $seqCreated")
|
||||
database.save(
|
||||
it.bodyAsClass<DataModel>().apply {
|
||||
this.entityName = entity
|
||||
if (this.uniqueIdentifier.isEmpty()) {
|
||||
this.uniqueIdentifier = nextUniqId(entity)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
put("/{entity}/{id}") {
|
||||
val e = database.findByEntityAndId(it.pathParam("entity"), it.pathParam("id"))
|
||||
val newData = it.bodyAsClass<Map<String, Any>>()
|
||||
e.data.putAll(newData)
|
||||
e.update()
|
||||
}
|
||||
patch("/{entity}/{id}") {
|
||||
val e = database.findByEntityAndId(it.pathParam("entity"), it.pathParam("id"))
|
||||
val pv = it.bodyAsClass<PatchValue>()
|
||||
e.data[pv.key] = pv.value;
|
||||
e.update()
|
||||
}
|
||||
delete("/{entity}/{id}") {
|
||||
val id = it.pathParam("id")
|
||||
val e = database.findByEntityAndId(it.pathParam("entity"), it.pathParam("id"))
|
||||
e.deletedBy = Session.currentUser()
|
||||
e.deletedOn = LocalDateTime.now()
|
||||
e.update()
|
||||
e.delete()
|
||||
}
|
||||
put("/{entity}/{id}", Entities::update, Roles(Role.Standard(Action.UPDATE)))
|
||||
patch("/{entity}/{id}", Entities::patch, Roles(Role.Standard(Action.UPDATE)))
|
||||
delete("/{entity}/{id}", Entities::delete, Roles(Role.Standard(Action.DELETE)))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
.exception(DuplicateKeyException::class.java) { _, ctx ->
|
||||
ctx.json(
|
||||
@@ -231,10 +172,19 @@ fun main(args: Array<String>) {
|
||||
.start(appConfig.portNumber())
|
||||
}
|
||||
|
||||
data class Query(
|
||||
val sql: String,
|
||||
val params: Map<String, Any>
|
||||
)
|
||||
|
||||
enum class Action {
|
||||
CREATE, VIEW, UPDATE, DELETE, APPROVE
|
||||
}
|
||||
|
||||
sealed class Role {
|
||||
open class Standard(vararg val action: Action) : Role()
|
||||
data object Entity : Role()
|
||||
data object DbOps : Role()
|
||||
}
|
||||
|
||||
open class Roles(vararg val roles: Role) : RouteRole
|
||||
|
||||
|
||||
private fun getFormDataAsString(formData: Map<String, String>): String {
|
||||
val formBodyBuilder = StringBuilder()
|
||||
@@ -249,4 +199,3 @@ private fun getFormDataAsString(formData: Map<String, String>): String {
|
||||
return formBodyBuilder.toString()
|
||||
}
|
||||
|
||||
data class PatchValue(val key: String, val value: Any)
|
||||
Reference in New Issue
Block a user