From 566beabb7a47a02d8833dc17ddfd6ccbb0aeb63f Mon Sep 17 00:00:00 2001 From: rustmailer Date: Thu, 6 Nov 2025 23:12:44 +0800 Subject: [PATCH] Add Graph API compatibility for delete-mailbox and update-mailbox operations --- src/modules/cache/vendor/gmail/sync/client.rs | 6 ++- .../cache/vendor/outlook/sync/client.rs | 27 +++++++++++ src/modules/common/http/mod.rs | 6 +-- src/modules/mailbox/delete.rs | 20 +++++++- src/modules/mailbox/rename.rs | 48 +++++++++++++++++-- 5 files changed, 96 insertions(+), 11 deletions(-) diff --git a/src/modules/cache/vendor/gmail/sync/client.rs b/src/modules/cache/vendor/gmail/sync/client.rs index 2680d4d..a03720e 100644 --- a/src/modules/cache/vendor/gmail/sync/client.rs +++ b/src/modules/cache/vendor/gmail/sync/client.rs @@ -181,11 +181,15 @@ impl GmailClient { let mut body = json!({ "id": label_id, - "name": request.new_name, "messageListVisibility": "show", "labelListVisibility": "labelShow", "type": "user" }); + + if let Some(new_name) = &request.new_name { + body["name"] = json!(new_name); + } + if let Some(color) = &request.label_color { body["color"] = json!({ "textColor": color.text_color, diff --git a/src/modules/cache/vendor/outlook/sync/client.rs b/src/modules/cache/vendor/outlook/sync/client.rs index bff7c19..f01aaf4 100644 --- a/src/modules/cache/vendor/outlook/sync/client.rs +++ b/src/modules/cache/vendor/outlook/sync/client.rs @@ -417,4 +417,31 @@ impl OutlookClient { client.post(&url, &access_token, Some(&body), false).await?; Ok(()) } + + pub async fn delete_folder( + account_id: u64, + use_proxy: Option, + folder_id: &str, + ) -> RustMailerResult<()> { + let url = format!("https://graph.microsoft.com/v1.0/me/mailFolders/{folder_id}"); + let client = HttpClient::new(use_proxy).await?; + let access_token = Self::get_access_token(account_id).await?; + client.delete(url.as_str(), &access_token).await + } + + pub async fn rename_folder( + account_id: u64, + use_proxy: Option, + folder_id: &str, + new_name: &str, + ) -> RustMailerResult<()> { + let url = format!("https://graph.microsoft.com/v1.0/me/mailFolders/{folder_id}"); + let client = HttpClient::new(use_proxy).await?; + let access_token = Self::get_access_token(account_id).await?; + let data = json!({ + "displayName": new_name + }); + client.patch(url.as_str(), &access_token, &data).await?; + Ok(()) + } } diff --git a/src/modules/common/http/mod.rs b/src/modules/common/http/mod.rs index 666a828..9a84c51 100644 --- a/src/modules/common/http/mod.rs +++ b/src/modules/common/http/mod.rs @@ -219,11 +219,7 @@ impl HttpClient { } } - pub async fn get_bytes( - &self, - url: &str, - access_token: &str, - ) -> RustMailerResult { + pub async fn get_bytes(&self, url: &str, access_token: &str) -> RustMailerResult { let mut attempt = 0; let max_attempts = 4; let mut delay_ms = 500; diff --git a/src/modules/mailbox/delete.rs b/src/modules/mailbox/delete.rs index 07e5e2a..e433a13 100644 --- a/src/modules/mailbox/delete.rs +++ b/src/modules/mailbox/delete.rs @@ -6,7 +6,7 @@ use crate::{ encode_mailbox_name, modules::{ account::{entity::MailerType, migration::AccountModel}, - cache::vendor::gmail::sync::client::GmailClient, + cache::vendor::{gmail::sync::client::GmailClient, outlook::sync::client::OutlookClient}, context::executors::RUST_MAIL_CONTEXT, error::{code::ErrorCode, RustMailerResult}, }, @@ -35,6 +35,22 @@ pub async fn delete_mailbox(account_id: u64, mailbox_name: &str) -> RustMailerRe })?; GmailClient::delete_label(account_id, account.use_proxy, label_id).await } - MailerType::GraphApi => todo!(), + MailerType::GraphApi => { + let mailboxes = OutlookClient::list_mailfolders(account_id, account.use_proxy).await?; + let target_folder = mailboxes + .iter() + .find(|f| f.display_name == mailbox_name) + .cloned(); + + if let Some(folder) = target_folder { + OutlookClient::delete_folder(account_id, account.use_proxy, &folder.id).await?; + return Ok(()); + } else { + return Err(raise_error!( + format!("Mailbox '{}' not found.", mailbox_name), + ErrorCode::ResourceNotFound + )); + } + } } } diff --git a/src/modules/mailbox/rename.rs b/src/modules/mailbox/rename.rs index 5b0d30e..c98fdc2 100644 --- a/src/modules/mailbox/rename.rs +++ b/src/modules/mailbox/rename.rs @@ -6,7 +6,7 @@ use crate::{ encode_mailbox_name, modules::{ account::{entity::MailerType, migration::AccountModel}, - cache::vendor::gmail::sync::client::GmailClient, + cache::vendor::{gmail::sync::client::GmailClient, outlook::sync::client::OutlookClient}, context::executors::RUST_MAIL_CONTEXT, error::{code::ErrorCode, RustMailerResult}, mailbox::create::LabelColor, @@ -22,7 +22,13 @@ pub struct MailboxUpdateRequest { /// Current name of the mailbox or label. /// /// - For IMAP accounts, this is the existing mailbox name. - /// - For Gmail API accounts, this is the existing label name. + /// - For Gmail API accounts, this is the existing label name. + /// - For Graph API accounts, this should be the full mailbox path as displayed by + /// `list-mailboxes?remote=true`, where subfolders are separated by `/`. + /// For example, if the folder path is `test1/test2`, you must provide the full name + /// `test1/test2` instead of just `test2`. + /// + /// The path format is handled internally by RustMailer to ensure consistent folder resolution. #[oai(validator(min_length = "1", max_length = "1024"))] pub current_name: String, /// New name for the mailbox or label (optional). @@ -58,6 +64,14 @@ pub async fn update_mailbox( .await } MailerType::GmailApi => { + if payload.new_name.is_none() && payload.label_color.is_none() { + return Err(raise_error!( + "You must provide either `new_name` or `label_color` to update a mailbox." + .into(), + ErrorCode::InvalidParameter + )); + } + let map = GmailClient::reverse_label_map(account_id, account.use_proxy, true).await?; let label_id = map.get(&payload.current_name).ok_or_else(|| { raise_error!( @@ -70,6 +84,34 @@ pub async fn update_mailbox( })?; GmailClient::update_label(account_id, account.use_proxy, label_id, &payload).await } - MailerType::GraphApi => todo!(), + MailerType::GraphApi => { + if payload.new_name.is_none() { + return Err(raise_error!( + "The `new_name` field is required when updating a mailbox.".into(), + ErrorCode::InvalidParameter + )); + } + let mailboxes = OutlookClient::list_mailfolders(account_id, account.use_proxy).await?; + let target_folder = mailboxes + .iter() + .find(|f| f.display_name == payload.current_name) + .cloned(); + + if let Some(folder) = target_folder { + OutlookClient::rename_folder( + account_id, + account.use_proxy, + &folder.id, + &payload.new_name.unwrap(), + ) + .await?; + return Ok(()); + } else { + return Err(raise_error!( + format!("Mailbox '{}' not found.", payload.current_name), + ErrorCode::ResourceNotFound + )); + } + } } }