Skip to main content

rusmes_cli/commands/
mailbox.rs

1//! Mailbox 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 MailboxInfo {
12    pub name: String,
13    pub messages: u32,
14    pub unseen: u32,
15    pub size_mb: u64,
16    pub subscribed: bool,
17}
18
19/// List mailboxes for a user
20pub async fn list(client: &Client, user: &str, json: bool) -> Result<()> {
21    let mailboxes: Vec<MailboxInfo> = client
22        .get(&format!("/api/users/{}/mailboxes", user))
23        .await?;
24
25    if json {
26        println!("{}", serde_json::to_string_pretty(&mailboxes)?);
27    } else {
28        if mailboxes.is_empty() {
29            println!("{}", "No mailboxes found".yellow());
30            return Ok(());
31        }
32
33        let table = Table::new(&mailboxes).to_string();
34        println!("{}", format!("Mailboxes for {}:", user).bold());
35        println!("{}", table);
36
37        let total_messages: u32 = mailboxes.iter().map(|m| m.messages).sum();
38        let total_size: u64 = mailboxes.iter().map(|m| m.size_mb).sum();
39
40        println!(
41            "\n{} mailboxes, {} messages, {} MB total",
42            mailboxes.len().to_string().bold(),
43            total_messages.to_string().bold(),
44            total_size.to_string().bold()
45        );
46    }
47
48    Ok(())
49}
50
51/// Create a new mailbox
52pub async fn create(client: &Client, user: &str, name: &str, json: bool) -> Result<()> {
53    #[derive(Serialize)]
54    struct CreateMailboxRequest {
55        name: String,
56    }
57
58    let request = CreateMailboxRequest {
59        name: name.to_string(),
60    };
61
62    #[derive(Deserialize, Serialize)]
63    struct CreateResponse {
64        success: bool,
65    }
66
67    let response: CreateResponse = client
68        .post(&format!("/api/users/{}/mailboxes", user), &request)
69        .await?;
70
71    if json {
72        println!("{}", serde_json::to_string_pretty(&response)?);
73    } else {
74        println!(
75            "{}",
76            format!("✓ Mailbox '{}' created for {}", name, user)
77                .green()
78                .bold()
79        );
80    }
81
82    Ok(())
83}
84
85/// Delete a mailbox
86pub async fn delete(
87    client: &Client,
88    user: &str,
89    name: &str,
90    force: bool,
91    json: bool,
92) -> Result<()> {
93    if !force && !json {
94        println!(
95            "{}",
96            format!("Delete mailbox '{}' for {}?", name, user).yellow()
97        );
98        println!("This will delete all messages in this mailbox.");
99        println!("Use --force to skip this confirmation.");
100
101        use std::io::{self, Write};
102        print!("Continue? [y/N]: ");
103        io::stdout().flush()?;
104
105        let mut input = String::new();
106        io::stdin().read_line(&mut input)?;
107
108        if !input.trim().eq_ignore_ascii_case("y") {
109            println!("{}", "Cancelled".yellow());
110            return Ok(());
111        }
112    }
113
114    #[derive(Deserialize, Serialize)]
115    struct DeleteResponse {
116        success: bool,
117    }
118
119    let response: DeleteResponse = client
120        .delete(&format!("/api/users/{}/mailboxes/{}", user, name))
121        .await?;
122
123    if json {
124        println!("{}", serde_json::to_string_pretty(&response)?);
125    } else {
126        println!("{}", format!("✓ Mailbox '{}' deleted", name).green().bold());
127    }
128
129    Ok(())
130}
131
132/// Rename a mailbox
133pub async fn rename(
134    client: &Client,
135    user: &str,
136    old_name: &str,
137    new_name: &str,
138    json: bool,
139) -> Result<()> {
140    #[derive(Serialize)]
141    struct RenameRequest {
142        new_name: String,
143    }
144
145    let request = RenameRequest {
146        new_name: new_name.to_string(),
147    };
148
149    #[derive(Deserialize, Serialize)]
150    struct RenameResponse {
151        success: bool,
152    }
153
154    let response: RenameResponse = client
155        .put(
156            &format!("/api/users/{}/mailboxes/{}/rename", user, old_name),
157            &request,
158        )
159        .await?;
160
161    if json {
162        println!("{}", serde_json::to_string_pretty(&response)?);
163    } else {
164        println!(
165            "{}",
166            format!("✓ Mailbox renamed: '{}' → '{}'", old_name, new_name)
167                .green()
168                .bold()
169        );
170    }
171
172    Ok(())
173}
174
175/// Repair mailbox (rebuild indexes, fix counts)
176pub async fn repair(client: &Client, user: &str, name: &str, json: bool) -> Result<()> {
177    #[derive(Deserialize, Serialize)]
178    struct RepairResponse {
179        messages_scanned: u32,
180        errors_fixed: u32,
181        indexes_rebuilt: u32,
182    }
183
184    let response: RepairResponse = client
185        .post(
186            &format!("/api/users/{}/mailboxes/{}/repair", user, name),
187            &serde_json::json!({}),
188        )
189        .await?;
190
191    if json {
192        println!("{}", serde_json::to_string_pretty(&response)?);
193    } else {
194        println!(
195            "{}",
196            format!("✓ Mailbox '{}' repaired", name).green().bold()
197        );
198        println!("  Messages scanned: {}", response.messages_scanned);
199        println!("  Errors fixed: {}", response.errors_fixed);
200        println!("  Indexes rebuilt: {}", response.indexes_rebuilt);
201    }
202
203    Ok(())
204}
205
206/// Subscribe to a mailbox
207pub async fn subscribe(client: &Client, user: &str, name: &str, json: bool) -> Result<()> {
208    #[derive(Serialize)]
209    struct SubscribeRequest {
210        subscribed: bool,
211    }
212
213    #[derive(Deserialize, Serialize)]
214    struct SubscribeResponse {
215        success: bool,
216    }
217
218    let request = SubscribeRequest { subscribed: true };
219
220    let response: SubscribeResponse = client
221        .put(
222            &format!("/api/users/{}/mailboxes/{}/subscribe", user, name),
223            &request,
224        )
225        .await?;
226
227    if json {
228        println!("{}", serde_json::to_string_pretty(&response)?);
229    } else {
230        println!(
231            "{}",
232            format!("✓ Subscribed to mailbox '{}'", name).green().bold()
233        );
234    }
235
236    Ok(())
237}
238
239/// Unsubscribe from a mailbox
240pub async fn unsubscribe(client: &Client, user: &str, name: &str, json: bool) -> Result<()> {
241    #[derive(Serialize)]
242    struct SubscribeRequest {
243        subscribed: bool,
244    }
245
246    #[derive(Deserialize, Serialize)]
247    struct UnsubscribeResponse {
248        success: bool,
249    }
250
251    let request = SubscribeRequest { subscribed: false };
252
253    let response: UnsubscribeResponse = client
254        .put(
255            &format!("/api/users/{}/mailboxes/{}/subscribe", user, name),
256            &request,
257        )
258        .await?;
259
260    if json {
261        println!("{}", serde_json::to_string_pretty(&response)?);
262    } else {
263        println!(
264            "{}",
265            format!("✓ Unsubscribed from mailbox '{}'", name)
266                .yellow()
267                .bold()
268        );
269    }
270
271    Ok(())
272}
273
274/// Show mailbox details
275pub async fn show(client: &Client, user: &str, name: &str, json: bool) -> Result<()> {
276    #[derive(Deserialize, Serialize)]
277    struct MailboxDetails {
278        name: String,
279        messages: u32,
280        unseen: u32,
281        recent: u32,
282        size_bytes: u64,
283        subscribed: bool,
284        created_at: String,
285        uid_validity: u32,
286        uid_next: u32,
287    }
288
289    let details: MailboxDetails = client
290        .get(&format!("/api/users/{}/mailboxes/{}", user, name))
291        .await?;
292
293    if json {
294        println!("{}", serde_json::to_string_pretty(&details)?);
295    } else {
296        println!("{}", format!("Mailbox: {}", name).bold());
297        println!("  User: {}", user);
298        println!(
299            "  Messages: {} total, {} unseen, {} recent",
300            details.messages, details.unseen, details.recent
301        );
302        println!("  Size: {} MB", details.size_bytes / (1024 * 1024));
303        println!(
304            "  Subscribed: {}",
305            if details.subscribed {
306                "Yes".green()
307            } else {
308                "No".yellow()
309            }
310        );
311        println!("  Created: {}", details.created_at);
312        println!("  UIDVALIDITY: {}", details.uid_validity);
313        println!("  UIDNEXT: {}", details.uid_next);
314    }
315
316    Ok(())
317}
318
319#[cfg(test)]
320mod tests {
321    use super::*;
322
323    #[test]
324    fn test_mailbox_info_serialization() {
325        let mailbox = MailboxInfo {
326            name: "INBOX".to_string(),
327            messages: 10,
328            unseen: 2,
329            size_mb: 5,
330            subscribed: true,
331        };
332
333        let json = serde_json::to_string(&mailbox).unwrap();
334        assert!(json.contains("INBOX"));
335    }
336
337    #[test]
338    fn test_mailbox_stats_calculation() {
339        let mailboxes = [
340            MailboxInfo {
341                name: "INBOX".to_string(),
342                messages: 10,
343                unseen: 2,
344                size_mb: 5,
345                subscribed: true,
346            },
347            MailboxInfo {
348                name: "Sent".to_string(),
349                messages: 5,
350                unseen: 0,
351                size_mb: 3,
352                subscribed: true,
353            },
354        ];
355
356        let total_messages: u32 = mailboxes.iter().map(|m| m.messages).sum();
357        let total_size: u64 = mailboxes.iter().map(|m| m.size_mb).sum();
358
359        assert_eq!(total_messages, 15);
360        assert_eq!(total_size, 8);
361    }
362
363    #[test]
364    fn test_mailbox_empty() {
365        let mailbox = MailboxInfo {
366            name: "Archive".to_string(),
367            messages: 0,
368            unseen: 0,
369            size_mb: 0,
370            subscribed: false,
371        };
372
373        assert_eq!(mailbox.messages, 0);
374        assert_eq!(mailbox.unseen, 0);
375        assert!(!mailbox.subscribed);
376    }
377
378    #[test]
379    fn test_mailbox_all_unseen() {
380        let mailbox = MailboxInfo {
381            name: "INBOX".to_string(),
382            messages: 10,
383            unseen: 10,
384            size_mb: 5,
385            subscribed: true,
386        };
387
388        assert_eq!(mailbox.messages, mailbox.unseen);
389    }
390
391    #[test]
392    fn test_mailbox_deserialization() {
393        let json = r#"{
394            "name": "Drafts",
395            "messages": 5,
396            "unseen": 3,
397            "size_mb": 2,
398            "subscribed": true
399        }"#;
400
401        let mailbox: MailboxInfo = serde_json::from_str(json).unwrap();
402        assert_eq!(mailbox.name, "Drafts");
403        assert_eq!(mailbox.messages, 5);
404        assert_eq!(mailbox.unseen, 3);
405    }
406
407    #[test]
408    fn test_mailbox_hierarchical_name() {
409        let mailbox = MailboxInfo {
410            name: "Archive/2024/January".to_string(),
411            messages: 100,
412            unseen: 0,
413            size_mb: 50,
414            subscribed: true,
415        };
416
417        assert!(mailbox.name.contains('/'));
418        assert_eq!(mailbox.name, "Archive/2024/January");
419    }
420
421    #[test]
422    fn test_mailbox_special_use() {
423        let mailboxes = [
424            MailboxInfo {
425                name: "Sent".to_string(),
426                messages: 10,
427                unseen: 0,
428                size_mb: 5,
429                subscribed: true,
430            },
431            MailboxInfo {
432                name: "Trash".to_string(),
433                messages: 20,
434                unseen: 0,
435                size_mb: 3,
436                subscribed: true,
437            },
438        ];
439
440        assert_eq!(mailboxes.len(), 2);
441        assert!(mailboxes.iter().any(|m| m.name == "Sent"));
442        assert!(mailboxes.iter().any(|m| m.name == "Trash"));
443    }
444}