more stuff

This commit is contained in:
gowthaman.b
2023-11-11 11:38:58 +05:30
parent 1e3d9aa1f1
commit dc0a59fcf2
11 changed files with 335 additions and 109 deletions

View File

@@ -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)