refactor: use UUID for envelope id to prevent accidental deletion

This commit is contained in:
rustmailer
2026-03-18 01:04:41 +08:00
parent 8a42fcdb4a
commit d690f57290
37 changed files with 393 additions and 393 deletions
+16 -14
View File
@@ -5,7 +5,6 @@ use crate::{
error::{code::ErrorCode, BichonResult},
imap::executor::ImapExecutor,
indexer::manager::{EML_INDEX_MANAGER, ENVELOPE_INDEX_MANAGER},
utils::create_hash,
},
raise_error,
};
@@ -16,16 +15,16 @@ const MAX_RESTORE_COUNT: usize = 100;
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
pub struct RestoreMessagesRequest {
/// Message IDs to restore (max 100)
pub message_ids: Vec<u64>,
/// envelope IDs to restore (max 100)
pub envelope_ids: Vec<String>,
}
pub async fn restore_emails(account_id: u64, message_ids: Vec<u64>) -> BichonResult<()> {
if message_ids.len() > MAX_RESTORE_COUNT {
pub async fn restore_emails(account_id: u64, envelope_ids: Vec<String>) -> BichonResult<()> {
if envelope_ids.len() > MAX_RESTORE_COUNT {
return Err(raise_error!(
format!(
"Too many messages to restore: {} (max {})",
message_ids.len(),
envelope_ids.len(),
MAX_RESTORE_COUNT
),
ErrorCode::InvalidParameter
@@ -42,27 +41,30 @@ pub async fn restore_emails(account_id: u64, message_ids: Vec<u64>) -> BichonRes
let mut failed = Vec::new();
let mut session = ImapExecutor::create_connection(account_id).await?;
for message_id in message_ids {
for envelope_id in envelope_ids {
let result: BichonResult<()> = async {
let eid = envelope_id.clone();
let envelope = ENVELOPE_INDEX_MANAGER
.get_envelope_by_id(account_id, message_id)
.get_envelope_by_id(account_id, eid)
.await?
.ok_or_else(|| {
raise_error!(
format!(
"Envelope not found: account_id={} message_id={}",
account_id, message_id
account_id, &envelope_id
),
ErrorCode::ResourceNotFound
)
})?;
let eml_id = create_hash(account_id, &envelope.message_id);
let eml = EML_INDEX_MANAGER
.get(account_id, eml_id)
.get(account_id, &envelope.content_hash)
.await?
.ok_or_else(|| {
raise_error!(
format!("Eml not found: account_id={} id={}", account_id, message_id),
format!(
"Eml not found: account_id={} id={}",
account_id, &envelope_id
),
ErrorCode::ResourceNotFound
)
})?;
@@ -83,13 +85,13 @@ pub async fn restore_emails(account_id: u64, message_ids: Vec<u64>) -> BichonRes
.await;
if let Err(err) = result {
failed.push(message_id);
tracing::warn!(
account_id = account_id,
message_id = message_id,
message_id = &envelope_id,
error = ?err,
"Failed to restore email"
);
failed.push(envelope_id);
}
}
+22 -11
View File
@@ -22,7 +22,6 @@ use crate::modules::envelope::extractor::extract_envelope_from_message;
use crate::modules::error::code::ErrorCode;
use crate::modules::indexer::envelope::Envelope;
use crate::modules::indexer::manager::{EML_INDEX_MANAGER, ENVELOPE_INDEX_MANAGER};
use crate::modules::utils::create_hash;
use crate::{modules::error::BichonResult, raise_error};
use mail_parser::{MessageParser, MimeHeaders};
@@ -144,29 +143,32 @@ pub struct FullNestedMessageContent {
pub envelope: Envelope,
}
pub async fn retrieve_email_content(account_id: u64, id: u64) -> BichonResult<FullMessageContent> {
pub async fn retrieve_email_content(
account_id: u64,
envelope_id: String,
) -> BichonResult<FullMessageContent> {
AccountModel::check_account_exists(account_id).await?;
let envelope = ENVELOPE_INDEX_MANAGER
.get_envelope_by_id(account_id, id)
.get_envelope_by_id(account_id, envelope_id.clone())
.await?
.ok_or_else(|| {
raise_error!(
format!(
"Email record not found: account_id={} id={}",
account_id, id
account_id, &envelope_id
),
ErrorCode::ResourceNotFound
)
})?;
let eml_id = create_hash(account_id, &envelope.message_id);
let eml = EML_INDEX_MANAGER
.get(account_id, eml_id)
.get(account_id, &envelope.content_hash)
.await?
.ok_or_else(|| {
raise_error!(
format!(
"Email record not found: account_id={} id={}",
account_id, id
account_id, &envelope_id
),
ErrorCode::ResourceNotFound
)
@@ -175,7 +177,7 @@ pub async fn retrieve_email_content(account_id: u64, id: u64) -> BichonResult<Fu
raise_error!(
format!(
"Failed to parse EML data (id={}) — the message may be corrupted.",
id
&envelope_id
),
ErrorCode::InternalError
)
@@ -186,14 +188,23 @@ pub async fn retrieve_email_content(account_id: u64, id: u64) -> BichonResult<Fu
for attachment in message.attachments() {
let content_type = attachment.content_type().ok_or_else(|| {
raise_error!(
format!("Attachment is missing Content-Type (email id={})", id),
format!(
"Attachment is missing Content-Type (email id={})",
&envelope_id
),
ErrorCode::InternalError
)
})?;
let filename = attachment
.attachment_name()
.map(|name| name.to_string())
.unwrap_or_else(|| format!("email{}_attachment{}", id, attachment.raw_body_offset()));
.unwrap_or_else(|| {
format!(
"email{}_attachment{}",
&envelope_id,
attachment.raw_body_offset()
)
});
let disposition = attachment.content_disposition();
@@ -242,7 +253,7 @@ pub async fn retrieve_email_content(account_id: u64, id: u64) -> BichonResult<Fu
pub async fn retrieve_nested_eml_content(
account_id: u64,
envelope_id: u64,
envelope_id: String,
name: &str,
) -> BichonResult<FullNestedMessageContent> {
let attachment_content = EML_INDEX_MANAGER
+1 -1
View File
@@ -20,7 +20,7 @@ use crate::modules::error::BichonResult;
use crate::modules::indexer::manager::{EML_INDEX_MANAGER, ENVELOPE_INDEX_MANAGER};
use std::collections::HashMap;
pub async fn delete_messages_impl(request: HashMap<u64, Vec<u64>>) -> BichonResult<()> {
pub async fn delete_messages_impl(request: HashMap<u64, Vec<String>>) -> BichonResult<()> {
EML_INDEX_MANAGER
.delete_email_multi_account(&request)
.await?;
+1 -2
View File
@@ -16,7 +16,6 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::{
modules::{
account::migration::AccountModel,
@@ -58,7 +57,7 @@ fn validate_pagination_params(page: u64, page_size: u64) -> BichonResult<()> {
pub async fn get_thread_messages(
account_id: u64,
thread_id: u64,
thread_id: String,
page: u64,
page_size: u64,
) -> BichonResult<DataPage<Envelope>> {
+1 -1
View File
@@ -24,7 +24,7 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)]
pub struct UpdateTagsRequest {
pub updates: HashMap<u64, Vec<u64>>, // account_id -> envelope_ids
pub updates: HashMap<u64, Vec<String>>, // account_id -> envelope_ids
pub tags: Vec<String>,
}