Skip to main content

rusmes_cli/commands/
user.rs

1//! User management commands
2
3use anyhow::Result;
4use colored::*;
5use serde::{Deserialize, Serialize};
6use tabled::{Table, Tabled};
7
8use crate::client::Client;
9
10#[derive(Debug, Serialize, Deserialize, Tabled)]
11pub struct UserInfo {
12    pub email: String,
13    pub created_at: String,
14    pub quota_used: u64,
15    pub quota_limit: u64,
16    pub enabled: bool,
17}
18
19/// Add a new user
20pub async fn add(
21    client: &Client,
22    email: &str,
23    password: &str,
24    quota: Option<u64>,
25    json: bool,
26) -> Result<()> {
27    #[derive(Serialize)]
28    struct AddUserRequest {
29        email: String,
30        password: String,
31        quota: Option<u64>,
32    }
33
34    #[derive(Deserialize, Serialize)]
35    struct AddUserResponse {
36        success: bool,
37    }
38
39    let request = AddUserRequest {
40        email: email.to_string(),
41        password: password.to_string(),
42        quota,
43    };
44
45    let response: AddUserResponse = client.post("/api/users", &request).await?;
46
47    if json {
48        println!("{}", serde_json::to_string_pretty(&response)?);
49    } else {
50        println!(
51            "{}",
52            format!("✓ User {} created successfully", email)
53                .green()
54                .bold()
55        );
56        if let Some(q) = quota {
57            println!("  Quota: {} MB", q / (1024 * 1024));
58        }
59    }
60
61    Ok(())
62}
63
64/// List all users
65pub async fn list(client: &Client, json: bool) -> Result<()> {
66    let users: Vec<UserInfo> = client.get("/api/users").await?;
67
68    if json {
69        println!("{}", serde_json::to_string_pretty(&users)?);
70    } else {
71        if users.is_empty() {
72            println!("{}", "No users found".yellow());
73            return Ok(());
74        }
75
76        let table = Table::new(&users).to_string();
77        println!("{}", table);
78        println!("\n{} total users", users.len().to_string().bold());
79    }
80
81    Ok(())
82}
83
84/// Delete a user
85pub async fn delete(client: &Client, email: &str, force: bool, json: bool) -> Result<()> {
86    if !force && !json {
87        println!("{}", format!("Delete user {}?", email).yellow());
88        println!("This will delete all mailboxes and messages for this user.");
89        println!("Use --force to skip this confirmation.");
90
91        use std::io::{self, Write};
92        print!("Continue? [y/N]: ");
93        io::stdout().flush()?;
94
95        let mut input = String::new();
96        io::stdin().read_line(&mut input)?;
97
98        if !input.trim().eq_ignore_ascii_case("y") {
99            println!("{}", "Cancelled".yellow());
100            return Ok(());
101        }
102    }
103
104    #[derive(Deserialize, Serialize)]
105    struct DeleteResponse {
106        success: bool,
107    }
108
109    let response: DeleteResponse = client.delete(&format!("/api/users/{}", email)).await?;
110
111    if json {
112        println!("{}", serde_json::to_string_pretty(&response)?);
113    } else {
114        println!(
115            "{}",
116            format!("✓ User {} deleted successfully", email)
117                .green()
118                .bold()
119        );
120    }
121
122    Ok(())
123}
124
125/// Change user password
126pub async fn passwd(client: &Client, email: &str, password: &str, json: bool) -> Result<()> {
127    #[derive(Serialize)]
128    struct PasswdRequest {
129        password: String,
130    }
131
132    #[derive(Deserialize, Serialize)]
133    struct PasswdResponse {
134        success: bool,
135    }
136
137    let request = PasswdRequest {
138        password: password.to_string(),
139    };
140
141    let response: PasswdResponse = client
142        .put(&format!("/api/users/{}/password", email), &request)
143        .await?;
144
145    if json {
146        println!("{}", serde_json::to_string_pretty(&response)?);
147    } else {
148        println!(
149            "{}",
150            format!("✓ Password changed for {}", email).green().bold()
151        );
152    }
153
154    Ok(())
155}
156
157/// Show user details
158pub async fn show(client: &Client, email: &str, json: bool) -> Result<()> {
159    let user: UserInfo = client.get(&format!("/api/users/{}", email)).await?;
160
161    if json {
162        println!("{}", serde_json::to_string_pretty(&user)?);
163    } else {
164        println!("{}", format!("User: {}", email).bold());
165        println!("  Created: {}", user.created_at);
166        println!(
167            "  Status: {}",
168            if user.enabled {
169                "Enabled".green()
170            } else {
171                "Disabled".red()
172            }
173        );
174        println!(
175            "  Quota: {} / {} MB ({:.1}%)",
176            user.quota_used / (1024 * 1024),
177            user.quota_limit / (1024 * 1024),
178            (user.quota_used as f64 / user.quota_limit as f64) * 100.0
179        );
180    }
181
182    Ok(())
183}
184
185/// Set user quota
186pub async fn set_quota(client: &Client, email: &str, quota_mb: u64, json: bool) -> Result<()> {
187    #[derive(Serialize)]
188    struct QuotaRequest {
189        quota: u64,
190    }
191
192    #[derive(Deserialize, Serialize)]
193    struct QuotaResponse {
194        success: bool,
195    }
196
197    let request = QuotaRequest {
198        quota: quota_mb * 1024 * 1024,
199    };
200
201    let response: QuotaResponse = client
202        .put(&format!("/api/users/{}/quota", email), &request)
203        .await?;
204
205    if json {
206        println!("{}", serde_json::to_string_pretty(&response)?);
207    } else {
208        println!(
209            "{}",
210            format!("✓ Quota set to {} MB for {}", quota_mb, email)
211                .green()
212                .bold()
213        );
214    }
215
216    Ok(())
217}
218
219/// Enable a user account
220pub async fn enable(client: &Client, email: &str, json: bool) -> Result<()> {
221    #[derive(Serialize)]
222    struct EnableRequest {
223        enabled: bool,
224    }
225
226    #[derive(Deserialize, Serialize)]
227    struct EnableResponse {
228        success: bool,
229    }
230
231    let request = EnableRequest { enabled: true };
232
233    let response: EnableResponse = client
234        .put(&format!("/api/users/{}/status", email), &request)
235        .await?;
236
237    if json {
238        println!("{}", serde_json::to_string_pretty(&response)?);
239    } else {
240        println!("{}", format!("✓ User {} enabled", email).green().bold());
241    }
242
243    Ok(())
244}
245
246/// Disable a user account
247pub async fn disable(client: &Client, email: &str, json: bool) -> Result<()> {
248    #[derive(Serialize)]
249    struct EnableRequest {
250        enabled: bool,
251    }
252
253    #[derive(Deserialize, Serialize)]
254    struct DisableResponse {
255        success: bool,
256    }
257
258    let request = EnableRequest { enabled: false };
259
260    let response: DisableResponse = client
261        .put(&format!("/api/users/{}/status", email), &request)
262        .await?;
263
264    if json {
265        println!("{}", serde_json::to_string_pretty(&response)?);
266    } else {
267        println!("{}", format!("✓ User {} disabled", email).yellow().bold());
268    }
269
270    Ok(())
271}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276
277    #[test]
278    fn test_user_info_serialization() {
279        let user = UserInfo {
280            email: "test@example.com".to_string(),
281            created_at: "2024-01-01T00:00:00Z".to_string(),
282            quota_used: 1024 * 1024,
283            quota_limit: 100 * 1024 * 1024,
284            enabled: true,
285        };
286
287        let json = serde_json::to_string(&user).unwrap();
288        assert!(json.contains("test@example.com"));
289    }
290
291    #[test]
292    fn test_quota_calculation() {
293        let quota_mb = 100u64;
294        let quota_bytes = quota_mb * 1024 * 1024;
295        assert_eq!(quota_bytes, 104_857_600);
296    }
297
298    #[test]
299    fn test_user_info_disabled() {
300        let user = UserInfo {
301            email: "disabled@example.com".to_string(),
302            created_at: "2024-01-01T00:00:00Z".to_string(),
303            quota_used: 0,
304            quota_limit: 100 * 1024 * 1024,
305            enabled: false,
306        };
307
308        assert!(!user.enabled);
309        assert_eq!(user.quota_used, 0);
310    }
311
312    #[test]
313    fn test_quota_percentage_calculation() {
314        let user = UserInfo {
315            email: "test@example.com".to_string(),
316            created_at: "2024-01-01T00:00:00Z".to_string(),
317            quota_used: 50 * 1024 * 1024,
318            quota_limit: 100 * 1024 * 1024,
319            enabled: true,
320        };
321
322        let percentage = (user.quota_used as f64 / user.quota_limit as f64) * 100.0;
323        assert_eq!(percentage, 50.0);
324    }
325
326    #[test]
327    fn test_quota_over_limit() {
328        let user = UserInfo {
329            email: "test@example.com".to_string(),
330            created_at: "2024-01-01T00:00:00Z".to_string(),
331            quota_used: 150 * 1024 * 1024,
332            quota_limit: 100 * 1024 * 1024,
333            enabled: true,
334        };
335
336        let percentage = (user.quota_used as f64 / user.quota_limit as f64) * 100.0;
337        assert!(percentage > 100.0);
338    }
339
340    #[test]
341    fn test_user_info_deserialization() {
342        let json = r#"{
343            "email": "test@example.com",
344            "created_at": "2024-01-01T00:00:00Z",
345            "quota_used": 1048576,
346            "quota_limit": 104857600,
347            "enabled": true
348        }"#;
349
350        let user: UserInfo = serde_json::from_str(json).unwrap();
351        assert_eq!(user.email, "test@example.com");
352        assert_eq!(user.quota_used, 1048576);
353        assert!(user.enabled);
354    }
355
356    #[test]
357    fn test_quota_zero() {
358        let quota_mb = 0u64;
359        let quota_bytes = quota_mb * 1024 * 1024;
360        assert_eq!(quota_bytes, 0);
361    }
362
363    #[test]
364    fn test_quota_large_value() {
365        let quota_mb = 10_000u64; // 10GB
366        let quota_bytes = quota_mb * 1024 * 1024;
367        assert_eq!(quota_bytes, 10_485_760_000);
368    }
369}