Phase A (P1 production blockers): - A1: Apply IP rate limiting to public routes (login/refresh) - A2: Publish domain events for workflow instance state transitions (completed/suspended/resumed/terminated) via outbox pattern - A3: Replace hardcoded nil UUID default tenant with dynamic DB lookup - A4: Add GET /api/v1/audit-logs query endpoint with pagination - A5: Enhance CORS wildcard warning for production environments Phase B (P2 functional gaps): - B1: Remove dead erp-common crate (zero references in codebase) - B2: Refactor 5 settings pages to use typed API modules instead of direct client calls; create api/themes.ts; delete dead errors.ts - B3: Add resume/suspend buttons to InstanceMonitor page - B4: Remove unused EventHandler trait from erp-core - B5: Handle task.completed events in message module (send notifications) - B6: Wire TimeoutChecker as 60s background task - B7: Auto-skip ServiceTask nodes instead of crashing the process - B8: Remove empty register_routes() from ErpModule trait and modules
83 lines
2.6 KiB
Rust
83 lines
2.6 KiB
Rust
use axum::extract::FromRef;
|
||
use sea_orm::DatabaseConnection;
|
||
|
||
use crate::config::AppConfig;
|
||
use erp_core::events::EventBus;
|
||
use erp_core::module::ModuleRegistry;
|
||
|
||
/// Axum shared application state.
|
||
/// All handlers access database connections, configuration, etc. through `State<AppState>`.
|
||
#[derive(Clone)]
|
||
pub struct AppState {
|
||
pub db: DatabaseConnection,
|
||
pub config: AppConfig,
|
||
pub event_bus: EventBus,
|
||
pub module_registry: ModuleRegistry,
|
||
pub redis: redis::Client,
|
||
/// 实际的默认租户 ID,从数据库种子数据中获取。
|
||
pub default_tenant_id: uuid::Uuid,
|
||
}
|
||
|
||
/// Allow handlers to extract `DatabaseConnection` directly from `State<AppState>`.
|
||
impl FromRef<AppState> for DatabaseConnection {
|
||
fn from_ref(state: &AppState) -> Self {
|
||
state.db.clone()
|
||
}
|
||
}
|
||
|
||
/// Allow handlers to extract `EventBus` directly from `State<AppState>`.
|
||
impl FromRef<AppState> for EventBus {
|
||
fn from_ref(state: &AppState) -> Self {
|
||
state.event_bus.clone()
|
||
}
|
||
}
|
||
|
||
/// Allow erp-auth handlers to extract their required state without depending on erp-server.
|
||
///
|
||
/// This bridges the gap: erp-auth defines `AuthState` with the fields it needs,
|
||
/// and erp-server fills them from `AppState`.
|
||
impl FromRef<AppState> for erp_auth::AuthState {
|
||
fn from_ref(state: &AppState) -> Self {
|
||
use erp_auth::auth_state::parse_ttl;
|
||
|
||
Self {
|
||
db: state.db.clone(),
|
||
event_bus: state.event_bus.clone(),
|
||
jwt_secret: state.config.jwt.secret.clone(),
|
||
access_ttl_secs: parse_ttl(&state.config.jwt.access_token_ttl),
|
||
refresh_ttl_secs: parse_ttl(&state.config.jwt.refresh_token_ttl),
|
||
default_tenant_id: state.default_tenant_id,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Allow erp-config handlers to extract their required state without depending on erp-server.
|
||
impl FromRef<AppState> for erp_config::ConfigState {
|
||
fn from_ref(state: &AppState) -> Self {
|
||
Self {
|
||
db: state.db.clone(),
|
||
event_bus: state.event_bus.clone(),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Allow erp-workflow handlers to extract their required state without depending on erp-server.
|
||
impl FromRef<AppState> for erp_workflow::WorkflowState {
|
||
fn from_ref(state: &AppState) -> Self {
|
||
Self {
|
||
db: state.db.clone(),
|
||
event_bus: state.event_bus.clone(),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Allow erp-message handlers to extract their required state without depending on erp-server.
|
||
impl FromRef<AppState> for erp_message::MessageState {
|
||
fn from_ref(state: &AppState) -> Self {
|
||
Self {
|
||
db: state.db.clone(),
|
||
event_bus: state.event_bus.clone(),
|
||
}
|
||
}
|
||
}
|