Files
hms/crates/erp-config/src/module.rs
iven 14f431efff feat: systematic functional audit — fix 18 issues across Phase A/B
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
2026-04-12 15:22:28 +08:00

141 lines
4.1 KiB
Rust

use axum::Router;
use axum::routing::{get, post, put};
use uuid::Uuid;
use erp_core::error::AppResult;
use erp_core::events::EventBus;
use erp_core::module::ErpModule;
use crate::handler::{
dictionary_handler, language_handler, menu_handler, numbering_handler, setting_handler,
theme_handler,
};
/// Config module implementing the `ErpModule` trait.
///
/// Manages system configuration: dictionaries, menus, settings,
/// numbering rules, languages, and themes.
pub struct ConfigModule;
impl ConfigModule {
pub fn new() -> Self {
Self
}
/// Build protected (authenticated) routes for the config module.
pub fn protected_routes<S>() -> Router<S>
where
crate::config_state::ConfigState: axum::extract::FromRef<S>,
S: Clone + Send + Sync + 'static,
{
Router::new()
// Dictionary routes
.route(
"/config/dictionaries",
get(dictionary_handler::list_dictionaries)
.post(dictionary_handler::create_dictionary),
)
.route(
"/config/dictionaries/{id}",
put(dictionary_handler::update_dictionary)
.delete(dictionary_handler::delete_dictionary),
)
.route(
"/config/dictionaries/items",
get(dictionary_handler::list_items_by_code),
)
.route(
"/config/dictionaries/{dict_id}/items",
post(dictionary_handler::create_item),
)
.route(
"/config/dictionaries/{dict_id}/items/{item_id}",
put(dictionary_handler::update_item)
.delete(dictionary_handler::delete_item),
)
// Menu routes
.route(
"/config/menus",
get(menu_handler::get_menus)
.post(menu_handler::create_menu)
.put(menu_handler::batch_save_menus),
)
.route(
"/config/menus/{id}",
put(menu_handler::update_menu)
.delete(menu_handler::delete_menu),
)
// Setting routes
.route(
"/config/settings/{key}",
get(setting_handler::get_setting)
.put(setting_handler::update_setting)
.delete(setting_handler::delete_setting),
)
// Numbering rule routes
.route(
"/config/numbering-rules",
get(numbering_handler::list_numbering_rules)
.post(numbering_handler::create_numbering_rule),
)
.route(
"/config/numbering-rules/{id}",
put(numbering_handler::update_numbering_rule)
.delete(numbering_handler::delete_numbering_rule),
)
.route(
"/config/numbering-rules/{id}/generate",
post(numbering_handler::generate_number),
)
// Theme routes
.route(
"/config/themes",
get(theme_handler::get_theme).put(theme_handler::update_theme),
)
// Language routes
.route(
"/config/languages",
get(language_handler::list_languages),
)
.route(
"/config/languages/{code}",
put(language_handler::update_language),
)
}
}
impl Default for ConfigModule {
fn default() -> Self {
Self::new()
}
}
#[async_trait::async_trait]
impl ErpModule for ConfigModule {
fn name(&self) -> &str {
"config"
}
fn version(&self) -> &str {
env!("CARGO_PKG_VERSION")
}
fn dependencies(&self) -> Vec<&str> {
vec!["auth"]
}
fn register_event_handlers(&self, _bus: &EventBus) {}
async fn on_tenant_created(&self, _tenant_id: Uuid) -> AppResult<()> {
Ok(())
}
async fn on_tenant_deleted(&self, _tenant_id: Uuid) -> AppResult<()> {
Ok(())
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}