use axum::Router; use uuid::Uuid; use erp_core::error::AppResult; use erp_core::events::EventBus; use erp_core::module::ErpModule; use crate::handler::{auth_handler, org_handler, role_handler, user_handler}; /// Auth module implementing the `ErpModule` trait. /// /// Manages identity, authentication, and user CRUD within the ERP platform. /// This module has no dependencies on other business modules. pub struct AuthModule; impl AuthModule { pub fn new() -> Self { Self } /// Build public (unauthenticated) routes for the auth module. /// /// These routes do not require a valid JWT token. /// The caller wraps this into whatever state type the application uses. pub fn public_routes() -> Router where crate::auth_state::AuthState: axum::extract::FromRef, S: Clone + Send + Sync + 'static, { Router::new() .route("/auth/login", axum::routing::post(auth_handler::login)) .route("/auth/refresh", axum::routing::post(auth_handler::refresh)) } /// Build protected (authenticated) routes for the auth module. /// /// These routes require a valid JWT token, verified by the middleware layer. /// The caller wraps this into whatever state type the application uses. pub fn protected_routes() -> Router where crate::auth_state::AuthState: axum::extract::FromRef, S: Clone + Send + Sync + 'static, { Router::new() .route("/auth/logout", axum::routing::post(auth_handler::logout)) .route( "/auth/change-password", axum::routing::post(auth_handler::change_password), ) .route( "/users", axum::routing::get(user_handler::list_users).post(user_handler::create_user), ) .route( "/users/{id}", axum::routing::get(user_handler::get_user) .put(user_handler::update_user) .delete(user_handler::delete_user), ) .route( "/users/{id}/roles", axum::routing::post(user_handler::assign_roles), ) .route( "/roles", axum::routing::get(role_handler::list_roles).post(role_handler::create_role), ) .route( "/roles/{id}", axum::routing::get(role_handler::get_role) .put(role_handler::update_role) .delete(role_handler::delete_role), ) .route( "/roles/{id}/permissions", axum::routing::get(role_handler::get_role_permissions) .post(role_handler::assign_permissions), ) .route( "/permissions", axum::routing::get(role_handler::list_permissions), ) // Organization routes .route( "/organizations", axum::routing::get(org_handler::list_organizations) .post(org_handler::create_organization), ) .route( "/organizations/{id}", axum::routing::put(org_handler::update_organization) .delete(org_handler::delete_organization), ) // Department routes (nested under organization) .route( "/organizations/{org_id}/departments", axum::routing::get(org_handler::list_departments) .post(org_handler::create_department), ) .route( "/departments/{id}", axum::routing::put(org_handler::update_department) .delete(org_handler::delete_department), ) // Position routes (nested under department) .route( "/departments/{dept_id}/positions", axum::routing::get(org_handler::list_positions).post(org_handler::create_position), ) .route( "/positions/{id}", axum::routing::put(org_handler::update_position) .delete(org_handler::delete_position), ) } } impl Default for AuthModule { fn default() -> Self { Self::new() } } #[async_trait::async_trait] impl ErpModule for AuthModule { fn name(&self) -> &str { "auth" } fn version(&self) -> &str { env!("CARGO_PKG_VERSION") } fn dependencies(&self) -> Vec<&str> { // Auth is a foundational module with no business-module dependencies. vec![] } fn register_event_handlers(&self, _bus: &EventBus) { // Auth 模块暂无跨模块事件订阅需求 } async fn on_tenant_created( &self, tenant_id: Uuid, db: &sea_orm::DatabaseConnection, _event_bus: &EventBus, ) -> AppResult<()> { let password = std::env::var("ERP__SUPER_ADMIN_PASSWORD") .unwrap_or_else(|_| "Admin@2026".to_string()); crate::service::seed::seed_tenant_auth(db, tenant_id, &password) .await .map_err(|e| erp_core::error::AppError::Internal(e.to_string()))?; tracing::info!(tenant_id = %tenant_id, "Tenant auth initialized"); Ok(()) } async fn on_tenant_deleted( &self, tenant_id: Uuid, db: &sea_orm::DatabaseConnection, ) -> AppResult<()> { use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set}; use chrono::Utc; let now = Utc::now(); // 软删除该租户下所有用户 let users = crate::entity::user::Entity::find() .filter(crate::entity::user::Column::TenantId.eq(tenant_id)) .filter(crate::entity::user::Column::DeletedAt.is_null()) .all(db) .await .map_err(|e| erp_core::error::AppError::Internal(e.to_string()))?; for user_model in users { let current_version = user_model.version; let active: crate::entity::user::ActiveModel = user_model.into(); let mut to_update: crate::entity::user::ActiveModel = active; to_update.deleted_at = Set(Some(now)); to_update.updated_at = Set(now); to_update.version = Set(current_version + 1); let _ = to_update .update(db) .await .map_err(|e| erp_core::error::AppError::Internal(e.to_string()))?; } tracing::info!(tenant_id = %tenant_id, "Tenant users soft-deleted"); Ok(()) } fn as_any(&self) -> &dyn std::any::Any { self } }