use config::{Config, ConfigError, Environment, File}; use serde::Deserialize; use std::path::PathBuf; #[derive(Debug, Clone, Deserialize)] pub struct Settings { // Application settings pub app_name: String, pub port: u16, pub api_v1_prefix: String, // Database settings pub database_url: String, pub database_url_supabase: Option, // Authentication pub secret_key: String, pub access_token_expire_minutes: u64, // Gacha probabilities (percentages) pub prob_normal: f64, pub prob_rare: f64, pub prob_super_rare: f64, pub prob_kira: f64, pub prob_unique: f64, // atproto settings pub atproto_pds_url: Option, pub atproto_handle: Option, // External data pub card_master_url: String, // File paths pub config_dir: PathBuf, } impl Settings { pub fn new() -> Result { let config_dir = dirs::home_dir() .unwrap_or_else(|| PathBuf::from(".")) .join(".config") .join("syui") .join("ai") .join("card"); // Ensure config directory exists if !config_dir.exists() { std::fs::create_dir_all(&config_dir) .map_err(|e| ConfigError::Message(format!("Failed to create config directory: {}", e)))?; } let mut builder = Config::builder() // Default values .set_default("app_name", "ai.card")? .set_default("port", 8000)? .set_default("api_v1_prefix", "/api/v1")? // Database defaults .set_default("database_url", format!("sqlite://{}?mode=rwc", config_dir.join("aicard.db").display()))? // Authentication defaults .set_default("secret_key", "your-secret-key-change-in-production")? .set_default("access_token_expire_minutes", 1440)? // 24 hours // Gacha probability defaults (matching Python implementation) .set_default("prob_normal", 99.789)? .set_default("prob_rare", 0.1)? .set_default("prob_super_rare", 0.01)? .set_default("prob_kira", 0.1)? .set_default("prob_unique", 0.0001)? // External data source .set_default("card_master_url", "https://git.syui.ai/ai/ai/raw/branch/main/ai.json")?; // Load from config file if it exists let config_file = config_dir.join("config.toml"); if config_file.exists() { builder = builder.add_source(File::from(config_file)); } // Override with environment variables (AI_CARD_ prefix) builder = builder.add_source(Environment::with_prefix("AI_CARD").separator("_")); let mut settings: Settings = builder.build()?.try_deserialize()?; // Set the config directory path settings.config_dir = config_dir; Ok(settings) } /// Get the gacha configuration for the gacha service pub fn gacha_config(&self) -> GachaConfig { GachaConfig { prob_normal: self.prob_normal, prob_rare: self.prob_rare, prob_super_rare: self.prob_super_rare, prob_kira: self.prob_kira, prob_unique: self.prob_unique, } } } #[derive(Debug, Clone)] pub struct GachaConfig { pub prob_normal: f64, pub prob_rare: f64, pub prob_super_rare: f64, pub prob_kira: f64, pub prob_unique: f64, } impl GachaConfig { /// Calculate cumulative probabilities for rarity determination pub fn cumulative_probabilities(&self, is_paid: bool) -> Vec<(f64, crate::models::CardRarity)> { let multiplier = if is_paid { 2.0 } else { 1.0 }; vec![ (self.prob_unique * multiplier, crate::models::CardRarity::Unique), (self.prob_kira * multiplier, crate::models::CardRarity::Kira), (self.prob_super_rare * multiplier, crate::models::CardRarity::SuperRare), (self.prob_rare * multiplier, crate::models::CardRarity::Rare), (self.prob_normal, crate::models::CardRarity::Normal), ] } }