fix(security): resolve audit findings and compilation errors (Phase 6)
Security fixes: - Add startup warning for default JWT secret in config - Add enum validation for priority, recipient_type, channel fields - Add pagination size cap (max 100) via safe_page_size() - Return generic "权限不足" instead of specific permission names Compilation fixes: - Fix missing standard fields in ActiveModel for tokens/process_variables - Fix migration imports for Statement/DatabaseBackend/Uuid - Add version_field to process_definition ActiveModel Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@ pub fn require_permission(ctx: &TenantContext, permission: &str) -> Result<(), A
|
|||||||
if ctx.permissions.iter().any(|p| p == permission) {
|
if ctx.permissions.iter().any(|p| p == permission) {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(AppError::Forbidden(format!("需要权限: {}", permission)))
|
Err(AppError::Forbidden("权限不足".to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,10 +26,7 @@ pub fn require_any_permission(
|
|||||||
if has_any {
|
if has_any {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(AppError::Forbidden(format!(
|
Err(AppError::Forbidden("权限不足".to_string()))
|
||||||
"需要以下权限之一: {}",
|
|
||||||
permissions.join(", ")
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +37,7 @@ pub fn require_role(ctx: &TenantContext, role: &str) -> Result<(), AppError> {
|
|||||||
if ctx.roles.iter().any(|r| r == role) {
|
if ctx.roles.iter().any(|r| r == role) {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(AppError::Forbidden(format!("需要角色: {}", role)))
|
Err(AppError::Forbidden("权限不足".to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,14 +41,30 @@ pub struct SendMessageReq {
|
|||||||
pub body: String,
|
pub body: String,
|
||||||
pub recipient_id: Uuid,
|
pub recipient_id: Uuid,
|
||||||
#[serde(default = "default_recipient_type")]
|
#[serde(default = "default_recipient_type")]
|
||||||
|
#[validate(custom(function = "validate_recipient_type"))]
|
||||||
pub recipient_type: String,
|
pub recipient_type: String,
|
||||||
#[serde(default = "default_priority")]
|
#[serde(default = "default_priority")]
|
||||||
|
#[validate(custom(function = "validate_priority"))]
|
||||||
pub priority: String,
|
pub priority: String,
|
||||||
pub template_id: Option<Uuid>,
|
pub template_id: Option<Uuid>,
|
||||||
pub business_type: Option<String>,
|
pub business_type: Option<String>,
|
||||||
pub business_id: Option<Uuid>,
|
pub business_id: Option<Uuid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_recipient_type(value: &str) -> Result<(), validator::ValidationError> {
|
||||||
|
match value {
|
||||||
|
"user" | "role" | "department" | "all" => Ok(()),
|
||||||
|
_ => Err(validator::ValidationError::new("invalid_recipient_type")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_priority(value: &str) -> Result<(), validator::ValidationError> {
|
||||||
|
match value {
|
||||||
|
"normal" | "important" | "urgent" => Ok(()),
|
||||||
|
_ => Err(validator::ValidationError::new("invalid_priority")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn default_recipient_type() -> String {
|
fn default_recipient_type() -> String {
|
||||||
"user".to_string()
|
"user".to_string()
|
||||||
}
|
}
|
||||||
@@ -68,6 +84,13 @@ pub struct MessageQuery {
|
|||||||
pub status: Option<String>,
|
pub status: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MessageQuery {
|
||||||
|
/// 获取安全的分页大小(上限 100)。
|
||||||
|
pub fn safe_page_size(&self) -> u64 {
|
||||||
|
self.page_size.unwrap_or(20).min(100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 未读消息计数响应
|
/// 未读消息计数响应
|
||||||
#[derive(Debug, Serialize, ToSchema)]
|
#[derive(Debug, Serialize, ToSchema)]
|
||||||
pub struct UnreadCountResp {
|
pub struct UnreadCountResp {
|
||||||
@@ -99,6 +122,7 @@ pub struct CreateTemplateReq {
|
|||||||
#[validate(length(min = 1, max = 50, message = "编码不能为空且不超过50字符"))]
|
#[validate(length(min = 1, max = 50, message = "编码不能为空且不超过50字符"))]
|
||||||
pub code: String,
|
pub code: String,
|
||||||
#[serde(default = "default_channel")]
|
#[serde(default = "default_channel")]
|
||||||
|
#[validate(custom(function = "validate_channel"))]
|
||||||
pub channel: String,
|
pub channel: String,
|
||||||
#[validate(length(min = 1, max = 200, message = "标题模板不能为空"))]
|
#[validate(length(min = 1, max = 200, message = "标题模板不能为空"))]
|
||||||
pub title_template: String,
|
pub title_template: String,
|
||||||
@@ -112,6 +136,13 @@ fn default_channel() -> String {
|
|||||||
"in_app".to_string()
|
"in_app".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_channel(value: &str) -> Result<(), validator::ValidationError> {
|
||||||
|
match value {
|
||||||
|
"in_app" | "email" | "sms" | "wechat" => Ok(()),
|
||||||
|
_ => Err(validator::ValidationError::new("invalid_channel")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn default_language() -> String {
|
fn default_language() -> String {
|
||||||
"zh-CN".to_string()
|
"zh-CN".to_string()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ impl MessageService {
|
|||||||
query: &MessageQuery,
|
query: &MessageQuery,
|
||||||
db: &sea_orm::DatabaseConnection,
|
db: &sea_orm::DatabaseConnection,
|
||||||
) -> MessageResult<(Vec<MessageResp>, u64)> {
|
) -> MessageResult<(Vec<MessageResp>, u64)> {
|
||||||
let page_size = query.page_size.unwrap_or(20);
|
let page_size = query.safe_page_size();
|
||||||
let mut q = message::Entity::find()
|
let mut q = message::Entity::find()
|
||||||
.filter(message::Column::TenantId.eq(tenant_id))
|
.filter(message::Column::TenantId.eq(tenant_id))
|
||||||
.filter(message::Column::RecipientId.eq(recipient_id))
|
.filter(message::Column::RecipientId.eq(recipient_id))
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ mod m20260413_000023_create_message_templates;
|
|||||||
mod m20260413_000024_create_messages;
|
mod m20260413_000024_create_messages;
|
||||||
mod m20260413_000025_create_message_subscriptions;
|
mod m20260413_000025_create_message_subscriptions;
|
||||||
mod m20260413_000026_create_audit_logs;
|
mod m20260413_000026_create_audit_logs;
|
||||||
|
mod m20260414_000027_fix_unique_indexes_soft_delete;
|
||||||
|
mod m20260414_000028_add_standard_fields_to_tokens;
|
||||||
|
mod m20260414_000029_add_standard_fields_to_process_variables;
|
||||||
|
|
||||||
pub struct Migrator;
|
pub struct Migrator;
|
||||||
|
|
||||||
@@ -59,6 +62,9 @@ impl MigratorTrait for Migrator {
|
|||||||
Box::new(m20260413_000024_create_messages::Migration),
|
Box::new(m20260413_000024_create_messages::Migration),
|
||||||
Box::new(m20260413_000025_create_message_subscriptions::Migration),
|
Box::new(m20260413_000025_create_message_subscriptions::Migration),
|
||||||
Box::new(m20260413_000026_create_audit_logs::Migration),
|
Box::new(m20260413_000026_create_audit_logs::Migration),
|
||||||
|
Box::new(m20260414_000027_fix_unique_indexes_soft_delete::Migration),
|
||||||
|
Box::new(m20260414_000028_add_standard_fields_to_tokens::Migration),
|
||||||
|
Box::new(m20260414_000029_add_standard_fields_to_process_variables::Migration),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
use sea_orm::Statement;
|
||||||
|
use sea_orm::DatabaseBackend;
|
||||||
|
|
||||||
|
/// Recreate unique indexes on roles and permissions to include soft-delete awareness.
|
||||||
|
#[derive(DeriveMigrationName)]
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.get_connection()
|
||||||
|
.execute(Statement::from_string(
|
||||||
|
DatabaseBackend::Postgres,
|
||||||
|
"DROP INDEX IF EXISTS idx_roles_tenant_code".to_string(),
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
manager
|
||||||
|
.get_connection()
|
||||||
|
.execute(Statement::from_string(
|
||||||
|
DatabaseBackend::Postgres,
|
||||||
|
"DROP INDEX IF EXISTS idx_permissions_tenant_code".to_string(),
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
manager
|
||||||
|
.get_connection()
|
||||||
|
.execute(Statement::from_string(
|
||||||
|
DatabaseBackend::Postgres,
|
||||||
|
"CREATE UNIQUE INDEX idx_roles_tenant_code \
|
||||||
|
ON roles (tenant_id, code) \
|
||||||
|
WHERE deleted_at IS NULL"
|
||||||
|
.to_string(),
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
manager
|
||||||
|
.get_connection()
|
||||||
|
.execute(Statement::from_string(
|
||||||
|
DatabaseBackend::Postgres,
|
||||||
|
"CREATE UNIQUE INDEX idx_permissions_tenant_code \
|
||||||
|
ON permissions (tenant_id, code) \
|
||||||
|
WHERE deleted_at IS NULL"
|
||||||
|
.to_string(),
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.get_connection()
|
||||||
|
.execute(Statement::from_string(
|
||||||
|
DatabaseBackend::Postgres,
|
||||||
|
"DROP INDEX IF EXISTS idx_roles_tenant_code".to_string(),
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
manager
|
||||||
|
.get_connection()
|
||||||
|
.execute(Statement::from_string(
|
||||||
|
DatabaseBackend::Postgres,
|
||||||
|
"DROP INDEX IF EXISTS idx_permissions_tenant_code".to_string(),
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
manager
|
||||||
|
.get_connection()
|
||||||
|
.execute(Statement::from_string(
|
||||||
|
DatabaseBackend::Postgres,
|
||||||
|
"CREATE UNIQUE INDEX idx_roles_tenant_code \
|
||||||
|
ON roles (tenant_id, code)"
|
||||||
|
.to_string(),
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
manager
|
||||||
|
.get_connection()
|
||||||
|
.execute(Statement::from_string(
|
||||||
|
DatabaseBackend::Postgres,
|
||||||
|
"CREATE UNIQUE INDEX idx_permissions_tenant_code \
|
||||||
|
ON permissions (tenant_id, code)"
|
||||||
|
.to_string(),
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
/// 为 tokens 表添加缺失的标准字段: updated_at, created_by, updated_by, deleted_at, version。
|
||||||
|
///
|
||||||
|
/// tokens 表原始迁移缺少 ERP 标准要求的审计和乐观锁字段。
|
||||||
|
#[derive(DeriveMigrationName)]
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.alter_table(
|
||||||
|
Table::alter()
|
||||||
|
.table(Tokens::Table)
|
||||||
|
.add_column(
|
||||||
|
ColumnDef::new(Tokens::UpdatedAt)
|
||||||
|
.timestamp_with_time_zone()
|
||||||
|
.not_null()
|
||||||
|
.default(Expr::current_timestamp()),
|
||||||
|
)
|
||||||
|
.add_column(
|
||||||
|
ColumnDef::new(Tokens::CreatedBy)
|
||||||
|
.uuid()
|
||||||
|
.not_null()
|
||||||
|
.default(Expr::val("00000000-0000-0000-0000-000000000000")),
|
||||||
|
)
|
||||||
|
.add_column(
|
||||||
|
ColumnDef::new(Tokens::UpdatedBy)
|
||||||
|
.uuid()
|
||||||
|
.not_null()
|
||||||
|
.default(Expr::val("00000000-0000-0000-0000-000000000000")),
|
||||||
|
)
|
||||||
|
.add_column(
|
||||||
|
ColumnDef::new(Tokens::DeletedAt)
|
||||||
|
.timestamp_with_time_zone()
|
||||||
|
.null(),
|
||||||
|
)
|
||||||
|
.add_column(
|
||||||
|
ColumnDef::new(Tokens::Version)
|
||||||
|
.integer()
|
||||||
|
.not_null()
|
||||||
|
.default(1),
|
||||||
|
)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.alter_table(
|
||||||
|
Table::alter()
|
||||||
|
.table(Tokens::Table)
|
||||||
|
.drop_column(Tokens::UpdatedAt)
|
||||||
|
.drop_column(Tokens::CreatedBy)
|
||||||
|
.drop_column(Tokens::UpdatedBy)
|
||||||
|
.drop_column(Tokens::DeletedAt)
|
||||||
|
.drop_column(Tokens::Version)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(DeriveIden)]
|
||||||
|
enum Tokens {
|
||||||
|
Table,
|
||||||
|
UpdatedAt,
|
||||||
|
CreatedBy,
|
||||||
|
UpdatedBy,
|
||||||
|
DeletedAt,
|
||||||
|
Version,
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
/// 为 process_variables 表添加缺失的标准字段: created_at, updated_at, created_by, updated_by, deleted_at, version。
|
||||||
|
///
|
||||||
|
/// process_variables 表原始迁移缺少 ERP 标准要求的审计和乐观锁字段。
|
||||||
|
#[derive(DeriveMigrationName)]
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.alter_table(
|
||||||
|
Table::alter()
|
||||||
|
.table(ProcessVariables::Table)
|
||||||
|
.add_column(
|
||||||
|
ColumnDef::new(ProcessVariables::CreatedAt)
|
||||||
|
.timestamp_with_time_zone()
|
||||||
|
.not_null()
|
||||||
|
.default(Expr::current_timestamp()),
|
||||||
|
)
|
||||||
|
.add_column(
|
||||||
|
ColumnDef::new(ProcessVariables::UpdatedAt)
|
||||||
|
.timestamp_with_time_zone()
|
||||||
|
.not_null()
|
||||||
|
.default(Expr::current_timestamp()),
|
||||||
|
)
|
||||||
|
.add_column(
|
||||||
|
ColumnDef::new(ProcessVariables::CreatedBy)
|
||||||
|
.uuid()
|
||||||
|
.not_null()
|
||||||
|
.default(Expr::val("00000000-0000-0000-0000-000000000000")),
|
||||||
|
)
|
||||||
|
.add_column(
|
||||||
|
ColumnDef::new(ProcessVariables::UpdatedBy)
|
||||||
|
.uuid()
|
||||||
|
.not_null()
|
||||||
|
.default(Expr::val("00000000-0000-0000-0000-000000000000")),
|
||||||
|
)
|
||||||
|
.add_column(
|
||||||
|
ColumnDef::new(ProcessVariables::DeletedAt)
|
||||||
|
.timestamp_with_time_zone()
|
||||||
|
.null(),
|
||||||
|
)
|
||||||
|
.add_column(
|
||||||
|
ColumnDef::new(ProcessVariables::Version)
|
||||||
|
.integer()
|
||||||
|
.not_null()
|
||||||
|
.default(1),
|
||||||
|
)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.alter_table(
|
||||||
|
Table::alter()
|
||||||
|
.table(ProcessVariables::Table)
|
||||||
|
.drop_column(ProcessVariables::CreatedAt)
|
||||||
|
.drop_column(ProcessVariables::UpdatedAt)
|
||||||
|
.drop_column(ProcessVariables::CreatedBy)
|
||||||
|
.drop_column(ProcessVariables::UpdatedBy)
|
||||||
|
.drop_column(ProcessVariables::DeletedAt)
|
||||||
|
.drop_column(ProcessVariables::Version)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(DeriveIden)]
|
||||||
|
enum ProcessVariables {
|
||||||
|
Table,
|
||||||
|
CreatedAt,
|
||||||
|
UpdatedAt,
|
||||||
|
CreatedBy,
|
||||||
|
UpdatedBy,
|
||||||
|
DeletedAt,
|
||||||
|
Version,
|
||||||
|
}
|
||||||
@@ -59,6 +59,13 @@ impl AppConfig {
|
|||||||
.add_source(config::File::with_name("config/default"))
|
.add_source(config::File::with_name("config/default"))
|
||||||
.add_source(config::Environment::with_prefix("ERP").separator("__"))
|
.add_source(config::Environment::with_prefix("ERP").separator("__"))
|
||||||
.build()?;
|
.build()?;
|
||||||
Ok(config.try_deserialize()?)
|
let app_config: Self = config.try_deserialize()?;
|
||||||
|
|
||||||
|
// 安全检查:禁止在生产使用默认 JWT 密钥
|
||||||
|
if app_config.jwt.secret == "change-me-in-production" {
|
||||||
|
tracing::warn!("⚠️ JWT 密钥使用默认值,请通过 ERP__JWT__SECRET 环境变量设置安全密钥");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(app_config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -250,6 +250,8 @@ impl FlowExecutor {
|
|||||||
let new_token_id = Uuid::now_v7();
|
let new_token_id = Uuid::now_v7();
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
|
|
||||||
|
let system_user = uuid::Uuid::nil();
|
||||||
|
|
||||||
let token_model = token::ActiveModel {
|
let token_model = token::ActiveModel {
|
||||||
id: Set(new_token_id),
|
id: Set(new_token_id),
|
||||||
tenant_id: Set(tenant_id),
|
tenant_id: Set(tenant_id),
|
||||||
@@ -257,6 +259,11 @@ impl FlowExecutor {
|
|||||||
node_id: Set(node_id.to_string()),
|
node_id: Set(node_id.to_string()),
|
||||||
status: Set("active".to_string()),
|
status: Set("active".to_string()),
|
||||||
created_at: Set(now),
|
created_at: Set(now),
|
||||||
|
updated_at: Set(now),
|
||||||
|
created_by: Set(system_user),
|
||||||
|
updated_by: Set(system_user),
|
||||||
|
deleted_at: Set(None),
|
||||||
|
version: Set(1),
|
||||||
consumed_at: Set(None),
|
consumed_at: Set(None),
|
||||||
};
|
};
|
||||||
token_model
|
token_model
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ impl DefinitionService {
|
|||||||
created_by: Set(operator_id),
|
created_by: Set(operator_id),
|
||||||
updated_by: Set(operator_id),
|
updated_by: Set(operator_id),
|
||||||
deleted_at: Set(None),
|
deleted_at: Set(None),
|
||||||
|
version_field: Set(1),
|
||||||
};
|
};
|
||||||
model
|
model
|
||||||
.insert(db)
|
.insert(db)
|
||||||
|
|||||||
@@ -257,6 +257,16 @@ impl InstanceService {
|
|||||||
Self::change_status(id, tenant_id, operator_id, "running", "terminated", db).await
|
Self::change_status(id, tenant_id, operator_id, "running", "terminated", db).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 恢复已挂起的流程实例。
|
||||||
|
pub async fn resume(
|
||||||
|
id: Uuid,
|
||||||
|
tenant_id: Uuid,
|
||||||
|
operator_id: Uuid,
|
||||||
|
db: &sea_orm::DatabaseConnection,
|
||||||
|
) -> WorkflowResult<()> {
|
||||||
|
Self::change_status(id, tenant_id, operator_id, "suspended", "running", db).await
|
||||||
|
}
|
||||||
|
|
||||||
async fn change_status(
|
async fn change_status(
|
||||||
id: Uuid,
|
id: Uuid,
|
||||||
tenant_id: Uuid,
|
tenant_id: Uuid,
|
||||||
@@ -332,6 +342,9 @@ impl InstanceService {
|
|||||||
_ => (Some(value.to_string()), None, None, None),
|
_ => (Some(value.to_string()), None, None, None),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let now = chrono::Utc::now();
|
||||||
|
let system_user = uuid::Uuid::nil();
|
||||||
|
|
||||||
let model = process_variable::ActiveModel {
|
let model = process_variable::ActiveModel {
|
||||||
id: Set(id),
|
id: Set(id),
|
||||||
tenant_id: Set(tenant_id),
|
tenant_id: Set(tenant_id),
|
||||||
@@ -342,6 +355,12 @@ impl InstanceService {
|
|||||||
value_number: Set(value_number),
|
value_number: Set(value_number),
|
||||||
value_boolean: Set(value_boolean),
|
value_boolean: Set(value_boolean),
|
||||||
value_date: Set(None),
|
value_date: Set(None),
|
||||||
|
created_at: Set(now),
|
||||||
|
updated_at: Set(now),
|
||||||
|
created_by: Set(system_user),
|
||||||
|
updated_by: Set(system_user),
|
||||||
|
deleted_at: Set(None),
|
||||||
|
version: Set(1),
|
||||||
};
|
};
|
||||||
model
|
model
|
||||||
.insert(txn)
|
.insert(txn)
|
||||||
|
|||||||
Reference in New Issue
Block a user