use anyhow::Result; use serde::{Deserialize, Serialize}; use std::path::Path; #[derive(Debug, Deserialize, Serialize, Clone)] pub struct AppConfig { pub server: ServerConfig, pub database: DatabaseConfig, pub auth: AuthConfig, pub retention: RetentionConfig, #[serde(default)] pub notify: NotifyConfig, /// Token required for device registration. Empty = any token accepted. #[serde(default)] pub registration_token: String, } #[derive(Debug, Deserialize, Serialize, Clone)] pub struct ServerConfig { pub http_addr: String, pub tcp_addr: String, /// Allowed CORS origins. Empty = same-origin only (no CORS headers). #[serde(default)] pub cors_origins: Vec, /// Optional TLS configuration for the TCP listener. #[serde(default)] pub tls: Option, } #[derive(Debug, Deserialize, Serialize, Clone)] pub struct TlsConfig { /// Path to the server certificate (PEM format) pub cert_path: String, /// Path to the server private key (PEM format) pub key_path: String, } #[derive(Debug, Deserialize, Serialize, Clone)] pub struct DatabaseConfig { pub path: String, } #[derive(Debug, Deserialize, Serialize, Clone)] pub struct AuthConfig { pub jwt_secret: String, #[serde(default = "default_access_ttl")] pub access_token_ttl_secs: u64, #[serde(default = "default_refresh_ttl")] pub refresh_token_ttl_secs: u64, } #[derive(Debug, Deserialize, Serialize, Clone)] pub struct RetentionConfig { #[serde(default = "default_status_history_days")] pub status_history_days: u32, #[serde(default = "default_usb_events_days")] pub usb_events_days: u32, #[serde(default = "default_asset_changes_days")] pub asset_changes_days: u32, #[serde(default = "default_alert_records_days")] pub alert_records_days: u32, #[serde(default = "default_audit_log_days")] pub audit_log_days: u32, } #[derive(Debug, Deserialize, Serialize, Clone, Default)] pub struct NotifyConfig { #[serde(default)] pub smtp: Option, #[serde(default)] pub webhook_urls: Vec, } #[derive(Debug, Deserialize, Serialize, Clone)] pub struct SmtpConfig { pub host: String, pub port: u16, pub username: String, pub password: String, pub from: String, } impl AppConfig { pub async fn load(path: &str) -> Result { if Path::new(path).exists() { let content = tokio::fs::read_to_string(path).await?; let config: AppConfig = toml::from_str(&content)?; Ok(config) } else { let config = default_config(); // Write default config for reference let toml_str = toml::to_string_pretty(&config)?; tokio::fs::write(path, &toml_str).await?; tracing::warn!("Created default config at {}", path); Ok(config) } } } fn default_access_ttl() -> u64 { 1800 } // 30 minutes fn default_refresh_ttl() -> u64 { 604800 } // 7 days fn default_status_history_days() -> u32 { 7 } fn default_usb_events_days() -> u32 { 90 } fn default_asset_changes_days() -> u32 { 365 } fn default_alert_records_days() -> u32 { 90 } fn default_audit_log_days() -> u32 { 365 } pub fn default_config() -> AppConfig { AppConfig { server: ServerConfig { http_addr: "0.0.0.0:9998".into(), tcp_addr: "0.0.0.0:9999".into(), cors_origins: vec![], tls: None, }, database: DatabaseConfig { path: "./csm.db".into(), }, auth: AuthConfig { jwt_secret: uuid::Uuid::new_v4().to_string(), access_token_ttl_secs: default_access_ttl(), refresh_token_ttl_secs: default_refresh_ttl(), }, retention: RetentionConfig { status_history_days: default_status_history_days(), usb_events_days: default_usb_events_days(), asset_changes_days: default_asset_changes_days(), alert_records_days: default_alert_records_days(), audit_log_days: default_audit_log_days(), }, notify: NotifyConfig::default(), registration_token: uuid::Uuid::new_v4().to_string(), } }