more optional validations

This commit is contained in:
gowthaman.b 2023-11-11 13:10:25 +05:30
parent ea14212337
commit 31388bae59
9 changed files with 441 additions and 68 deletions

View File

@ -1,22 +1,55 @@
package com.restapi package com.restapi
import com.restapi.config.AppConfig.Companion.appConfig
import com.restapi.domain.EntityModel
import io.javalin.http.Context import io.javalin.http.Context
import io.javalin.http.Handler import io.javalin.http.Handler
import io.javalin.http.HttpStatus import io.javalin.http.HttpStatus
import io.javalin.security.AccessManager import io.javalin.security.AccessManager
import io.javalin.security.RouteRole import io.javalin.security.RouteRole
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import com.restapi.domain.Session.currentRoles
import com.restapi.domain.Session.database
class AppAccessManager : AccessManager { class AppAccessManager : AccessManager {
private val logger = LoggerFactory.getLogger("Access") private val logger = LoggerFactory.getLogger("Access")
private fun loadEntityActionRole(entity: String?, action: String?): List<String> {
if (entity == null || action == null) return emptyList()
return database.find(EntityModel::class.java)
.where()
.eq("name", entity)
.findOne()?.actions
?.filter { it.equals(action, ignoreCase = true) }
?.map { "role_${entity}_$it" } ?: emptyList()
}
override fun manage(handler: Handler, ctx: Context, routeRoles: Set<RouteRole>) { override fun manage(handler: Handler, ctx: Context, routeRoles: Set<RouteRole>) {
logger.warn("access {}, {}", ctx.pathParamMap(), routeRoles) val pathParamMap = ctx.pathParamMap()
logger.warn("access {}, {}", pathParamMap, routeRoles)
val regex = Regex("^[a-zA-Z0-9\\-_\\.]+$") val regex = Regex("^[a-zA-Z0-9\\-_\\.]+$")
if(ctx.pathParamMap().values.count { !regex.matches(it) } > 0){ if (pathParamMap.values.count { !regex.matches(it) } > 0) {
ctx.status(HttpStatus.FORBIDDEN).result("invalid request") ctx.status(HttpStatus.FORBIDDEN).result("invalid request")
} else { } else {
val entity = pathParamMap["entity"]
val action = pathParamMap["action"]
val allowedRoles = routeRoles.map { it as Role }.flatMap {
when (it) {
Role.DbOps -> listOf("ROLE_DB_OPS")
Role.Entity -> loadEntityActionRole(entity, action)
is Role.Standard -> listOf("ROLE_${entity}_${it.action}")
}.map(String::uppercase)
}
val isAllowed = currentRoles().count { allowedRoles.contains(it) } > 0
if (isAllowed || !appConfig.enforceRoleRestriction() || allowedRoles.isEmpty()) {
//if role is allowed, or enforcement is turned off or no roles are explicitly allowed
handler.handle(ctx) handler.handle(ctx)
} else {
ctx.status(HttpStatus.UNAUTHORIZED).result("unauthorized request")
}
} }
} }
} }

View File

@ -8,11 +8,9 @@ import com.restapi.config.Auth.getAuthEndpoint
import com.restapi.config.Auth.parseAuthToken import com.restapi.config.Auth.parseAuthToken
import com.restapi.controllers.Entities import com.restapi.controllers.Entities
import com.restapi.domain.DataNotFoundException import com.restapi.domain.DataNotFoundException
import com.restapi.domain.Session.database
import com.restapi.domain.Session.objectMapper import com.restapi.domain.Session.objectMapper
import com.restapi.domain.Session.redis import com.restapi.domain.Session.redis
import com.restapi.domain.Session.setAuthorizedUser import com.restapi.domain.Session.setAuthorizedUser
import io.ebean.CallableSql
import io.ebean.DuplicateKeyException import io.ebean.DuplicateKeyException
import io.javalin.Javalin import io.javalin.Javalin
import io.javalin.apibuilder.ApiBuilder.* import io.javalin.apibuilder.ApiBuilder.*
@ -20,7 +18,6 @@ import io.javalin.http.*
import io.javalin.http.util.NaiveRateLimit import io.javalin.http.util.NaiveRateLimit
import io.javalin.http.util.RateLimitUtil import io.javalin.http.util.RateLimitUtil
import io.javalin.json.JavalinJackson import io.javalin.json.JavalinJackson
import io.javalin.security.AccessManager
import io.javalin.security.RouteRole import io.javalin.security.RouteRole
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.net.URI import java.net.URI
@ -99,11 +96,15 @@ fun main(args: Array<String>) {
} }
before("/api/*") { ctx -> before("/api/*") { ctx ->
//validate, auth token
NaiveRateLimit.requestPerTimeUnit(ctx, appConfig.rateLimit().getOrDefault(30), TimeUnit.MINUTES) // throws if rate limit is exceeded NaiveRateLimit.requestPerTimeUnit(
ctx,
appConfig.rateLimit().getOrDefault(30),
TimeUnit.MINUTES
)
val authToken = ctx.header("Authorization")?.replace("Bearer ", "") val authToken = ctx.header("Authorization")
?.replace("Bearer ", "")
?.replace("Bearer: ", "") ?.replace("Bearer: ", "")
?.trim() ?: throw UnauthorizedResponse() ?.trim() ?: throw UnauthorizedResponse()
@ -111,21 +112,29 @@ fun main(args: Array<String>) {
setAuthorizedUser(parseAuthToken(authToken = authToken)) setAuthorizedUser(parseAuthToken(authToken = authToken))
} }
val adminRole = Role.Standard(Action.ADMIN)
val viewRole = Role.Standard(Action.VIEW)
val createRole = Role.Standard(Action.CREATE)
val updateRole = Role.Standard(Action.UPDATE)
val approveOrRejectRole = Role.Standard(Action.APPROVE)
path("/api") { path("/api") {
post("/execute/{name}", Entities::executeStoredProcedure, Roles(Role.DbOps)) post("/execute/{name}", Entities::executeStoredProcedure, Roles(adminRole, Role.DbOps))
post("/script/{name}", Entities::executeScript, Roles(adminRole, Role.DbOps))
get("/{entity}/{id}", Entities::view, Roles(Role.Standard(Action.VIEW))) get("/{entity}/{id}", Entities::view, Roles(adminRole, viewRole))
post("/{entity}/query/{id}", Entities::sqlQueryId, Roles(Role.Standard(Action.VIEW))) post("/{entity}/query/{id}", Entities::sqlQueryById, Roles(adminRole, viewRole))
post("/{entity}/query", Entities::sqlQueryRaw, Roles(Role.Standard(Action.VIEW))) post("/{entity}/query", Entities::sqlQueryRaw, Roles(adminRole, viewRole))
post("/{entity}", Entities::create, Roles(Role.Standard(Action.CREATE))) post("/{entity}", Entities::create, Roles(adminRole, createRole))
put("/{entity}/approve/{id}", Entities::approve, Roles(Role.Standard(Action.APPROVE))) put("/{entity}/approve/{id}", Entities::approve, Roles(adminRole, approveOrRejectRole))
put("/{entity}/reject/{id}", Entities::reject, Roles(Role.Standard(Action.APPROVE))) put("/{entity}/reject/{id}", Entities::reject, Roles(adminRole, approveOrRejectRole))
put("/{entity}/{action}/{id}", Entities::action, Roles(Role.Entity)) put("/{entity}/{action}/{id}", Entities::action, Roles(adminRole, Role.Entity))
put("/{entity}/{id}", Entities::update, Roles(Role.Standard(Action.UPDATE))) put("/{entity}/{id}", Entities::update, Roles(adminRole, updateRole))
patch("/{entity}/{id}", Entities::patch, Roles(Role.Standard(Action.UPDATE))) patch("/{entity}/{id}", Entities::patch, Roles(adminRole, updateRole))
delete("/{entity}/{id}", Entities::delete, Roles(Role.Standard(Action.DELETE))) delete("/{entity}/{id}", Entities::delete, Roles(adminRole, Role.Standard(Action.DELETE)))
} }
@ -163,7 +172,7 @@ fun main(args: Array<String>) {
enum class Action { enum class Action {
CREATE, VIEW, UPDATE, DELETE, APPROVE CREATE, VIEW, UPDATE, DELETE, APPROVE, ADMIN
} }
sealed class Role { sealed class Role {

View File

@ -66,6 +66,13 @@ interface AppConfig {
@Key("app.network.rate_limit") @Key("app.network.rate_limit")
fun rateLimit(): Optional<Int> fun rateLimit(): Optional<Int>
@Key("app.security.enforce_role_restriction")
@Default("true")
fun enforceRoleRestriction(): Boolean
@Key("app.scripts.path")
fun scriptsPath(): String
companion object { companion object {
val appConfig: AppConfig = ConfigFactory.builder().build().create(AppConfig::class.java) val appConfig: AppConfig = ConfigFactory.builder().build().create(AppConfig::class.java)
} }

View File

@ -1,16 +1,23 @@
package com.restapi.controllers package com.restapi.controllers
import com.restapi.domain.ApprovalStatus
import com.restapi.domain.DataModel import com.restapi.domain.DataModel
import com.restapi.domain.EntityModel
import com.restapi.domain.Session import com.restapi.domain.Session
import com.restapi.domain.Session.database import com.restapi.domain.Session.database
import com.restapi.domain.Session.findByEntityAndId import com.restapi.domain.Session.findByEntityAndId
import com.restapi.integ.Scripting
import io.ebean.CallableSql import io.ebean.CallableSql
import io.ebean.RawSqlBuilder import io.ebean.RawSqlBuilder
import io.javalin.http.BadRequestResponse
import io.javalin.http.Context import io.javalin.http.Context
import io.javalin.http.NotFoundResponse import io.javalin.http.NotFoundResponse
import io.javalin.http.bodyAsClass import io.javalin.http.bodyAsClass
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.LocalTime
import java.time.format.DateTimeFormatter
data class PatchValue(val key: String, val value: Any) data class PatchValue(val key: String, val value: Any)
data class Query( data class Query(
@ -46,6 +53,14 @@ object Entities {
fun approve(ctx: Context) {} fun approve(ctx: Context) {}
fun reject(ctx: Context) {} fun reject(ctx: Context) {}
fun executeScript(ctx: Context) {
val name = ctx.pathParam("name")
val params = ctx.bodyAsClass<Map<String, Any>>()
ctx.json(
Scripting.execute(name, params)
)
}
fun executeStoredProcedure(ctx: Context) { fun executeStoredProcedure(ctx: Context) {
val name = ctx.pathParam("name") val name = ctx.pathParam("name")
val params = ctx.bodyAsClass<Map<String, Any>>() val params = ctx.bodyAsClass<Map<String, Any>>()
@ -58,7 +73,12 @@ object Entities {
cs.setParameter(index + 1, entry.value) cs.setParameter(index + 1, entry.value)
} }
cs.setParameter(params.entries.size + 1, Session.currentTenant()) cs.setParameter(params.entries.size + 1, Session.currentTenant())
database.execute(cs) val done = database.execute(cs)
ctx.json(
mapOf(
"done" to done
)
)
} }
fun sqlQueryRaw(ctx: Context) { fun sqlQueryRaw(ctx: Context) {
@ -77,7 +97,7 @@ object Entities {
} }
fun sqlQueryId(ctx: Context) { fun sqlQueryById(ctx: Context) {
val sql = ctx.bodyAsClass<Query>() val sql = ctx.bodyAsClass<Query>()
val query = database.findByEntityAndId(ctx.pathParam("entity"), ctx.pathParam("id")) val query = database.findByEntityAndId(ctx.pathParam("entity"), ctx.pathParam("id"))
@ -104,16 +124,112 @@ object Entities {
} }
fun create(ctx: Context) { fun create(ctx: Context) {
val entity = ctx.pathParam("entity") val entity = ctx.pathParam("entity")
val seqCreated = Session.creatSeq(entity)
logger.debug("sequence created for $entity? = $seqCreated")
database.save( //may be approval flow is configured?
ctx.bodyAsClass<DataModel>().apply { val setupEntity = database.find(EntityModel::class.java)
.where()
.eq("name", entity)
.findOne()
Session.creatSeq(entity)
val dataModel = ctx.bodyAsClass<DataModel>().apply {
this.entityName = entity this.entityName = entity
if (this.uniqueIdentifier.isEmpty()) { if (this.uniqueIdentifier.isEmpty()) {
this.uniqueIdentifier = Session.nextUniqId(entity) this.uniqueIdentifier = Session.nextUniqId(entity)
} }
} }
)
database.save(
dataModel.apply {
if (setupEntity != null) {
val allowedFields = setupEntity.allowedFields.map { it.lowercase() }
if (allowedFields.isNotEmpty()) {
val moreFields =
dataModel.data.keys.map { it.lowercase() }.filter { !allowedFields.contains(it) }
if (moreFields.isNotEmpty()) {
logger.warn("Data Keys = ${dataModel.data.keys} is more than $allowedFields, extra fields = $moreFields")
throw BadRequestResponse("data contains more fields than allowed")
}
setupEntity.allowedFieldTypes.forEach { (key, expectedType) ->
val valueFromUser = dataModel.data[key] ?: return@forEach
val isDate = expectedType.equals("date", ignoreCase = true)
val isDateTime = expectedType.equals("datetime", ignoreCase = true)
val isTime = expectedType.equals("time", ignoreCase = true)
if (isDate || isDateTime || isTime) {
//this should be a string of a particular format
if (valueFromUser !is String) {
throw BadRequestResponse("field $key, is of type ${valueFromUser.javaClass.simpleName} expected $expectedType")
} else {
val dtPattern = Regex("^\\d{4}-\\d{2}-\\d{2}$")
val dtmPattern = Regex("^\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}$")
val timePattern = Regex("^\\d{2}:\\d{2}$")
if (isDate
&& !dtPattern.matches(valueFromUser)
&& !isValidDate(valueFromUser)
) {
throw BadRequestResponse("field $key, is of type ${valueFromUser.javaClass.simpleName} expected $expectedType")
}
if (isDateTime
&& !dtmPattern.matches(valueFromUser)
&& !isValidDateTime(valueFromUser)
) {
throw BadRequestResponse("field $key, is of type ${valueFromUser.javaClass.simpleName} expected $expectedType")
}
if (isTime
&& !timePattern.matches(valueFromUser)
&& !isValidTime(valueFromUser)
) {
throw BadRequestResponse("field $key, is of type ${valueFromUser.javaClass.simpleName} expected $expectedType")
}
}
}
if (valueFromUser.javaClass.simpleName != expectedType) {
throw BadRequestResponse("field $key, is of type ${valueFromUser.javaClass.simpleName} expected $expectedType")
}
}
}
if (setupEntity.approvalLevels > 0) {
this.approvalStatus = ApprovalStatus.PENDING
this.requiredApprovalLevels = setupEntity.approvalLevels
}
}
})
}
private fun isValidDate(f: String) = try {
LocalDate.parse(f, DateTimeFormatter.ofPattern("yyyy-MM-dd"))
true
} catch (e: Exception) {
false
}
private fun isValidDateTime(f: String) = try {
LocalDateTime.parse(f, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
true
} catch (e: Exception) {
false
}
private fun isValidTime(f: String) = try {
LocalTime.parse(f, DateTimeFormatter.ofPattern("HH:mm"))
true
} catch (e: Exception) {
false
} }
} }

View File

@ -47,6 +47,7 @@ object Session {
fun currentUser() = currentUser.get().userName fun currentUser() = currentUser.get().userName
fun currentTenant() = currentUser.get().tenant fun currentTenant() = currentUser.get().tenant
fun currentRoles() = currentUser.get().roles
fun Database.findByEntityAndId(entity: String, id: String): DataModel { fun Database.findByEntityAndId(entity: String, id: String): DataModel {
return find(DataModel::class.java) return find(DataModel::class.java)

View File

@ -20,6 +20,10 @@ import javax.persistence.*
data class Comments(val text: String = "", val by: String = "", val at: LocalDateTime = LocalDateTime.now()) data class Comments(val text: String = "", val by: String = "", val at: LocalDateTime = LocalDateTime.now())
enum class ApprovalStatus {
PENDING, APPROVED, REJECTED
}
@MappedSuperclass @MappedSuperclass
abstract class BaseModel : Model() { abstract class BaseModel : Model() {
@Id @Id
@ -51,6 +55,13 @@ abstract class BaseModel : Model() {
var deletedBy: String? = null var deletedBy: String? = null
var currentApprovalLevel: Int = 0
var requiredApprovalLevels: Int = 0
@Enumerated(EnumType.STRING)
var approvalStatus: ApprovalStatus = ApprovalStatus.PENDING
@DbArray @DbArray
var tags: MutableList<String> = arrayListOf() var tags: MutableList<String> = arrayListOf()
@ -70,11 +81,15 @@ open class TenantModel : BaseModel() {
} }
enum class AuditType { enum class AuditType {
CREATE, UPDATE, DELETE, VIEW CREATE, UPDATE, DELETE, VIEW, APPROVE, REJECT
} }
@Entity
@Index(columnNames = ["audit_type", "entity", "unique_identifier", "tenant_id", "created_by"])
open class AuditLog : BaseModel() { open class AuditLog : BaseModel() {
@Enumerated(EnumType.STRING)
var auditType: AuditType = AuditType.CREATE var auditType: AuditType = AuditType.CREATE
var entity: String = "" var entity: String = ""
var uniqueIdentifier: String = "" var uniqueIdentifier: String = ""
@ -108,6 +123,10 @@ open class EntityModel : BaseModel() {
@DbArray @DbArray
var allowedFields: List<String> = emptyList() var allowedFields: List<String> = emptyList()
//enforce field types, if this is present, only fields that are present is validated
@DbJsonB
var allowedFieldTypes: Map<String, String> = hashMapOf()
//when an entity is saved/updated audit logs will be populated, when this is empty, all fields are logged //when an entity is saved/updated audit logs will be populated, when this is empty, all fields are logged
@DbArray @DbArray
var auditLogFields: List<String> = emptyList() var auditLogFields: List<String> = emptyList()
@ -116,7 +135,7 @@ open class EntityModel : BaseModel() {
var preferences: MutableMap<String, Any> = hashMapOf() var preferences: MutableMap<String, Any> = hashMapOf()
//if '0' then its auto saved, no approval steps are required, for further steps, //if '0' then its auto saved, no approval steps are required, for further steps,
//a user needs to have ROLE_ENTITY_APPROVE_LEVEL1, ROLE_ENTITY_APPROVE_LEVEL2 roles //a user needs to have ROLE_ENTITY_APPROVE_LEVEL1, ROLE_ENTITY_APPROVE_LEVEL2 roles for further approvals
var approvalLevels: Int = 0 var approvalLevels: Int = 0
} }

View File

@ -1,43 +1,17 @@
package com.restapi.integ package com.restapi.integ
import com.restapi.config.AppConfig.Companion.appConfig
import java.io.File
import javax.script.Invocable import javax.script.Invocable
import javax.script.ScriptEngineManager import javax.script.ScriptEngineManager
object Scripting { object Scripting {
const val a = "1"
@JvmStatic
fun main(args: Array<String>) {
fun execute(name: String, params: Map<String, Any>): Any {
k()
}
fun k(){
val engine = ScriptEngineManager().getEngineByExtension("kts")!! val engine = ScriptEngineManager().getEngineByExtension("kts")!!
val res1 = engine.eval(""" engine.eval(File(appConfig.scriptsPath(), "$name.kts").reader())
fun fn(x: Int) = x + 2 val invocator = engine as Invocable
val obj = object { return invocator.invokeFunction("execute", params)
fun fn1(x: Int) = x + 3
}
obj""".trimIndent())
println(res1)
val invocator = engine as? Invocable
println(invocator)
try {
println(invocator!!.invokeFunction("fn1", 3))
} catch (e: NoSuchMethodException) {
println(e)
}
val res2 = invocator!!.invokeFunction("fn", 3)
println(res2)
val res3 = invocator.invokeMethod(res1, "fn1", 3)
println(res3)
} }
} }

View File

@ -1,13 +1,42 @@
-- apply changes -- apply changes
create table data_model ( create table audit_log (
sys_pk bigint generated by default as identity not null, sys_pk bigint generated by default as identity not null,
deleted_on timestamp, deleted_on timestamp,
current_approval_level integer not null,
required_approval_levels integer not null,
deleted boolean default false not null, deleted boolean default false not null,
version integer not null, version integer not null,
created_at timestamp not null, created_at timestamp not null,
modified_at timestamp not null, modified_at timestamp not null,
tenant_id varchar(255) not null, tenant_id varchar(255) not null,
deleted_by varchar(255), deleted_by varchar(255),
approval_status varchar(8) not null,
tags varchar[] not null,
comments jsonb not null,
audit_type varchar(7) not null,
entity varchar(255) not null,
unique_identifier varchar(255) not null,
data jsonb not null,
changes jsonb not null,
created_by varchar(255) not null,
modified_by varchar(255) not null,
constraint ck_audit_log_approval_status check ( approval_status in ('PENDING','APPROVED','REJECTED')),
constraint ck_audit_log_audit_type check ( audit_type in ('CREATE','UPDATE','DELETE','VIEW','APPROVE','REJECT')),
constraint pk_audit_log primary key (sys_pk)
);
create table data_model (
sys_pk bigint generated by default as identity not null,
deleted_on timestamp,
current_approval_level integer not null,
required_approval_levels integer not null,
deleted boolean default false not null,
version integer not null,
created_at timestamp not null,
modified_at timestamp not null,
tenant_id varchar(255) not null,
deleted_by varchar(255),
approval_status varchar(8) not null,
tags varchar[] not null, tags varchar[] not null,
comments jsonb not null, comments jsonb not null,
unique_identifier varchar(255) not null, unique_identifier varchar(255) not null,
@ -15,9 +44,95 @@ create table data_model (
data jsonb not null, data jsonb not null,
created_by varchar(255) not null, created_by varchar(255) not null,
modified_by varchar(255) not null, modified_by varchar(255) not null,
constraint ck_data_model_approval_status check ( approval_status in ('PENDING','APPROVED','REJECTED')),
constraint entity_unique_id unique (entity_name,unique_identifier,tenant_id), constraint entity_unique_id unique (entity_name,unique_identifier,tenant_id),
constraint pk_data_model primary key (sys_pk) constraint pk_data_model primary key (sys_pk)
); );
create table entity_model (
sys_pk bigint generated by default as identity not null,
deleted_on timestamp,
current_approval_level integer not null,
required_approval_levels integer not null,
approval_levels integer not null,
deleted boolean default false not null,
version integer not null,
created_at timestamp not null,
modified_at timestamp not null,
tenant_id varchar(255) not null,
deleted_by varchar(255),
approval_status varchar(8) not null,
tags varchar[] not null,
comments jsonb not null,
name varchar(255) not null,
pre_save_script varchar(255) not null,
post_save_script varchar(255) not null,
actions varchar[] not null,
allowed_fields varchar[] not null,
allowed_field_types jsonb not null,
audit_log_fields varchar[] not null,
preferences jsonb not null,
created_by varchar(255) not null,
modified_by varchar(255) not null,
constraint ck_entity_model_approval_status check ( approval_status in ('PENDING','APPROVED','REJECTED')),
constraint uq_entity_model_name unique (name),
constraint pk_entity_model primary key (sys_pk)
);
create table job_model (
sys_pk bigint generated by default as identity not null,
deleted_on timestamp,
current_approval_level integer not null,
required_approval_levels integer not null,
deleted boolean default false not null,
version integer not null,
created_at timestamp not null,
modified_at timestamp not null,
tenant_id varchar(255) not null,
deleted_by varchar(255),
approval_status varchar(8) not null,
tags varchar[] not null,
comments jsonb not null,
job_name varchar(255) not null,
job_type varchar(6) not null,
job_path varchar(255) not null,
tenants varchar[] not null,
job_frequency_type varchar(8) not null,
frequency varchar(255) not null,
created_by varchar(255) not null,
modified_by varchar(255) not null,
constraint ck_job_model_approval_status check ( approval_status in ('PENDING','APPROVED','REJECTED')),
constraint ck_job_model_job_type check ( job_type in ('SCRIPT','DB')),
constraint ck_job_model_job_frequency_type check ( job_frequency_type in ('SPECIFIC','EVERY','CRON')),
constraint uq_job_model_job_name unique (job_name),
constraint pk_job_model primary key (sys_pk)
);
create table tenant_model (
sys_pk bigint generated by default as identity not null,
deleted_on timestamp,
current_approval_level integer not null,
required_approval_levels integer not null,
deleted boolean default false not null,
version integer not null,
created_at timestamp not null,
modified_at timestamp not null,
tenant_id varchar(255) not null,
deleted_by varchar(255),
approval_status varchar(8) not null,
tags varchar[] not null,
comments jsonb not null,
name varchar(255) not null,
domain varchar(255) not null,
preferences jsonb not null,
created_by varchar(255) not null,
modified_by varchar(255) not null,
constraint ck_tenant_model_approval_status check ( approval_status in ('PENDING','APPROVED','REJECTED')),
constraint pk_tenant_model primary key (sys_pk)
);
-- foreign keys and indices -- foreign keys and indices
create index if not exists ix_audit_log_audit_type_entity_unique_identifier_tenant_i_1 on audit_log (audit_type,entity,unique_identifier,tenant_id,created_by);
create index audit_log_values_idx on audit_log using GIN (data) ;
create index audit_log_changes_idx on audit_log using GIN (changes) ;
create index data_jsonb_idx on data_model using GIN (data) ; create index data_jsonb_idx on data_model using GIN (data) ;

View File

@ -1,11 +1,36 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<migration xmlns="http://ebean-orm.github.io/xml/ns/dbmigration"> <migration xmlns="http://ebean-orm.github.io/xml/ns/dbmigration">
<changeSet type="apply"> <changeSet type="apply">
<createTable name="audit_log" pkName="pk_audit_log">
<column name="sys_pk" type="bigint" primaryKey="true"/>
<column name="tenant_id" type="varchar" notnull="true"/>
<column name="deleted_on" type="localdatetime"/>
<column name="deleted_by" type="varchar"/>
<column name="current_approval_level" type="integer" notnull="true"/>
<column name="required_approval_levels" type="integer" notnull="true"/>
<column name="approval_status" type="varchar(8)" notnull="true" checkConstraint="check ( approval_status in ('PENDING','APPROVED','REJECTED'))" checkConstraintName="ck_audit_log_approval_status"/>
<column name="tags" type="varchar[]" notnull="true"/>
<column name="comments" type="jsonb" notnull="true"/>
<column name="audit_type" type="varchar(7)" notnull="true" checkConstraint="check ( audit_type in ('CREATE','UPDATE','DELETE','VIEW','APPROVE','REJECT'))" checkConstraintName="ck_audit_log_audit_type"/>
<column name="entity" type="varchar" notnull="true"/>
<column name="unique_identifier" type="varchar" notnull="true"/>
<column name="data" type="jsonb" notnull="true"/>
<column name="changes" type="jsonb" notnull="true"/>
<column name="deleted" type="boolean" defaultValue="false" notnull="true"/>
<column name="version" type="integer" notnull="true"/>
<column name="created_at" type="localdatetime" notnull="true"/>
<column name="modified_at" type="localdatetime" notnull="true"/>
<column name="created_by" type="varchar" notnull="true"/>
<column name="modified_by" type="varchar" notnull="true"/>
</createTable>
<createTable name="data_model" pkName="pk_data_model"> <createTable name="data_model" pkName="pk_data_model">
<column name="sys_pk" type="bigint" primaryKey="true"/> <column name="sys_pk" type="bigint" primaryKey="true"/>
<column name="tenant_id" type="varchar" notnull="true"/> <column name="tenant_id" type="varchar" notnull="true"/>
<column name="deleted_on" type="localdatetime"/> <column name="deleted_on" type="localdatetime"/>
<column name="deleted_by" type="varchar"/> <column name="deleted_by" type="varchar"/>
<column name="current_approval_level" type="integer" notnull="true"/>
<column name="required_approval_levels" type="integer" notnull="true"/>
<column name="approval_status" type="varchar(8)" notnull="true" checkConstraint="check ( approval_status in ('PENDING','APPROVED','REJECTED'))" checkConstraintName="ck_data_model_approval_status"/>
<column name="tags" type="varchar[]" notnull="true"/> <column name="tags" type="varchar[]" notnull="true"/>
<column name="comments" type="jsonb" notnull="true"/> <column name="comments" type="jsonb" notnull="true"/>
<column name="unique_identifier" type="varchar" notnull="true"/> <column name="unique_identifier" type="varchar" notnull="true"/>
@ -19,6 +44,80 @@
<column name="modified_by" type="varchar" notnull="true"/> <column name="modified_by" type="varchar" notnull="true"/>
<uniqueConstraint name="entity_unique_id" columnNames="entity_name,unique_identifier,tenant_id" oneToOne="false" nullableColumns=""/> <uniqueConstraint name="entity_unique_id" columnNames="entity_name,unique_identifier,tenant_id" oneToOne="false" nullableColumns=""/>
</createTable> </createTable>
<createTable name="entity_model" pkName="pk_entity_model">
<column name="sys_pk" type="bigint" primaryKey="true"/>
<column name="tenant_id" type="varchar" notnull="true"/>
<column name="deleted_on" type="localdatetime"/>
<column name="deleted_by" type="varchar"/>
<column name="current_approval_level" type="integer" notnull="true"/>
<column name="required_approval_levels" type="integer" notnull="true"/>
<column name="approval_status" type="varchar(8)" notnull="true" checkConstraint="check ( approval_status in ('PENDING','APPROVED','REJECTED'))" checkConstraintName="ck_entity_model_approval_status"/>
<column name="tags" type="varchar[]" notnull="true"/>
<column name="comments" type="jsonb" notnull="true"/>
<column name="name" type="varchar" notnull="true"/>
<column name="pre_save_script" type="varchar" notnull="true"/>
<column name="post_save_script" type="varchar" notnull="true"/>
<column name="actions" type="varchar[]" notnull="true"/>
<column name="allowed_fields" type="varchar[]" notnull="true"/>
<column name="allowed_field_types" type="jsonb" notnull="true"/>
<column name="audit_log_fields" type="varchar[]" notnull="true"/>
<column name="preferences" type="jsonb" notnull="true"/>
<column name="approval_levels" type="integer" notnull="true"/>
<column name="deleted" type="boolean" defaultValue="false" notnull="true"/>
<column name="version" type="integer" notnull="true"/>
<column name="created_at" type="localdatetime" notnull="true"/>
<column name="modified_at" type="localdatetime" notnull="true"/>
<column name="created_by" type="varchar" notnull="true"/>
<column name="modified_by" type="varchar" notnull="true"/>
<uniqueConstraint name="uq_entity_model_name" columnNames="name" oneToOne="false" nullableColumns=""/>
</createTable>
<createTable name="job_model" pkName="pk_job_model">
<column name="sys_pk" type="bigint" primaryKey="true"/>
<column name="tenant_id" type="varchar" notnull="true"/>
<column name="deleted_on" type="localdatetime"/>
<column name="deleted_by" type="varchar"/>
<column name="current_approval_level" type="integer" notnull="true"/>
<column name="required_approval_levels" type="integer" notnull="true"/>
<column name="approval_status" type="varchar(8)" notnull="true" checkConstraint="check ( approval_status in ('PENDING','APPROVED','REJECTED'))" checkConstraintName="ck_job_model_approval_status"/>
<column name="tags" type="varchar[]" notnull="true"/>
<column name="comments" type="jsonb" notnull="true"/>
<column name="job_name" type="varchar" notnull="true"/>
<column name="job_type" type="varchar(6)" notnull="true" checkConstraint="check ( job_type in ('SCRIPT','DB'))" checkConstraintName="ck_job_model_job_type"/>
<column name="job_path" type="varchar" notnull="true"/>
<column name="tenants" type="varchar[]" notnull="true"/>
<column name="job_frequency_type" type="varchar(8)" notnull="true" checkConstraint="check ( job_frequency_type in ('SPECIFIC','EVERY','CRON'))" checkConstraintName="ck_job_model_job_frequency_type"/>
<column name="frequency" type="varchar" notnull="true"/>
<column name="deleted" type="boolean" defaultValue="false" notnull="true"/>
<column name="version" type="integer" notnull="true"/>
<column name="created_at" type="localdatetime" notnull="true"/>
<column name="modified_at" type="localdatetime" notnull="true"/>
<column name="created_by" type="varchar" notnull="true"/>
<column name="modified_by" type="varchar" notnull="true"/>
<uniqueConstraint name="uq_job_model_job_name" columnNames="job_name" oneToOne="false" nullableColumns=""/>
</createTable>
<createTable name="tenant_model" pkName="pk_tenant_model">
<column name="sys_pk" type="bigint" primaryKey="true"/>
<column name="tenant_id" type="varchar" notnull="true"/>
<column name="deleted_on" type="localdatetime"/>
<column name="deleted_by" type="varchar"/>
<column name="current_approval_level" type="integer" notnull="true"/>
<column name="required_approval_levels" type="integer" notnull="true"/>
<column name="approval_status" type="varchar(8)" notnull="true" checkConstraint="check ( approval_status in ('PENDING','APPROVED','REJECTED'))" checkConstraintName="ck_tenant_model_approval_status"/>
<column name="tags" type="varchar[]" notnull="true"/>
<column name="comments" type="jsonb" notnull="true"/>
<column name="name" type="varchar" notnull="true"/>
<column name="domain" type="varchar" notnull="true"/>
<column name="preferences" type="jsonb" notnull="true"/>
<column name="deleted" type="boolean" defaultValue="false" notnull="true"/>
<column name="version" type="integer" notnull="true"/>
<column name="created_at" type="localdatetime" notnull="true"/>
<column name="modified_at" type="localdatetime" notnull="true"/>
<column name="created_by" type="varchar" notnull="true"/>
<column name="modified_by" type="varchar" notnull="true"/>
</createTable>
<createIndex indexName="ix_audit_log_audit_type_entity_unique_identifier_tenant_i_1" tableName="audit_log" columns="audit_type,entity,unique_identifier,tenant_id,created_by"/>
<createIndex indexName="ix_audit_log_data" tableName="audit_log" columns="data" definition="create index audit_log_values_idx on audit_log using GIN (data) " platforms="POSTGRES"/>
<createIndex indexName="ix_audit_log_changes" tableName="audit_log" columns="changes" definition="create index audit_log_changes_idx on audit_log using GIN (changes) " platforms="POSTGRES"/>
<createIndex indexName="ix_data_model_data" tableName="data_model" columns="data" definition="create index data_jsonb_idx on data_model using GIN (data) " platforms="POSTGRES"/> <createIndex indexName="ix_data_model_data" tableName="data_model" columns="data" definition="create index data_jsonb_idx on data_model using GIN (data) " platforms="POSTGRES"/>
</changeSet> </changeSet>
</migration> </migration>