1use 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
19pub 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
64pub 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
84pub 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
125pub 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
157pub 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
185pub 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
219pub 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
246pub 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; let quota_bytes = quota_mb * 1024 * 1024;
367 assert_eq!(quota_bytes, 10_485_760_000);
368 }
369}