feat: initialize ERP base platform (extracted from HMS)
- Stripped 11 business crates (health, ai, dialysis, plugins) - Cleaned AppState, AppConfig, main.rs from business coupling - Reduced migrations from 169 to 53 (base-only) - Removed health_provider trait from erp-core - Removed business integration tests - Removed gateway rate limiting middleware - Base capabilities: auth, RBAC, JWT, config, workflow, message, plugin, audit, crypto, RLS, multi-tenant Cargo check: OK Cargo test: OK
This commit is contained in:
8
crates/erp-server/migration/Cargo.toml
Normal file
8
crates/erp-server/migration/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "erp-server-migration"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
sea-orm-migration.workspace = true
|
||||
tokio.workspace = true
|
||||
120
crates/erp-server/migration/src/lib.rs
Normal file
120
crates/erp-server/migration/src/lib.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
pub use sea_orm_migration::prelude::*;
|
||||
|
||||
mod m20260410_000001_create_tenant;
|
||||
mod m20260411_000002_create_users;
|
||||
mod m20260411_000003_create_user_credentials;
|
||||
mod m20260411_000004_create_user_tokens;
|
||||
mod m20260411_000005_create_roles;
|
||||
mod m20260411_000006_create_permissions;
|
||||
mod m20260411_000007_create_role_permissions;
|
||||
mod m20260411_000008_create_user_roles;
|
||||
mod m20260411_000009_create_organizations;
|
||||
mod m20260411_000010_create_departments;
|
||||
mod m20260411_000011_create_positions;
|
||||
mod m20260412_000012_create_dictionaries;
|
||||
mod m20260412_000013_create_dictionary_items;
|
||||
mod m20260412_000014_create_menus;
|
||||
mod m20260412_000015_create_menu_roles;
|
||||
mod m20260412_000016_create_settings;
|
||||
mod m20260412_000017_create_numbering_rules;
|
||||
mod m20260412_000018_create_process_definitions;
|
||||
mod m20260412_000019_create_process_instances;
|
||||
mod m20260412_000020_create_tokens;
|
||||
mod m20260412_000021_create_tasks;
|
||||
mod m20260412_000022_create_process_variables;
|
||||
mod m20260413_000023_create_message_templates;
|
||||
mod m20260413_000024_create_messages;
|
||||
mod m20260413_000025_create_message_subscriptions;
|
||||
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;
|
||||
mod m20260415_000030_add_version_to_message_tables;
|
||||
mod m20260416_000031_create_domain_events;
|
||||
mod m20260414_000032_fix_settings_unique_index_null;
|
||||
mod m20260417_000033_create_plugins;
|
||||
mod m20260417_000034_seed_plugin_permissions;
|
||||
mod m20260418_000035_pg_trgm_and_entity_columns;
|
||||
mod m20260418_000036_add_data_scope_to_role_permissions;
|
||||
mod m20260419_000037_create_user_departments;
|
||||
mod m20260419_000039_entity_registry_columns;
|
||||
mod m20260419_000040_plugin_market;
|
||||
mod m20260419_000041_plugin_user_views;
|
||||
mod m20260423_000043_create_wechat_users;
|
||||
mod m20260427_000062_create_tenant_crypto_keys;
|
||||
mod m20260427_000084_domain_events_cleanup;
|
||||
mod m20260427_000085_processed_events;
|
||||
mod m20260427_000086_enable_rls_all_tables;
|
||||
mod m20260427_000087_audit_logs_hash_chain;
|
||||
mod m20260428_000088_rls_policy_strict;
|
||||
mod m20260428_000089_blind_indexes;
|
||||
mod m20260428_000091_dead_letter_events;
|
||||
mod m20260504_000106_create_api_clients;
|
||||
mod m20260513_000144_enforce_version_optimistic_lock;
|
||||
mod m20260518_000149_fix_admin_permissions;
|
||||
mod m20260529_000169_supplement_rls_for_new_tables;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigratorTrait for Migrator {
|
||||
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
||||
vec![
|
||||
Box::new(m20260410_000001_create_tenant::Migration),
|
||||
Box::new(m20260411_000002_create_users::Migration),
|
||||
Box::new(m20260411_000003_create_user_credentials::Migration),
|
||||
Box::new(m20260411_000004_create_user_tokens::Migration),
|
||||
Box::new(m20260411_000005_create_roles::Migration),
|
||||
Box::new(m20260411_000006_create_permissions::Migration),
|
||||
Box::new(m20260411_000007_create_role_permissions::Migration),
|
||||
Box::new(m20260411_000008_create_user_roles::Migration),
|
||||
Box::new(m20260411_000009_create_organizations::Migration),
|
||||
Box::new(m20260411_000010_create_departments::Migration),
|
||||
Box::new(m20260411_000011_create_positions::Migration),
|
||||
Box::new(m20260412_000012_create_dictionaries::Migration),
|
||||
Box::new(m20260412_000013_create_dictionary_items::Migration),
|
||||
Box::new(m20260412_000014_create_menus::Migration),
|
||||
Box::new(m20260412_000015_create_menu_roles::Migration),
|
||||
Box::new(m20260412_000016_create_settings::Migration),
|
||||
Box::new(m20260412_000017_create_numbering_rules::Migration),
|
||||
Box::new(m20260412_000018_create_process_definitions::Migration),
|
||||
Box::new(m20260412_000019_create_process_instances::Migration),
|
||||
Box::new(m20260412_000020_create_tokens::Migration),
|
||||
Box::new(m20260412_000021_create_tasks::Migration),
|
||||
Box::new(m20260412_000022_create_process_variables::Migration),
|
||||
Box::new(m20260413_000023_create_message_templates::Migration),
|
||||
Box::new(m20260413_000024_create_messages::Migration),
|
||||
Box::new(m20260413_000025_create_message_subscriptions::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),
|
||||
Box::new(m20260415_000030_add_version_to_message_tables::Migration),
|
||||
Box::new(m20260416_000031_create_domain_events::Migration),
|
||||
Box::new(m20260414_000032_fix_settings_unique_index_null::Migration),
|
||||
Box::new(m20260417_000033_create_plugins::Migration),
|
||||
Box::new(m20260417_000034_seed_plugin_permissions::Migration),
|
||||
Box::new(m20260418_000035_pg_trgm_and_entity_columns::Migration),
|
||||
Box::new(m20260418_000036_add_data_scope_to_role_permissions::Migration),
|
||||
Box::new(m20260419_000037_create_user_departments::Migration),
|
||||
Box::new(m20260419_000039_entity_registry_columns::Migration),
|
||||
Box::new(m20260419_000040_plugin_market::Migration),
|
||||
Box::new(m20260419_000041_plugin_user_views::Migration),
|
||||
Box::new(m20260423_000043_create_wechat_users::Migration),
|
||||
Box::new(m20260427_000062_create_tenant_crypto_keys::Migration),
|
||||
Box::new(m20260427_000084_domain_events_cleanup::Migration),
|
||||
Box::new(m20260427_000085_processed_events::Migration),
|
||||
Box::new(m20260427_000086_enable_rls_all_tables::Migration),
|
||||
Box::new(m20260427_000087_audit_logs_hash_chain::Migration),
|
||||
Box::new(m20260428_000088_rls_policy_strict::Migration),
|
||||
Box::new(m20260428_000089_blind_indexes::Migration),
|
||||
Box::new(m20260428_000091_dead_letter_events::Migration),
|
||||
Box::new(m20260504_000106_create_api_clients::Migration),
|
||||
Box::new(m20260513_000144_enforce_version_optimistic_lock::Migration),
|
||||
Box::new(m20260518_000149_fix_admin_permissions::Migration),
|
||||
Box::new(m20260529_000169_supplement_rls_for_new_tables::Migration),
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Tenant::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Tenant::Id).uuid().not_null().primary_key())
|
||||
.col(ColumnDef::new(Tenant::Name).string().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Tenant::Code)
|
||||
.string()
|
||||
.not_null()
|
||||
.unique_key(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Tenant::Status)
|
||||
.string()
|
||||
.not_null()
|
||||
.default("active"),
|
||||
)
|
||||
.col(ColumnDef::new(Tenant::Settings).json().null())
|
||||
.col(
|
||||
ColumnDef::new(Tenant::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Tenant::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Tenant::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Tenant::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Tenant {
|
||||
Table,
|
||||
Id,
|
||||
Name,
|
||||
Code,
|
||||
Status,
|
||||
Settings,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
DeletedAt,
|
||||
}
|
||||
104
crates/erp-server/migration/src/m20260411_000002_create_users.rs
Normal file
104
crates/erp-server/migration/src/m20260411_000002_create_users.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Users::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Users::Id).uuid().not_null().primary_key())
|
||||
.col(ColumnDef::new(Users::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(Users::Username).string().not_null())
|
||||
.col(ColumnDef::new(Users::Email).string().null())
|
||||
.col(ColumnDef::new(Users::Phone).string().null())
|
||||
.col(ColumnDef::new(Users::DisplayName).string().null())
|
||||
.col(ColumnDef::new(Users::AvatarUrl).string().null())
|
||||
.col(
|
||||
ColumnDef::new(Users::Status)
|
||||
.string()
|
||||
.not_null()
|
||||
.default("active"),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Users::LastLoginAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Users::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Users::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(Users::CreatedBy).uuid().not_null())
|
||||
.col(ColumnDef::new(Users::UpdatedBy).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Users::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Users::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_users_tenant_id")
|
||||
.table(Users::Table)
|
||||
.col(Users::TenantId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager.get_connection().execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"CREATE UNIQUE INDEX idx_users_tenant_username ON users (tenant_id, username) WHERE deleted_at IS NULL".to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Users::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Users {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
Username,
|
||||
Email,
|
||||
Phone,
|
||||
DisplayName,
|
||||
AvatarUrl,
|
||||
Status,
|
||||
LastLoginAt,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
Version,
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(UserCredentials::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(UserCredentials::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(UserCredentials::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(UserCredentials::UserId).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(UserCredentials::CredentialType)
|
||||
.string()
|
||||
.not_null()
|
||||
.default("password"),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(UserCredentials::CredentialData)
|
||||
.json()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(UserCredentials::Verified)
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(false),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(UserCredentials::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(UserCredentials::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(UserCredentials::CreatedBy).uuid().not_null())
|
||||
.col(ColumnDef::new(UserCredentials::UpdatedBy).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(UserCredentials::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(UserCredentials::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.foreign_key(
|
||||
&mut ForeignKey::create()
|
||||
.name("fk_user_credentials_user_id")
|
||||
.from(UserCredentials::Table, UserCredentials::UserId)
|
||||
.to(Users::Table, Users::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.to_owned(),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_user_credentials_tenant_id")
|
||||
.table(UserCredentials::Table)
|
||||
.col(UserCredentials::TenantId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_user_credentials_user_id")
|
||||
.table(UserCredentials::Table)
|
||||
.col(UserCredentials::UserId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(UserCredentials::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum UserCredentials {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
UserId,
|
||||
CredentialType,
|
||||
CredentialData,
|
||||
Verified,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
Version,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Users {
|
||||
Table,
|
||||
Id,
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(UserTokens::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(UserTokens::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(UserTokens::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(UserTokens::UserId).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(UserTokens::TokenHash)
|
||||
.string()
|
||||
.not_null()
|
||||
.unique_key(),
|
||||
)
|
||||
.col(ColumnDef::new(UserTokens::TokenType).string().not_null())
|
||||
.col(
|
||||
ColumnDef::new(UserTokens::ExpiresAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(UserTokens::RevokedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(ColumnDef::new(UserTokens::DeviceInfo).string().null())
|
||||
.col(
|
||||
ColumnDef::new(UserTokens::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(UserTokens::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(UserTokens::CreatedBy).uuid().not_null())
|
||||
.col(ColumnDef::new(UserTokens::UpdatedBy).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(UserTokens::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(UserTokens::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.foreign_key(
|
||||
&mut ForeignKey::create()
|
||||
.name("fk_user_tokens_user_id")
|
||||
.from(UserTokens::Table, UserTokens::UserId)
|
||||
.to(Users::Table, Users::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.to_owned(),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_user_tokens_tenant_id")
|
||||
.table(UserTokens::Table)
|
||||
.col(UserTokens::TenantId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_user_tokens_user_id")
|
||||
.table(UserTokens::Table)
|
||||
.col(UserTokens::UserId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_user_tokens_token_hash")
|
||||
.table(UserTokens::Table)
|
||||
.col(UserTokens::TokenHash)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(UserTokens::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum UserTokens {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
UserId,
|
||||
TokenHash,
|
||||
TokenType,
|
||||
ExpiresAt,
|
||||
RevokedAt,
|
||||
DeviceInfo,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
Version,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Users {
|
||||
Table,
|
||||
Id,
|
||||
}
|
||||
101
crates/erp-server/migration/src/m20260411_000005_create_roles.rs
Normal file
101
crates/erp-server/migration/src/m20260411_000005_create_roles.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Roles::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Roles::Id).uuid().not_null().primary_key())
|
||||
.col(ColumnDef::new(Roles::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(Roles::Name).string().not_null())
|
||||
.col(ColumnDef::new(Roles::Code).string().not_null())
|
||||
.col(ColumnDef::new(Roles::Description).text().null())
|
||||
.col(
|
||||
ColumnDef::new(Roles::IsSystem)
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(false),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Roles::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Roles::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(Roles::CreatedBy).uuid().not_null())
|
||||
.col(ColumnDef::new(Roles::UpdatedBy).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Roles::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Roles::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_roles_tenant_id")
|
||||
.table(Roles::Table)
|
||||
.col(Roles::TenantId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_roles_tenant_code")
|
||||
.table(Roles::Table)
|
||||
.col(Roles::TenantId)
|
||||
.col(Roles::Code)
|
||||
.unique()
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Roles::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Roles {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
Name,
|
||||
Code,
|
||||
Description,
|
||||
IsSystem,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
Version,
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Permissions::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Permissions::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Permissions::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(Permissions::Code).string().not_null())
|
||||
.col(ColumnDef::new(Permissions::Name).string().not_null())
|
||||
.col(ColumnDef::new(Permissions::Resource).string().not_null())
|
||||
.col(ColumnDef::new(Permissions::Action).string().not_null())
|
||||
.col(ColumnDef::new(Permissions::Description).text().null())
|
||||
.col(
|
||||
ColumnDef::new(Permissions::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Permissions::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(Permissions::CreatedBy).uuid().not_null())
|
||||
.col(ColumnDef::new(Permissions::UpdatedBy).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Permissions::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Permissions::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_permissions_tenant_id")
|
||||
.table(Permissions::Table)
|
||||
.col(Permissions::TenantId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_permissions_tenant_code")
|
||||
.table(Permissions::Table)
|
||||
.col(Permissions::TenantId)
|
||||
.col(Permissions::Code)
|
||||
.unique()
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Permissions::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Permissions {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
Code,
|
||||
Name,
|
||||
Resource,
|
||||
Action,
|
||||
Description,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
Version,
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(RolePermissions::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(RolePermissions::RoleId).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(RolePermissions::PermissionId)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(RolePermissions::TenantId).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(RolePermissions::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(RolePermissions::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(RolePermissions::CreatedBy).uuid().not_null())
|
||||
.col(ColumnDef::new(RolePermissions::UpdatedBy).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(RolePermissions::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(RolePermissions::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.primary_key(
|
||||
Index::create()
|
||||
.col(RolePermissions::RoleId)
|
||||
.col(RolePermissions::PermissionId),
|
||||
)
|
||||
.foreign_key(
|
||||
&mut ForeignKey::create()
|
||||
.name("fk_role_permissions_role_id")
|
||||
.from(RolePermissions::Table, RolePermissions::RoleId)
|
||||
.to(Roles::Table, Roles::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.to_owned(),
|
||||
)
|
||||
.foreign_key(
|
||||
&mut ForeignKey::create()
|
||||
.name("fk_role_permissions_permission_id")
|
||||
.from(RolePermissions::Table, RolePermissions::PermissionId)
|
||||
.to(Permissions::Table, Permissions::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.to_owned(),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_role_permissions_tenant_id")
|
||||
.table(RolePermissions::Table)
|
||||
.col(RolePermissions::TenantId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(RolePermissions::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum RolePermissions {
|
||||
Table,
|
||||
RoleId,
|
||||
PermissionId,
|
||||
TenantId,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
Version,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Roles {
|
||||
Table,
|
||||
Id,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Permissions {
|
||||
Table,
|
||||
Id,
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(UserRoles::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(UserRoles::UserId).uuid().not_null())
|
||||
.col(ColumnDef::new(UserRoles::RoleId).uuid().not_null())
|
||||
.col(ColumnDef::new(UserRoles::TenantId).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(UserRoles::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(UserRoles::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(UserRoles::CreatedBy).uuid().not_null())
|
||||
.col(ColumnDef::new(UserRoles::UpdatedBy).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(UserRoles::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(UserRoles::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.primary_key(
|
||||
Index::create()
|
||||
.col(UserRoles::UserId)
|
||||
.col(UserRoles::RoleId),
|
||||
)
|
||||
.foreign_key(
|
||||
&mut ForeignKey::create()
|
||||
.name("fk_user_roles_user_id")
|
||||
.from(UserRoles::Table, UserRoles::UserId)
|
||||
.to(Users::Table, Users::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.to_owned(),
|
||||
)
|
||||
.foreign_key(
|
||||
&mut ForeignKey::create()
|
||||
.name("fk_user_roles_role_id")
|
||||
.from(UserRoles::Table, UserRoles::RoleId)
|
||||
.to(Roles::Table, Roles::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.to_owned(),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_user_roles_tenant_id")
|
||||
.table(UserRoles::Table)
|
||||
.col(UserRoles::TenantId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(UserRoles::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum UserRoles {
|
||||
Table,
|
||||
UserId,
|
||||
RoleId,
|
||||
TenantId,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
Version,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Users {
|
||||
Table,
|
||||
Id,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Roles {
|
||||
Table,
|
||||
Id,
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Organizations::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Organizations::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Organizations::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(Organizations::Name).string().not_null())
|
||||
.col(ColumnDef::new(Organizations::Code).string().null())
|
||||
.col(ColumnDef::new(Organizations::ParentId).uuid().null())
|
||||
.col(ColumnDef::new(Organizations::Path).string().null())
|
||||
.col(
|
||||
ColumnDef::new(Organizations::Level)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Organizations::SortOrder)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Organizations::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Organizations::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(Organizations::CreatedBy).uuid().not_null())
|
||||
.col(ColumnDef::new(Organizations::UpdatedBy).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Organizations::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Organizations::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.foreign_key(
|
||||
&mut ForeignKey::create()
|
||||
.name("fk_organizations_parent_id")
|
||||
.from(Organizations::Table, Organizations::ParentId)
|
||||
.to(Organizations::Table, Organizations::Id)
|
||||
.on_delete(ForeignKeyAction::Restrict)
|
||||
.to_owned(),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_organizations_tenant_id")
|
||||
.table(Organizations::Table)
|
||||
.col(Organizations::TenantId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager.get_connection().execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"CREATE UNIQUE INDEX idx_organizations_tenant_code ON organizations (tenant_id, code) WHERE code IS NOT NULL".to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Organizations::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Organizations {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
Name,
|
||||
Code,
|
||||
ParentId,
|
||||
Path,
|
||||
Level,
|
||||
SortOrder,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
Version,
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Departments::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Departments::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Departments::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(Departments::OrgId).uuid().not_null())
|
||||
.col(ColumnDef::new(Departments::Name).string().not_null())
|
||||
.col(ColumnDef::new(Departments::Code).string().null())
|
||||
.col(ColumnDef::new(Departments::ParentId).uuid().null())
|
||||
.col(ColumnDef::new(Departments::ManagerId).uuid().null())
|
||||
.col(ColumnDef::new(Departments::Path).string().null())
|
||||
.col(
|
||||
ColumnDef::new(Departments::SortOrder)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Departments::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Departments::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(Departments::CreatedBy).uuid().not_null())
|
||||
.col(ColumnDef::new(Departments::UpdatedBy).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Departments::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Departments::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.foreign_key(
|
||||
&mut ForeignKey::create()
|
||||
.name("fk_departments_org_id")
|
||||
.from(Departments::Table, Departments::OrgId)
|
||||
.to(Organizations::Table, Organizations::Id)
|
||||
.on_delete(ForeignKeyAction::Restrict)
|
||||
.to_owned(),
|
||||
)
|
||||
.foreign_key(
|
||||
&mut ForeignKey::create()
|
||||
.name("fk_departments_parent_id")
|
||||
.from(Departments::Table, Departments::ParentId)
|
||||
.to(Departments::Table, Departments::Id)
|
||||
.on_delete(ForeignKeyAction::Restrict)
|
||||
.to_owned(),
|
||||
)
|
||||
.foreign_key(
|
||||
&mut ForeignKey::create()
|
||||
.name("fk_departments_manager_id")
|
||||
.from(Departments::Table, Departments::ManagerId)
|
||||
.to(Users::Table, Users::Id)
|
||||
.on_delete(ForeignKeyAction::SetNull)
|
||||
.to_owned(),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_departments_tenant_id")
|
||||
.table(Departments::Table)
|
||||
.col(Departments::TenantId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_departments_org_id")
|
||||
.table(Departments::Table)
|
||||
.col(Departments::OrgId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Departments::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Departments {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
OrgId,
|
||||
Name,
|
||||
Code,
|
||||
ParentId,
|
||||
ManagerId,
|
||||
Path,
|
||||
SortOrder,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
Version,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Organizations {
|
||||
Table,
|
||||
Id,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Users {
|
||||
Table,
|
||||
Id,
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Positions::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Positions::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Positions::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(Positions::DeptId).uuid().not_null())
|
||||
.col(ColumnDef::new(Positions::Name).string().not_null())
|
||||
.col(ColumnDef::new(Positions::Code).string().null())
|
||||
.col(
|
||||
ColumnDef::new(Positions::Level)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Positions::SortOrder)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Positions::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Positions::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(Positions::CreatedBy).uuid().not_null())
|
||||
.col(ColumnDef::new(Positions::UpdatedBy).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Positions::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Positions::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.foreign_key(
|
||||
&mut ForeignKey::create()
|
||||
.name("fk_positions_dept_id")
|
||||
.from(Positions::Table, Positions::DeptId)
|
||||
.to(Departments::Table, Departments::Id)
|
||||
.on_delete(ForeignKeyAction::Restrict)
|
||||
.to_owned(),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_positions_tenant_id")
|
||||
.table(Positions::Table)
|
||||
.col(Positions::TenantId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_positions_dept_id")
|
||||
.table(Positions::Table)
|
||||
.col(Positions::DeptId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Positions::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Positions {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
DeptId,
|
||||
Name,
|
||||
Code,
|
||||
Level,
|
||||
SortOrder,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
Version,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Departments {
|
||||
Table,
|
||||
Id,
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Dictionaries::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Dictionaries::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Dictionaries::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(Dictionaries::Name).string().not_null())
|
||||
.col(ColumnDef::new(Dictionaries::Code).string().not_null())
|
||||
.col(ColumnDef::new(Dictionaries::Description).text().null())
|
||||
.col(
|
||||
ColumnDef::new(Dictionaries::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Dictionaries::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(Dictionaries::CreatedBy).uuid().not_null())
|
||||
.col(ColumnDef::new(Dictionaries::UpdatedBy).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Dictionaries::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Dictionaries::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_dictionaries_tenant_id")
|
||||
.table(Dictionaries::Table)
|
||||
.col(Dictionaries::TenantId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager.get_connection().execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"CREATE UNIQUE INDEX idx_dictionaries_tenant_code ON dictionaries (tenant_id, code) WHERE deleted_at IS NULL".to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Dictionaries::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Dictionaries {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
Name,
|
||||
Code,
|
||||
Description,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
Version,
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(DictionaryItems::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(DictionaryItems::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(DictionaryItems::TenantId).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(DictionaryItems::DictionaryId)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(DictionaryItems::Label).string().not_null())
|
||||
.col(ColumnDef::new(DictionaryItems::Value).string().not_null())
|
||||
.col(
|
||||
ColumnDef::new(DictionaryItems::SortOrder)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(ColumnDef::new(DictionaryItems::Color).string().null())
|
||||
.col(
|
||||
ColumnDef::new(DictionaryItems::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(DictionaryItems::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(DictionaryItems::CreatedBy).uuid().not_null())
|
||||
.col(ColumnDef::new(DictionaryItems::UpdatedBy).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(DictionaryItems::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(DictionaryItems::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk_dict_items_dictionary")
|
||||
.from(DictionaryItems::Table, DictionaryItems::DictionaryId)
|
||||
.to(Dictionaries::Table, Dictionaries::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_dict_items_dictionary_id")
|
||||
.table(DictionaryItems::Table)
|
||||
.col(DictionaryItems::DictionaryId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager.get_connection().execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"CREATE UNIQUE INDEX idx_dict_items_dict_value ON dictionary_items (dictionary_id, value) WHERE deleted_at IS NULL".to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(DictionaryItems::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Dictionaries {
|
||||
Table,
|
||||
Id,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum DictionaryItems {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
DictionaryId,
|
||||
Label,
|
||||
Value,
|
||||
SortOrder,
|
||||
Color,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
Version,
|
||||
}
|
||||
124
crates/erp-server/migration/src/m20260412_000014_create_menus.rs
Normal file
124
crates/erp-server/migration/src/m20260412_000014_create_menus.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Menus::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Menus::Id).uuid().not_null().primary_key())
|
||||
.col(ColumnDef::new(Menus::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(Menus::ParentId).uuid().null())
|
||||
.col(ColumnDef::new(Menus::Title).string().not_null())
|
||||
.col(ColumnDef::new(Menus::Path).string().null())
|
||||
.col(ColumnDef::new(Menus::Icon).string().null())
|
||||
.col(
|
||||
ColumnDef::new(Menus::SortOrder)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Menus::Visible)
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(true),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Menus::MenuType)
|
||||
.string()
|
||||
.not_null()
|
||||
.default("page"),
|
||||
)
|
||||
.col(ColumnDef::new(Menus::Permission).string().null())
|
||||
.col(
|
||||
ColumnDef::new(Menus::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Menus::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(Menus::CreatedBy).uuid().not_null())
|
||||
.col(ColumnDef::new(Menus::UpdatedBy).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Menus::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Menus::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk_menus_parent")
|
||||
.from(Menus::Table, Menus::ParentId)
|
||||
.to(Menus::Table, Menus::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_menus_tenant_id")
|
||||
.table(Menus::Table)
|
||||
.col(Menus::TenantId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_menus_parent_id")
|
||||
.table(Menus::Table)
|
||||
.col(Menus::ParentId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Menus::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Menus {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
ParentId,
|
||||
Title,
|
||||
Path,
|
||||
Icon,
|
||||
SortOrder,
|
||||
Visible,
|
||||
MenuType,
|
||||
Permission,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
Version,
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(MenuRoles::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(MenuRoles::MenuId).uuid().not_null())
|
||||
.col(ColumnDef::new(MenuRoles::RoleId).uuid().not_null())
|
||||
.col(ColumnDef::new(MenuRoles::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(MenuRoles::Id).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(MenuRoles::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(MenuRoles::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(MenuRoles::CreatedBy).uuid().not_null())
|
||||
.col(ColumnDef::new(MenuRoles::UpdatedBy).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(MenuRoles::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(MenuRoles::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.primary_key(Index::create().col(MenuRoles::Id))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager.get_connection().execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"CREATE UNIQUE INDEX idx_menu_roles_unique ON menu_roles (menu_id, role_id) WHERE deleted_at IS NULL".to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_menu_roles_menu_id")
|
||||
.table(MenuRoles::Table)
|
||||
.col(MenuRoles::MenuId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_menu_roles_role_id")
|
||||
.table(MenuRoles::Table)
|
||||
.col(MenuRoles::RoleId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(MenuRoles::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum MenuRoles {
|
||||
Table,
|
||||
Id,
|
||||
MenuId,
|
||||
RoleId,
|
||||
TenantId,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
Version,
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Settings::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Settings::Id).uuid().not_null().primary_key())
|
||||
.col(ColumnDef::new(Settings::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(Settings::Scope).string().not_null())
|
||||
.col(ColumnDef::new(Settings::ScopeId).uuid().null())
|
||||
.col(ColumnDef::new(Settings::SettingKey).string().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Settings::SettingValue)
|
||||
.json_binary()
|
||||
.not_null()
|
||||
.default(Expr::val("{}")),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Settings::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Settings::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(Settings::CreatedBy).uuid().not_null())
|
||||
.col(ColumnDef::new(Settings::UpdatedBy).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Settings::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Settings::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_settings_tenant_id")
|
||||
.table(Settings::Table)
|
||||
.col(Settings::TenantId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager.get_connection().execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"CREATE UNIQUE INDEX idx_settings_scope_key ON settings (tenant_id, scope, scope_id, setting_key) WHERE deleted_at IS NULL".to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Settings::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Settings {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
Scope,
|
||||
ScopeId,
|
||||
SettingKey,
|
||||
SettingValue,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
Version,
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(NumberingRules::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(NumberingRules::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(NumberingRules::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(NumberingRules::Name).string().not_null())
|
||||
.col(ColumnDef::new(NumberingRules::Code).string().not_null())
|
||||
.col(
|
||||
ColumnDef::new(NumberingRules::Prefix)
|
||||
.string()
|
||||
.not_null()
|
||||
.default(""),
|
||||
)
|
||||
.col(ColumnDef::new(NumberingRules::DateFormat).string().null())
|
||||
.col(
|
||||
ColumnDef::new(NumberingRules::SeqLength)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(4),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(NumberingRules::SeqStart)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(NumberingRules::SeqCurrent)
|
||||
.big_integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(NumberingRules::Separator)
|
||||
.string()
|
||||
.not_null()
|
||||
.default("-"),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(NumberingRules::ResetCycle)
|
||||
.string()
|
||||
.not_null()
|
||||
.default("never"),
|
||||
)
|
||||
.col(ColumnDef::new(NumberingRules::LastResetDate).date().null())
|
||||
.col(
|
||||
ColumnDef::new(NumberingRules::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(NumberingRules::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(NumberingRules::CreatedBy).uuid().not_null())
|
||||
.col(ColumnDef::new(NumberingRules::UpdatedBy).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(NumberingRules::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(NumberingRules::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_numbering_rules_tenant_id")
|
||||
.table(NumberingRules::Table)
|
||||
.col(NumberingRules::TenantId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager.get_connection().execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"CREATE UNIQUE INDEX idx_numbering_rules_tenant_code ON numbering_rules (tenant_id, code) WHERE deleted_at IS NULL".to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(NumberingRules::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum NumberingRules {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
Name,
|
||||
Code,
|
||||
Prefix,
|
||||
DateFormat,
|
||||
SeqLength,
|
||||
SeqStart,
|
||||
SeqCurrent,
|
||||
Separator,
|
||||
ResetCycle,
|
||||
LastResetDate,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
Version,
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(ProcessDefinitions::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(ProcessDefinitions::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ProcessDefinitions::TenantId)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(ProcessDefinitions::Name).string().not_null())
|
||||
.col(ColumnDef::new(ProcessDefinitions::Key).string().not_null())
|
||||
.col(
|
||||
ColumnDef::new(ProcessDefinitions::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.col(ColumnDef::new(ProcessDefinitions::Category).string().null())
|
||||
.col(
|
||||
ColumnDef::new(ProcessDefinitions::Description)
|
||||
.text()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ProcessDefinitions::Nodes)
|
||||
.json_binary()
|
||||
.not_null()
|
||||
.default(Expr::val("[]")),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ProcessDefinitions::Edges)
|
||||
.json_binary()
|
||||
.not_null()
|
||||
.default(Expr::val("[]")),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ProcessDefinitions::Status)
|
||||
.string()
|
||||
.not_null()
|
||||
.default("draft"),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ProcessDefinitions::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ProcessDefinitions::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ProcessDefinitions::CreatedBy)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ProcessDefinitions::UpdatedBy)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ProcessDefinitions::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ProcessDefinitions::VersionField)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_process_definitions_tenant_id")
|
||||
.table(ProcessDefinitions::Table)
|
||||
.col(ProcessDefinitions::TenantId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager.get_connection().execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"CREATE UNIQUE INDEX idx_process_definitions_key_version ON process_definitions (tenant_id, key, version) WHERE deleted_at IS NULL".to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(ProcessDefinitions::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum ProcessDefinitions {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
Name,
|
||||
Key,
|
||||
Version,
|
||||
Category,
|
||||
Description,
|
||||
Nodes,
|
||||
Edges,
|
||||
Status,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
VersionField,
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(ProcessInstances::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(ProcessInstances::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(ProcessInstances::TenantId).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(ProcessInstances::DefinitionId)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ProcessInstances::BusinessKey)
|
||||
.string()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ProcessInstances::Status)
|
||||
.string()
|
||||
.not_null()
|
||||
.default("running"),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ProcessInstances::StartedBy)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ProcessInstances::StartedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ProcessInstances::CompletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ProcessInstances::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ProcessInstances::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ProcessInstances::CreatedBy)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ProcessInstances::UpdatedBy)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ProcessInstances::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ProcessInstances::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_instances_tenant_status")
|
||||
.table(ProcessInstances::Table)
|
||||
.col(ProcessInstances::TenantId)
|
||||
.col(ProcessInstances::Status)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk_instances_definition")
|
||||
.from(ProcessInstances::Table, ProcessInstances::DefinitionId)
|
||||
.to(ProcessDefinitions::Table, ProcessDefinitions::Id)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(ProcessInstances::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum ProcessInstances {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
DefinitionId,
|
||||
BusinessKey,
|
||||
Status,
|
||||
StartedBy,
|
||||
StartedAt,
|
||||
CompletedAt,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
Version,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum ProcessDefinitions {
|
||||
Table,
|
||||
Id,
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Tokens::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Tokens::Id).uuid().not_null().primary_key())
|
||||
.col(ColumnDef::new(Tokens::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(Tokens::InstanceId).uuid().not_null())
|
||||
.col(ColumnDef::new(Tokens::NodeId).string().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Tokens::Status)
|
||||
.string()
|
||||
.not_null()
|
||||
.default("active"),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Tokens::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Tokens::ConsumedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_tokens_instance")
|
||||
.table(Tokens::Table)
|
||||
.col(Tokens::InstanceId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk_tokens_instance")
|
||||
.from(Tokens::Table, Tokens::InstanceId)
|
||||
.to(ProcessInstances::Table, ProcessInstances::Id)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Tokens::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Tokens {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
InstanceId,
|
||||
NodeId,
|
||||
Status,
|
||||
CreatedAt,
|
||||
ConsumedAt,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum ProcessInstances {
|
||||
Table,
|
||||
Id,
|
||||
}
|
||||
155
crates/erp-server/migration/src/m20260412_000021_create_tasks.rs
Normal file
155
crates/erp-server/migration/src/m20260412_000021_create_tasks.rs
Normal file
@@ -0,0 +1,155 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Tasks::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Tasks::Id).uuid().not_null().primary_key())
|
||||
.col(ColumnDef::new(Tasks::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(Tasks::InstanceId).uuid().not_null())
|
||||
.col(ColumnDef::new(Tasks::TokenId).uuid().not_null())
|
||||
.col(ColumnDef::new(Tasks::NodeId).string().not_null())
|
||||
.col(ColumnDef::new(Tasks::NodeName).string().null())
|
||||
.col(ColumnDef::new(Tasks::AssigneeId).uuid().null())
|
||||
.col(ColumnDef::new(Tasks::CandidateGroups).json_binary().null())
|
||||
.col(
|
||||
ColumnDef::new(Tasks::Status)
|
||||
.string()
|
||||
.not_null()
|
||||
.default("pending"),
|
||||
)
|
||||
.col(ColumnDef::new(Tasks::Outcome).string().null())
|
||||
.col(ColumnDef::new(Tasks::FormData).json_binary().null())
|
||||
.col(
|
||||
ColumnDef::new(Tasks::DueDate)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Tasks::CompletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Tasks::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Tasks::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(Tasks::CreatedBy).uuid().not_null())
|
||||
.col(ColumnDef::new(Tasks::UpdatedBy).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Tasks::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Tasks::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_tasks_assignee")
|
||||
.table(Tasks::Table)
|
||||
.col(Tasks::TenantId)
|
||||
.col(Tasks::AssigneeId)
|
||||
.col(Tasks::Status)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_tasks_instance")
|
||||
.table(Tasks::Table)
|
||||
.col(Tasks::InstanceId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk_tasks_instance")
|
||||
.from(Tasks::Table, Tasks::InstanceId)
|
||||
.to(ProcessInstances::Table, ProcessInstances::Id)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk_tasks_token")
|
||||
.from(Tasks::Table, Tasks::TokenId)
|
||||
.to(Tokens::Table, Tokens::Id)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Tasks::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Tasks {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
InstanceId,
|
||||
TokenId,
|
||||
NodeId,
|
||||
NodeName,
|
||||
AssigneeId,
|
||||
CandidateGroups,
|
||||
Status,
|
||||
Outcome,
|
||||
FormData,
|
||||
DueDate,
|
||||
CompletedAt,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
Version,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum ProcessInstances {
|
||||
Table,
|
||||
Id,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Tokens {
|
||||
Table,
|
||||
Id,
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(ProcessVariables::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(ProcessVariables::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(ProcessVariables::TenantId).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(ProcessVariables::InstanceId)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(ProcessVariables::Name).string().not_null())
|
||||
.col(
|
||||
ColumnDef::new(ProcessVariables::VarType)
|
||||
.string()
|
||||
.not_null()
|
||||
.default("string"),
|
||||
)
|
||||
.col(ColumnDef::new(ProcessVariables::ValueString).text().null())
|
||||
.col(
|
||||
ColumnDef::new(ProcessVariables::ValueNumber)
|
||||
.double()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ProcessVariables::ValueBoolean)
|
||||
.boolean()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ProcessVariables::ValueDate)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager.get_connection().execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"CREATE UNIQUE INDEX idx_process_variables_instance_name ON process_variables (instance_id, name)".to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
manager
|
||||
.create_foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk_variables_instance")
|
||||
.from(ProcessVariables::Table, ProcessVariables::InstanceId)
|
||||
.to(ProcessInstances::Table, ProcessInstances::Id)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(ProcessVariables::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum ProcessVariables {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
InstanceId,
|
||||
Name,
|
||||
VarType,
|
||||
ValueString,
|
||||
ValueNumber,
|
||||
ValueBoolean,
|
||||
ValueDate,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum ProcessInstances {
|
||||
Table,
|
||||
Id,
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(MessageTemplates::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(MessageTemplates::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(MessageTemplates::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(MessageTemplates::Name).string().not_null())
|
||||
.col(ColumnDef::new(MessageTemplates::Code).string().not_null())
|
||||
.col(
|
||||
ColumnDef::new(MessageTemplates::Channel)
|
||||
.string()
|
||||
.not_null()
|
||||
.default("in_app"),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(MessageTemplates::TitleTemplate)
|
||||
.string()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(MessageTemplates::BodyTemplate)
|
||||
.text()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(MessageTemplates::Language)
|
||||
.string()
|
||||
.not_null()
|
||||
.default("zh-CN"),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(MessageTemplates::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(MessageTemplates::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(MessageTemplates::CreatedBy)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(MessageTemplates::UpdatedBy)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(MessageTemplates::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager.get_connection().execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"CREATE UNIQUE INDEX idx_message_templates_tenant_code ON message_templates (tenant_id, code) WHERE deleted_at IS NULL".to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(MessageTemplates::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum MessageTemplates {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
Name,
|
||||
Code,
|
||||
Channel,
|
||||
TitleTemplate,
|
||||
BodyTemplate,
|
||||
Language,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Messages::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Messages::Id).uuid().not_null().primary_key())
|
||||
.col(ColumnDef::new(Messages::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(Messages::TemplateId).uuid().null())
|
||||
.col(ColumnDef::new(Messages::SenderId).uuid().null())
|
||||
.col(
|
||||
ColumnDef::new(Messages::SenderType)
|
||||
.string()
|
||||
.not_null()
|
||||
.default("system"),
|
||||
)
|
||||
.col(ColumnDef::new(Messages::RecipientId).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Messages::RecipientType)
|
||||
.string()
|
||||
.not_null()
|
||||
.default("user"),
|
||||
)
|
||||
.col(ColumnDef::new(Messages::Title).string().not_null())
|
||||
.col(ColumnDef::new(Messages::Body).text().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Messages::Priority)
|
||||
.string()
|
||||
.not_null()
|
||||
.default("normal"),
|
||||
)
|
||||
.col(ColumnDef::new(Messages::BusinessType).string().null())
|
||||
.col(ColumnDef::new(Messages::BusinessId).uuid().null())
|
||||
.col(
|
||||
ColumnDef::new(Messages::IsRead)
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(false),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Messages::ReadAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Messages::IsArchived)
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(false),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Messages::ArchivedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Messages::SentAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Messages::Status)
|
||||
.string()
|
||||
.not_null()
|
||||
.default("sent"),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Messages::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Messages::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Messages::CreatedBy).uuid().not_null())
|
||||
.col(ColumnDef::new(Messages::UpdatedBy).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Messages::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager.get_connection().execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"CREATE INDEX idx_messages_tenant_recipient ON messages (tenant_id, recipient_id) WHERE deleted_at IS NULL".to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
manager.get_connection().execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"CREATE INDEX idx_messages_tenant_recipient_unread ON messages (tenant_id, recipient_id) WHERE deleted_at IS NULL AND is_read = false".to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
manager.get_connection().execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"CREATE INDEX idx_messages_tenant_business ON messages (tenant_id, business_type, business_id) WHERE deleted_at IS NULL".to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
manager
|
||||
.create_foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk_messages_template")
|
||||
.from(Messages::Table, Messages::TemplateId)
|
||||
.to(MessageTemplates::Table, MessageTemplates::Id)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Messages::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Messages {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
TemplateId,
|
||||
SenderId,
|
||||
SenderType,
|
||||
RecipientId,
|
||||
RecipientType,
|
||||
Title,
|
||||
Body,
|
||||
Priority,
|
||||
BusinessType,
|
||||
BusinessId,
|
||||
IsRead,
|
||||
ReadAt,
|
||||
IsArchived,
|
||||
ArchivedAt,
|
||||
SentAt,
|
||||
Status,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum MessageTemplates {
|
||||
Table,
|
||||
Id,
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(MessageSubscriptions::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(MessageSubscriptions::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(MessageSubscriptions::TenantId)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(MessageSubscriptions::UserId)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(MessageSubscriptions::NotificationTypes)
|
||||
.json()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(MessageSubscriptions::ChannelPreferences)
|
||||
.json()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(MessageSubscriptions::DndEnabled)
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(false),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(MessageSubscriptions::DndStart)
|
||||
.string()
|
||||
.null(),
|
||||
)
|
||||
.col(ColumnDef::new(MessageSubscriptions::DndEnd).string().null())
|
||||
.col(
|
||||
ColumnDef::new(MessageSubscriptions::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(MessageSubscriptions::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(MessageSubscriptions::CreatedBy)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(MessageSubscriptions::UpdatedBy)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(MessageSubscriptions::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager.get_connection().execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"CREATE UNIQUE INDEX idx_message_subscriptions_tenant_user ON message_subscriptions (tenant_id, user_id) WHERE deleted_at IS NULL".to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(MessageSubscriptions::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum MessageSubscriptions {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
UserId,
|
||||
NotificationTypes,
|
||||
ChannelPreferences,
|
||||
DndEnabled,
|
||||
DndStart,
|
||||
DndEnd,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(AuditLogs::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(AuditLogs::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(AuditLogs::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(AuditLogs::UserId).uuid().null())
|
||||
.col(ColumnDef::new(AuditLogs::Action).string().not_null())
|
||||
.col(ColumnDef::new(AuditLogs::ResourceType).string().not_null())
|
||||
.col(ColumnDef::new(AuditLogs::ResourceId).uuid().null())
|
||||
.col(ColumnDef::new(AuditLogs::OldValue).json().null())
|
||||
.col(ColumnDef::new(AuditLogs::NewValue).json().null())
|
||||
.col(ColumnDef::new(AuditLogs::IpAddress).string().null())
|
||||
.col(ColumnDef::new(AuditLogs::UserAgent).text().null())
|
||||
.col(
|
||||
ColumnDef::new(AuditLogs::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null(),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.get_connection()
|
||||
.execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"CREATE INDEX idx_audit_logs_tenant ON audit_logs (tenant_id)".to_string(),
|
||||
))
|
||||
.await
|
||||
.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
manager
|
||||
.get_connection()
|
||||
.execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"CREATE INDEX idx_audit_logs_resource ON audit_logs (resource_type, resource_id)"
|
||||
.to_string(),
|
||||
))
|
||||
.await
|
||||
.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(AuditLogs::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum AuditLogs {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
UserId,
|
||||
Action,
|
||||
ResourceType,
|
||||
ResourceId,
|
||||
OldValue,
|
||||
NewValue,
|
||||
IpAddress,
|
||||
UserAgent,
|
||||
CreatedAt,
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
use sea_orm::DatabaseBackend;
|
||||
use sea_orm::Statement;
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
/// 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,
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
/// 修复 settings 表唯一索引:原索引使用 scope_id 列,当 scope_id 为 NULL 时
|
||||
/// PostgreSQL B-tree 不认为两行重复(NULL != NULL),导致可插入重复数据。
|
||||
/// 修复方案:使用 COALESCE(scope_id, '00000000-0000-0000-0000-000000000000')
|
||||
/// 将 NULL 转为固定 UUID,使索引能正确约束 NULL scope_id 的行。
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
// 1. 删除旧索引
|
||||
manager
|
||||
.get_connection()
|
||||
.execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"DROP INDEX IF EXISTS idx_settings_scope_key".to_string(),
|
||||
))
|
||||
.await
|
||||
.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
// 2. 先清理可能已存在的重复数据(保留每组最新的一条)
|
||||
manager.get_connection().execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
r#"
|
||||
DELETE FROM settings a USING settings b
|
||||
WHERE a.id < b.id
|
||||
AND a.tenant_id = b.tenant_id
|
||||
AND a.scope = b.scope
|
||||
AND a.setting_key = b.setting_key
|
||||
AND a.deleted_at IS NULL
|
||||
AND b.deleted_at IS NULL
|
||||
AND COALESCE(a.scope_id, '00000000-0000-0000-0000-000000000000') = COALESCE(b.scope_id, '00000000-0000-0000-0000-000000000000')
|
||||
"#.to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
// 3. 创建新索引,使用 COALESCE 处理 NULL scope_id
|
||||
manager.get_connection().execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"CREATE UNIQUE INDEX idx_settings_scope_key ON settings (tenant_id, scope, COALESCE(scope_id, '00000000-0000-0000-0000-000000000000'), setting_key) WHERE deleted_at IS NULL".to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
// 回滚:删除新索引,恢复旧索引
|
||||
manager
|
||||
.get_connection()
|
||||
.execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"DROP INDEX IF EXISTS idx_settings_scope_key".to_string(),
|
||||
))
|
||||
.await
|
||||
.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
manager.get_connection().execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"CREATE UNIQUE INDEX idx_settings_scope_key ON settings (tenant_id, scope, scope_id, setting_key) WHERE deleted_at IS NULL".to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
/// 为三个消息表添加缺失的 version 列(乐观锁字段)。
|
||||
///
|
||||
/// CLAUDE.md 要求所有表包含 version 字段用于乐观锁,但消息模块的三个表遗漏了。
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
// message_templates
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(MessageTemplates::Table)
|
||||
.add_column(
|
||||
ColumnDef::new(MessageTemplates::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// messages
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Messages::Table)
|
||||
.add_column(
|
||||
ColumnDef::new(Messages::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// message_subscriptions
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(MessageSubscriptions::Table)
|
||||
.add_column(
|
||||
ColumnDef::new(MessageSubscriptions::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(MessageTemplates::Table)
|
||||
.drop_column(MessageTemplates::Version)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Messages::Table)
|
||||
.drop_column(Messages::Version)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(MessageSubscriptions::Table)
|
||||
.drop_column(MessageSubscriptions::Version)
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum MessageTemplates {
|
||||
Table,
|
||||
Version,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Messages {
|
||||
Table,
|
||||
Version,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum MessageSubscriptions {
|
||||
Table,
|
||||
Version,
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Alias::new("domain_events"))
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("event_type"))
|
||||
.string_len(200)
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("payload")).json().null())
|
||||
.col(ColumnDef::new(Alias::new("correlation_id")).uuid().null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("status"))
|
||||
.string_len(20)
|
||||
.not_null()
|
||||
.default("pending"),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("attempts"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("last_error")).text().null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("published_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_domain_events_status")
|
||||
.table(Alias::new("domain_events"))
|
||||
.col(Alias::new("status"))
|
||||
.col(Alias::new("created_at"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_domain_events_tenant")
|
||||
.table(Alias::new("domain_events"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("domain_events")).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
// 1. plugins 表 — 插件注册与生命周期
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Alias::new("plugins"))
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("name"))
|
||||
.string_len(200)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("plugin_version"))
|
||||
.string_len(50)
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("description")).text().null())
|
||||
.col(ColumnDef::new(Alias::new("author")).string_len(200).null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("status"))
|
||||
.string_len(20)
|
||||
.not_null()
|
||||
.default("uploaded"),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("manifest_json"))
|
||||
.json()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("wasm_binary"))
|
||||
.binary()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("wasm_hash"))
|
||||
.string_len(64)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("config_json"))
|
||||
.json()
|
||||
.not_null()
|
||||
.default(Expr::val("{}")),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("error_message")).text().null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("installed_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("enabled_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
// 标准字段
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid().null())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid().null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("deleted_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_plugins_tenant_status")
|
||||
.table(Alias::new("plugins"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
.col(Alias::new("status"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_plugins_name")
|
||||
.table(Alias::new("plugins"))
|
||||
.col(Alias::new("name"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 2. plugin_entities 表 — 插件动态表注册
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Alias::new("plugin_entities"))
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("plugin_id")).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("entity_name"))
|
||||
.string_len(100)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("table_name"))
|
||||
.string_len(200)
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("schema_json")).json().not_null())
|
||||
// 标准字段
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid().null())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid().null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("deleted_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_plugin_entities_plugin")
|
||||
.table(Alias::new("plugin_entities"))
|
||||
.col(Alias::new("plugin_id"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 3. plugin_event_subscriptions 表 — 事件订阅
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Alias::new("plugin_event_subscriptions"))
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("plugin_id")).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("event_pattern"))
|
||||
.string_len(200)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_plugin_event_subs_plugin")
|
||||
.table(Alias::new("plugin_event_subscriptions"))
|
||||
.col(Alias::new("plugin_id"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("plugin_event_subscriptions"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("plugin_entities"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("plugins")).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
/// 为已存在的租户补充 plugin 模块权限,并分配给 admin 角色。
|
||||
/// seed_tenant_auth 只在租户创建时执行,已存在的租户缺少 plugin 相关权限。
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let db = manager.get_connection();
|
||||
|
||||
// 插入 plugin 权限(如果不存在)
|
||||
db.execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
r#"
|
||||
INSERT INTO permissions (id, tenant_id, code, name, resource, action, description, created_at, updated_at, created_by, updated_by, deleted_at, version)
|
||||
SELECT gen_random_uuid(), t.id, 'plugin.admin', '插件管理', 'plugin', 'admin', '管理插件全生命周期', NOW(), NOW(), '00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000000', NULL, 1
|
||||
FROM tenant t
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM permissions p WHERE p.code = 'plugin.admin' AND p.tenant_id = t.id AND p.deleted_at IS NULL
|
||||
)
|
||||
"#.to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
db.execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
r#"
|
||||
INSERT INTO permissions (id, tenant_id, code, name, resource, action, description, created_at, updated_at, created_by, updated_by, deleted_at, version)
|
||||
SELECT gen_random_uuid(), t.id, 'plugin.list', '查看插件', 'plugin', 'list', '查看插件列表', NOW(), NOW(), '00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000000', NULL, 1
|
||||
FROM tenant t
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM permissions p WHERE p.code = 'plugin.list' AND p.tenant_id = t.id AND p.deleted_at IS NULL
|
||||
)
|
||||
"#.to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
// 将 plugin 权限分配给 admin 角色(如果尚未分配)
|
||||
db.execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
r#"
|
||||
INSERT INTO role_permissions (role_id, permission_id, tenant_id, created_at, updated_at, created_by, updated_by, deleted_at, version)
|
||||
SELECT r.id, p.id, r.tenant_id, NOW(), NOW(), '00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000000', NULL, 1
|
||||
FROM roles r
|
||||
JOIN permissions p ON p.tenant_id = r.tenant_id AND p.code IN ('plugin.admin', 'plugin.list') AND p.deleted_at IS NULL
|
||||
WHERE r.code = 'admin' AND r.deleted_at IS NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM role_permissions rp
|
||||
WHERE rp.role_id = r.id AND rp.permission_id = p.id AND rp.deleted_at IS NULL
|
||||
)
|
||||
"#.to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let db = manager.get_connection();
|
||||
|
||||
// 删除 plugin 权限的角色关联
|
||||
db.execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
r#"
|
||||
DELETE FROM role_permissions
|
||||
WHERE permission_id IN (
|
||||
SELECT id FROM permissions WHERE code IN ('plugin.admin', 'plugin.list')
|
||||
)
|
||||
"#
|
||||
.to_string(),
|
||||
))
|
||||
.await
|
||||
.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
// 删除 plugin 权限
|
||||
db.execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"DELETE FROM permissions WHERE code IN ('plugin.admin', 'plugin.list')".to_string(),
|
||||
))
|
||||
.await
|
||||
.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
// 启用 pg_trgm 扩展(加速 ILIKE '%keyword%' 搜索)
|
||||
manager
|
||||
.get_connection()
|
||||
.execute_unprepared("CREATE EXTENSION IF NOT EXISTS pg_trgm")
|
||||
.await?;
|
||||
|
||||
// 插件实体列元数据表 — 记录哪些字段被提取为 Generated Column
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Alias::new("plugin_entity_columns"))
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.default(Expr::cust("gen_random_uuid()"))
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("plugin_entity_id"))
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("field_name")).string().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("column_name"))
|
||||
.string()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("sql_type")).string().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("is_generated"))
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(true),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::cust("NOW()")),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// plugin_entity_id 外键
|
||||
manager
|
||||
.create_foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk_plugin_entity_columns_entity")
|
||||
.from(
|
||||
Alias::new("plugin_entity_columns"),
|
||||
Alias::new("plugin_entity_id"),
|
||||
)
|
||||
.to(Alias::new("plugin_entities"), Alias::new("id"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("plugin_entity_columns"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
// pg_trgm 不卸载(其他功能可能依赖)
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
// 添加 data_scope 列 — 行级数据权限范围
|
||||
// 可选值: all, self, department, department_tree
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Alias::new("role_permissions"))
|
||||
.add_column(
|
||||
ColumnDef::new(Alias::new("data_scope"))
|
||||
.string()
|
||||
.not_null()
|
||||
.default("all"),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Alias::new("role_permissions"))
|
||||
.drop_column(Alias::new("data_scope"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Alias::new("user_departments"))
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Alias::new("user_id")).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("department_id"))
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("is_primary"))
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(false),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.primary_key(
|
||||
Index::create()
|
||||
.col(Alias::new("user_id"))
|
||||
.col(Alias::new("department_id")),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 索引:按租户 + 用户查询部门
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.if_not_exists()
|
||||
.name("idx_user_departments_tenant_user")
|
||||
.table(Alias::new("user_departments"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
.col(Alias::new("user_id"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 索引:按部门查询成员
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.if_not_exists()
|
||||
.name("idx_user_departments_dept")
|
||||
.table(Alias::new("user_departments"))
|
||||
.col(Alias::new("department_id"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("user_departments"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
// plugin_entities 新增 manifest_id 列 — 避免跨插件查询时 JOIN plugins 表
|
||||
manager
|
||||
.get_connection()
|
||||
.execute_unprepared(
|
||||
r#"
|
||||
ALTER TABLE plugin_entities
|
||||
ADD COLUMN IF NOT EXISTS manifest_id TEXT NOT NULL DEFAULT '';
|
||||
|
||||
ALTER TABLE plugin_entities
|
||||
ADD COLUMN IF NOT EXISTS is_public BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
-- 回填 manifest_id(从 plugins.manifest_json 提取 metadata.id)
|
||||
UPDATE plugin_entities pe
|
||||
SET manifest_id = COALESCE(p.manifest_json->'metadata'->>'id', '')
|
||||
FROM plugins p
|
||||
WHERE pe.plugin_id = p.id AND pe.deleted_at IS NULL;
|
||||
|
||||
-- 跨插件实体查找索引
|
||||
CREATE INDEX IF NOT EXISTS idx_plugin_entities_cross_ref
|
||||
ON plugin_entities (manifest_id, entity_name, tenant_id)
|
||||
WHERE deleted_at IS NULL;
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.get_connection()
|
||||
.execute_unprepared(
|
||||
r#"
|
||||
DROP INDEX IF EXISTS idx_plugin_entities_cross_ref;
|
||||
ALTER TABLE plugin_entities DROP COLUMN IF EXISTS is_public;
|
||||
ALTER TABLE plugin_entities DROP COLUMN IF EXISTS manifest_id;
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
/// 插件市场目录表 — P4 插件市场基础设施
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Alias::new("plugin_market_entries"))
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("plugin_id")).string().not_null())
|
||||
.col(ColumnDef::new(Alias::new("name")).string().not_null())
|
||||
.col(ColumnDef::new(Alias::new("version")).string().not_null())
|
||||
.col(ColumnDef::new(Alias::new("description")).text())
|
||||
.col(ColumnDef::new(Alias::new("author")).string())
|
||||
.col(ColumnDef::new(Alias::new("category")).string()) // 行业分类
|
||||
.col(ColumnDef::new(Alias::new("tags")).json()) // 标签列表
|
||||
.col(ColumnDef::new(Alias::new("icon_url")).string())
|
||||
.col(ColumnDef::new(Alias::new("screenshots")).json()) // 截图 URL 列表
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("wasm_binary"))
|
||||
.binary()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("manifest_toml"))
|
||||
.text()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("wasm_hash")).string().not_null())
|
||||
.col(ColumnDef::new(Alias::new("min_platform_version")).string())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("status"))
|
||||
.string()
|
||||
.not_null()
|
||||
.default("published"),
|
||||
) // published | suspended
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("download_count"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("rating_avg"))
|
||||
.decimal()
|
||||
.not_null()
|
||||
.default(0.0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("rating_count"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("changelog")).text()) // 版本更新日志
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 插件市场评论/评分表
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Alias::new("plugin_market_reviews"))
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("user_id")).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("market_entry_id"))
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("rating")).integer().not_null()) // 1-5
|
||||
.col(ColumnDef::new(Alias::new("review_text")).text())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 唯一索引:每个用户对每个市场条目只能评一次
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.if_not_exists()
|
||||
.unique()
|
||||
.name("uq_market_review_tenant_user_entry")
|
||||
.table(Alias::new("plugin_market_reviews"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
.col(Alias::new("user_id"))
|
||||
.col(Alias::new("market_entry_id"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("plugin_market_reviews"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("plugin_market_entries"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
/// 插件用户视图 — 用户自定义的列表视图配置
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Alias::new("plugin_user_views"))
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("user_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("plugin_id")).string().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("entity_name"))
|
||||
.string()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("view_name")).string().not_null())
|
||||
.col(ColumnDef::new(Alias::new("view_config")).json().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("is_default"))
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(false),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("plugin_user_views"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(WechatUsers::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(WechatUsers::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(WechatUsers::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(WechatUsers::Openid).string().not_null())
|
||||
.col(ColumnDef::new(WechatUsers::UnionId).string())
|
||||
.col(ColumnDef::new(WechatUsers::UserId).uuid().not_null())
|
||||
.col(ColumnDef::new(WechatUsers::Phone).string())
|
||||
.col(
|
||||
ColumnDef::new(WechatUsers::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(WechatUsers::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(WechatUsers::CreatedBy).uuid())
|
||||
.col(ColumnDef::new(WechatUsers::UpdatedBy).uuid())
|
||||
.col(ColumnDef::new(WechatUsers::DeletedAt).timestamp_with_time_zone())
|
||||
.col(
|
||||
ColumnDef::new(WechatUsers::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_wechat_users_openid")
|
||||
.table(WechatUsers::Table)
|
||||
.col(WechatUsers::Openid)
|
||||
.col(WechatUsers::TenantId)
|
||||
.unique()
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_index(Index::drop().name("idx_wechat_users_openid").to_owned())
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(WechatUsers::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum WechatUsers {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
Openid,
|
||||
UnionId,
|
||||
UserId,
|
||||
Phone,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
DeletedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
Version,
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(TenantCryptoKey::Table)
|
||||
.col(
|
||||
ColumnDef::new(TenantCryptoKey::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(TenantCryptoKey::TenantId).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(TenantCryptoKey::EncryptedDek)
|
||||
.string_len(128)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(TenantCryptoKey::KeyVersion)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(TenantCryptoKey::IsActive)
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(true),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(TenantCryptoKey::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(TenantCryptoKey::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(TenantCryptoKey::CreatedBy).uuid())
|
||||
.col(ColumnDef::new(TenantCryptoKey::UpdatedBy).uuid())
|
||||
.col(ColumnDef::new(TenantCryptoKey::DeletedAt).timestamp_with_time_zone())
|
||||
.col(
|
||||
ColumnDef::new(TenantCryptoKey::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.index(
|
||||
Index::create()
|
||||
.col(TenantCryptoKey::TenantId)
|
||||
.col(TenantCryptoKey::KeyVersion)
|
||||
.unique(),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_tenant_crypto_keys_tenant")
|
||||
.table(TenantCryptoKey::Table)
|
||||
.col(TenantCryptoKey::TenantId)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(TenantCryptoKey::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum TenantCryptoKey {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
EncryptedDek,
|
||||
KeyVersion,
|
||||
IsActive,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
Version,
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
// 归档表 — 与 domain_events 结构相同,用于存放 >90 天的已发布事件
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Alias::new("domain_events_archive"))
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("event_type"))
|
||||
.string_len(200)
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("payload")).json().null())
|
||||
.col(ColumnDef::new(Alias::new("correlation_id")).uuid().null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("status"))
|
||||
.string_len(20)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("attempts"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("last_error")).text().null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("published_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("archived_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_domain_events_archive_created")
|
||||
.table(Alias::new("domain_events_archive"))
|
||||
.col(Alias::new("created_at"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 清理函数:将 >90 天的已发布事件迁移到归档表
|
||||
manager
|
||||
.get_connection()
|
||||
.execute_unprepared(
|
||||
r#"
|
||||
CREATE OR REPLACE FUNCTION cleanup_old_published_events(
|
||||
retention_days INT DEFAULT 90,
|
||||
batch_size INT DEFAULT 1000
|
||||
) RETURNS INT AS $$
|
||||
DECLARE
|
||||
moved_count INT;
|
||||
BEGIN
|
||||
INSERT INTO domain_events_archive (id, tenant_id, event_type, payload, correlation_id, status, attempts, last_error, created_at, published_at)
|
||||
SELECT id, tenant_id, event_type, payload, correlation_id, status, attempts, last_error, created_at, published_at
|
||||
FROM domain_events
|
||||
WHERE status = 'published'
|
||||
AND published_at < NOW() - (retention_days || ' days')::INTERVAL
|
||||
ORDER BY created_at ASC
|
||||
LIMIT batch_size;
|
||||
|
||||
GET DIAGNOSTICS moved_count = ROW_COUNT;
|
||||
|
||||
DELETE FROM domain_events
|
||||
WHERE ctid IN (
|
||||
SELECT ctid FROM domain_events
|
||||
WHERE status = 'published'
|
||||
AND published_at < NOW() - (retention_days || ' days')::INTERVAL
|
||||
ORDER BY created_at ASC
|
||||
LIMIT batch_size
|
||||
);
|
||||
|
||||
RETURN moved_count;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.get_connection()
|
||||
.execute_unprepared("DROP FUNCTION IF EXISTS cleanup_old_published_events(INT, INT);")
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("domain_events_archive"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Alias::new("processed_events"))
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Alias::new("event_id")).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("consumer_id"))
|
||||
.string_len(200)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("processed_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.primary_key(
|
||||
Index::create()
|
||||
.col(Alias::new("event_id"))
|
||||
.col(Alias::new("consumer_id")),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 7 天 TTL 清理函数
|
||||
manager
|
||||
.get_connection()
|
||||
.execute_unprepared(
|
||||
r#"
|
||||
CREATE OR REPLACE FUNCTION cleanup_old_processed_events(
|
||||
retention_days INT DEFAULT 7,
|
||||
batch_size INT DEFAULT 1000
|
||||
) RETURNS INT AS $$
|
||||
DECLARE
|
||||
deleted_count INT;
|
||||
BEGIN
|
||||
DELETE FROM processed_events
|
||||
WHERE ctid IN (
|
||||
SELECT ctid FROM processed_events
|
||||
WHERE processed_at < NOW() - (retention_days || ' days')::INTERVAL
|
||||
LIMIT batch_size
|
||||
);
|
||||
|
||||
GET DIAGNOSTICS deleted_count = ROW_COUNT;
|
||||
RETURN deleted_count;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.get_connection()
|
||||
.execute_unprepared("DROP FUNCTION IF EXISTS cleanup_old_processed_events(INT, INT);")
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("processed_events"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let conn = manager.get_connection();
|
||||
|
||||
// PL/pgSQL 动态为所有含 tenant_id 列的表启用 RLS
|
||||
conn.execute_unprepared(
|
||||
r#"
|
||||
DO $$
|
||||
DECLARE
|
||||
tbl TEXT;
|
||||
BEGIN
|
||||
FOR tbl IN
|
||||
SELECT c.table_name FROM information_schema.columns c
|
||||
JOIN information_schema.tables t
|
||||
ON c.table_name = t.table_name AND c.table_schema = t.table_schema
|
||||
WHERE c.column_name = 'tenant_id'
|
||||
AND c.table_schema = 'public'
|
||||
AND t.table_type = 'BASE TABLE'
|
||||
ORDER BY c.table_name
|
||||
LOOP
|
||||
EXECUTE format('ALTER TABLE %I ENABLE ROW LEVEL SECURITY', tbl);
|
||||
EXECUTE format('DROP POLICY IF EXISTS tenant_isolation ON %I', tbl);
|
||||
EXECUTE format(
|
||||
'CREATE POLICY tenant_isolation ON %I USING (
|
||||
current_setting(''app.current_tenant_id'', true) = ''''
|
||||
OR tenant_id = current_setting(''app.current_tenant_id'', true)::uuid
|
||||
)',
|
||||
tbl
|
||||
);
|
||||
END LOOP;
|
||||
END;
|
||||
$$;
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let conn = manager.get_connection();
|
||||
|
||||
conn.execute_unprepared(
|
||||
r#"
|
||||
DO $$
|
||||
DECLARE
|
||||
tbl TEXT;
|
||||
BEGIN
|
||||
FOR tbl IN
|
||||
SELECT c.table_name FROM information_schema.columns c
|
||||
JOIN information_schema.tables t
|
||||
ON c.table_name = t.table_name AND c.table_schema = t.table_schema
|
||||
WHERE c.column_name = 'tenant_id'
|
||||
AND c.table_schema = 'public'
|
||||
AND t.table_type = 'BASE TABLE'
|
||||
ORDER BY c.table_name
|
||||
LOOP
|
||||
EXECUTE format('DROP POLICY IF EXISTS tenant_isolation ON %I', tbl);
|
||||
EXECUTE format('ALTER TABLE %I DISABLE ROW LEVEL SECURITY', tbl);
|
||||
END LOOP;
|
||||
END;
|
||||
$$;
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let conn = manager.get_connection();
|
||||
|
||||
conn.execute_unprepared("ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS prev_hash TEXT")
|
||||
.await?;
|
||||
|
||||
conn.execute_unprepared("ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS record_hash TEXT")
|
||||
.await?;
|
||||
|
||||
// 为 record_hash 创建索引(用于快速查找最新哈希)
|
||||
conn.execute_unprepared(
|
||||
"CREATE INDEX IF NOT EXISTS idx_audit_logs_record_hash
|
||||
ON audit_logs (record_hash) WHERE record_hash IS NOT NULL",
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 按 tenant_id + created_at DESC 查找最新哈希的索引
|
||||
conn.execute_unprepared(
|
||||
"CREATE INDEX IF NOT EXISTS idx_audit_logs_tenant_created
|
||||
ON audit_logs (tenant_id, created_at DESC)",
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let conn = manager.get_connection();
|
||||
|
||||
conn.execute_unprepared("DROP INDEX IF EXISTS idx_audit_logs_tenant_created")
|
||||
.await?;
|
||||
|
||||
conn.execute_unprepared("DROP INDEX IF EXISTS idx_audit_logs_record_hash")
|
||||
.await?;
|
||||
|
||||
conn.execute_unprepared("ALTER TABLE audit_logs DROP COLUMN IF EXISTS record_hash")
|
||||
.await?;
|
||||
|
||||
conn.execute_unprepared("ALTER TABLE audit_logs DROP COLUMN IF EXISTS prev_hash")
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let conn = manager.get_connection();
|
||||
|
||||
// 替换所有表的 RLS 策略:移除空字符串绕过条件
|
||||
// 原策略允许 current_setting(...) = '' 时通过(绕过 RLS),现在要求变量已设置且匹配
|
||||
conn.execute_unprepared(
|
||||
r#"
|
||||
DO $$
|
||||
DECLARE
|
||||
tbl TEXT;
|
||||
BEGIN
|
||||
FOR tbl IN
|
||||
SELECT c.table_name FROM information_schema.columns c
|
||||
JOIN information_schema.tables t
|
||||
ON c.table_name = t.table_name AND c.table_schema = t.table_schema
|
||||
WHERE c.column_name = 'tenant_id'
|
||||
AND c.table_schema = 'public'
|
||||
AND t.table_type = 'BASE TABLE'
|
||||
ORDER BY c.table_name
|
||||
LOOP
|
||||
EXECUTE format('DROP POLICY IF EXISTS tenant_isolation ON %I', tbl);
|
||||
EXECUTE format(
|
||||
'CREATE POLICY tenant_isolation ON %I USING (
|
||||
current_setting(''app.current_tenant_id'', true) != ''''
|
||||
AND tenant_id = current_setting(''app.current_tenant_id'', true)::uuid
|
||||
)',
|
||||
tbl
|
||||
);
|
||||
END LOOP;
|
||||
END;
|
||||
$$;
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let conn = manager.get_connection();
|
||||
|
||||
// 回滚:恢复允许空字符串绕过的原策略
|
||||
conn.execute_unprepared(
|
||||
r#"
|
||||
DO $$
|
||||
DECLARE
|
||||
tbl TEXT;
|
||||
BEGIN
|
||||
FOR tbl IN
|
||||
SELECT c.table_name FROM information_schema.columns c
|
||||
JOIN information_schema.tables t
|
||||
ON c.table_name = t.table_name AND c.table_schema = t.table_schema
|
||||
WHERE c.column_name = 'tenant_id'
|
||||
AND c.table_schema = 'public'
|
||||
AND t.table_type = 'BASE TABLE'
|
||||
ORDER BY c.table_name
|
||||
LOOP
|
||||
EXECUTE format('DROP POLICY IF EXISTS tenant_isolation ON %I', tbl);
|
||||
EXECUTE format(
|
||||
'CREATE POLICY tenant_isolation ON %I USING (
|
||||
current_setting(''app.current_tenant_id'', true) = ''''
|
||||
OR tenant_id = current_setting(''app.current_tenant_id'', true)::uuid
|
||||
)',
|
||||
tbl
|
||||
);
|
||||
END LOOP;
|
||||
END;
|
||||
$$;
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[derive(Iden)]
|
||||
enum BlindIndex {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
EntityType,
|
||||
EntityId,
|
||||
FieldName,
|
||||
BlindHash,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(BlindIndex::Table)
|
||||
.col(
|
||||
ColumnDef::new(BlindIndex::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key()
|
||||
.default(PgFunc::gen_random_uuid()),
|
||||
)
|
||||
.col(ColumnDef::new(BlindIndex::TenantId).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(BlindIndex::EntityType)
|
||||
.string_len(64)
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(BlindIndex::EntityId).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(BlindIndex::FieldName)
|
||||
.string_len(64)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(BlindIndex::BlindHash)
|
||||
.string_len(64)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(BlindIndex::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(BlindIndex::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.index(
|
||||
Index::create()
|
||||
.col(BlindIndex::TenantId)
|
||||
.col(BlindIndex::EntityType)
|
||||
.col(BlindIndex::FieldName)
|
||||
.col(BlindIndex::BlindHash)
|
||||
.unique(),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_blind_hashes")
|
||||
.table(BlindIndex::Table)
|
||||
.col(BlindIndex::TenantId)
|
||||
.col(BlindIndex::EntityType)
|
||||
.col(BlindIndex::FieldName)
|
||||
.col(BlindIndex::BlindHash)
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(BlindIndex::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[derive(Iden)]
|
||||
enum DeadLetterEvent {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
OriginalEventId,
|
||||
EventType,
|
||||
Payload,
|
||||
ConsumerId,
|
||||
Attempts,
|
||||
LastError,
|
||||
CreatedAt,
|
||||
ResolvedAt,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(DeadLetterEvent::Table)
|
||||
.col(
|
||||
ColumnDef::new(DeadLetterEvent::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key()
|
||||
.default(PgFunc::gen_random_uuid()),
|
||||
)
|
||||
.col(ColumnDef::new(DeadLetterEvent::TenantId).uuid())
|
||||
.col(
|
||||
ColumnDef::new(DeadLetterEvent::OriginalEventId)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(DeadLetterEvent::EventType)
|
||||
.string_len(128)
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(DeadLetterEvent::Payload).json_binary())
|
||||
.col(
|
||||
ColumnDef::new(DeadLetterEvent::ConsumerId)
|
||||
.string_len(128)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(DeadLetterEvent::Attempts)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(ColumnDef::new(DeadLetterEvent::LastError).text())
|
||||
.col(
|
||||
ColumnDef::new(DeadLetterEvent::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(DeadLetterEvent::ResolvedAt).timestamp_with_time_zone())
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(DeadLetterEvent::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Alias::new("api_clients"))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.default(Expr::cust("gen_random_uuid()")),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("client_id"))
|
||||
.string_len(128)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("client_secret_hash"))
|
||||
.string_len(256)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("client_name"))
|
||||
.string_len(200)
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("scopes")).json().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("allowed_patient_ids"))
|
||||
.json()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("rate_limit_per_minute"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(60),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("is_active"))
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(true),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("token_lifetime_seconds"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(3600),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::cust("NOW()")),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::cust("NOW()")),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid().null())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid().null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("deleted_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.primary_key(Index::create().col(Alias::new("id")))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_api_clients_client_id_unique")
|
||||
.table(Alias::new("api_clients"))
|
||||
.col(Alias::new("client_id"))
|
||||
.unique()
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_api_clients_tenant_id")
|
||||
.table(Alias::new("api_clients"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("api_clients")).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let db = manager.get_connection();
|
||||
|
||||
// 1. 创建触发器函数:适用于 `version` 列(erp-health / erp-auth / erp-config 等所有模块)
|
||||
db.execute_unprepared(
|
||||
r#"
|
||||
CREATE OR REPLACE FUNCTION enforce_version() RETURNS trigger AS $$
|
||||
BEGIN
|
||||
IF NEW.version IS DISTINCT FROM OLD.version + 1 THEN
|
||||
RAISE EXCEPTION 'Optimistic lock conflict on %: expected version %, got %',
|
||||
TG_TABLE_NAME, OLD.version + 1, NEW.version;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 2. 创建触发器函数:适用于 `version_lock` 列(erp-ai 模块)
|
||||
db.execute_unprepared(
|
||||
r#"
|
||||
CREATE OR REPLACE FUNCTION enforce_version_lock() RETURNS trigger AS $$
|
||||
BEGIN
|
||||
IF NEW.version_lock IS DISTINCT FROM OLD.version_lock + 1 THEN
|
||||
RAISE EXCEPTION 'Optimistic lock conflict on %: expected version_lock %, got %',
|
||||
TG_TABLE_NAME, OLD.version_lock + 1, NEW.version_lock;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 3. 自动发现所有含 version / version_lock 的表并绑定触发器
|
||||
// 排除 market_entry(version 是 String,非乐观锁)
|
||||
db.execute_unprepared(
|
||||
r#"
|
||||
DO $$
|
||||
DECLARE
|
||||
rec RECORD;
|
||||
trig_name text;
|
||||
BEGIN
|
||||
FOR rec IN
|
||||
SELECT table_name, column_name
|
||||
FROM information_schema.columns
|
||||
WHERE column_name IN ('version', 'version_lock')
|
||||
AND table_schema = 'public'
|
||||
AND table_name NOT IN ('market_entry', 'process_definitions', 'ai_prompt')
|
||||
LOOP
|
||||
IF rec.column_name = 'version' THEN
|
||||
trig_name := 'trg_enforce_version';
|
||||
EXECUTE format(
|
||||
'CREATE TRIGGER %I BEFORE UPDATE ON %I
|
||||
FOR EACH ROW EXECUTE FUNCTION enforce_version()',
|
||||
trig_name, rec.table_name
|
||||
);
|
||||
ELSE
|
||||
trig_name := 'trg_enforce_version_lock';
|
||||
EXECUTE format(
|
||||
'CREATE TRIGGER %I BEFORE UPDATE ON %I
|
||||
FOR EACH ROW EXECUTE FUNCTION enforce_version_lock()',
|
||||
trig_name, rec.table_name
|
||||
);
|
||||
END IF;
|
||||
END LOOP;
|
||||
END;
|
||||
$$;
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let db = manager.get_connection();
|
||||
|
||||
// 1. 删除所有乐观锁触发器
|
||||
db.execute_unprepared(
|
||||
r#"
|
||||
DO $$
|
||||
DECLARE
|
||||
rec RECORD;
|
||||
BEGIN
|
||||
FOR rec IN
|
||||
SELECT event_object_table AS table_name, trigger_name
|
||||
FROM information_schema.triggers
|
||||
WHERE trigger_name IN ('trg_enforce_version', 'trg_enforce_version_lock')
|
||||
LOOP
|
||||
EXECUTE format('DROP TRIGGER IF EXISTS %I ON %I', rec.trigger_name, rec.table_name);
|
||||
END LOOP;
|
||||
END;
|
||||
$$;
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 2. 删除触发器函数
|
||||
db.execute_unprepared("DROP FUNCTION IF EXISTS enforce_version() CASCADE")
|
||||
.await?;
|
||||
db.execute_unprepared("DROP FUNCTION IF EXISTS enforce_version_lock() CASCADE")
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
//! 修复 admin 角色权限绑定
|
||||
//!
|
||||
//! 根因链:
|
||||
//! 1. m20260506_000126 对部分角色执行了软删除(SET deleted_at = NOW())
|
||||
//! 2. m20260508_000131 执行 `DELETE FROM role_permissions WHERE deleted_at IS NOT NULL`
|
||||
//! 物理删除了所有被软删除的记录
|
||||
//! 3. m20260508_000131 只重新分配了 doctor/nurse/operator 的权限,遗漏了 admin 角色
|
||||
//! 4. 后续的 assign_permissions API 调用可能在内部先软删除再 INSERT,
|
||||
//! INSERT 失败时 admin 权限全部丢失
|
||||
//!
|
||||
//! 本迁移:
|
||||
//! - Step 1: 恢复所有被软删除的 admin role_permissions(deleted_at IS NOT NULL → NULL)
|
||||
//! - Step 2: 插入所有缺失的 admin role_permissions(ON CONFLICT DO NOTHING 保证幂等)
|
||||
//!
|
||||
//! 覆盖范围:全系统 128 个权限码(auth/config/workflow/message/plugin/health/ai/copilot/points)
|
||||
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let db = manager.get_connection();
|
||||
|
||||
// ================================================================
|
||||
// Step 1: 恢复被软删除的 admin role_permissions
|
||||
// ================================================================
|
||||
// 如果 admin 的某些权限记录仍然存在但被软删除了,恢复它们
|
||||
db.execute_unprepared(
|
||||
r#"
|
||||
UPDATE role_permissions rp
|
||||
SET deleted_at = NULL, updated_at = NOW(), version = rp.version + 1
|
||||
FROM roles r
|
||||
WHERE rp.role_id = r.id
|
||||
AND r.code = 'admin'
|
||||
AND r.deleted_at IS NULL
|
||||
AND rp.deleted_at IS NOT NULL
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// ================================================================
|
||||
// Step 2: 插入缺失的 admin role_permissions
|
||||
// ================================================================
|
||||
// 将 permissions 表中所有未被软删除的权限绑定到 admin 角色
|
||||
// ON CONFLICT (role_id, permission_id) DO NOTHING — 已存在(含刚恢复的)的跳过
|
||||
db.execute_unprepared(
|
||||
r#"
|
||||
INSERT INTO role_permissions (role_id, permission_id, tenant_id, data_scope,
|
||||
created_at, updated_at, created_by, updated_by,
|
||||
deleted_at, version)
|
||||
SELECT r.id, p.id, r.tenant_id, 'all',
|
||||
NOW(), NOW(), r.id, r.id,
|
||||
NULL, 1
|
||||
FROM roles r
|
||||
JOIN permissions p ON p.tenant_id = r.tenant_id AND p.deleted_at IS NULL
|
||||
WHERE r.code = 'admin' AND r.deleted_at IS NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM role_permissions rp
|
||||
WHERE rp.role_id = r.id
|
||||
AND rp.permission_id = p.id
|
||||
AND rp.deleted_at IS NULL
|
||||
)
|
||||
ON CONFLICT (role_id, permission_id) DO NOTHING
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
// 不回滚 — 这是修复性迁移,admin 应该始终拥有全部权限
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let conn = manager.get_connection();
|
||||
|
||||
// 为 m000088 之后创建的新表补充 RLS 策略。
|
||||
// 幂等操作:仅影响尚未启用 RLS 或缺少策略的表。
|
||||
conn.execute_unprepared(
|
||||
r#"
|
||||
DO $$
|
||||
DECLARE
|
||||
tbl TEXT;
|
||||
policy_exists BOOLEAN;
|
||||
BEGIN
|
||||
FOR tbl IN
|
||||
SELECT c.table_name FROM information_schema.columns c
|
||||
JOIN information_schema.tables t
|
||||
ON c.table_name = t.table_name AND c.table_schema = t.table_schema
|
||||
WHERE c.column_name = 'tenant_id'
|
||||
AND c.table_schema = 'public'
|
||||
AND t.table_type = 'BASE TABLE'
|
||||
ORDER BY c.table_name
|
||||
LOOP
|
||||
-- 启用 RLS(幂等)
|
||||
EXECUTE format('ALTER TABLE %I ENABLE ROW LEVEL SECURITY', tbl);
|
||||
|
||||
-- 检查是否已有 tenant_isolation 策略
|
||||
SELECT EXISTS(
|
||||
SELECT 1 FROM pg_policies
|
||||
WHERE tablename = tbl
|
||||
AND policyname = 'tenant_isolation'
|
||||
) INTO policy_exists;
|
||||
|
||||
IF NOT policy_exists THEN
|
||||
EXECUTE format(
|
||||
'CREATE POLICY tenant_isolation ON %I USING (
|
||||
current_setting(''app.current_tenant_id'', true) != ''''
|
||||
AND tenant_id = current_setting(''app.current_tenant_id'', true)::uuid
|
||||
)',
|
||||
tbl
|
||||
);
|
||||
RAISE NOTICE 'Created RLS policy for table: %', tbl;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END;
|
||||
$$;
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
// 回滚不需要移除 RLS,保持 m000088 的策略不变
|
||||
// 此迁移补充的 RLS 策略在 down() 中保留,因为 m000088 已处理回滚
|
||||
let _ = manager;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user