feat(auth): add tenant seed data and bootstrap logic
- seed.rs: creates 21 permissions, admin+viewer roles, admin user with Argon2 password - AuthConfig added to server config with default password Admin@2026 - Server startup: auto-creates default tenant and seeds auth data if not exists - Idempotent: checks for existing tenant before seeding
This commit is contained in:
@@ -6,6 +6,7 @@ pub struct AppConfig {
|
||||
pub database: DatabaseConfig,
|
||||
pub redis: RedisConfig,
|
||||
pub jwt: JwtConfig,
|
||||
pub auth: AuthConfig,
|
||||
pub log: LogConfig,
|
||||
}
|
||||
|
||||
@@ -39,6 +40,11 @@ pub struct LogConfig {
|
||||
pub level: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct AuthConfig {
|
||||
pub super_admin_password: String,
|
||||
}
|
||||
|
||||
impl AppConfig {
|
||||
pub fn load() -> anyhow::Result<Self> {
|
||||
let config = config::Config::builder()
|
||||
|
||||
@@ -13,6 +13,7 @@ use tracing_subscriber::EnvFilter;
|
||||
use erp_core::events::EventBus;
|
||||
use erp_core::module::{ErpModule, ModuleRegistry};
|
||||
use erp_server_migration::MigratorTrait;
|
||||
use sea_orm::{ConnectionTrait, FromQueryResult};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
@@ -37,6 +38,58 @@ async fn main() -> anyhow::Result<()> {
|
||||
erp_server_migration::Migrator::up(&db, None).await?;
|
||||
tracing::info!("Database migrations applied");
|
||||
|
||||
// Seed default tenant and auth data if not present
|
||||
{
|
||||
#[derive(sea_orm::FromQueryResult)]
|
||||
struct TenantId {
|
||||
id: uuid::Uuid,
|
||||
}
|
||||
|
||||
let existing = TenantId::find_by_statement(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"SELECT id FROM tenants WHERE deleted_at IS NULL LIMIT 1".to_string(),
|
||||
))
|
||||
.one(&db)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("Failed to query tenants: {}", e))?;
|
||||
|
||||
match existing {
|
||||
Some(row) => {
|
||||
tracing::info!(tenant_id = %row.id, "Default tenant already exists, skipping seed");
|
||||
}
|
||||
None => {
|
||||
let new_tenant_id = uuid::Uuid::now_v7();
|
||||
|
||||
// Insert default tenant using raw SQL (no tenant entity in erp-server)
|
||||
db.execute(sea_orm::Statement::from_sql_and_values(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"INSERT INTO tenants (id, name, code, status, created_at, updated_at) VALUES ($1, $2, $3, $4, NOW(), NOW())",
|
||||
[
|
||||
new_tenant_id.into(),
|
||||
"Default Tenant".into(),
|
||||
"default".into(),
|
||||
"active".into(),
|
||||
],
|
||||
))
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("Failed to create default tenant: {}", e))?;
|
||||
|
||||
tracing::info!(tenant_id = %new_tenant_id, "Created default tenant");
|
||||
|
||||
// Seed auth data (permissions, roles, admin user)
|
||||
erp_auth::service::seed::seed_tenant_auth(
|
||||
&db,
|
||||
new_tenant_id,
|
||||
&config.auth.super_admin_password,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("Failed to seed auth data: {}", e))?;
|
||||
|
||||
tracing::info!(tenant_id = %new_tenant_id, "Default tenant ready with auth seed data");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Connect to Redis
|
||||
let _redis_client = redis::Client::open(&config.redis.url[..])?;
|
||||
tracing::info!("Redis client created");
|
||||
|
||||
Reference in New Issue
Block a user