// // Copyright (c) 2025 rustmailer.com (https://rustmailer.com) // // This file is part of the Bichon Email Archiving Project // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . use crate::{ decode_mailbox_name, encode_mailbox_name, modules::{ database::{ async_find_impl, batch_delete_impl, batch_insert_impl, batch_upsert_impl, delete_impl, filter_by_secondary_key_impl, manager::DB_MANAGER, }, error::{code::ErrorCode, BichonResult}, }, raise_error, }; use async_imap::types::{Name, NameAttribute}; use itertools::Itertools; use native_db::*; use native_model::{native_model, Model}; use poem_openapi::{Enum, Object}; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] #[native_model(id = 1, version = 1)] #[native_db] pub struct MailBox { /// The unique identifier for the mailbox #[primary_key] pub id: u64, /// The ID of the account associated with the mailbox #[secondary_key] pub account_id: u64, /// The unique, decoded, human-readable name of the mailbox (e.g., "INBOX", "Sent Items"). /// This is the decoded name as presented to users, derived from the IMAP server's mailbox name /// (e.g., after decoding UTF-7 or other encodings per RFC 3501). pub name: String, /// Optional delimiter used to separate mailbox names in a hierarchy (e.g., "/" or "."). /// Used in IMAP to structure nested mailboxes (e.g., "INBOX/Archive"). pub delimiter: Option, /// List of attributes associated with the mailbox (e.g., `\NoSelect`, `\Deleted`). /// These indicate special properties, such as whether the mailbox can hold messages. pub attributes: Vec, /// The number of messages that currently exist in the mailbox. pub exists: u32, /// Optional number of unseen messages in the mailbox (i.e., messages without the `\Seen` flag). pub unseen: Option, /// The next unique identifier (UID) that will be assigned to a new message in the mailbox. /// If `None`, the IMAP server has not provided this information. pub uid_next: Option, /// The validity identifier for UIDs in this mailbox, used to ensure UID consistency across sessions. /// If `None`, the IMAP server has not provided this information. pub uid_validity: Option, } impl MailBox { pub fn encoded_name(&self) -> String { encode_mailbox_name!(&self.name) } // pub async fn batch_delete(mailboxes: Vec) -> BichonResult<()> { // batch_delete_impl(DB_MANAGER.envelope_db(), move |rw| { // let mut to_deleted = Vec::new(); // for mailbox in mailboxes { // let retrived = rw // .get() // .primary::(mailbox.id) // .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; // if let Some(retrived) = retrived { // to_deleted.push(retrived); // } // } // Ok(to_deleted) // }) // .await?; // Ok(()) // } pub async fn get(id: u64) -> BichonResult { let result = async_find_impl::(DB_MANAGER.envelope_db(), id).await?; Ok(result.ok_or_else(|| { raise_error!( format!("mailbox {} not found", id), ErrorCode::InternalError ) })?) } pub async fn delete(id: u64) -> BichonResult<()> { delete_impl(DB_MANAGER.envelope_db(), move |rw| { rw.get() .primary::(id) .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))? .ok_or_else(|| raise_error!("mailbox missing".into(), ErrorCode::InternalError)) }) .await } pub async fn list_all(account_id: u64) -> BichonResult> { filter_by_secondary_key_impl(DB_MANAGER.envelope_db(), MailBoxKey::account_id, account_id) .await } pub async fn batch_insert(mailboxes: &[MailBox]) -> BichonResult<()> { batch_insert_impl(DB_MANAGER.envelope_db(), mailboxes.to_vec()).await } pub async fn batch_upsert(mailboxes: &[MailBox]) -> BichonResult<()> { batch_upsert_impl(DB_MANAGER.envelope_db(), mailboxes.to_vec()).await } pub async fn clean(account_id: u64) -> BichonResult<()> { batch_delete_impl(DB_MANAGER.envelope_db(), move |rw| { let mailboxes: Vec = rw .scan() .secondary::(MailBoxKey::account_id) .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))? .start_with(account_id) .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))? .try_collect() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; Ok(mailboxes) }) .await?; Ok(()) } } #[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize, Object)] pub struct Attribute { pub attr: AttributeEnum, pub extension: Option, } impl Attribute { pub fn new(attr: AttributeEnum, extension: Option) -> Self { Self { attr, extension } } } #[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize, Enum)] pub enum AttributeEnum { NoInferiors, NoSelect, Marked, Unmarked, All, Archive, Drafts, Flagged, Junk, Sent, Trash, Extension, Unknown, } impl From<&Name> for MailBox { fn from(value: &Name) -> Self { let name = decode_mailbox_name!(value.name().to_string()); let delimiter = value.delimiter().map(|f| f.to_owned()); let attributes: Vec = value.attributes().iter().map(|na| na.into()).collect(); //The remaining parts will be supplemented during the examine_mailbox process. MailBox { name, delimiter, attributes, ..Default::default() //has_synced is initialized to false here } } } impl From<&NameAttribute<'_>> for Attribute { fn from(value: &NameAttribute) -> Self { match value { NameAttribute::NoInferiors => Attribute::new(AttributeEnum::NoInferiors, None), NameAttribute::NoSelect => Attribute::new(AttributeEnum::NoSelect, None), NameAttribute::Marked => Attribute::new(AttributeEnum::Marked, None), NameAttribute::Unmarked => Attribute::new(AttributeEnum::Unmarked, None), NameAttribute::All => Attribute::new(AttributeEnum::All, None), NameAttribute::Archive => Attribute::new(AttributeEnum::Archive, None), NameAttribute::Drafts => Attribute::new(AttributeEnum::Drafts, None), NameAttribute::Flagged => Attribute::new(AttributeEnum::Flagged, None), NameAttribute::Junk => Attribute::new(AttributeEnum::Junk, None), NameAttribute::Sent => Attribute::new(AttributeEnum::Sent, None), NameAttribute::Trash => Attribute::new(AttributeEnum::Trash, None), NameAttribute::Extension(s) => { Attribute::new(AttributeEnum::Extension, Some(s.to_string())) } _ => Attribute::new(AttributeEnum::Unknown, None), } } }