Skip to main content

nexus_tui_common/
config.rs

1use serde::{Deserialize, Serialize};
2
3/// Server configuration with rate limits, moderation, and file upload settings
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct ServerConfig {
6    pub network: NetworkConfig,
7    pub rate_limits: RateLimitConfig,
8    pub file_upload: FileUploadConfig,
9    pub moderation: ModerationConfig,
10    pub database: DatabaseConfig,
11    pub security: SecurityConfig,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct NetworkConfig {
16    pub bind_address: String,
17    pub port: u16,
18    pub max_connections: usize,
19    pub connection_timeout_seconds: u64,
20    pub keepalive_interval_seconds: u64,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct RateLimitConfig {
25    pub messages_per_minute: usize,
26    pub requests_per_second: usize,
27    pub file_uploads_per_hour: usize,
28    pub registration_attempts_per_hour: usize,
29    pub login_attempts_per_minute: usize,
30    pub channel_joins_per_minute: usize,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct FileUploadConfig {
35    pub enabled: bool,
36    pub max_file_size_mb: usize,
37    pub allowed_types: Vec<String>,
38    pub max_files_per_user: usize,
39    pub storage_path: String,
40    pub cleanup_interval_hours: u64,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct ModerationConfig {
45    pub auto_moderation_enabled: bool,
46    pub blocked_words: Vec<String>,
47    pub blocked_patterns: Vec<String>,
48    pub auto_ban_threshold: usize,
49    pub warning_threshold: usize,
50    pub message_length_limit: usize,
51    pub channel_creation_role: String, // "Admin", "Moderator", "User"
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct DatabaseConfig {
56    pub path: String,
57    pub backup_interval_hours: u64,
58    pub backup_retention_days: u32,
59    pub connection_pool_size: usize,
60    pub query_timeout_seconds: u64,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct SecurityConfig {
65    pub require_secure_passwords: bool,
66    pub min_password_length: usize,
67    pub session_timeout_hours: u64,
68    pub audit_logging_enabled: bool,
69    pub ip_whitelist: Vec<String>,
70    pub ip_blacklist: Vec<String>,
71}
72
73impl Default for ServerConfig {
74    fn default() -> Self {
75        Self {
76            network: NetworkConfig {
77                bind_address: "127.0.0.1".to_string(),
78                port: 8080,
79                max_connections: 1000,
80                connection_timeout_seconds: 30,
81                keepalive_interval_seconds: 60,
82            },
83            rate_limits: RateLimitConfig {
84                messages_per_minute: 60,
85                requests_per_second: 10,
86                file_uploads_per_hour: 10,
87                registration_attempts_per_hour: 5,
88                login_attempts_per_minute: 5,
89                channel_joins_per_minute: 20,
90            },
91            file_upload: FileUploadConfig {
92                enabled: true,
93                max_file_size_mb: 10,
94                allowed_types: vec![
95                    "image/png".to_string(),
96                    "image/jpeg".to_string(),
97                    "image/gif".to_string(),
98                    "image/webp".to_string(),
99                    "text/plain".to_string(),
100                ],
101                max_files_per_user: 100,
102                storage_path: "./uploads".to_string(),
103                cleanup_interval_hours: 24,
104            },
105            moderation: ModerationConfig {
106                auto_moderation_enabled: true,
107                blocked_words: vec![],
108                blocked_patterns: vec![],
109                auto_ban_threshold: 5,
110                warning_threshold: 3,
111                message_length_limit: 2000,
112                channel_creation_role: "Moderator".to_string(),
113            },
114            database: DatabaseConfig {
115                path: "nexus.db".to_string(),
116                backup_interval_hours: 6,
117                backup_retention_days: 30,
118                connection_pool_size: 10,
119                query_timeout_seconds: 30,
120            },
121            security: SecurityConfig {
122                require_secure_passwords: true,
123                min_password_length: 8,
124                session_timeout_hours: 24,
125                audit_logging_enabled: true,
126                ip_whitelist: vec![],
127                ip_blacklist: vec![],
128            },
129        }
130    }
131}
132
133impl ServerConfig {
134    /// Load configuration from file or create default
135    pub fn load_or_default(path: &str) -> Self {
136        if let Ok(content) = std::fs::read_to_string(path) {
137            match toml::from_str(&content) {
138                Ok(config) => {
139                    tracing::info!("Loaded server configuration from {}", path);
140                    config
141                }
142                Err(e) => {
143                    tracing::warn!("Failed to parse config file {}: {}. Using defaults.", path, e);
144                    Self::default()
145                }
146            }
147        } else {
148            let default = Self::default();
149            if let Err(e) = default.save(path) {
150                tracing::warn!("Failed to save default config to {}: {}", path, e);
151            }
152            default
153        }
154    }
155    
156    /// Save configuration to file
157    pub fn save(&self, path: &str) -> Result<(), Box<dyn std::error::Error>> {
158        let content = toml::to_string_pretty(self)?;
159        std::fs::write(path, content)?;
160        tracing::info!("Saved server configuration to {}", path);
161        Ok(())
162    }
163    
164    /// Validate configuration values
165    pub fn validate(&self) -> Result<(), String> {
166        if self.network.port == 0 {
167            return Err("Network port cannot be 0".to_string());
168        }
169        
170        if self.network.max_connections == 0 {
171            return Err("Max connections must be greater than 0".to_string());
172        }
173        
174        if self.rate_limits.messages_per_minute == 0 {
175            return Err("Messages per minute must be greater than 0".to_string());
176        }
177        
178        if self.moderation.message_length_limit == 0 {
179            return Err("Message length limit must be greater than 0".to_string());
180        }
181        
182        if self.security.min_password_length < 4 {
183            return Err("Minimum password length must be at least 4".to_string());
184        }
185        
186        Ok(())
187    }
188}
189
190/// Client configuration for UI preferences and behavior
191#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct ClientConfig {
193    pub connection: ConnectionConfig,
194    pub ui: UiConfig,
195    pub notifications: NotificationConfig,
196    pub audio: AudioConfig,
197    pub performance: PerformanceConfig,
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct ConnectionConfig {
202    pub auto_connect: bool,
203    pub default_server: Option<String>,
204    pub reconnect_attempts: usize,
205    pub reconnect_delay_seconds: u64,
206    pub connection_timeout_seconds: u64,
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct UiConfig {
211    pub theme: String,
212    pub font_size: u16,
213    pub show_timestamps: bool,
214    pub show_avatars: bool,
215    pub show_user_list: bool,
216    pub message_grouping: bool,
217    pub compact_mode: bool,
218    pub sidebar_width: u16,
219    pub max_message_history: usize,
220}
221
222#[derive(Debug, Clone, Serialize, Deserialize)]
223pub struct NotificationConfig {
224    pub enabled: bool,
225    pub show_mentions: bool,
226    pub show_direct_messages: bool,
227    pub show_server_invites: bool,
228    pub notification_timeout_ms: u64,
229    pub quiet_hours_start: Option<String>, // "22:00"
230    pub quiet_hours_end: Option<String>,   // "08:00"
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct AudioConfig {
235    pub enabled: bool,
236    pub volume: f32,
237    pub play_on_mention: bool,
238    pub play_on_dm: bool,
239    pub play_on_channel_message: bool,
240    pub play_ui_sounds: bool,
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct PerformanceConfig {
245    pub image_cache_size_mb: usize,
246    pub max_cached_images: usize,
247    pub lazy_load_images: bool,
248    pub message_render_limit: usize,
249    pub scroll_buffer_size: usize,
250}
251
252impl Default for ClientConfig {
253    fn default() -> Self {
254        Self {
255            connection: ConnectionConfig {
256                auto_connect: false,
257                default_server: None,
258                reconnect_attempts: 3,
259                reconnect_delay_seconds: 5,
260                connection_timeout_seconds: 10,
261            },
262            ui: UiConfig {
263                theme: "cyberpunk".to_string(),
264                font_size: 14,
265                show_timestamps: true,
266                show_avatars: true,
267                show_user_list: true,
268                message_grouping: true,
269                compact_mode: false,
270                sidebar_width: 30,
271                max_message_history: 1000,
272            },
273            notifications: NotificationConfig {
274                enabled: true,
275                show_mentions: true,
276                show_direct_messages: true,
277                show_server_invites: true,
278                notification_timeout_ms: 4000,
279                quiet_hours_start: None,
280                quiet_hours_end: None,
281            },
282            audio: AudioConfig {
283                enabled: true,
284                volume: 0.7,
285                play_on_mention: true,
286                play_on_dm: true,
287                play_on_channel_message: false,
288                play_ui_sounds: true,
289            },
290            performance: PerformanceConfig {
291                image_cache_size_mb: 50,
292                max_cached_images: 200,
293                lazy_load_images: true,
294                message_render_limit: 100,
295                scroll_buffer_size: 500,
296            },
297        }
298    }
299}
300
301impl ClientConfig {
302    /// Load configuration from file or create default
303    pub fn load_or_default(path: &str) -> Self {
304        if let Ok(content) = std::fs::read_to_string(path) {
305            match toml::from_str(&content) {
306                Ok(config) => {
307                    println!("Loaded client configuration from {}", path);
308                    config
309                }
310                Err(e) => {
311                    println!("Failed to parse config file {}: {}. Using defaults.", path, e);
312                    Self::default()
313                }
314            }
315        } else {
316            let default = Self::default();
317            if let Err(e) = default.save(path) {
318                println!("Failed to save default config to {}: {}", path, e);
319            }
320            default
321        }
322    }
323    
324    /// Save configuration to file
325    pub fn save(&self, path: &str) -> Result<(), Box<dyn std::error::Error>> {
326        // Ensure config directory exists
327        if let Some(parent) = std::path::Path::new(path).parent() {
328            std::fs::create_dir_all(parent)?;
329        }
330        
331        let content = toml::to_string_pretty(self)?;
332        std::fs::write(path, content)?;
333        println!("Saved client configuration to {}", path);
334        Ok(())
335    }
336    
337    /// Get config file path
338    pub fn default_path() -> String {
339        let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
340        format!("{}/.config/nexus/client.toml", home)
341    }
342}