1use serde::{Deserialize, Serialize};
2
3#[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, }
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 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 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 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#[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>, pub quiet_hours_end: Option<String>, }
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 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 pub fn save(&self, path: &str) -> Result<(), Box<dyn std::error::Error>> {
326 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 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}