package com.restapi.domain import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.databind.DeserializationContext import com.fasterxml.jackson.databind.JsonDeserializer import com.fasterxml.jackson.databind.annotation.JsonDeserialize import io.ebean.Model import io.ebean.annotation.* import io.ebean.annotation.Index import java.time.LocalDateTime import javax.persistence.* data class Comments(val text: String = "", val by: String = "", val at: LocalDateTime = LocalDateTime.now()) enum class ApprovalStatus { PENDING, APPROVED, REJECTED } @MappedSuperclass abstract class BaseModel : Model() { @Id @GeneratedValue var sysPk: Long = 0 @SoftDelete @DbDefault("false") var deleted: Boolean = false @Version @DbDefault("1") var version: Int = 0 @WhenCreated @DbDefault("now()") var createdAt: LocalDateTime = LocalDateTime.now() @WhenModified @DbDefault("now()") var modifiedAt: LocalDateTime? = null @WhoCreated var createdBy: String = "" @WhoModified var modifiedBy: String? = null var deletedOn: LocalDateTime? = null var deletedBy: String? = null @DbDefault("0") var currentApprovalLevel: Int = 0 @DbDefault("0") var requiredApprovalLevels: Int = 0 @Enumerated(EnumType.STRING) @DbDefault("APPROVED") var approvalStatus: ApprovalStatus = ApprovalStatus.APPROVED @DbArray @DbDefault("{}") var tags: MutableList = arrayListOf() @DbJsonB @DbDefault("[]") var comments: MutableList = arrayListOf() } @MappedSuperclass abstract class BaseTenantModel : BaseModel() { @TenantId var tenantId: String = "" } @Entity open class TenantModel : BaseModel() { @Index(unique = true) var name: String = "" var domain: String = "" var mobile: List = emptyList() var emails: List = emptyList() @DbJsonB var preferences: MutableMap = hashMapOf() } enum class AuditType { CREATE, UPDATE, DELETE, VIEW, APPROVE, REJECT } @Entity @Index(columnNames = ["audit_type", "entity", "unique_identifier", "tenant_id", "created_by"]) open class AuditLog : BaseTenantModel() { @Enumerated(EnumType.STRING) var auditType: AuditType = AuditType.CREATE var entity: String = "" var uniqueIdentifier: String = "" @DbJsonB @Index( definition = "create index audit_log_values_idx on audit_log using GIN (data)", platforms = [Platform.POSTGRES] ) var data: Map = hashMapOf() @DbJsonB @Index( definition = "create index audit_log_changes_idx on audit_log using GIN (changes)", platforms = [Platform.POSTGRES] ) var changes: Map = hashMapOf() } @Entity open class EntityModel : BaseTenantModel() { @Index(unique = true) @JsonDeserialize(using = SafeStringDeserializer::class) var name: String = "" //a kts script that will return true/false along with errors before saving var preSaveScript: String? = "" //a kts script that will do something ... returns void var postSaveScript: String? = "" //this will create extra actions/roles in keycloak //the default actions are create, update, view, delete @DbArray @DbDefault("{}") var actions: List = emptyList() //allow only these fields, if this is empty, then all fields are allowed @DbArray @DbDefault("{}") var allowedFields: List = emptyList() //enforce field types, if this is present, only fields that are present is validated @DbJsonB @DbDefault("{}") var allowedFieldTypes: Map = hashMapOf() //when an entity is saved/updated audit logs will be populated, when this is empty, all fields are logged @DbArray @DbDefault("{}") var auditLogFields: List = emptyList() @DbJsonB @DbDefault("{}") var preferences: MutableMap = hashMapOf() //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 for further approvals @DbDefault("0") var approvalLevels: Int = 0 } enum class JobFrequencyType { SPECIFIC, EVERY, CRON } enum class JobType { SCRIPT, DB } @Entity @Index(unique = true, name = "sql_unique_id", columnNames = ["entity_name", "sql_id", "tenant_id"]) open class SqlModel : BaseTenantModel() { @JsonDeserialize(using = SafeStringDeserializer::class) var sqlId: String = "" var entityName: String = "" @Column(columnDefinition = "text") var sql: String = "" } @Entity open class JobModel : BaseTenantModel() { @Index(unique = true) var jobName: String = "" @Enumerated(EnumType.STRING) var jobType: JobType = JobType.SCRIPT var jobPath: String = "" @DbArray var tenants: List = emptyList() @Enumerated(EnumType.STRING) var jobFrequencyType = JobFrequencyType.EVERY var frequency: String = "1h" } @Entity @Index(unique = true, name = "entity_unique_id", columnNames = ["entity_name", "unique_identifier", "tenant_id"]) open class DataModel : BaseTenantModel() { @JsonDeserialize(using = SafeStringDeserializer::class) var uniqueIdentifier: String = "" @JsonDeserialize(using = SafeStringDeserializer::class) var entityName: String = "" @Index(definition = "create index data_jsonb_idx on data_model using GIN (data) ", platforms = [Platform.POSTGRES]) @DbJsonB var data: MutableMap = hashMapOf() } @Entity @Index(unique = true, name = "unique_session_id", columnNames = ["session_id"]) open class AnonSession : BaseTenantModel() { var sessionId: String? = null var ip: String? = null var firstSeenAt: LocalDateTime? = null var lastSeenAt: LocalDateTime? = null @DbJsonB var headerMap: Map = hashMapOf() } class SafeStringDeserializer : JsonDeserializer() { private val regex = Regex("^[a-zA-Z0-9\\-_\\.]+$") override fun deserialize(p: JsonParser, ctxt: DeserializationContext?): String { val text = p.text if (!regex.matches(text)) throw IllegalArgumentException() return text } }