mirror of
https://github.com/rustmailer/rustmailer.git
synced 2026-08-18 16:01:07 +00:00
feat(account): add MailerType to support Gmail API and other mailers
- Introduced `MailerType` enum in Account model - Enables selection of different mailer backends (e.g., Gmail API)
This commit is contained in:
+19
-7
@@ -104,9 +104,9 @@ message Account {
|
||||
// The unique identifier of the account.
|
||||
uint64 id = 1;
|
||||
// The IMAP server configuration for the account.
|
||||
ImapConfig imap = 2;
|
||||
optional ImapConfig imap = 2;
|
||||
// The SMTP server configuration for the account.
|
||||
SmtpConfig smtp = 3;
|
||||
optional SmtpConfig smtp = 3;
|
||||
// Indicates whether the account is enabled for synchronization.
|
||||
bool enabled = 4;
|
||||
// The email address of the account.
|
||||
@@ -114,7 +114,7 @@ message Account {
|
||||
// Optional: A display name for the account.
|
||||
optional string name = 6;
|
||||
// If true, only minimal metadata will be synced initially (e.g., no full message bodies).
|
||||
bool minimal_sync = 7;
|
||||
optional bool minimal_sync = 7;
|
||||
// A list of capabilities supported by the account (e.g., "IMAP4REV1", "AUTH=PLAIN").
|
||||
repeated string capabilities = 8;
|
||||
// Optional: Indicates if the account is capable of Delivery Status Notifications (DSN).
|
||||
@@ -126,13 +126,15 @@ message Account {
|
||||
// A list of known folders (e.g., "INBOX", "Sent Items").
|
||||
repeated string known_folders = 12;
|
||||
// The interval (in minutes) for a full synchronization pass.
|
||||
int64 full_sync_interval_min = 13;
|
||||
optional int64 full_sync_interval_min = 13;
|
||||
// The interval (in seconds) for incremental synchronization updates.
|
||||
int64 incremental_sync_interval_sec = 14;
|
||||
// The timestamp when the account was created.
|
||||
int64 created_at = 15;
|
||||
// The timestamp when the account was last updated.
|
||||
int64 updated_at = 16;
|
||||
// Method used to access and manage emails.
|
||||
MailerType mailer_type = 17;
|
||||
}
|
||||
|
||||
// PagedAccount represents a paginated list of Account messages.
|
||||
@@ -156,19 +158,21 @@ message AccountCreateRequest {
|
||||
// Optional: A display name for the new account.
|
||||
optional string name = 2;
|
||||
// The IMAP configuration for the new account.
|
||||
ImapConfig imap = 3;
|
||||
optional ImapConfig imap = 3;
|
||||
// The SMTP configuration for the new account.
|
||||
SmtpConfig smtp = 4;
|
||||
optional SmtpConfig smtp = 4;
|
||||
// Indicates whether the new account should be enabled immediately.
|
||||
bool enabled = 5;
|
||||
// Optional: The date from which to start syncing emails for this account.
|
||||
optional DateSince date_since = 6;
|
||||
// If true, only minimal metadata will be synced initially for the new account.
|
||||
bool minimal_sync = 7;
|
||||
optional bool minimal_sync = 7;
|
||||
// Optional: The interval (in minutes) for a full synchronization pass.
|
||||
optional int64 full_sync_interval_min = 8;
|
||||
// Optional: The interval (in seconds) for incremental synchronization updates.
|
||||
optional int64 incremental_sync_interval_sec = 9;
|
||||
// Method used to access and manage emails.
|
||||
MailerType mailer_type = 10;
|
||||
}
|
||||
|
||||
// AccountUpdateRequest defines the parameters for updating an existing email account.
|
||||
@@ -261,6 +265,14 @@ message ListMinimalAccountsResponse {
|
||||
repeated MinimalAccount accounts = 1;
|
||||
}
|
||||
|
||||
// Represents the method used to access/manage emails.
|
||||
enum MailerType {
|
||||
// Default value: use IMAP/SMTP protocol
|
||||
IMAP_SMTP = 0;
|
||||
// Use Gmail API
|
||||
GMAIL_API = 1;
|
||||
}
|
||||
|
||||
// AccountService provides APIs for managing email accounts.
|
||||
service AccountService {
|
||||
// Retrieves a specific email account by its ID.
|
||||
|
||||
+12
-330
@@ -4,45 +4,13 @@
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use crate::encrypt;
|
||||
use crate::id;
|
||||
use crate::modules::account::payload::AccountCreateRequest;
|
||||
use crate::modules::account::payload::AccountUpdateRequest;
|
||||
use crate::modules::account::payload::MinimalAccount;
|
||||
use crate::modules::account::since::DateSince;
|
||||
use crate::modules::cache::imap::mailbox::MailBox;
|
||||
use crate::modules::cache::imap::manager::EnvelopeFlagsManager;
|
||||
use crate::modules::cache::imap::task::IMAP_TASKS;
|
||||
use crate::modules::context::controller::SYNC_CONTROLLER;
|
||||
use crate::modules::context::executors::RUST_MAIL_CONTEXT;
|
||||
use crate::modules::database::count_by_unique_secondary_key_impl;
|
||||
use crate::modules::database::delete_impl;
|
||||
use crate::modules::database::manager::DB_MANAGER;
|
||||
use crate::modules::database::{
|
||||
paginate_query_primary_scan_all_impl, secondary_find_impl, update_impl,
|
||||
};
|
||||
use crate::modules::error::code::ErrorCode;
|
||||
use crate::modules::hook::entity::EventHooks;
|
||||
use crate::modules::license::License;
|
||||
use crate::modules::oauth2::token::OAuth2AccessToken;
|
||||
use crate::modules::rest::response::DataPage;
|
||||
use crate::modules::smtp::template::entity::EmailTemplate;
|
||||
use crate::modules::token::AccessToken;
|
||||
use crate::raise_error;
|
||||
use crate::{
|
||||
modules::database::{insert_impl, list_all_impl},
|
||||
modules::error::RustMailerResult,
|
||||
utc_now,
|
||||
};
|
||||
use crate::modules::error::RustMailerResult;
|
||||
use crate::{encrypt, modules::account::since::DateSince};
|
||||
use native_db::*;
|
||||
use native_model::{native_model, Model};
|
||||
|
||||
use poem_openapi::{Enum, Object};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::error;
|
||||
use tracing::info;
|
||||
|
||||
use super::status::AccountRunningState;
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
|
||||
#[native_model(id = 5, version = 1)]
|
||||
@@ -109,302 +77,6 @@ impl Account {
|
||||
fn pk(&self) -> String {
|
||||
format!("{}_{}", self.created_at, self.id)
|
||||
}
|
||||
|
||||
pub fn create(request: AccountCreateRequest) -> RustMailerResult<Self> {
|
||||
Ok(Account {
|
||||
id: id!(64),
|
||||
email: request.email,
|
||||
name: request.name,
|
||||
imap: request.imap.try_encrypt_password()?,
|
||||
smtp: request.smtp.try_encrypt_password()?,
|
||||
enabled: request.enabled,
|
||||
minimal_sync: request.minimal_sync,
|
||||
capabilities: Vec::new(),
|
||||
//status: AccountStatus::Registered,
|
||||
//error_reason: None,
|
||||
date_since: request.date_since,
|
||||
dsn_capable: None,
|
||||
sync_folders: vec![],
|
||||
known_folders: BTreeSet::new(),
|
||||
full_sync_interval_min: request.full_sync_interval_min.unwrap_or(30),
|
||||
incremental_sync_interval_sec: request.incremental_sync_interval_sec.unwrap_or(60),
|
||||
created_at: utc_now!(),
|
||||
updated_at: utc_now!(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn check_account_active(account_id: u64) -> RustMailerResult<Account> {
|
||||
let account_entity =
|
||||
secondary_find_impl::<Account>(DB_MANAGER.meta_db(), AccountKey::id, account_id)
|
||||
.await?;
|
||||
match account_entity {
|
||||
Some(entity) if entity.enabled => Ok(entity),
|
||||
Some(_) => Err(raise_error!(
|
||||
format!("Account id='{account_id}' is disabled"),
|
||||
ErrorCode::AccountDisabled
|
||||
)),
|
||||
None => Err(raise_error!(
|
||||
format!("Account id='{account_id}' not found"),
|
||||
ErrorCode::ResourceNotFound
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetches an `AccountEntity` by its `id`.
|
||||
pub async fn get(account_id: u64) -> RustMailerResult<Account> {
|
||||
let result = Self::find(account_id).await?.ok_or_else(|| {
|
||||
raise_error!(
|
||||
format!("Account with ID '{account_id}' not found"),
|
||||
ErrorCode::ResourceNotFound
|
||||
)
|
||||
})?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn find(account_id: u64) -> RustMailerResult<Option<Account>> {
|
||||
secondary_find_impl::<Account>(DB_MANAGER.meta_db(), AccountKey::id, account_id).await
|
||||
}
|
||||
|
||||
/// Saves the current `AccountEntity` by persisting it to storage.
|
||||
pub async fn save(&self) -> RustMailerResult<()> {
|
||||
insert_impl(DB_MANAGER.meta_db(), self.to_owned()).await
|
||||
}
|
||||
|
||||
pub async fn create_account(request: AccountCreateRequest) -> RustMailerResult<Account> {
|
||||
// Validate license limits before creating entity
|
||||
if let Some(license) = License::get_current_license().await? {
|
||||
let current_count = Account::count().await?;
|
||||
if let Some(max_accounts) = license.max_accounts {
|
||||
if current_count >= max_accounts as usize {
|
||||
return Err(raise_error!(
|
||||
"Maximum account limit reached".into(),
|
||||
ErrorCode::LicenseAccountLimitReached
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
let entity = request.create_entity()?;
|
||||
entity.save().await?;
|
||||
SYNC_CONTROLLER
|
||||
.trigger_start(entity.id, entity.email.clone())
|
||||
.await;
|
||||
Ok(entity)
|
||||
}
|
||||
|
||||
pub async fn update(
|
||||
account_id: u64,
|
||||
request: AccountUpdateRequest,
|
||||
validate: bool,
|
||||
) -> RustMailerResult<()> {
|
||||
if validate {
|
||||
request.validate_update_request()?;
|
||||
}
|
||||
update_impl(DB_MANAGER.meta_db(), move |rw| {
|
||||
rw.get().secondary::<Account>(AccountKey::id, account_id).map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?.ok_or_else(|| raise_error!(format!(
|
||||
"Attempted to edit the account's base information, but the corresponding account metadata was not found. account_id={}",
|
||||
account_id
|
||||
), ErrorCode::ResourceNotFound))
|
||||
}, |current|{
|
||||
Self::apply_update_fields(current, request)
|
||||
}).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete(account_id: u64) -> RustMailerResult<()> {
|
||||
let request = AccountUpdateRequest {
|
||||
enabled: Some(false),
|
||||
..Default::default()
|
||||
};
|
||||
Self::update(account_id, request, false).await?;
|
||||
IMAP_TASKS.stop(account_id).await?;
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = Self::cleanup_account_resources_sequential(account_id).await {
|
||||
error!("Account cleanup failed for {}: {:?}", account_id, e);
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_account(account_id: u64) -> RustMailerResult<()> {
|
||||
delete_impl(DB_MANAGER.meta_db(), move|rw|{
|
||||
rw.get().secondary::<Account>(AccountKey::id, account_id).map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
|
||||
.ok_or_else(||raise_error!(format!("The account entity with id={account_id} that you want to delete was not found."), ErrorCode::ResourceNotFound))
|
||||
}).await
|
||||
}
|
||||
|
||||
async fn cleanup_account_resources_sequential(account_id: u64) -> RustMailerResult<()> {
|
||||
let account = Self::get(account_id).await?;
|
||||
EmailTemplate::remove_account_templates(account_id).await?;
|
||||
OAuth2AccessToken::try_delete(account_id).await?;
|
||||
EventHooks::try_delete(account_id).await?;
|
||||
AccessToken::cleanup_account(account_id).await?;
|
||||
MailBox::clean(account_id).await?;
|
||||
AccountRunningState::delete(account_id).await?;
|
||||
//INDEX_MANAGER.clean(&account).await?;
|
||||
EnvelopeFlagsManager::clean_account(account.id).await?;
|
||||
RUST_MAIL_CONTEXT.clean_account(account_id).await?;
|
||||
Self::delete_account(account_id).await?;
|
||||
info!("Sequential cleanup completed for account: {}", account_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_sync_folders(
|
||||
account_id: u64,
|
||||
sync_folders: Vec<String>,
|
||||
) -> RustMailerResult<()> {
|
||||
update_impl(DB_MANAGER.meta_db(), move |rw| {
|
||||
rw.get().secondary::<Account>(AccountKey::id, account_id).map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
|
||||
.ok_or_else(|| raise_error!(format!("When trying to update account sync_folders, the corresponding record was not found. account_id={}", account_id), ErrorCode::ResourceNotFound))
|
||||
}, |current|{
|
||||
let mut updated = current.clone();
|
||||
updated.sync_folders = sync_folders;
|
||||
Ok(updated)
|
||||
}).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_known_folders(
|
||||
account_id: u64,
|
||||
known_folders: BTreeSet<String>,
|
||||
) -> RustMailerResult<()> {
|
||||
update_impl(DB_MANAGER.meta_db(), move |rw| {
|
||||
rw.get().secondary::<Account>(AccountKey::id, account_id).map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
|
||||
.ok_or_else(|| raise_error!(format!("When trying to update account known_folders, the corresponding record was not found. account_id={}", account_id), ErrorCode::ResourceNotFound))
|
||||
}, |current|{
|
||||
let mut updated = current.clone();
|
||||
updated.known_folders = known_folders;
|
||||
Ok(updated)
|
||||
}).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
pub async fn update_capabilities(
|
||||
account_id: u64,
|
||||
capabilities: Vec<String>,
|
||||
) -> RustMailerResult<()> {
|
||||
update_impl(DB_MANAGER.meta_db(), move |rw| {
|
||||
rw.get().secondary::<Account>(AccountKey::id, account_id).map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
|
||||
.ok_or_else(|| raise_error!(format!("When trying to update account capabilities, the corresponding record was not found. account_id={}", account_id), ErrorCode::ResourceNotFound))
|
||||
}, |current|{
|
||||
let mut updated = current.clone();
|
||||
updated.capabilities = capabilities;
|
||||
Ok(updated)
|
||||
}).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_dsn_capable(account_id: u64, dsn: bool) -> RustMailerResult<()> {
|
||||
update_impl(
|
||||
DB_MANAGER.meta_db(),
|
||||
move |rw| {
|
||||
rw.get()
|
||||
.secondary::<Account>(AccountKey::id, account_id)
|
||||
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
|
||||
.ok_or_else(|| {
|
||||
raise_error!(format!(
|
||||
"When trying to update account dsn capabilities, the corresponding record was not found. account_id={}",
|
||||
account_id
|
||||
), ErrorCode::ResourceNotFound)
|
||||
})
|
||||
},
|
||||
move |current| {
|
||||
let mut updated = current.clone();
|
||||
updated.dsn_capable = Some(dsn);
|
||||
Ok(updated)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retrieves a list of all `AccountEntity` instances.
|
||||
pub async fn list_all() -> RustMailerResult<Vec<Account>> {
|
||||
list_all_impl(DB_MANAGER.meta_db()).await
|
||||
}
|
||||
|
||||
pub async fn minimal_list() -> RustMailerResult<Vec<MinimalAccount>> {
|
||||
let result = list_all_impl(DB_MANAGER.meta_db())
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|a: &Account| a.enabled)
|
||||
.map(|account: Account| MinimalAccount {
|
||||
id: account.id,
|
||||
email: account.email.clone(),
|
||||
})
|
||||
.collect::<Vec<MinimalAccount>>();
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn count() -> RustMailerResult<usize> {
|
||||
count_by_unique_secondary_key_impl::<Account>(DB_MANAGER.meta_db(), AccountKey::id).await
|
||||
}
|
||||
|
||||
pub async fn paginate_list(
|
||||
page: Option<u64>,
|
||||
page_size: Option<u64>,
|
||||
desc: Option<bool>,
|
||||
) -> RustMailerResult<DataPage<Account>> {
|
||||
paginate_query_primary_scan_all_impl(DB_MANAGER.meta_db(), page, page_size, desc)
|
||||
.await
|
||||
.map(DataPage::from)
|
||||
}
|
||||
|
||||
// This method applies the updates from the request to the old account entity
|
||||
fn apply_update_fields(
|
||||
old: &Account,
|
||||
request: AccountUpdateRequest,
|
||||
) -> RustMailerResult<Account> {
|
||||
let mut new = old.clone();
|
||||
|
||||
if let Some(date_since) = request.date_since {
|
||||
new.date_since = Some(date_since);
|
||||
}
|
||||
|
||||
if let Some(name) = &request.name {
|
||||
new.name = Some(name.clone());
|
||||
}
|
||||
|
||||
if let Some(imap) = &request.imap {
|
||||
new.imap.host = imap.host.clone();
|
||||
new.imap.port = imap.port.clone();
|
||||
new.imap.encryption = imap.encryption.clone();
|
||||
new.imap.auth.auth_type = imap.auth.auth_type.clone();
|
||||
if let Some(password) = &imap.auth.password {
|
||||
let encrypted_password = encrypt!(password)?;
|
||||
new.imap.auth.password = Some(encrypted_password);
|
||||
}
|
||||
new.imap.use_proxy = imap.use_proxy;
|
||||
}
|
||||
|
||||
if let Some(smtp) = &request.smtp {
|
||||
new.smtp.host = smtp.host.clone();
|
||||
new.smtp.port = smtp.port.clone();
|
||||
new.smtp.encryption = smtp.encryption.clone();
|
||||
new.smtp.auth.auth_type = smtp.auth.auth_type.clone();
|
||||
if let Some(password) = &smtp.auth.password {
|
||||
let encrypted_password = encrypt!(password)?;
|
||||
new.smtp.auth.password = Some(encrypted_password);
|
||||
}
|
||||
new.smtp.use_proxy = smtp.use_proxy;
|
||||
}
|
||||
|
||||
if let Some(mailboxes) = request.sync_folders {
|
||||
new.sync_folders = mailboxes;
|
||||
}
|
||||
if let Some(full_sync_interval_min) = &request.full_sync_interval_min {
|
||||
new.full_sync_interval_min = *full_sync_interval_min;
|
||||
}
|
||||
if let Some(incremental_sync_interval_sec) = &request.incremental_sync_interval_sec {
|
||||
new.incremental_sync_interval_sec = *incremental_sync_interval_sec;
|
||||
}
|
||||
if let Some(enabled) = request.enabled {
|
||||
new.enabled = enabled;
|
||||
}
|
||||
new.updated_at = utc_now!();
|
||||
Ok(new)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize, Object)]
|
||||
@@ -532,3 +204,13 @@ impl From<bool> for Encryption {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize, Enum)]
|
||||
pub enum MailerType {
|
||||
/// Use IMAP/SMTP protocol
|
||||
#[default]
|
||||
ImapSmtp,
|
||||
|
||||
/// Use Gmail API
|
||||
GmailApi,
|
||||
}
|
||||
|
||||
@@ -7,3 +7,4 @@ pub mod entity;
|
||||
pub mod payload;
|
||||
pub mod since;
|
||||
pub mod status;
|
||||
pub mod v2;
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use crate::modules::account::entity::{Account, ImapConfig, SmtpConfig};
|
||||
use crate::modules::account::entity::{ImapConfig, MailerType, SmtpConfig};
|
||||
use crate::modules::account::since::DateSince;
|
||||
use crate::modules::account::v2::AccountV2;
|
||||
use crate::modules::error::code::ErrorCode;
|
||||
use crate::modules::error::RustMailerResult;
|
||||
use crate::modules::token::AccountInfo;
|
||||
@@ -24,15 +25,17 @@ pub struct AccountCreateRequest {
|
||||
/// Display name for the account (optional)
|
||||
pub name: Option<String>,
|
||||
/// IMAP server configuration
|
||||
pub imap: ImapConfig,
|
||||
pub imap: Option<ImapConfig>,
|
||||
/// SMTP server configuration
|
||||
pub smtp: SmtpConfig,
|
||||
pub smtp: Option<SmtpConfig>,
|
||||
/// Represents the account activation status.
|
||||
///
|
||||
/// If this value is `false`, all account-related resources will be unavailable
|
||||
/// and any attempts to access them should return an error indicating the account
|
||||
/// is inactive.
|
||||
pub enabled: bool,
|
||||
/// Method used to access and manage emails.
|
||||
pub mailer_type: MailerType,
|
||||
/// Controls initial synchronization time range
|
||||
///
|
||||
/// When dealing with large mailboxes, this restricts scanning to:
|
||||
@@ -50,7 +53,7 @@ pub struct AccountCreateRequest {
|
||||
/// Recommended for:
|
||||
/// - Extremely resource-constrained environments
|
||||
/// - Accounts where only new message notification is needed
|
||||
pub minimal_sync: bool,
|
||||
pub minimal_sync: Option<bool>,
|
||||
/// Full sync interval (minutes), default 30m
|
||||
#[oai(validator(minimum(value = "10"), maximum(value = "10080")))]
|
||||
pub full_sync_interval_min: Option<i64>,
|
||||
@@ -60,13 +63,38 @@ pub struct AccountCreateRequest {
|
||||
}
|
||||
|
||||
impl AccountCreateRequest {
|
||||
pub fn create_entity(self) -> RustMailerResult<Account> {
|
||||
pub fn create_entity(self) -> RustMailerResult<AccountV2> {
|
||||
if let Some(date_since) = self.date_since.as_ref() {
|
||||
date_since.validate()?;
|
||||
}
|
||||
if matches!(self.mailer_type, MailerType::ImapSmtp) {
|
||||
if self.imap.is_none() || self.smtp.is_none() {
|
||||
return Err(raise_error!(
|
||||
"Invalid input: Both 'imap' and 'smtp' must be provided.".into(),
|
||||
ErrorCode::InvalidParameter
|
||||
));
|
||||
}
|
||||
Self::validate_request(
|
||||
&self.imap.clone().unwrap(),
|
||||
&self.smtp.clone().unwrap(),
|
||||
&self.email,
|
||||
)?;
|
||||
|
||||
Self::validate_request(&self.imap, &self.smtp, &self.email)?;
|
||||
Ok(Account::create(self)?)
|
||||
if self.full_sync_interval_min.is_none() {
|
||||
return Err(raise_error!(
|
||||
"Invalid input: 'full_sync_interval_min' must be provided.".into(),
|
||||
ErrorCode::InvalidParameter
|
||||
));
|
||||
}
|
||||
|
||||
if self.incremental_sync_interval_sec.is_none() {
|
||||
return Err(raise_error!(
|
||||
"Invalid input: 'incremental_sync_interval_sec' must be provided.".into(),
|
||||
ErrorCode::InvalidParameter
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(AccountV2::create(self)?)
|
||||
}
|
||||
|
||||
fn validate_request(imap: &ImapConfig, smtp: &SmtpConfig, email: &str) -> RustMailerResult<()> {
|
||||
|
||||
@@ -0,0 +1,474 @@
|
||||
// Copyright © 2025 rustmailer.com
|
||||
// Licensed under RustMailer License Agreement v1.0
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
use native_db::*;
|
||||
use native_model::{native_model, Model};
|
||||
|
||||
use poem_openapi::Object;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeSet;
|
||||
use tracing::{error, info};
|
||||
|
||||
use crate::{
|
||||
encrypt,
|
||||
modules::{
|
||||
account::{
|
||||
entity::{Account, ImapConfig, MailerType, SmtpConfig},
|
||||
since::DateSince,
|
||||
status::AccountRunningState,
|
||||
},
|
||||
database::{insert_impl, list_all_impl},
|
||||
error::RustMailerResult,
|
||||
},
|
||||
utc_now,
|
||||
};
|
||||
|
||||
use crate::id;
|
||||
use crate::modules::account::payload::AccountCreateRequest;
|
||||
use crate::modules::account::payload::AccountUpdateRequest;
|
||||
use crate::modules::account::payload::MinimalAccount;
|
||||
use crate::modules::cache::imap::mailbox::MailBox;
|
||||
use crate::modules::cache::imap::manager::EnvelopeFlagsManager;
|
||||
use crate::modules::cache::imap::task::IMAP_TASKS;
|
||||
use crate::modules::context::controller::SYNC_CONTROLLER;
|
||||
use crate::modules::context::executors::RUST_MAIL_CONTEXT;
|
||||
use crate::modules::database::count_by_unique_secondary_key_impl;
|
||||
use crate::modules::database::delete_impl;
|
||||
use crate::modules::database::manager::DB_MANAGER;
|
||||
use crate::modules::database::{
|
||||
paginate_query_primary_scan_all_impl, secondary_find_impl, update_impl,
|
||||
};
|
||||
use crate::modules::error::code::ErrorCode;
|
||||
use crate::modules::hook::entity::EventHooks;
|
||||
use crate::modules::license::License;
|
||||
use crate::modules::oauth2::token::OAuth2AccessToken;
|
||||
use crate::modules::rest::response::DataPage;
|
||||
use crate::modules::smtp::template::entity::EmailTemplate;
|
||||
use crate::modules::token::AccessToken;
|
||||
use crate::raise_error;
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
|
||||
#[native_model(id = 5, version = 2, from = Account)]
|
||||
#[native_db(primary_key(pk -> String))]
|
||||
pub struct AccountV2 {
|
||||
/// Unique account identifier
|
||||
#[secondary_key(unique)]
|
||||
pub id: u64,
|
||||
/// IMAP server configuration
|
||||
pub imap: Option<ImapConfig>,
|
||||
/// SMTP server configuration
|
||||
pub smtp: Option<SmtpConfig>,
|
||||
/// Represents the account activation status.
|
||||
///
|
||||
/// If this value is `false`, all account-related resources will be unavailable
|
||||
/// and any attempts to access them should return an error indicating the account
|
||||
/// is inactive.
|
||||
pub enabled: bool,
|
||||
/// Method used to access and manage emails.
|
||||
pub mailer_type: MailerType,
|
||||
/// Email address associated with this account
|
||||
#[oai(validator(custom = "crate::modules::common::validator::EmailValidator"))]
|
||||
pub email: String,
|
||||
/// Display name for the account (optional)
|
||||
pub name: Option<String>,
|
||||
/// Minimal sync mode flag
|
||||
///
|
||||
/// When enabled (`true`), only the most essential metadata will be synchronized:
|
||||
/// Recommended for:
|
||||
/// - Extremely resource-constrained environments
|
||||
/// - Accounts where only new message notification is needed
|
||||
pub minimal_sync: Option<bool>,
|
||||
/// IMAP Server-supported capability flags
|
||||
pub capabilities: Option<Vec<String>>,
|
||||
/// DSN (Delivery Status Notification) support flag
|
||||
pub dsn_capable: Option<bool>,
|
||||
/// Controls initial synchronization time range
|
||||
///
|
||||
/// When dealing with large mailboxes, this restricts scanning to:
|
||||
/// - Messages after specified starting point
|
||||
/// - Or within sliding window
|
||||
///
|
||||
/// ### Use Cases
|
||||
/// - Event-driven systems (only sync recent actionable emails)
|
||||
/// - First-time sync optimization for large accounts
|
||||
/// - Reducing server load during resyncs
|
||||
pub date_since: Option<DateSince>,
|
||||
/// Configuration for selective folder synchronization
|
||||
///
|
||||
/// Defaults to standard folders (`INBOX`, `Sent`) if empty.
|
||||
/// Modified folders will be automatically synced on next update.
|
||||
pub sync_folders: Vec<String>,
|
||||
/// Full sync interval (minutes), default 30m
|
||||
pub full_sync_interval_min: Option<i64>,
|
||||
/// Incremental sync interval (seconds), default 60s
|
||||
pub incremental_sync_interval_sec: i64,
|
||||
/// Tracks known mail folders and detects changes (creations/deletions)
|
||||
pub known_folders: BTreeSet<String>,
|
||||
/// Creation timestamp (UNIX epoch milliseconds)
|
||||
pub created_at: i64,
|
||||
/// Last update timestamp (UNIX epoch milliseconds)
|
||||
pub updated_at: i64,
|
||||
}
|
||||
|
||||
impl AccountV2 {
|
||||
fn pk(&self) -> String {
|
||||
format!("{}_{}", self.created_at, self.id)
|
||||
}
|
||||
|
||||
pub fn minimal_sync(&self) -> bool {
|
||||
self.minimal_sync.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn create(request: AccountCreateRequest) -> RustMailerResult<Self> {
|
||||
Ok(Self {
|
||||
id: id!(64),
|
||||
email: request.email,
|
||||
name: request.name,
|
||||
imap: request
|
||||
.imap
|
||||
.map(|imap| imap.try_encrypt_password())
|
||||
.transpose()?,
|
||||
smtp: request
|
||||
.smtp
|
||||
.map(|smtp| smtp.try_encrypt_password())
|
||||
.transpose()?,
|
||||
enabled: request.enabled,
|
||||
mailer_type: request.mailer_type,
|
||||
minimal_sync: request.minimal_sync,
|
||||
capabilities: None,
|
||||
date_since: request.date_since,
|
||||
dsn_capable: None,
|
||||
sync_folders: vec![],
|
||||
known_folders: BTreeSet::new(),
|
||||
full_sync_interval_min: request.full_sync_interval_min,
|
||||
incremental_sync_interval_sec: request.incremental_sync_interval_sec.unwrap_or(60),
|
||||
created_at: utc_now!(),
|
||||
updated_at: utc_now!(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn check_account_active(account_id: u64) -> RustMailerResult<AccountV2> {
|
||||
let account_entity =
|
||||
secondary_find_impl::<AccountV2>(DB_MANAGER.meta_db(), AccountV2Key::id, account_id)
|
||||
.await?;
|
||||
match account_entity {
|
||||
Some(entity) if entity.enabled => Ok(entity),
|
||||
Some(_) => Err(raise_error!(
|
||||
format!("Account id='{account_id}' is disabled"),
|
||||
ErrorCode::AccountDisabled
|
||||
)),
|
||||
None => Err(raise_error!(
|
||||
format!("Account id='{account_id}' not found"),
|
||||
ErrorCode::ResourceNotFound
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetches an `AccountEntity` by its `id`.
|
||||
pub async fn get(account_id: u64) -> RustMailerResult<AccountV2> {
|
||||
let result = Self::find(account_id).await?.ok_or_else(|| {
|
||||
raise_error!(
|
||||
format!("Account with ID '{account_id}' not found"),
|
||||
ErrorCode::ResourceNotFound
|
||||
)
|
||||
})?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn find(account_id: u64) -> RustMailerResult<Option<AccountV2>> {
|
||||
secondary_find_impl::<AccountV2>(DB_MANAGER.meta_db(), AccountV2Key::id, account_id).await
|
||||
}
|
||||
|
||||
/// Saves the current `AccountEntity` by persisting it to storage.
|
||||
pub async fn save(&self) -> RustMailerResult<()> {
|
||||
insert_impl(DB_MANAGER.meta_db(), self.to_owned()).await
|
||||
}
|
||||
|
||||
pub async fn create_account(request: AccountCreateRequest) -> RustMailerResult<AccountV2> {
|
||||
// Validate license limits before creating entity
|
||||
if let Some(license) = License::get_current_license().await? {
|
||||
let current_count = AccountV2::count().await?;
|
||||
if let Some(max_accounts) = license.max_accounts {
|
||||
if current_count >= max_accounts as usize {
|
||||
return Err(raise_error!(
|
||||
"Maximum account limit reached".into(),
|
||||
ErrorCode::LicenseAccountLimitReached
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
let entity = request.create_entity()?;
|
||||
entity.save().await?;
|
||||
SYNC_CONTROLLER
|
||||
.trigger_start(entity.id, entity.email.clone())
|
||||
.await;
|
||||
Ok(entity)
|
||||
}
|
||||
|
||||
pub async fn update(
|
||||
account_id: u64,
|
||||
request: AccountUpdateRequest,
|
||||
validate: bool,
|
||||
) -> RustMailerResult<()> {
|
||||
if validate {
|
||||
request.validate_update_request()?;
|
||||
}
|
||||
update_impl(DB_MANAGER.meta_db(), move |rw| {
|
||||
rw.get().secondary::<AccountV2>(AccountV2Key::id, account_id).map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?.ok_or_else(|| raise_error!(format!(
|
||||
"Attempted to edit the account's base information, but the corresponding account metadata was not found. account_id={}",
|
||||
account_id
|
||||
), ErrorCode::ResourceNotFound))
|
||||
}, |current|{
|
||||
Self::apply_update_fields(current, request)
|
||||
}).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete(account_id: u64) -> RustMailerResult<()> {
|
||||
let request = AccountUpdateRequest {
|
||||
enabled: Some(false),
|
||||
..Default::default()
|
||||
};
|
||||
Self::update(account_id, request, false).await?;
|
||||
IMAP_TASKS.stop(account_id).await?;
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = Self::cleanup_account_resources_sequential(account_id).await {
|
||||
error!("Account cleanup failed for {}: {:?}", account_id, e);
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_account(account_id: u64) -> RustMailerResult<()> {
|
||||
delete_impl(DB_MANAGER.meta_db(), move|rw|{
|
||||
rw.get().secondary::<AccountV2>(AccountV2Key::id, account_id).map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
|
||||
.ok_or_else(||raise_error!(format!("The account entity with id={account_id} that you want to delete was not found."), ErrorCode::ResourceNotFound))
|
||||
}).await
|
||||
}
|
||||
|
||||
async fn cleanup_account_resources_sequential(account_id: u64) -> RustMailerResult<()> {
|
||||
let account = Self::get(account_id).await?;
|
||||
EmailTemplate::remove_account_templates(account_id).await?;
|
||||
OAuth2AccessToken::try_delete(account_id).await?;
|
||||
EventHooks::try_delete(account_id).await?;
|
||||
AccessToken::cleanup_account(account_id).await?;
|
||||
MailBox::clean(account_id).await?;
|
||||
AccountRunningState::delete(account_id).await?;
|
||||
EnvelopeFlagsManager::clean_account(account.id).await?;
|
||||
RUST_MAIL_CONTEXT.clean_account(account_id).await?;
|
||||
Self::delete_account(account_id).await?;
|
||||
info!("Sequential cleanup completed for account: {}", account_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_sync_folders(
|
||||
account_id: u64,
|
||||
sync_folders: Vec<String>,
|
||||
) -> RustMailerResult<()> {
|
||||
update_impl(DB_MANAGER.meta_db(), move |rw| {
|
||||
rw.get().secondary::<AccountV2>(AccountV2Key::id, account_id).map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
|
||||
.ok_or_else(|| raise_error!(format!("When trying to update account sync_folders, the corresponding record was not found. account_id={}", account_id), ErrorCode::ResourceNotFound))
|
||||
}, |current|{
|
||||
let mut updated = current.clone();
|
||||
updated.sync_folders = sync_folders;
|
||||
Ok(updated)
|
||||
}).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_known_folders(
|
||||
account_id: u64,
|
||||
known_folders: BTreeSet<String>,
|
||||
) -> RustMailerResult<()> {
|
||||
update_impl(DB_MANAGER.meta_db(), move |rw| {
|
||||
rw.get().secondary::<AccountV2>(AccountV2Key::id, account_id).map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
|
||||
.ok_or_else(|| raise_error!(format!("When trying to update account known_folders, the corresponding record was not found. account_id={}", account_id), ErrorCode::ResourceNotFound))
|
||||
}, |current|{
|
||||
let mut updated = current.clone();
|
||||
updated.known_folders = known_folders;
|
||||
Ok(updated)
|
||||
}).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_capabilities(
|
||||
account_id: u64,
|
||||
capabilities: Vec<String>,
|
||||
) -> RustMailerResult<()> {
|
||||
update_impl(DB_MANAGER.meta_db(), move |rw| {
|
||||
rw.get().secondary::<AccountV2>(AccountV2Key::id, account_id).map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
|
||||
.ok_or_else(|| raise_error!(format!("When trying to update account capabilities, the corresponding record was not found. account_id={}", account_id), ErrorCode::ResourceNotFound))
|
||||
}, |current|{
|
||||
let mut updated = current.clone();
|
||||
updated.capabilities = Some(capabilities);
|
||||
Ok(updated)
|
||||
}).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_dsn_capable(account_id: u64, dsn: bool) -> RustMailerResult<()> {
|
||||
update_impl(
|
||||
DB_MANAGER.meta_db(),
|
||||
move |rw| {
|
||||
rw.get()
|
||||
.secondary::<AccountV2>(AccountV2Key::id, account_id)
|
||||
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
|
||||
.ok_or_else(|| {
|
||||
raise_error!(format!(
|
||||
"When trying to update account dsn capabilities, the corresponding record was not found. account_id={}",
|
||||
account_id
|
||||
), ErrorCode::ResourceNotFound)
|
||||
})
|
||||
},
|
||||
move |current| {
|
||||
let mut updated = current.clone();
|
||||
updated.dsn_capable = Some(dsn);
|
||||
Ok(updated)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retrieves a list of all `AccountEntity` instances.
|
||||
pub async fn list_all() -> RustMailerResult<Vec<AccountV2>> {
|
||||
list_all_impl(DB_MANAGER.meta_db()).await
|
||||
}
|
||||
|
||||
pub async fn minimal_list() -> RustMailerResult<Vec<MinimalAccount>> {
|
||||
let result = list_all_impl(DB_MANAGER.meta_db())
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|a: &AccountV2| a.enabled)
|
||||
.map(|account: AccountV2| MinimalAccount {
|
||||
id: account.id,
|
||||
email: account.email.clone(),
|
||||
})
|
||||
.collect::<Vec<MinimalAccount>>();
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn count() -> RustMailerResult<usize> {
|
||||
count_by_unique_secondary_key_impl::<AccountV2>(DB_MANAGER.meta_db(), AccountV2Key::id)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn paginate_list(
|
||||
page: Option<u64>,
|
||||
page_size: Option<u64>,
|
||||
desc: Option<bool>,
|
||||
) -> RustMailerResult<DataPage<AccountV2>> {
|
||||
paginate_query_primary_scan_all_impl(DB_MANAGER.meta_db(), page, page_size, desc)
|
||||
.await
|
||||
.map(DataPage::from)
|
||||
}
|
||||
|
||||
// This method applies the updates from the request to the old account entity
|
||||
fn apply_update_fields(
|
||||
old: &AccountV2,
|
||||
request: AccountUpdateRequest,
|
||||
) -> RustMailerResult<AccountV2> {
|
||||
let mut new = old.clone();
|
||||
|
||||
if let Some(date_since) = request.date_since {
|
||||
new.date_since = Some(date_since);
|
||||
}
|
||||
|
||||
if let Some(name) = &request.name {
|
||||
new.name = Some(name.clone());
|
||||
}
|
||||
|
||||
if let Some(imap) = &request.imap {
|
||||
if let Some(current_imap) = &mut new.imap {
|
||||
current_imap.host = imap.host.clone();
|
||||
current_imap.port = imap.port.clone();
|
||||
current_imap.encryption = imap.encryption.clone();
|
||||
current_imap.auth.auth_type = imap.auth.auth_type.clone();
|
||||
if let Some(password) = &imap.auth.password {
|
||||
let encrypted_password = encrypt!(password)?;
|
||||
current_imap.auth.password = Some(encrypted_password);
|
||||
}
|
||||
current_imap.use_proxy = imap.use_proxy;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(smtp) = &request.smtp {
|
||||
if let Some(current_smtp) = &mut new.smtp {
|
||||
current_smtp.host = smtp.host.clone();
|
||||
current_smtp.port = smtp.port.clone();
|
||||
current_smtp.encryption = smtp.encryption.clone();
|
||||
current_smtp.auth.auth_type = smtp.auth.auth_type.clone();
|
||||
if let Some(password) = &smtp.auth.password {
|
||||
let encrypted_password = encrypt!(password)?;
|
||||
current_smtp.auth.password = Some(encrypted_password);
|
||||
}
|
||||
current_smtp.use_proxy = smtp.use_proxy;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mailboxes) = request.sync_folders {
|
||||
new.sync_folders = mailboxes;
|
||||
}
|
||||
if let Some(full_sync_interval_min) = &request.full_sync_interval_min {
|
||||
new.full_sync_interval_min = Some(*full_sync_interval_min);
|
||||
}
|
||||
if let Some(incremental_sync_interval_sec) = &request.incremental_sync_interval_sec {
|
||||
new.incremental_sync_interval_sec = *incremental_sync_interval_sec;
|
||||
}
|
||||
if let Some(enabled) = request.enabled {
|
||||
new.enabled = enabled;
|
||||
}
|
||||
new.updated_at = utc_now!();
|
||||
Ok(new)
|
||||
}
|
||||
}
|
||||
|
||||
// Will never be used
|
||||
impl From<AccountV2> for Account {
|
||||
fn from(value: AccountV2) -> Self {
|
||||
Self {
|
||||
id: value.id,
|
||||
imap: value.imap.unwrap(),
|
||||
smtp: value.smtp.unwrap(),
|
||||
enabled: value.enabled,
|
||||
email: value.email,
|
||||
name: value.name,
|
||||
minimal_sync: value.minimal_sync.unwrap(),
|
||||
capabilities: value.capabilities.unwrap(),
|
||||
dsn_capable: value.dsn_capable,
|
||||
date_since: value.date_since,
|
||||
sync_folders: value.sync_folders,
|
||||
full_sync_interval_min: value.full_sync_interval_min.unwrap(),
|
||||
incremental_sync_interval_sec: value.incremental_sync_interval_sec,
|
||||
known_folders: value.known_folders,
|
||||
created_at: value.created_at,
|
||||
updated_at: value.updated_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Account> for AccountV2 {
|
||||
fn from(value: Account) -> Self {
|
||||
Self {
|
||||
id: value.id,
|
||||
imap: Some(value.imap),
|
||||
smtp: Some(value.smtp),
|
||||
enabled: value.enabled,
|
||||
mailer_type: MailerType::ImapSmtp,
|
||||
email: value.email,
|
||||
name: value.name,
|
||||
minimal_sync: Some(value.minimal_sync),
|
||||
capabilities: Some(value.capabilities),
|
||||
dsn_capable: value.dsn_capable,
|
||||
date_since: value.date_since,
|
||||
sync_folders: value.sync_folders,
|
||||
full_sync_interval_min: Some(value.full_sync_interval_min),
|
||||
incremental_sync_interval_sec: value.incremental_sync_interval_sec,
|
||||
known_folders: value.known_folders,
|
||||
created_at: value.created_at,
|
||||
updated_at: value.updated_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+1
-1
@@ -14,7 +14,7 @@ use tracing::info;
|
||||
use crate::{
|
||||
id,
|
||||
modules::{
|
||||
cache::imap::envelope_v2::EmailEnvelopeV2,
|
||||
cache::imap::v2::EmailEnvelopeV2,
|
||||
database::{batch_delete_impl, filter_by_secondary_key_impl, manager::DB_MANAGER},
|
||||
error::{code::ErrorCode, RustMailerResult},
|
||||
utils::envelope_hash,
|
||||
|
||||
Vendored
+6
-6
@@ -9,13 +9,13 @@ use std::collections::HashSet;
|
||||
use std::sync::LazyLock;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::modules::account::entity::Account;
|
||||
use crate::modules::account::v2::AccountV2;
|
||||
use crate::modules::cache::imap::address::AddressEntity;
|
||||
use crate::modules::cache::imap::envelope_v2::EmailEnvelopeV2;
|
||||
use crate::modules::cache::imap::flags_to_hash;
|
||||
use crate::modules::cache::imap::mailbox::EnvelopeFlag;
|
||||
use crate::modules::cache::imap::minimal::MinimalEnvelope;
|
||||
use crate::modules::cache::imap::thread::EmailThread;
|
||||
use crate::modules::cache::imap::v2::EmailEnvelopeV2;
|
||||
use crate::modules::context::Initialize;
|
||||
use crate::modules::error::RustMailerResult;
|
||||
use crate::modules::hook::channel::{Event, EVENT_CHANNEL};
|
||||
@@ -36,7 +36,7 @@ pub struct EnvelopeFlagsManager;
|
||||
|
||||
impl EnvelopeFlagsManager {
|
||||
pub async fn load_state() -> RustMailerResult<()> {
|
||||
let all_accounts = Account::list_all().await?;
|
||||
let all_accounts = AccountV2::list_all().await?;
|
||||
|
||||
stream::iter(all_accounts)
|
||||
.filter(|account| futures::future::ready(account.enabled))
|
||||
@@ -132,13 +132,13 @@ impl EnvelopeFlagsManager {
|
||||
}
|
||||
|
||||
pub async fn update_envelope_flags(
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
mailbox_id: u64,
|
||||
data: Vec<(u32, Vec<EnvelopeFlag>)>,
|
||||
) -> RustMailerResult<()> {
|
||||
RUSTMAILER_MAIL_FLAG_CHANGE_TOTAL.inc_by(data.len() as u64);
|
||||
for (uid, flags) in data {
|
||||
if !account.minimal_sync
|
||||
if !account.minimal_sync()
|
||||
&& EventHookTask::event_watched(account.id, EventType::EmailFlagsChanged).await?
|
||||
{
|
||||
if let Some(current) = EmailEnvelopeV2::find(account.id, mailbox_id, uid).await? {
|
||||
@@ -170,7 +170,7 @@ impl EnvelopeFlagsManager {
|
||||
}
|
||||
|
||||
let flags_hash = flags_to_hash(&flags);
|
||||
if !account.minimal_sync {
|
||||
if !account.minimal_sync() {
|
||||
EmailEnvelopeV2::update_flags(account.id, mailbox_id, uid, &flags, flags_hash)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -11,7 +11,7 @@ use tracing::{error, info};
|
||||
|
||||
use crate::{
|
||||
modules::{
|
||||
cache::imap::{envelope_v2::EmailEnvelopeV2, manager::EnvelopeFlagsManager},
|
||||
cache::imap::{v2::EmailEnvelopeV2, manager::EnvelopeFlagsManager},
|
||||
database::{
|
||||
batch_delete_impl, batch_insert_impl, delete_impl, filter_by_secondary_key_impl,
|
||||
manager::DB_MANAGER, update_impl,
|
||||
|
||||
Vendored
+3
-3
@@ -8,8 +8,8 @@ use crate::{
|
||||
calculate_hash,
|
||||
modules::{
|
||||
cache::imap::{
|
||||
address::AddressEntity, envelope::EmailEnvelope, envelope_v2::EmailEnvelopeV2,
|
||||
minimal::MinimalEnvelope, thread::EmailThread,
|
||||
address::AddressEntity, envelope::EmailEnvelope, minimal::MinimalEnvelope,
|
||||
thread::EmailThread, v2::EmailEnvelopeV2,
|
||||
},
|
||||
database::ModelsAdapter,
|
||||
},
|
||||
@@ -19,7 +19,6 @@ use mailbox::{EmailFlag, EnvelopeFlag, MailBox};
|
||||
use native_db::Models;
|
||||
pub mod address;
|
||||
pub mod envelope;
|
||||
pub mod envelope_v2;
|
||||
pub mod mailbox;
|
||||
pub mod manager;
|
||||
pub mod minimal;
|
||||
@@ -28,6 +27,7 @@ pub mod task;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
pub mod thread;
|
||||
pub mod v2;
|
||||
|
||||
pub static ENVELOPE_MODELS: LazyLock<Models> = LazyLock::new(|| {
|
||||
let mut adapter = ModelsAdapter::new();
|
||||
|
||||
Vendored
+22
-23
@@ -4,12 +4,10 @@
|
||||
|
||||
use crate::{
|
||||
modules::{
|
||||
account::{entity::Account, since::DateSince, status::AccountRunningState},
|
||||
account::{since::DateSince, status::AccountRunningState, v2::AccountV2},
|
||||
bounce::parser::{extract_bounce_report, BounceReport},
|
||||
cache::imap::{
|
||||
diff,
|
||||
envelope_v2::EmailEnvelopeV2,
|
||||
find_deleted_mailboxes, find_flag_updates, find_intersecting_mailboxes,
|
||||
diff, find_deleted_mailboxes, find_flag_updates, find_intersecting_mailboxes,
|
||||
find_missing_mailboxes, find_missing_remote_uids,
|
||||
mailbox::{EnvelopeFlag, MailBox},
|
||||
manager::EnvelopeFlagsManager,
|
||||
@@ -18,6 +16,7 @@ use crate::{
|
||||
rebuild::{rebuild_mailbox_cache, rebuild_mailbox_cache_since_date},
|
||||
sync_type::SyncType,
|
||||
},
|
||||
v2::EmailEnvelopeV2,
|
||||
},
|
||||
common::AddrVec,
|
||||
context::executors::RUST_MAIL_CONTEXT,
|
||||
@@ -138,7 +137,7 @@ pub async fn fetch_and_save_since_date(
|
||||
}
|
||||
|
||||
pub async fn fetch_and_save_full_mailbox(
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
mailbox: &MailBox,
|
||||
total: u32,
|
||||
initial: bool,
|
||||
@@ -148,7 +147,7 @@ pub async fn fetch_and_save_full_mailbox(
|
||||
let mut inserted_count = 0;
|
||||
|
||||
let account_id = account.id;
|
||||
let minimal_sync = account.minimal_sync;
|
||||
let minimal_sync = account.minimal_sync();
|
||||
|
||||
if initial {
|
||||
AccountRunningState::set_initial_current_syncing_folder(
|
||||
@@ -298,7 +297,7 @@ pub fn compress_uid_list(nums: Vec<u32>) -> String {
|
||||
}
|
||||
|
||||
pub async fn compare_and_sync_mailbox(
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
remote_mailboxes: &[MailBox],
|
||||
local_mailboxes: &[MailBox],
|
||||
sync_type: &SyncType,
|
||||
@@ -407,7 +406,7 @@ pub async fn compare_and_sync_mailbox(
|
||||
}
|
||||
|
||||
async fn cleanup_deleted_mailboxes(
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
deleted_mailboxes: &[MailBox],
|
||||
) -> RustMailerResult<()> {
|
||||
let start_time = Instant::now();
|
||||
@@ -424,7 +423,7 @@ async fn cleanup_deleted_mailboxes(
|
||||
}
|
||||
/// Sync recent 200 envelopes's flags
|
||||
async fn sync_recent_envelope_flags(
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
local_mailbox: &MailBox,
|
||||
remote_mailbox: &MailBox,
|
||||
uid_next: u32,
|
||||
@@ -457,7 +456,7 @@ async fn sync_recent_envelope_flags(
|
||||
|
||||
//only check new emails and sync
|
||||
async fn perform_incremental_sync(
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
local_mailbox: &MailBox,
|
||||
remote_mailbox: &MailBox,
|
||||
sync_count: usize,
|
||||
@@ -491,10 +490,10 @@ async fn perform_incremental_sync(
|
||||
.fetch_uid_list(
|
||||
max_uid + 1,
|
||||
&remote_mailbox.encoded_name(),
|
||||
account.minimal_sync,
|
||||
account.minimal_sync(),
|
||||
)
|
||||
.await?;
|
||||
let uid_list = parse_fetch_metadata(fetches, !account.minimal_sync)?;
|
||||
let uid_list = parse_fetch_metadata(fetches, !account.minimal_sync())?;
|
||||
|
||||
// This is just a precaution in case the server's behavior deviates from expectations.
|
||||
let uid_list = uid_list
|
||||
@@ -522,7 +521,7 @@ async fn perform_incremental_sync(
|
||||
date_since.since_date()?.as_str(),
|
||||
remote_mailbox,
|
||||
false,
|
||||
account.minimal_sync,
|
||||
account.minimal_sync(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@@ -544,7 +543,7 @@ async fn perform_incremental_sync(
|
||||
}
|
||||
//
|
||||
async fn perform_full_sync(
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
local_mailbox: &MailBox,
|
||||
remote_mailbox: &MailBox,
|
||||
) -> RustMailerResult<()> {
|
||||
@@ -666,7 +665,7 @@ async fn diff_uids_and_flags(
|
||||
}
|
||||
|
||||
async fn cleanup_missing_remote_emails(
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
mailbox_id: u64,
|
||||
mailbox_name: &str,
|
||||
local_uid_flags_index: &AHashMap<u32, u64>,
|
||||
@@ -687,7 +686,7 @@ async fn cleanup_missing_remote_emails(
|
||||
}
|
||||
|
||||
pub async fn fetch_and_store_new_envelopes_by_uid_list(
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
local_mailbox_id: u64,
|
||||
remote: &MailBox,
|
||||
uid_list: Vec<(u32, u64)>,
|
||||
@@ -718,7 +717,7 @@ pub async fn fetch_and_store_new_envelopes_by_uid_list(
|
||||
info!("Account {}: Mailbox '{}' has {} new message UID(s) to fetch metadata. Starting download...", account.id, &remote.name, len);
|
||||
|
||||
// Process minimal sync case first
|
||||
if account.minimal_sync {
|
||||
if account.minimal_sync() {
|
||||
let envelopes: Vec<MinimalEnvelope> = uid_list
|
||||
.clone()
|
||||
.into_iter()
|
||||
@@ -768,7 +767,7 @@ pub async fn fetch_and_store_new_envelopes_by_uid_list(
|
||||
}
|
||||
|
||||
async fn process_email_added_events(
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
remote: &MailBox,
|
||||
fetches: &[Fetch],
|
||||
) -> RustMailerResult<()> {
|
||||
@@ -834,13 +833,13 @@ async fn process_email_added_events(
|
||||
}
|
||||
|
||||
async fn handle_minimal_sync_or_metadata_fetch(
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
local_mailbox_id: u64,
|
||||
remote: &MailBox,
|
||||
uid_list: Vec<(u32, u64)>,
|
||||
len: usize,
|
||||
) -> RustMailerResult<()> {
|
||||
if account.minimal_sync {
|
||||
if account.minimal_sync() {
|
||||
let envelopes: Vec<MinimalEnvelope> = uid_list
|
||||
.into_iter()
|
||||
.map(|(uid, flags_hash)| MinimalEnvelope {
|
||||
@@ -877,7 +876,7 @@ async fn handle_minimal_sync_or_metadata_fetch(
|
||||
}
|
||||
|
||||
async fn process_bounce_reports(
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
remote: &MailBox,
|
||||
fetches: &[Fetch],
|
||||
) -> RustMailerResult<()> {
|
||||
@@ -925,7 +924,7 @@ async fn process_bounce_reports(
|
||||
}
|
||||
|
||||
async fn submit_bounce_event(
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
remote: &MailBox,
|
||||
uid: u32,
|
||||
fetch: &Fetch,
|
||||
@@ -960,7 +959,7 @@ async fn submit_bounce_event(
|
||||
}
|
||||
|
||||
async fn submit_feedback_report_event(
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
remote: &MailBox,
|
||||
uid: u32,
|
||||
fetch: &Fetch,
|
||||
|
||||
Vendored
+2
-2
@@ -3,7 +3,7 @@
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
use crate::modules::{
|
||||
account::{entity::Account, status::AccountRunningState},
|
||||
account::{status::AccountRunningState, v2::AccountV2},
|
||||
cache::imap::{mailbox::MailBox, manager::EnvelopeFlagsManager},
|
||||
error::RustMailerResult,
|
||||
hook::{
|
||||
@@ -29,7 +29,7 @@ pub mod sync_type;
|
||||
|
||||
static SYNC_COUNTER: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
pub async fn execute_account_sync(account: &Account) -> RustMailerResult<()> {
|
||||
pub async fn execute_account_sync(account: &AccountV2) -> RustMailerResult<()> {
|
||||
let start_time = Instant::now();
|
||||
let account_id = account.id;
|
||||
|
||||
|
||||
+8
-16
@@ -3,7 +3,7 @@
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
use crate::modules::{
|
||||
account::{entity::Account, since::DateSince},
|
||||
account::{since::DateSince, v2::AccountV2},
|
||||
cache::imap::{
|
||||
mailbox::MailBox,
|
||||
manager::EnvelopeFlagsManager,
|
||||
@@ -15,7 +15,7 @@ use std::time::Instant;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
pub async fn rebuild_cache(
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
remote_mailboxes: &[MailBox],
|
||||
) -> RustMailerResult<()> {
|
||||
let start_time = Instant::now();
|
||||
@@ -62,7 +62,7 @@ pub async fn rebuild_cache(
|
||||
}
|
||||
|
||||
pub async fn rebuild_cache_since_date(
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
remote_mailboxes: &[MailBox],
|
||||
date_since: &DateSince,
|
||||
) -> RustMailerResult<()> {
|
||||
@@ -80,21 +80,13 @@ pub async fn rebuild_cache_since_date(
|
||||
);
|
||||
continue;
|
||||
}
|
||||
// total_inserted += fetch_and_save_since_date(
|
||||
// account.id,
|
||||
// date.as_str(),
|
||||
// mailbox,
|
||||
// true,
|
||||
// account.minimal_sync,
|
||||
// )
|
||||
// .await?;
|
||||
|
||||
match fetch_and_save_since_date(
|
||||
account.id,
|
||||
date.as_str(),
|
||||
mailbox,
|
||||
true,
|
||||
account.minimal_sync,
|
||||
account.minimal_sync(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -126,7 +118,7 @@ pub async fn rebuild_cache_since_date(
|
||||
}
|
||||
|
||||
pub async fn should_rebuild_cache(
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
mailbox_count: usize,
|
||||
local_envelope_count: usize,
|
||||
) -> RustMailerResult<bool> {
|
||||
@@ -147,7 +139,7 @@ pub async fn should_rebuild_cache(
|
||||
}
|
||||
|
||||
pub async fn rebuild_mailbox_cache(
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
local_mailbox: &MailBox,
|
||||
remote_mailbox: &MailBox,
|
||||
) -> RustMailerResult<()> {
|
||||
@@ -171,7 +163,7 @@ pub async fn rebuild_mailbox_cache(
|
||||
}
|
||||
|
||||
pub async fn rebuild_mailbox_cache_since_date(
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
local_mailbox_id: u64,
|
||||
date_since: &DateSince,
|
||||
remote: &MailBox,
|
||||
@@ -191,7 +183,7 @@ pub async fn rebuild_mailbox_cache_since_date(
|
||||
date_since.since_date()?.as_str(),
|
||||
remote,
|
||||
false,
|
||||
account.minimal_sync,
|
||||
account.minimal_sync(),
|
||||
)
|
||||
.await?;
|
||||
info!(
|
||||
|
||||
+8
-8
@@ -7,7 +7,7 @@ use std::collections::BTreeSet;
|
||||
use crate::{
|
||||
decode_mailbox_name,
|
||||
modules::{
|
||||
account::entity::Account,
|
||||
account::v2::AccountV2,
|
||||
cache::imap::mailbox::{AttributeEnum, MailBox},
|
||||
context::executors::RUST_MAIL_CONTEXT,
|
||||
error::{code::ErrorCode, RustMailerResult},
|
||||
@@ -26,7 +26,7 @@ use crate::{
|
||||
use async_imap::types::Name;
|
||||
use tracing::{info, warn};
|
||||
|
||||
pub async fn get_sync_folders(account: &Account) -> RustMailerResult<Vec<MailBox>> {
|
||||
pub async fn get_sync_folders(account: &AccountV2) -> RustMailerResult<Vec<MailBox>> {
|
||||
let executor = RUST_MAIL_CONTEXT.imap(account.id).await?;
|
||||
let names = executor.list_all_mailboxes().await?;
|
||||
if names.is_empty() {
|
||||
@@ -45,7 +45,7 @@ pub async fn get_sync_folders(account: &Account) -> RustMailerResult<Vec<MailBox
|
||||
mailboxes.iter().map(|(m, _)| m.name.clone()).collect(),
|
||||
)
|
||||
.await?;
|
||||
let account = Account::get(account.id).await?;
|
||||
let account = AccountV2::get(account.id).await?;
|
||||
let subscribed = &account.sync_folders;
|
||||
let is_noselect = |mailbox: &MailBox| {
|
||||
mailbox
|
||||
@@ -83,7 +83,7 @@ pub async fn get_sync_folders(account: &Account) -> RustMailerResult<Vec<MailBox
|
||||
.iter()
|
||||
.map(|n| decode_mailbox_name!(n.name().to_string()))
|
||||
.collect();
|
||||
Account::update_sync_folders(account.id, sync_folders).await?;
|
||||
AccountV2::update_sync_folders(account.id, sync_folders).await?;
|
||||
} else {
|
||||
warn!(
|
||||
"Account {}: No subscribed mailboxes found. This is unexpected — IMAP server should at least provide INBOX.",
|
||||
@@ -99,12 +99,12 @@ pub async fn get_sync_folders(account: &Account) -> RustMailerResult<Vec<MailBox
|
||||
}
|
||||
|
||||
pub async fn detect_mailbox_changes(
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
all_names: BTreeSet<String>,
|
||||
) -> RustMailerResult<()> {
|
||||
if account.known_folders.is_empty() {
|
||||
// First time sync: just save without comparing
|
||||
Account::update_known_folders(account.id, all_names).await?;
|
||||
AccountV2::update_known_folders(account.id, all_names).await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ pub async fn detect_mailbox_changes(
|
||||
// Note: When all subscribed folders are deleted (remaining_sync_folders empty),
|
||||
// the system's default behavior is to automatically fall back to syncing
|
||||
// only the default folders (INBOX and Sent) in subsequent operations
|
||||
Account::update_sync_folders(account.id, remaining_sync_folders).await?;
|
||||
AccountV2::update_sync_folders(account.id, remaining_sync_folders).await?;
|
||||
}
|
||||
|
||||
info!(
|
||||
@@ -187,7 +187,7 @@ pub async fn detect_mailbox_changes(
|
||||
|
||||
// Update known folders only if there were changes
|
||||
if has_changes {
|
||||
Account::update_known_folders(account.id, all_names).await?;
|
||||
AccountV2::update_known_folders(account.id, all_names).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+8
-3
@@ -4,12 +4,15 @@
|
||||
|
||||
use crate::{
|
||||
modules::{
|
||||
account::{entity::Account, status::AccountRunningState},
|
||||
account::{status::AccountRunningState, v2::AccountV2},
|
||||
error::RustMailerResult,
|
||||
},
|
||||
utc_now,
|
||||
};
|
||||
|
||||
/// Default interval (in minutes) for full synchronization.
|
||||
pub const DEFAULT_FULL_SYNC_INTERVAL_MIN: i64 = 30;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum SyncType {
|
||||
/// Full synchronization, typically used for the first sync or after major changes.
|
||||
@@ -20,14 +23,16 @@ pub enum SyncType {
|
||||
SkipSync,
|
||||
}
|
||||
|
||||
pub async fn determine_sync_type(account: &Account) -> RustMailerResult<SyncType> {
|
||||
pub async fn determine_sync_type(account: &AccountV2) -> RustMailerResult<SyncType> {
|
||||
Ok(match AccountRunningState::get(account.id).await? {
|
||||
Some(info) => {
|
||||
let now = utc_now!();
|
||||
if is_time_for_full_sync(
|
||||
now,
|
||||
info.last_full_sync_start,
|
||||
account.full_sync_interval_min,
|
||||
account
|
||||
.full_sync_interval_min
|
||||
.unwrap_or(DEFAULT_FULL_SYNC_INTERVAL_MIN),
|
||||
) {
|
||||
AccountRunningState::set_full_sync_start(account.id).await?;
|
||||
SyncType::FullSync
|
||||
|
||||
Vendored
+11
-3
@@ -7,7 +7,7 @@ use crate::modules::cache::imap::sync::execute_account_sync;
|
||||
use crate::modules::oauth2::token::OAuth2AccessToken;
|
||||
use crate::modules::scheduler::periodic::TaskHandle;
|
||||
use crate::modules::{
|
||||
account::{dispatcher::STATUS_DISPATCHER, entity::Account},
|
||||
account::{dispatcher::STATUS_DISPATCHER, v2::AccountV2},
|
||||
error::RustMailerResult,
|
||||
scheduler::periodic::PeriodicTask,
|
||||
};
|
||||
@@ -40,7 +40,7 @@ impl AccountSyncTask {
|
||||
let task = move |param: Option<u64>| {
|
||||
let account_id = param.unwrap();
|
||||
Box::pin(async move {
|
||||
let account = Account::get(account_id).await.ok();
|
||||
let account = AccountV2::get(account_id).await.ok();
|
||||
match account {
|
||||
Some(account) => {
|
||||
if !account.enabled {
|
||||
@@ -54,7 +54,15 @@ impl AccountSyncTask {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if let AuthType::OAuth2 = account.imap.auth.auth_type {
|
||||
if let AuthType::OAuth2 = account
|
||||
.imap
|
||||
.as_ref()
|
||||
.expect(
|
||||
"BUG: account.imap is None, but this should never happen here",
|
||||
)
|
||||
.auth
|
||||
.auth_type
|
||||
{
|
||||
if OAuth2AccessToken::get(account.id).await?.is_none() {
|
||||
if utc_now!() % 300_000 == 0 {
|
||||
warn!("Account {}: Sync aborted. OAuth2 authorization not completed. Please visit the rustmailer admin page to authorize this account.", account_id);
|
||||
|
||||
Vendored
+1
-1
@@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
modules::{
|
||||
cache::imap::envelope_v2::EmailEnvelopeV2,
|
||||
cache::imap::v2::EmailEnvelopeV2,
|
||||
database::{
|
||||
batch_delete_impl, delete_impl, manager::DB_MANAGER, paginate_secondary_scan_impl,
|
||||
},
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::modules::error::code::ErrorCode;
|
||||
use crate::raise_error;
|
||||
use crate::{
|
||||
modules::{
|
||||
account::entity::Account,
|
||||
account::v2::AccountV2,
|
||||
context::controller::SYNC_CONTROLLER,
|
||||
error::RustMailerResult,
|
||||
imap::{executor::ImapExecutor, pool::build_imap_pool},
|
||||
@@ -115,8 +115,8 @@ impl EmailClientExecutors {
|
||||
}
|
||||
|
||||
pub async fn start_account_syncers(&self) -> RustMailerResult<()> {
|
||||
let accounts = Account::list_all().await?;
|
||||
let active_accounts: Vec<Account> = accounts.into_iter().filter(|a| a.enabled).collect();
|
||||
let accounts = AccountV2::list_all().await?;
|
||||
let active_accounts: Vec<AccountV2> = accounts.into_iter().filter(|a| a.enabled).collect();
|
||||
|
||||
if active_accounts.is_empty() {
|
||||
info!("No active accounts found for IMAP initialization.");
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
// Licensed under RustMailer License Agreement v1.0
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
use crate::modules::cache::imap::envelope_v2::EmailEnvelopeV2;
|
||||
use crate::modules::account::v2::AccountV2;
|
||||
use crate::modules::cache::imap::v2::EmailEnvelopeV2;
|
||||
use crate::modules::cache::imap::ENVELOPE_MODELS;
|
||||
use crate::modules::context::Initialize;
|
||||
use crate::modules::error::{code::ErrorCode, RustMailerError};
|
||||
@@ -20,7 +21,7 @@ use tracing::{info, warn};
|
||||
pub static DB_MANAGER: LazyLock<DatabaseManager> = LazyLock::new(DatabaseManager::new);
|
||||
|
||||
use crate::modules::{
|
||||
account::{entity::Account, status::AccountRunningState},
|
||||
account::status::AccountRunningState,
|
||||
autoconfig::CachedMailSettings,
|
||||
cache::disk::CacheItem,
|
||||
database::{batch_insert_impl, list_all_impl},
|
||||
@@ -85,6 +86,15 @@ impl DatabaseManager {
|
||||
) //default 128MB
|
||||
.create(&META_MODELS, DATA_DIR_MANAGER.meta_db.clone())
|
||||
.map_err(Self::handle_database_error)?;
|
||||
|
||||
let rw = database
|
||||
.rw_transaction()
|
||||
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
|
||||
rw.migrate::<AccountV2>()
|
||||
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
|
||||
rw.commit()
|
||||
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
|
||||
|
||||
database
|
||||
.compact()
|
||||
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
|
||||
@@ -127,7 +137,7 @@ impl DatabaseManager {
|
||||
spawn_migration_task!(SystemSetting);
|
||||
spawn_migration_task!(License);
|
||||
spawn_migration_task!(CachedMailSettings);
|
||||
spawn_migration_task!(Account);
|
||||
spawn_migration_task!(AccountV2);
|
||||
spawn_migration_task!(EmailTemplate);
|
||||
spawn_migration_task!(Mta);
|
||||
spawn_migration_task!(OAuth2);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
use crate::modules::account::status::AccountRunningState;
|
||||
use crate::modules::account::v2::AccountV2;
|
||||
use crate::modules::autoconfig::CachedMailSettings;
|
||||
use crate::modules::cache::disk::CacheItem;
|
||||
use crate::modules::error::RustMailerResult;
|
||||
@@ -59,6 +60,7 @@ impl ModelsAdapter {
|
||||
self.register_model::<License>();
|
||||
self.register_model::<CachedMailSettings>();
|
||||
self.register_model::<Account>();
|
||||
self.register_model::<AccountV2>();
|
||||
self.register_model::<EmailTemplate>();
|
||||
self.register_model::<Mta>();
|
||||
self.register_model::<OAuth2>();
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::{collections::BTreeMap, path::PathBuf};
|
||||
use crate::{
|
||||
id,
|
||||
modules::{
|
||||
account::entity::{Account, AccountKey},
|
||||
account::{entity::AccountKey, v2::AccountV2},
|
||||
cache::imap::mailbox::MailBox,
|
||||
database::META_MODELS,
|
||||
hook::{
|
||||
@@ -28,7 +28,7 @@ async fn test2() {
|
||||
println!("mailbox: {}", mailbox.id)
|
||||
}
|
||||
|
||||
let all = Account::minimal_list().await.unwrap();
|
||||
let all = AccountV2::minimal_list().await.unwrap();
|
||||
for mailbox in all {
|
||||
println!("account:{}", mailbox.id)
|
||||
}
|
||||
@@ -41,7 +41,7 @@ fn test3() {
|
||||
.unwrap();
|
||||
//database.compact().unwrap();
|
||||
let r_transaction = database.r_transaction().unwrap();
|
||||
let entities: Vec<Account> = r_transaction
|
||||
let entities: Vec<AccountV2> = r_transaction
|
||||
.scan()
|
||||
.secondary(AccountKey::id)
|
||||
.unwrap()
|
||||
@@ -51,7 +51,7 @@ fn test3() {
|
||||
.unwrap();
|
||||
println!("{:#?}", entities);
|
||||
|
||||
let entities: Vec<Account> = r_transaction
|
||||
let entities: Vec<AccountV2> = r_transaction
|
||||
.scan()
|
||||
.primary()
|
||||
.unwrap()
|
||||
@@ -89,12 +89,12 @@ async fn test4() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn test5() {
|
||||
let mut account = Account::default();
|
||||
let mut account = AccountV2::default();
|
||||
let id = id!(64);
|
||||
account.id = id;
|
||||
|
||||
account.save().await.unwrap();
|
||||
let account = Account::get(id).await.unwrap();
|
||||
let account = AccountV2::get(id).await.unwrap();
|
||||
println!("{:#?}", account);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
use crate::id;
|
||||
use crate::modules::cache::imap::envelope_v2::EmailEnvelopeV2;
|
||||
use crate::modules::cache::imap::v2::EmailEnvelopeV2;
|
||||
use crate::modules::cache::imap::flags_to_hash;
|
||||
use crate::modules::cache::imap::mailbox::EnvelopeFlag;
|
||||
use crate::modules::cache::imap::minimal::MinimalEnvelope;
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
|
||||
use crate::modules::{
|
||||
account::{
|
||||
entity::{Account, AuthConfig, AuthType, Encryption, ImapConfig, SmtpConfig},
|
||||
entity::{AuthConfig, AuthType, Encryption, ImapConfig, MailerType, SmtpConfig},
|
||||
payload::{AccountCreateRequest, AccountUpdateRequest, MinimalAccount},
|
||||
since::{DateSince, RelativeDate, Unit},
|
||||
status::{AccountError, AccountRunningState},
|
||||
v2::AccountV2,
|
||||
},
|
||||
grpc::service::rustmailer_grpc,
|
||||
};
|
||||
@@ -208,25 +209,24 @@ impl From<DateSince> for rustmailer_grpc::DateSince {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<rustmailer_grpc::Account> for Account {
|
||||
impl TryFrom<rustmailer_grpc::Account> for AccountV2 {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(value: rustmailer_grpc::Account) -> Result<Self, Self::Error> {
|
||||
Ok(Account {
|
||||
Ok(AccountV2 {
|
||||
id: value.id,
|
||||
imap: value
|
||||
.imap
|
||||
.ok_or("IMAP configuration is required")?
|
||||
.try_into()?,
|
||||
smtp: value
|
||||
.smtp
|
||||
.ok_or("SMTP configuration is required")?
|
||||
.try_into()?,
|
||||
imap: value.imap.map(|imap| imap.try_into()).transpose()?,
|
||||
smtp: value.smtp.map(|smtp| smtp.try_into()).transpose()?,
|
||||
enabled: value.enabled,
|
||||
mailer_type: value.mailer_type.try_into()?,
|
||||
email: value.email,
|
||||
name: value.name,
|
||||
minimal_sync: value.minimal_sync,
|
||||
capabilities: value.capabilities,
|
||||
capabilities: if value.capabilities.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(value.capabilities)
|
||||
},
|
||||
dsn_capable: value.dsn_capable,
|
||||
date_since: value.date_since.map(|ds| ds.try_into()).transpose()?,
|
||||
sync_folders: value.sync_folders,
|
||||
@@ -239,17 +239,18 @@ impl TryFrom<rustmailer_grpc::Account> for Account {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Account> for rustmailer_grpc::Account {
|
||||
fn from(value: Account) -> Self {
|
||||
impl From<AccountV2> for rustmailer_grpc::Account {
|
||||
fn from(value: AccountV2) -> Self {
|
||||
rustmailer_grpc::Account {
|
||||
id: value.id,
|
||||
imap: Some(value.imap.into()),
|
||||
smtp: Some(value.smtp.into()),
|
||||
imap: value.imap.map(|imap| imap.into()),
|
||||
smtp: value.smtp.map(|smtp| smtp.into()),
|
||||
enabled: value.enabled,
|
||||
mailer_type: value.mailer_type.into(),
|
||||
email: value.email,
|
||||
name: value.name,
|
||||
minimal_sync: value.minimal_sync,
|
||||
capabilities: value.capabilities,
|
||||
capabilities: value.capabilities.unwrap_or_default(),
|
||||
dsn_capable: value.dsn_capable,
|
||||
date_since: value.date_since.map(Into::into),
|
||||
sync_folders: value.sync_folders,
|
||||
@@ -269,15 +270,10 @@ impl TryFrom<rustmailer_grpc::AccountCreateRequest> for AccountCreateRequest {
|
||||
Ok(AccountCreateRequest {
|
||||
email: value.email,
|
||||
name: value.name,
|
||||
imap: value
|
||||
.imap
|
||||
.ok_or("IMAP configuration is required")?
|
||||
.try_into()?,
|
||||
smtp: value
|
||||
.smtp
|
||||
.ok_or("SMTP configuration is required")?
|
||||
.try_into()?,
|
||||
imap: value.imap.map(|imap| imap.try_into()).transpose()?,
|
||||
smtp: value.smtp.map(|smtp| smtp.try_into()).transpose()?,
|
||||
enabled: value.enabled,
|
||||
mailer_type: value.mailer_type.try_into()?,
|
||||
date_since: value.date_since.map(|ds| ds.try_into()).transpose()?,
|
||||
minimal_sync: value.minimal_sync,
|
||||
full_sync_interval_min: value.full_sync_interval_min,
|
||||
@@ -340,3 +336,24 @@ impl From<MinimalAccount> for rustmailer_grpc::MinimalAccount {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<i32> for MailerType {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(value: i32) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0 => Ok(MailerType::ImapSmtp),
|
||||
1 => Ok(MailerType::GmailApi),
|
||||
_ => Err("Invalid value for Unit"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MailerType> for i32 {
|
||||
fn from(value: MailerType) -> Self {
|
||||
match value {
|
||||
MailerType::ImapSmtp => 0,
|
||||
MailerType::GmailApi => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Licensed under RustMailer License Agreement v1.0
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
use crate::modules::account::entity::Account as RustMailerAccount;
|
||||
use crate::modules::account::v2::AccountV2 as RustMailerAccount;
|
||||
use crate::modules::account::payload::filter_accessible_accounts;
|
||||
use crate::modules::account::payload::AccountCreateRequest as RustMailerAccountCreateRequest;
|
||||
use crate::modules::account::payload::AccountUpdateRequest as RustMailerAccountUpdateRequest;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
use crate::modules::{
|
||||
cache::imap::{
|
||||
envelope::Received,
|
||||
envelope_v2::EmailEnvelopeV2,
|
||||
v2::EmailEnvelopeV2,
|
||||
mailbox::{EmailFlag, EnvelopeFlag},
|
||||
},
|
||||
common::Addr,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
use crate::id;
|
||||
use crate::modules::account::entity::Account;
|
||||
use crate::modules::account::v2::AccountV2;
|
||||
use crate::modules::database::manager::DB_MANAGER;
|
||||
use crate::modules::database::{
|
||||
delete_impl, filter_by_secondary_key_impl, paginate_query_primary_scan_all_impl,
|
||||
@@ -29,7 +29,7 @@ use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
use url::Url;
|
||||
|
||||
use super::payload::{apply_internal_update, InternalEventHookUpdateRequest};
|
||||
use crate::modules::hook::payload::{apply_internal_update, InternalEventHookUpdateRequest};
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Enum)]
|
||||
pub enum HttpMethod {
|
||||
@@ -129,7 +129,7 @@ impl EventHooks {
|
||||
|
||||
pub async fn new(request: EventhookCreateRequest) -> RustMailerResult<Self> {
|
||||
let (email, global) = if let Some(account_id) = request.account_id {
|
||||
(Some(Account::get(account_id).await?.email), 0)
|
||||
(Some(AccountV2::get(account_id).await?.email), 0)
|
||||
} else {
|
||||
(None, 1)
|
||||
};
|
||||
@@ -263,7 +263,7 @@ impl EventHooks {
|
||||
|
||||
async fn validate(&self) -> RustMailerResult<()> {
|
||||
if let Some(account_id) = self.account_id {
|
||||
if Account::get(account_id).await?.is_none() {
|
||||
if AccountV2::get(account_id).await?.is_none() {
|
||||
return Err(raise_error!(
|
||||
format!("Account with id '{}' not exists", account_id),
|
||||
ErrorCode::InvalidParameter
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
use crate::modules::error::code::ErrorCode;
|
||||
use crate::modules::imap::session::SessionStream;
|
||||
use crate::{modules::error::RustMailerResult, raise_error};
|
||||
#[cfg(not(test))]
|
||||
use async_imap::types::Capability;
|
||||
use async_imap::{types::Capabilities, Session};
|
||||
|
||||
@@ -27,7 +26,7 @@ pub fn check_capabilities(capabilities: &Capabilities) -> RustMailerResult<()> {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(not(test))]
|
||||
|
||||
pub fn capability_to_string(capability: &Capability) -> String {
|
||||
match capability {
|
||||
Capability::Imap4rev1 => "IMAP4rev1".into(),
|
||||
|
||||
+23
-26
@@ -2,14 +2,14 @@
|
||||
// Licensed under RustMailer License Agreement v1.0
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
#[cfg(not(test))]
|
||||
use crate::modules::account::dispatcher::STATUS_DISPATCHER;
|
||||
use crate::modules::account::entity::{Account, AuthType};
|
||||
use crate::modules::account::entity::AuthType;
|
||||
use crate::modules::account::v2::AccountV2;
|
||||
use crate::modules::error::code::ErrorCode;
|
||||
use crate::modules::error::RustMailerResult;
|
||||
#[cfg(not(test))]
|
||||
use crate::modules::imap::capabilities::capability_to_string;
|
||||
use crate::modules::imap::capabilities::{check_capabilities, fetch_capabilities};
|
||||
use crate::modules::imap::capabilities::{
|
||||
capability_to_string, check_capabilities, fetch_capabilities,
|
||||
};
|
||||
use crate::modules::imap::client::Client;
|
||||
use crate::modules::imap::oauth2::OAuth2;
|
||||
use crate::modules::imap::session::SessionStream;
|
||||
@@ -28,29 +28,32 @@ impl ImapConnectionManager {
|
||||
Self { account_id }
|
||||
}
|
||||
|
||||
pub async fn fetch_account(&self) -> RustMailerResult<Account> {
|
||||
pub async fn fetch_account(&self) -> RustMailerResult<AccountV2> {
|
||||
// Fetch the account entity in non-test environment
|
||||
Account::get(self.account_id).await
|
||||
AccountV2::get(self.account_id).await
|
||||
}
|
||||
|
||||
async fn create_client(&self, account: &Account) -> RustMailerResult<Client> {
|
||||
Client::connection(
|
||||
account.imap.host.clone(),
|
||||
account.imap.encryption.clone(),
|
||||
account.imap.port,
|
||||
account.imap.use_proxy,
|
||||
)
|
||||
.await
|
||||
async fn create_client(&self, account: &AccountV2) -> RustMailerResult<Client> {
|
||||
let imap = account
|
||||
.imap
|
||||
.clone()
|
||||
.expect("BUG: account.imap is None, but it should always be present");
|
||||
Client::connection(imap.host, imap.encryption, imap.port, imap.use_proxy).await
|
||||
}
|
||||
|
||||
async fn authenticate(
|
||||
&self,
|
||||
client: Client,
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
) -> RustMailerResult<Session<Box<dyn SessionStream>>> {
|
||||
match &account.imap.auth.auth_type {
|
||||
let imap = account
|
||||
.imap
|
||||
.clone()
|
||||
.expect("BUG: account.imap is None, but it should always be present");
|
||||
|
||||
match &imap.auth.auth_type {
|
||||
AuthType::Password => {
|
||||
let password = account.imap.auth.password.clone().ok_or_else(|| {
|
||||
let password = imap.auth.password.clone().ok_or_else(|| {
|
||||
raise_error!(
|
||||
"Imap auth type is Passwd, but password not set".into(),
|
||||
ErrorCode::MissingConfiguration
|
||||
@@ -86,7 +89,6 @@ impl ImapConnectionManager {
|
||||
"Failed to create IMAP {}'s client: {:#?}",
|
||||
&account.email, error
|
||||
);
|
||||
#[cfg(not(test))]
|
||||
STATUS_DISPATCHER
|
||||
.append_error(
|
||||
self.account_id,
|
||||
@@ -101,7 +103,7 @@ impl ImapConnectionManager {
|
||||
Ok(session) => session,
|
||||
Err(error) => {
|
||||
error!("Failed to authenticate IMAP session: {:#?}", error);
|
||||
#[cfg(not(test))]
|
||||
|
||||
STATUS_DISPATCHER
|
||||
.append_error(
|
||||
self.account_id,
|
||||
@@ -114,14 +116,10 @@ impl ImapConnectionManager {
|
||||
|
||||
match fetch_capabilities(&mut session).await {
|
||||
Ok(capabilities) => {
|
||||
#[cfg(not(test))]
|
||||
let to_save: Vec<String> = capabilities.iter().map(capability_to_string).collect();
|
||||
#[cfg(not(test))]
|
||||
Account::update_capabilities(self.account_id, to_save).await?;
|
||||
|
||||
AccountV2::update_capabilities(self.account_id, to_save).await?;
|
||||
if let Err(error) = check_capabilities(&capabilities) {
|
||||
error!("Failed to check IMAP capabilities: {:#?}", error);
|
||||
#[cfg(not(test))]
|
||||
STATUS_DISPATCHER
|
||||
.append_error(
|
||||
self.account_id,
|
||||
@@ -133,7 +131,6 @@ impl ImapConnectionManager {
|
||||
}
|
||||
Err(error) => {
|
||||
error!("Failed to fetch IMAP capabilities: {:#?}", error);
|
||||
#[cfg(not(test))]
|
||||
STATUS_DISPATCHER
|
||||
.append_error(
|
||||
self.account_id,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Licensed under RustMailer License Agreement v1.0
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
use crate::modules::account::entity::Account;
|
||||
use crate::modules::account::v2::AccountV2;
|
||||
use crate::modules::context::Initialize;
|
||||
use crate::modules::database::manager::DB_MANAGER;
|
||||
use crate::modules::database::{async_find_impl, upsert_impl};
|
||||
@@ -174,7 +174,7 @@ impl License {
|
||||
let license_str = license_str.into();
|
||||
let license = match current_license {
|
||||
Some(license) => {
|
||||
let count = Account::count().await?;
|
||||
let count = AccountV2::count().await?;
|
||||
if let Some(max_accounts) = license_content.max_accounts {
|
||||
if count > max_accounts as usize {
|
||||
return Err(raise_error!(format!(
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
use crate::{
|
||||
encode_mailbox_name, modules::account::entity::Account,
|
||||
encode_mailbox_name, modules::account::v2::AccountV2,
|
||||
modules::context::executors::RUST_MAIL_CONTEXT, modules::error::RustMailerResult,
|
||||
};
|
||||
|
||||
pub async fn create_mailbox(account_id: u64, mailbox_name: &str) -> RustMailerResult<()> {
|
||||
Account::check_account_active(account_id).await?;
|
||||
AccountV2::check_account_active(account_id).await?;
|
||||
let executor = RUST_MAIL_CONTEXT.imap(account_id).await?;
|
||||
executor
|
||||
.create_mailbox(encode_mailbox_name!(mailbox_name).as_str())
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
use crate::{encode_mailbox_name, modules::{
|
||||
account::entity::Account, context::executors::RUST_MAIL_CONTEXT, error::RustMailerResult,
|
||||
account::v2::AccountV2, context::executors::RUST_MAIL_CONTEXT, error::RustMailerResult,
|
||||
}};
|
||||
|
||||
pub async fn delete_mailbox(account_id: u64, mailbox_name: &str) -> RustMailerResult<()> {
|
||||
Account::check_account_active(account_id).await?;
|
||||
AccountV2::check_account_active(account_id).await?;
|
||||
let executor = RUST_MAIL_CONTEXT.imap(account_id).await?;
|
||||
executor.delete_mailbox(encode_mailbox_name!(mailbox_name).as_str()).await
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Licensed under RustMailer License Agreement v1.0
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
use crate::modules::account::entity::Account;
|
||||
use crate::modules::account::v2::AccountV2;
|
||||
use crate::modules::cache::imap::mailbox::{Attribute, AttributeEnum, MailBox};
|
||||
use crate::modules::context::executors::RUST_MAIL_CONTEXT;
|
||||
use crate::modules::error::code::ErrorCode;
|
||||
@@ -15,8 +15,8 @@ pub async fn get_account_mailboxes(
|
||||
account_id: u64,
|
||||
remote: bool,
|
||||
) -> RustMailerResult<Vec<MailBox>> {
|
||||
let account = Account::check_account_active(account_id).await?;
|
||||
let remote = remote || account.minimal_sync;
|
||||
let account = AccountV2::check_account_active(account_id).await?;
|
||||
let remote = remote || account.minimal_sync();
|
||||
if remote {
|
||||
request_imap_all_mailbox_list(account_id).await
|
||||
} else {
|
||||
@@ -25,7 +25,7 @@ pub async fn get_account_mailboxes(
|
||||
}
|
||||
|
||||
pub async fn list_subscribed_mailboxes(account_id: u64) -> RustMailerResult<Vec<MailBox>> {
|
||||
Account::check_account_active(account_id).await?;
|
||||
AccountV2::check_account_active(account_id).await?;
|
||||
request_imap_subscribed_mailbox_list(account_id).await
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
// Licensed under RustMailer License Agreement v1.0
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
use crate::{encode_mailbox_name, modules::{
|
||||
account::entity::Account, context::executors::RUST_MAIL_CONTEXT, error::RustMailerResult,
|
||||
}};
|
||||
use crate::{
|
||||
encode_mailbox_name,
|
||||
modules::{
|
||||
account::v2::AccountV2, context::executors::RUST_MAIL_CONTEXT, error::RustMailerResult,
|
||||
},
|
||||
};
|
||||
use poem_openapi::Object;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -22,9 +25,12 @@ pub async fn rename_mailbox(
|
||||
account_id: u64,
|
||||
payload: MailboxRenameRequest,
|
||||
) -> RustMailerResult<()> {
|
||||
Account::check_account_active(account_id).await?;
|
||||
AccountV2::check_account_active(account_id).await?;
|
||||
let executor = RUST_MAIL_CONTEXT.imap(account_id).await?;
|
||||
executor
|
||||
.rename_mailbox(encode_mailbox_name!(&payload.current_name).as_str(), encode_mailbox_name!(&payload.new_name).as_str())
|
||||
.rename_mailbox(
|
||||
encode_mailbox_name!(&payload.current_name).as_str(),
|
||||
encode_mailbox_name!(&payload.new_name).as_str(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
use crate::{
|
||||
encode_mailbox_name,
|
||||
modules::{
|
||||
account::entity::Account, context::executors::RUST_MAIL_CONTEXT, error::RustMailerResult,
|
||||
account::v2::AccountV2, context::executors::RUST_MAIL_CONTEXT, error::RustMailerResult,
|
||||
},
|
||||
};
|
||||
|
||||
pub async fn subscribe_mailbox(account_id: u64, mailbox_name: &str) -> RustMailerResult<()> {
|
||||
Account::check_account_active(account_id).await?;
|
||||
AccountV2::check_account_active(account_id).await?;
|
||||
let executor = RUST_MAIL_CONTEXT.imap(account_id).await?;
|
||||
executor
|
||||
.subscribe_mailbox(encode_mailbox_name!(mailbox_name).as_str())
|
||||
@@ -18,7 +18,7 @@ pub async fn subscribe_mailbox(account_id: u64, mailbox_name: &str) -> RustMaile
|
||||
}
|
||||
|
||||
pub async fn unsubscribe_mailbox(account_id: u64, mailbox_name: &str) -> RustMailerResult<()> {
|
||||
Account::check_account_active(account_id).await?;
|
||||
AccountV2::check_account_active(account_id).await?;
|
||||
let executor = RUST_MAIL_CONTEXT.imap(account_id).await?;
|
||||
executor
|
||||
.unsubscribe_mailbox(encode_mailbox_name!(mailbox_name).as_str())
|
||||
|
||||
@@ -14,7 +14,7 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::{
|
||||
encode_mailbox_name,
|
||||
modules::{
|
||||
account::entity::Account,
|
||||
account::v2::AccountV2,
|
||||
context::executors::RUST_MAIL_CONTEXT,
|
||||
error::{code::ErrorCode, RustMailerResult},
|
||||
smtp::request::{reply::apply_references, EmailHandler},
|
||||
@@ -51,7 +51,7 @@ pub struct AppendReplyToDraftRequest {
|
||||
|
||||
impl AppendReplyToDraftRequest {
|
||||
pub async fn append_reply_to_draft(&self, account_id: u64) -> RustMailerResult<()> {
|
||||
let account = Account::check_account_active(account_id).await?;
|
||||
let account = AccountV2::check_account_active(account_id).await?;
|
||||
let envelope = EmailHandler::get_envelope(&account, &self.mailbox_name, self.uid).await?;
|
||||
let from = Address::new_address(
|
||||
account.name.as_ref().map(|n| Cow::Owned(n.to_string())),
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::modules::error::code::ErrorCode;
|
||||
use crate::modules::message::get_minimal_meta;
|
||||
use crate::{
|
||||
encode_mailbox_name,
|
||||
modules::account::entity::Account,
|
||||
modules::account::v2::AccountV2,
|
||||
modules::cache::disk::DISK_CACHE,
|
||||
modules::context::executors::RUST_MAIL_CONTEXT,
|
||||
modules::error::RustMailerResult,
|
||||
@@ -59,7 +59,7 @@ pub async fn retrieve_email_attachment(
|
||||
account_id: u64,
|
||||
request: AttachmentRequest,
|
||||
) -> RustMailerResult<cacache::Reader> {
|
||||
Account::check_account_active(account_id).await?;
|
||||
AccountV2::check_account_active(account_id).await?;
|
||||
|
||||
if request.attachment.size >= MAX_ATTACHMENT_SIZE {
|
||||
return Err(raise_error!(
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
// Licensed under RustMailer License Agreement v1.0
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
use crate::modules::account::v2::AccountV2;
|
||||
use crate::modules::error::code::ErrorCode;
|
||||
use crate::modules::imap::section::Encoding;
|
||||
use crate::modules::message::attachment::inline_attachment_diskcache_key;
|
||||
use crate::{
|
||||
encode_mailbox_name,
|
||||
modules::{
|
||||
account::entity::Account,
|
||||
cache::disk::DISK_CACHE,
|
||||
context::executors::RUST_MAIL_CONTEXT,
|
||||
error::RustMailerResult,
|
||||
@@ -283,7 +283,7 @@ pub async fn retrieve_email_content(
|
||||
request: MessageContentRequest,
|
||||
skip_cache: bool,
|
||||
) -> RustMailerResult<MessageContent> {
|
||||
Account::check_account_active(account_id).await?;
|
||||
AccountV2::check_account_active(account_id).await?;
|
||||
|
||||
let mut plain: Option<PlainText> = None;
|
||||
let mut html: Option<String> = None;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
use crate::{
|
||||
encode_mailbox_name, modules::account::entity::Account,
|
||||
encode_mailbox_name, modules::account::v2::AccountV2,
|
||||
modules::context::executors::RUST_MAIL_CONTEXT, modules::envelope::generate_uid_set,
|
||||
modules::error::RustMailerResult,
|
||||
};
|
||||
@@ -33,7 +33,7 @@ pub async fn copy_mailbox_messages(
|
||||
payload: &MailboxTransferRequest,
|
||||
) -> RustMailerResult<()> {
|
||||
// Ensure the account exists before proceeding
|
||||
Account::check_account_active(account_id).await?;
|
||||
AccountV2::check_account_active(account_id).await?;
|
||||
|
||||
// Generate a set of UIDs from the payload
|
||||
let uid_set = generate_uid_set(payload.uids.clone());
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
use crate::encode_mailbox_name;
|
||||
use crate::modules::account::entity::Account;
|
||||
use crate::modules::account::v2::AccountV2;
|
||||
use crate::modules::cache::imap::mailbox::{AttributeEnum, MailBox};
|
||||
use crate::modules::context::executors::RUST_MAIL_CONTEXT;
|
||||
use crate::modules::{envelope::generate_uid_set, error::RustMailerResult};
|
||||
@@ -25,7 +25,7 @@ pub async fn move_to_trash_or_delete_messages_directly(
|
||||
account_id: u64,
|
||||
request: &MessageDeleteRequest,
|
||||
) -> RustMailerResult<()> {
|
||||
Account::check_account_active(account_id).await?;
|
||||
AccountV2::check_account_active(account_id).await?;
|
||||
let uid_set = generate_uid_set(request.uids.clone());
|
||||
let executor = RUST_MAIL_CONTEXT.imap(account_id).await?;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
use crate::{
|
||||
modules::{
|
||||
account::entity::Account,
|
||||
account::v2::AccountV2,
|
||||
cache::imap::mailbox::EnvelopeFlag,
|
||||
context::executors::RUST_MAIL_CONTEXT,
|
||||
envelope::generate_uid_set,
|
||||
@@ -81,7 +81,7 @@ impl FlagAction {
|
||||
}
|
||||
|
||||
pub async fn modify_flags(account_id: u64, request: FlagMessageRequest) -> RustMailerResult<()> {
|
||||
Account::check_account_active(account_id).await?;
|
||||
AccountV2::check_account_active(account_id).await?;
|
||||
request.validate()?;
|
||||
|
||||
let executor = RUST_MAIL_CONTEXT.imap(account_id).await?;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
use crate::{
|
||||
modules::{
|
||||
account::entity::Account,
|
||||
account::v2::AccountV2,
|
||||
cache::disk::DISK_CACHE,
|
||||
context::executors::RUST_MAIL_CONTEXT,
|
||||
error::{code::ErrorCode, RustMailerResult},
|
||||
@@ -35,7 +35,7 @@ pub async fn retrieve_full_email(
|
||||
mailbox: String,
|
||||
uid: u32,
|
||||
) -> RustMailerResult<cacache::Reader> {
|
||||
Account::check_account_active(account_id).await?;
|
||||
AccountV2::check_account_active(account_id).await?;
|
||||
let meta = get_minimal_meta(account_id, &mailbox, uid).await?;
|
||||
if meta.size > MAX_EMAIL_TOTAL_SIZE {
|
||||
return Err(raise_error!(format!(
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
use crate::{
|
||||
encode_mailbox_name,
|
||||
modules::{
|
||||
account::entity::Account,
|
||||
cache::imap::{envelope_v2::EmailEnvelopeV2, mailbox::MailBox, thread::EmailThread},
|
||||
account::v2::AccountV2,
|
||||
cache::imap::{mailbox::MailBox, thread::EmailThread, v2::EmailEnvelopeV2},
|
||||
context::executors::RUST_MAIL_CONTEXT,
|
||||
envelope::extractor::extract_envelope,
|
||||
error::{code::ErrorCode, RustMailerResult},
|
||||
@@ -24,9 +24,9 @@ pub async fn list_messages_in_mailbox(
|
||||
remote: bool,
|
||||
desc: bool,
|
||||
) -> RustMailerResult<DataPage<EmailEnvelopeV2>> {
|
||||
let account = Account::check_account_active(account_id).await?;
|
||||
let account = AccountV2::check_account_active(account_id).await?;
|
||||
validate_pagination_params(page, page_size)?;
|
||||
let remote = remote || account.minimal_sync;
|
||||
let remote = remote || account.minimal_sync();
|
||||
|
||||
if remote {
|
||||
fetch_remote_messages(account_id, mailbox_name, page, page_size, desc).await
|
||||
@@ -99,7 +99,7 @@ async fn process_fetches(
|
||||
}
|
||||
|
||||
async fn fetch_local_messages(
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
mailbox_name: &str,
|
||||
page: u64,
|
||||
page_size: u64,
|
||||
@@ -125,9 +125,9 @@ pub async fn list_threads_in_mailbox(
|
||||
page_size: u64,
|
||||
desc: bool,
|
||||
) -> RustMailerResult<DataPage<EmailEnvelopeV2>> {
|
||||
let account = Account::check_account_active(account_id).await?;
|
||||
let account = AccountV2::check_account_active(account_id).await?;
|
||||
validate_pagination_params(page, page_size)?;
|
||||
if account.minimal_sync {
|
||||
if account.minimal_sync() {
|
||||
return Err(raise_error!(
|
||||
format!(
|
||||
"Account {} is in minimal sync mode. Listing threads in a mailbox is not supported. \
|
||||
@@ -160,8 +160,8 @@ pub async fn get_thread_messages(
|
||||
mailbox_name: &str,
|
||||
thread_id: u64,
|
||||
) -> RustMailerResult<Vec<EmailEnvelopeV2>> {
|
||||
let account = Account::check_account_active(account_id).await?;
|
||||
if account.minimal_sync {
|
||||
let account = AccountV2::check_account_active(account_id).await?;
|
||||
if account.minimal_sync() {
|
||||
return Err(raise_error!(
|
||||
format!(
|
||||
"Account {} is in minimal sync mode. Listing threads in a mailbox is not supported. \
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
use crate::modules::message::copy::MailboxTransferRequest;
|
||||
use crate::{
|
||||
encode_mailbox_name, modules::account::entity::Account,
|
||||
encode_mailbox_name, modules::account::v2::AccountV2,
|
||||
modules::context::executors::RUST_MAIL_CONTEXT, modules::envelope::generate_uid_set,
|
||||
modules::error::RustMailerResult,
|
||||
};
|
||||
@@ -14,7 +14,7 @@ pub async fn move_mailbox_messages(
|
||||
payload: &MailboxTransferRequest,
|
||||
) -> RustMailerResult<()> {
|
||||
// Ensure the account exists before proceeding
|
||||
Account::check_account_active(account_id).await?;
|
||||
AccountV2::check_account_active(account_id).await?;
|
||||
|
||||
// Generate a set of UIDs from the payload
|
||||
let uid_set = generate_uid_set(payload.uids.clone());
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
use crate::modules::cache::imap::address::AddressEntity;
|
||||
use crate::modules::cache::imap::envelope_v2::EmailEnvelopeV2;
|
||||
use crate::modules::cache::imap::sync::flow::generate_uid_sequence_hashset;
|
||||
use crate::modules::cache::imap::v2::EmailEnvelopeV2;
|
||||
use crate::modules::common::paginated::paginate_vec;
|
||||
use crate::modules::database::Paginated;
|
||||
use crate::modules::error::code::ErrorCode;
|
||||
@@ -12,7 +12,7 @@ use crate::modules::message::search::cache::IMAP_SEARCH_CACHE;
|
||||
use crate::{
|
||||
encode_mailbox_name,
|
||||
modules::{
|
||||
account::entity::Account, context::executors::RUST_MAIL_CONTEXT,
|
||||
account::v2::AccountV2, context::executors::RUST_MAIL_CONTEXT,
|
||||
envelope::extractor::extract_envelope, error::RustMailerResult, rest::response::DataPage,
|
||||
},
|
||||
raise_error,
|
||||
@@ -437,13 +437,13 @@ impl MessageSearchRequest {
|
||||
page_size: u64,
|
||||
desc: bool,
|
||||
) -> RustMailerResult<DataPage<EmailEnvelopeV2>> {
|
||||
let account = Account::check_account_active(account_id).await?;
|
||||
let account = AccountV2::check_account_active(account_id).await?;
|
||||
self.search_remote(&account, page, page_size, desc).await
|
||||
}
|
||||
|
||||
async fn search_remote(
|
||||
&self,
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
page: u64,
|
||||
page_size: u64,
|
||||
desc: bool,
|
||||
|
||||
@@ -6,7 +6,7 @@ use poem_openapi::Object;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::modules::{
|
||||
account::entity::Account,
|
||||
account::v2::AccountV2,
|
||||
context::executors::RUST_MAIL_CONTEXT,
|
||||
error::RustMailerResult,
|
||||
metrics::{
|
||||
@@ -51,7 +51,7 @@ impl Overview {
|
||||
let pending_hook_tasks = send_queue
|
||||
.list_hook_tasks_by_status(TaskStatus::Scheduled)
|
||||
.await?;
|
||||
let account_num = Account::count().await?;
|
||||
let account_num = AccountV2::count().await?;
|
||||
let mut time_series = MetricsTimeSeries::get().await?;
|
||||
time_series.sort_by_timestamp();
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use crate::modules::account::entity::Account;
|
||||
use crate::modules::account::payload::{
|
||||
filter_accessible_accounts, AccountCreateRequest, AccountUpdateRequest, MinimalAccount,
|
||||
};
|
||||
use crate::modules::account::status::AccountRunningState;
|
||||
use crate::modules::account::v2::AccountV2;
|
||||
use crate::modules::common::auth::ClientContext;
|
||||
use crate::modules::common::paginated::paginate_vec;
|
||||
use crate::modules::error::code::ErrorCode;
|
||||
@@ -37,11 +37,11 @@ impl AccountApi {
|
||||
/// The account ID to retrieve
|
||||
account_id: Path<u64>,
|
||||
context: ClientContext,
|
||||
) -> ApiResult<Json<Account>> {
|
||||
) -> ApiResult<Json<AccountV2>> {
|
||||
let account_id = account_id.0;
|
||||
context.require_account_access(account_id)?;
|
||||
println!("{}", account_id);
|
||||
Ok(Json(Account::get(account_id).await?))
|
||||
Ok(Json(AccountV2::get(account_id).await?))
|
||||
}
|
||||
|
||||
/// Delete an account by ID - WARNING: This permanently removes the account and all associated resources
|
||||
@@ -58,7 +58,7 @@ impl AccountApi {
|
||||
) -> ApiResult<()> {
|
||||
let account_id = account_id.0;
|
||||
context.require_account_access(account_id)?;
|
||||
Ok(Account::delete(account_id).await?)
|
||||
Ok(AccountV2::delete(account_id).await?)
|
||||
}
|
||||
|
||||
/// Create a new account
|
||||
@@ -68,8 +68,8 @@ impl AccountApi {
|
||||
/// Account creation request payload
|
||||
payload: Json<AccountCreateRequest>,
|
||||
context: ClientContext,
|
||||
) -> ApiResult<Json<Account>> {
|
||||
let account = Account::create_account(payload.0).await?;
|
||||
) -> ApiResult<Json<AccountV2>> {
|
||||
let account = AccountV2::create_account(payload.0).await?;
|
||||
if let Some(access_token) = &context.access_token {
|
||||
let account_info = AccountInfo {
|
||||
id: account.id,
|
||||
@@ -96,7 +96,7 @@ impl AccountApi {
|
||||
) -> ApiResult<()> {
|
||||
let account_id = account_id.0;
|
||||
context.require_account_access(account_id)?;
|
||||
Ok(Account::update(account_id, payload.0, true).await?)
|
||||
Ok(AccountV2::update(account_id, payload.0, true).await?)
|
||||
}
|
||||
|
||||
/// List accounts with optional pagination parameters
|
||||
@@ -114,20 +114,20 @@ impl AccountApi {
|
||||
/// Optional. Whether to sort the list in descending order.
|
||||
desc: Query<Option<bool>>,
|
||||
context: ClientContext,
|
||||
) -> ApiResult<Json<DataPage<Account>>> {
|
||||
) -> ApiResult<Json<DataPage<AccountV2>>> {
|
||||
let accessible_accounts = context.accessible_accounts()?;
|
||||
|
||||
if accessible_accounts.is_none() {
|
||||
return Ok(Json(
|
||||
Account::paginate_list(page.0, page_size.0, desc.0).await?,
|
||||
AccountV2::paginate_list(page.0, page_size.0, desc.0).await?,
|
||||
));
|
||||
}
|
||||
|
||||
let all_accounts = Account::list_all().await?;
|
||||
let all_accounts = AccountV2::list_all().await?;
|
||||
let allowed_ids: BTreeSet<u64> =
|
||||
accessible_accounts.unwrap().iter().map(|a| a.id).collect();
|
||||
|
||||
let mut filtered_accounts: Vec<Account> = all_accounts
|
||||
let mut filtered_accounts: Vec<AccountV2> = all_accounts
|
||||
.into_iter()
|
||||
.filter(|acct| allowed_ids.contains(&acct.id))
|
||||
.collect();
|
||||
@@ -183,7 +183,7 @@ impl AccountApi {
|
||||
) -> ApiResult<Json<Vec<MinimalAccount>>> {
|
||||
let accessible_accounts = context.accessible_accounts()?;
|
||||
|
||||
let minimal_list = Account::minimal_list().await?;
|
||||
let minimal_list = AccountV2::minimal_list().await?;
|
||||
let result = match accessible_accounts {
|
||||
Some(set) => filter_accessible_accounts(&minimal_list, set),
|
||||
None => minimal_list,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
use crate::current_datetime;
|
||||
use crate::modules::cache::imap::envelope_v2::EmailEnvelopeV2;
|
||||
use crate::modules::cache::imap::v2::EmailEnvelopeV2;
|
||||
use crate::modules::common::auth::ClientContext;
|
||||
use crate::modules::message::append::AppendReplyToDraftRequest;
|
||||
use crate::modules::message::attachment::{retrieve_email_attachment, AttachmentRequest};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Licensed under RustMailer License Agreement v1.0
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
use crate::modules::cache::imap::envelope_v2::EmailEnvelopeV2;
|
||||
use crate::modules::cache::imap::v2::EmailEnvelopeV2;
|
||||
use scraper::{Html, Selector};
|
||||
use time::{macros::format_description, OffsetDateTime};
|
||||
use time_tz::timezones;
|
||||
@@ -229,7 +229,7 @@ mod tests {
|
||||
id,
|
||||
modules::{
|
||||
cache::imap::{
|
||||
envelope_v2::EmailEnvelopeV2,
|
||||
v2::EmailEnvelopeV2,
|
||||
mailbox::{EmailFlag, EnvelopeFlag},
|
||||
},
|
||||
common::Addr,
|
||||
|
||||
+20
-16
@@ -3,13 +3,14 @@
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
use crate::modules::account::entity::{AuthType, Encryption};
|
||||
use crate::modules::account::v2::AccountV2;
|
||||
use crate::modules::error::code::ErrorCode;
|
||||
use crate::modules::error::RustMailerResult;
|
||||
use crate::modules::oauth2::token::OAuth2AccessToken;
|
||||
use crate::modules::settings::proxy::Proxy;
|
||||
use crate::modules::smtp::client::RustMailSmtpClient;
|
||||
use crate::modules::smtp::mta::entity::Mta;
|
||||
use crate::modules::utils::net::parse_proxy_addr;
|
||||
use crate::modules::{account::entity::Account, error::RustMailerResult};
|
||||
use crate::{decrypt, raise_error};
|
||||
use mail_send::smtp::tls::build_tls_connector;
|
||||
use mail_send::smtp::AssertReply;
|
||||
@@ -103,10 +104,16 @@ impl SmtpClientManager {
|
||||
}
|
||||
|
||||
async fn build_client(account_id: u64) -> RustMailerResult<RustMailSmtpClient> {
|
||||
let account = Account::get(account_id).await?;
|
||||
let credentials = match &account.smtp.auth.auth_type {
|
||||
let account = AccountV2::get(account_id).await?;
|
||||
|
||||
let smtp = account
|
||||
.smtp
|
||||
.as_ref()
|
||||
.expect("BUG: account.smtp is None, but it should always be present here");
|
||||
|
||||
let credentials = match smtp.auth.auth_type {
|
||||
AuthType::Password => {
|
||||
let password = account.smtp.auth.password.as_ref().ok_or_else(|| {
|
||||
let password = smtp.auth.password.as_ref().ok_or_else(|| {
|
||||
raise_error!(
|
||||
"smtp auth type is Password, but password not set".into(),
|
||||
ErrorCode::MissingConfiguration
|
||||
@@ -129,20 +136,17 @@ impl SmtpClientManager {
|
||||
};
|
||||
|
||||
let timeout = Duration::from_secs(30);
|
||||
if let Some(proxy_id) = &account.smtp.use_proxy {
|
||||
let proxy = Proxy::get(*proxy_id).await?;
|
||||
if let Some(proxy_id) = smtp.use_proxy {
|
||||
let proxy = Proxy::get(proxy_id).await?;
|
||||
let proxy = parse_proxy_addr(&proxy.url)?;
|
||||
let socks_stream = Socks5Stream::connect(
|
||||
proxy,
|
||||
format!("{}:{}", &account.smtp.host, account.smtp.port),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
|
||||
let socks_stream = Socks5Stream::connect(proxy, format!("{}:{}", smtp.host, smtp.port))
|
||||
.await
|
||||
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
|
||||
|
||||
let tcp_stream = socks_stream.into_inner();
|
||||
return Self::connect(
|
||||
account.smtp.encryption,
|
||||
&account.smtp.host,
|
||||
smtp.encryption.clone(),
|
||||
&smtp.host,
|
||||
timeout,
|
||||
tcp_stream,
|
||||
credentials,
|
||||
@@ -151,11 +155,11 @@ impl SmtpClientManager {
|
||||
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::SmtpConnectionFailed));
|
||||
}
|
||||
|
||||
let builder = SmtpClientBuilder::new(account.smtp.host, account.smtp.port)
|
||||
let builder = SmtpClientBuilder::new(smtp.host.clone(), smtp.port)
|
||||
.credentials(credentials)
|
||||
.timeout(timeout);
|
||||
|
||||
let client = match account.smtp.encryption {
|
||||
let client = match smtp.encryption {
|
||||
Encryption::Ssl => {
|
||||
let client = builder.implicit_tls(true).connect().await.map_err(|e| {
|
||||
raise_error!(format!("{:#?}", e), ErrorCode::SmtpConnectionFailed)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Licensed under RustMailer License Agreement v1.0
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
use crate::modules::cache::imap::envelope_v2::EmailEnvelopeV2;
|
||||
use crate::modules::cache::imap::v2::EmailEnvelopeV2;
|
||||
use crate::modules::error::code::ErrorCode;
|
||||
use crate::modules::smtp::request::builder::EmailBuilder;
|
||||
use crate::modules::smtp::request::headers::HeaderValue;
|
||||
@@ -13,7 +13,7 @@ use crate::modules::smtp::util::generate_message_id;
|
||||
use crate::validate_email;
|
||||
use crate::{
|
||||
modules::{
|
||||
account::entity::Account,
|
||||
account::v2::AccountV2,
|
||||
error::RustMailerResult,
|
||||
smtp::{
|
||||
composer::BodyComposer,
|
||||
@@ -153,7 +153,7 @@ impl EmailBuilder for ForwardEmailRequest {
|
||||
|
||||
async fn build(&self, account_id: u64) -> RustMailerResult<()> {
|
||||
self.validate().await?;
|
||||
let account = &Account::get(account_id).await?;
|
||||
let account = &AccountV2::get(account_id).await?;
|
||||
let envelope = EmailHandler::get_envelope(account, &self.mailbox_name, self.uid).await?;
|
||||
let from = Address::new_address(
|
||||
account.name.as_ref().map(|n| Cow::Owned(n.to_string())),
|
||||
@@ -241,7 +241,7 @@ impl ForwardEmailRequest {
|
||||
&self,
|
||||
mut builder: MessageBuilder<'static>,
|
||||
envelope: &EmailEnvelopeV2,
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
) -> RustMailerResult<MessageBuilder<'static>> {
|
||||
let timezone = self.timezone.as_deref().unwrap_or("UTC");
|
||||
|
||||
@@ -316,7 +316,7 @@ impl ForwardEmailRequest {
|
||||
async fn apply_attachments(
|
||||
&self,
|
||||
mut builder: MessageBuilder<'static>,
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
) -> RustMailerResult<MessageBuilder<'static>> {
|
||||
if let Some(attachments) = &self.attachments {
|
||||
for attachment in attachments {
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
use crate::encode_mailbox_name;
|
||||
use crate::generate_token;
|
||||
use crate::modules::cache::disk::DISK_CACHE;
|
||||
use crate::modules::cache::imap::envelope_v2::EmailEnvelopeV2;
|
||||
use crate::modules::cache::imap::mailbox::EmailFlag;
|
||||
use crate::modules::cache::imap::mailbox::EnvelopeFlag;
|
||||
use crate::modules::cache::imap::mailbox::MailBox;
|
||||
use crate::modules::cache::imap::v2::EmailEnvelopeV2;
|
||||
use crate::modules::common::Addr;
|
||||
use crate::modules::context::executors::RUST_MAIL_CONTEXT;
|
||||
use crate::modules::envelope::extractor::extract_envelope;
|
||||
@@ -23,7 +23,7 @@ use crate::validate_email;
|
||||
use crate::{
|
||||
base64_decode_safe,
|
||||
modules::{
|
||||
account::entity::Account,
|
||||
account::v2::AccountV2,
|
||||
error::RustMailerResult,
|
||||
imap::section::ImapAttachment,
|
||||
message::attachment::{retrieve_email_attachment, AttachmentRequest},
|
||||
@@ -154,7 +154,7 @@ pub struct MailAttachment {
|
||||
}
|
||||
|
||||
impl MailAttachment {
|
||||
pub async fn get_content(&self, account: &Account) -> RustMailerResult<BodyPart<'static>> {
|
||||
pub async fn get_content(&self, account: &AccountV2) -> RustMailerResult<BodyPart<'static>> {
|
||||
if let Some(content) = &self.payload.base64_content {
|
||||
return Self::decode_base64_content(content, &self.mime_type);
|
||||
}
|
||||
@@ -204,7 +204,7 @@ impl MailAttachment {
|
||||
async fn retrieve_and_decode_attachment(
|
||||
attachment_ref: &AttachmentRef,
|
||||
mime_type: &str,
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
) -> RustMailerResult<BodyPart<'static>> {
|
||||
let mut reader = retrieve_email_attachment(
|
||||
account.id,
|
||||
@@ -600,7 +600,7 @@ impl EmailHandler {
|
||||
}
|
||||
|
||||
pub async fn retrieve_message_content(
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
envelope: &EmailEnvelopeV2,
|
||||
) -> RustMailerResult<Option<MessageContent>> {
|
||||
let body_meta = match &envelope.body_meta {
|
||||
@@ -626,12 +626,12 @@ impl EmailHandler {
|
||||
}
|
||||
|
||||
pub async fn get_envelope(
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
mailbox_name: &str,
|
||||
uid: u32,
|
||||
) -> RustMailerResult<EmailEnvelopeV2> {
|
||||
if let Ok(mailbox) = MailBox::get(account.id, mailbox_name).await {
|
||||
if !account.minimal_sync {
|
||||
if !account.minimal_sync() {
|
||||
let envelope = EmailEnvelopeV2::find(account.id, mailbox.id, uid).await?;
|
||||
if let Some(envelope) = envelope {
|
||||
return Ok(envelope);
|
||||
@@ -705,7 +705,7 @@ impl EmailHandler {
|
||||
attachment: &ImapAttachment,
|
||||
envelope: &EmailEnvelopeV2,
|
||||
inline: bool,
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
) -> RustMailerResult<MessageBuilder<'static>> {
|
||||
let attachment_ref = AttachmentRef {
|
||||
mailbox_name: envelope.mailbox_name.clone(),
|
||||
@@ -747,7 +747,7 @@ impl EmailHandler {
|
||||
}
|
||||
|
||||
pub async fn schedule_task(
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
subject: Option<String>,
|
||||
message_id: String,
|
||||
cc: Option<Vec<EmailAddress>>,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
use crate::{
|
||||
modules::{
|
||||
account::entity::Account,
|
||||
account::v2::AccountV2,
|
||||
error::{code::ErrorCode, RustMailerResult},
|
||||
settings::cli::SETTINGS,
|
||||
smtp::{
|
||||
@@ -197,7 +197,7 @@ impl EmailBuilder for SendEmailRequest {
|
||||
|
||||
async fn build(&self, account_id: u64) -> RustMailerResult<()> {
|
||||
self.validate().await?;
|
||||
let account = &Account::get(account_id).await?;
|
||||
let account = &AccountV2::get(account_id).await?;
|
||||
let from = self.from.clone().map(Into::into).unwrap_or_else(|| {
|
||||
Address::new_address(
|
||||
account.name.as_ref().map(|n| Cow::Owned(n.to_string())),
|
||||
@@ -330,7 +330,7 @@ impl SendEmailRequest {
|
||||
&self,
|
||||
mut builder: MessageBuilder<'static>,
|
||||
recipient: &Recipient,
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
tracker: Option<EmailTracker>,
|
||||
) -> RustMailerResult<MessageBuilder<'static>> {
|
||||
if let Some(attachments) = &self.attachments {
|
||||
@@ -432,7 +432,7 @@ impl SendEmailRequest {
|
||||
async fn apply_mail_attachments(
|
||||
mut builder: MessageBuilder<'static>,
|
||||
attachments: &[MailAttachment],
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
) -> RustMailerResult<MessageBuilder<'static>> {
|
||||
for attachment in attachments {
|
||||
let content = attachment.get_content(account).await?;
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
use crate::{
|
||||
modules::{
|
||||
account::entity::Account,
|
||||
cache::imap::envelope_v2::EmailEnvelopeV2,
|
||||
account::v2::AccountV2,
|
||||
cache::imap::v2::EmailEnvelopeV2,
|
||||
error::{code::ErrorCode, RustMailerResult},
|
||||
smtp::{
|
||||
composer::BodyComposer,
|
||||
@@ -127,7 +127,7 @@ impl EmailBuilder for ReplyEmailRequest {
|
||||
}
|
||||
|
||||
async fn build(&self, account_id: u64) -> RustMailerResult<()> {
|
||||
let account = &Account::get(account_id).await?;
|
||||
let account = &AccountV2::get(account_id).await?;
|
||||
let envelope = EmailHandler::get_envelope(account, &self.mailbox_name, self.uid).await?;
|
||||
|
||||
let from = Address::new_address(
|
||||
@@ -227,7 +227,7 @@ impl ReplyEmailRequest {
|
||||
&self,
|
||||
mut builder: MessageBuilder<'static>,
|
||||
envelope: &EmailEnvelopeV2,
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
) -> RustMailerResult<MessageBuilder<'static>> {
|
||||
let timezone = self.timezone.as_deref().unwrap_or("UTC");
|
||||
|
||||
@@ -302,7 +302,7 @@ impl ReplyEmailRequest {
|
||||
async fn apply_attachments(
|
||||
&self,
|
||||
mut builder: MessageBuilder<'static>,
|
||||
account: &Account,
|
||||
account: &AccountV2,
|
||||
) -> RustMailerResult<MessageBuilder<'static>> {
|
||||
if let Some(attachments) = &self.attachments {
|
||||
for attachment in attachments {
|
||||
|
||||
@@ -30,7 +30,7 @@ use crate::modules::smtp::{
|
||||
request::{EmailHandler, MailEnvelope, SendControl, Strategy},
|
||||
};
|
||||
|
||||
use crate::modules::{account::entity::Account, context::executors::RUST_MAIL_CONTEXT};
|
||||
use crate::modules::{account::v2::AccountV2, context::executors::RUST_MAIL_CONTEXT};
|
||||
|
||||
use mail_send::smtp::message::{Address, Message, Parameters};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -144,15 +144,15 @@ impl Task for SmtpTask {
|
||||
(executor, params)
|
||||
}
|
||||
None => {
|
||||
let account = Account::get(self.account_id).await?;
|
||||
let account = AccountV2::get(self.account_id).await?;
|
||||
let executor = RUST_MAIL_CONTEXT.smtp(account.id).await?;
|
||||
|
||||
let dsn_capable = if let Some(dsn_capable) = &account.dsn_capable {
|
||||
*dsn_capable
|
||||
} else {
|
||||
let capabilities = executor.capabilities(&account.smtp.host).await?;
|
||||
let capabilities = executor.capabilities(&account.smtp.as_ref().expect("BUG: account.smtp is None, but it should always be present at this point").host).await?;
|
||||
let dsn_capable = capabilities & EXT_DSN != 0;
|
||||
Account::update_dsn_capable(account.id, dsn_capable).await?;
|
||||
AccountV2::update_dsn_capable(account.id, dsn_capable).await?;
|
||||
dsn_capable
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under RustMailer License Agreement v1.0
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
use crate::modules::account::v2::AccountV2;
|
||||
use crate::modules::database::manager::DB_MANAGER;
|
||||
use crate::modules::database::{
|
||||
batch_delete_impl, delete_impl, paginate_query_primary_scan_all_impl,
|
||||
@@ -13,10 +14,7 @@ use crate::modules::rest::response::DataPage;
|
||||
use crate::modules::smtp::template::payload::{TemplateCreateRequest, TemplateUpdateRequest};
|
||||
use crate::modules::token::AccountInfo;
|
||||
use crate::{id, raise_error};
|
||||
use crate::{
|
||||
modules::account::entity::Account, modules::database::insert_impl,
|
||||
modules::error::RustMailerResult, utc_now,
|
||||
};
|
||||
use crate::{modules::database::insert_impl, modules::error::RustMailerResult, utc_now};
|
||||
use handlebars::Handlebars;
|
||||
use itertools::Itertools;
|
||||
use native_db::*;
|
||||
@@ -78,7 +76,7 @@ impl EmailTemplate {
|
||||
|
||||
pub async fn new(value: TemplateCreateRequest) -> RustMailerResult<Self> {
|
||||
let account_info = if let Some(account_id) = value.account_id {
|
||||
Account::get(account_id).await.map(|account| {
|
||||
AccountV2::get(account_id).await.map(|account| {
|
||||
Some(AccountInfo {
|
||||
id: account_id,
|
||||
email: account.email,
|
||||
@@ -217,7 +215,7 @@ impl EmailTemplate {
|
||||
}
|
||||
|
||||
async fn check_account_id(account_id: u64) -> RustMailerResult<()> {
|
||||
let _ = Account::get(account_id).await?;
|
||||
let _ = AccountV2::get(account_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use mail_send::{
|
||||
|
||||
use crate::{
|
||||
modules::{
|
||||
account::entity::Account,
|
||||
account::v2::AccountV2,
|
||||
context::executors::RUST_MAIL_CONTEXT,
|
||||
error::{code::ErrorCode, RustMailerResult},
|
||||
smtp::{
|
||||
@@ -35,7 +35,7 @@ pub async fn send_template_test_email(
|
||||
} = reqwest;
|
||||
|
||||
let template = EmailTemplate::get(template_id).await?;
|
||||
let account = Account::get(account_id).await?;
|
||||
let account = AccountV2::get(account_id).await?;
|
||||
|
||||
let (subject, text, html) = Templates::render(&template, &template_params)?;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Licensed under RustMailer License Agreement v1.0
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
use crate::modules::account::entity::Account;
|
||||
use crate::modules::account::v2::AccountV2;
|
||||
use crate::modules::database::delete_impl;
|
||||
use crate::modules::database::manager::DB_MANAGER;
|
||||
use crate::modules::database::{insert_impl, list_all_impl, update_impl};
|
||||
@@ -201,7 +201,7 @@ impl AccessToken {
|
||||
let account_infos = if let Some(accounts) = &request.accounts {
|
||||
let mut account_infos = BTreeSet::new();
|
||||
for account_id in accounts {
|
||||
let account = Account::get(*account_id).await?;
|
||||
let account = AccountV2::get(*account_id).await?;
|
||||
account_infos.insert(AccountInfo {
|
||||
id: *account_id,
|
||||
email: account.email,
|
||||
@@ -268,7 +268,7 @@ impl AccessToken {
|
||||
|
||||
let mut account_infos = BTreeSet::new();
|
||||
for &account_id in &accounts {
|
||||
let account = Account::get(account_id).await?;
|
||||
let account = AccountV2::get(account_id).await?;
|
||||
account_infos.insert(AccountInfo {
|
||||
id: account_id,
|
||||
email: account.email,
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::collections::BTreeSet;
|
||||
|
||||
use crate::{
|
||||
modules::{
|
||||
account::entity::Account,
|
||||
account::v2::AccountV2,
|
||||
error::{code::ErrorCode, RustMailerResult},
|
||||
token::{AccessControl, AccessTokenScope},
|
||||
},
|
||||
@@ -44,7 +44,7 @@ impl AccessTokenCreateRequest {
|
||||
|
||||
let mut not_found = Vec::new();
|
||||
for account_id in &self.accounts {
|
||||
if Account::find(*account_id).await?.is_none() {
|
||||
if AccountV2::find(*account_id).await?.is_none() {
|
||||
not_found.push(*account_id);
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,7 @@ impl AccessTokenUpdateRequest {
|
||||
|
||||
let mut not_found = Vec::new();
|
||||
for account_id in accounts {
|
||||
if Account::find(*account_id).await?.is_none() {
|
||||
if AccountV2::find(*account_id).await?.is_none() {
|
||||
not_found.push(*account_id);
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
// Copyright © 2025 rustmailer.com
|
||||
// Licensed under RustMailer License Agreement v1.0
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
|
||||
Vendored
+44
@@ -0,0 +1,44 @@
|
||||
// Copyright © 2025 rustmailer.com
|
||||
// Licensed under RustMailer License Agreement v1.0
|
||||
// Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
use reqwest::header::{AUTHORIZATION, CONTENT_TYPE};
|
||||
use serde_json::Value;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::rustmailer_version;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test1() {
|
||||
let access_token = "ya29.A0AS3H6Nx8aJrWEN2JG6zsSmhp_wg9BSS6i0uPoEbgPsdfdPtEydMK25ne2P0mL7FVu6g_F4rpTIDZxi4CO96LtlWokbTPd69vaVHx07qJfnRchq3lrFBLpm-OPqV0zLXbJ6X9VSsVW0Cd5W5ykSYjkQMXUA4u7iM7bpjps49VQo10_-y4l_FO7L28-Q0HGRCvdXJNRqgaaCgYKAVsSARUSFQHGX2Mi9nJ8LfBUMLimPBr_wEahxg0207";
|
||||
let url = "https://gmail.googleapis.com/gmail/v1/users/me/labels";
|
||||
let url = "https://gmail.googleapis.com/gmail/v1/users/me/labels/Label_6886728075529239043";
|
||||
let url = "https://gmail.googleapis.com/gmail/v1/users/me/messages?labelIds=INBOX&maxResults=10&pageToken=08792416985640480557";
|
||||
let url = "https://gmail.googleapis.com/gmail/v1/users/me/messages/198e590baf688394?format=metadata";
|
||||
let url = "https://gmail.googleapis.com/gmail/v1/users/me/messages?labelIds=INBOX&maxResults=10";
|
||||
let mut builder = reqwest::ClientBuilder::new()
|
||||
.user_agent(rustmailer_version!())
|
||||
.timeout(Duration::from_secs(10))
|
||||
.connect_timeout(Duration::from_secs(10));
|
||||
|
||||
let proxy_obj = reqwest::Proxy::all("socks5://127.0.0.1:22308").unwrap();
|
||||
builder = builder
|
||||
.redirect(reqwest::redirect::Policy::none())
|
||||
.proxy(proxy_obj);
|
||||
let client = builder.build().unwrap();
|
||||
let res = client
|
||||
.get(url)
|
||||
.header(AUTHORIZATION, format!("Bearer {}", access_token))
|
||||
.header(CONTENT_TYPE, "application/json")
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if res.status().is_success() {
|
||||
let body: Value = res.json().await.unwrap();
|
||||
let pretty = serde_json::to_string_pretty(&body).unwrap();
|
||||
println!("Response = {}", pretty);
|
||||
} else {
|
||||
eprintln!("Error: {} - {:?}", res.status(), res.text().await.unwrap());
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
* Unauthorized use or distribution is prohibited.
|
||||
*/
|
||||
|
||||
import { AccountEntity } from '../data/schema'
|
||||
import { AccountEntity, MailerType } from '../data/schema'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
@@ -62,13 +62,21 @@ export function AccountDetailDrawer({ open, onOpenChange, currentRow }: Props) {
|
||||
<span className="text-muted-foreground">Enabled:</span>
|
||||
<Checkbox checked={currentRow.enabled} disabled />
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-muted-foreground">Mailer Type:</span>
|
||||
<span>{currentRow.mailer_type}</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-muted-foreground">Minimal Sync:</span>
|
||||
<Checkbox checked={currentRow.minimal_sync} disabled />
|
||||
{currentRow.minimal_sync !== undefined ? (
|
||||
<Checkbox checked={currentRow.minimal_sync} disabled />
|
||||
) : (
|
||||
<span className="text-muted-foreground">n/a</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-muted-foreground">Full Sync Interval:</span>
|
||||
<span>every {currentRow.full_sync_interval_min} min</span>
|
||||
<span>{currentRow.full_sync_interval_min ? `every ${currentRow.full_sync_interval_min} min` : "n/a"}</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-muted-foreground">Incremental Sync Interval:</span>
|
||||
@@ -77,7 +85,7 @@ export function AccountDetailDrawer({ open, onOpenChange, currentRow }: Props) {
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="text-muted-foreground">Capabilities:</span>
|
||||
<code className="rounded-md bg-muted/50 px-2 py-1 text-sm border overflow-x-auto inline-block">
|
||||
{currentRow.capabilities.join(', ')}
|
||||
{currentRow.capabilities ? currentRow.capabilities.join(', ') : "n/a"}
|
||||
</code>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
@@ -97,7 +105,7 @@ export function AccountDetailDrawer({ open, onOpenChange, currentRow }: Props) {
|
||||
|
||||
{/* Server Configurations Tab */}
|
||||
<TabsContent value="server">
|
||||
<div className="grid gap-4 mt-4 md:grid-cols-2">
|
||||
{currentRow.mailer_type === MailerType.ImapSmtp ? <div className="grid gap-4 mt-4 md:grid-cols-2">
|
||||
{/* IMAP Configuration Card */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@@ -107,19 +115,19 @@ export function AccountDetailDrawer({ open, onOpenChange, currentRow }: Props) {
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-muted-foreground">Host:</span>
|
||||
<span>{currentRow.imap.host}</span>
|
||||
<span>{currentRow.imap?.host}</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-muted-foreground">Port:</span>
|
||||
<span>{currentRow.imap.port}</span>
|
||||
<span>{currentRow.imap?.port}</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-muted-foreground">Encryption:</span>
|
||||
<span>{currentRow.imap.encryption}</span>
|
||||
<span>{currentRow.imap?.encryption}</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-muted-foreground">Auth:</span>
|
||||
{currentRow.imap.auth.auth_type === 'OAuth2' ? (
|
||||
{currentRow.imap?.auth.auth_type === 'OAuth2' ? (
|
||||
<Badge variant="outline" className="bg-blue-100 text-blue-800">
|
||||
OAuth2
|
||||
</Badge>
|
||||
@@ -131,7 +139,7 @@ export function AccountDetailDrawer({ open, onOpenChange, currentRow }: Props) {
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-muted-foreground">Use Proxy:</span>
|
||||
<span>{currentRow.imap.use_proxy ? "true" : "false"}</span>
|
||||
<span>{currentRow.imap?.use_proxy ? "true" : "false"}</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -146,19 +154,19 @@ export function AccountDetailDrawer({ open, onOpenChange, currentRow }: Props) {
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-muted-foreground">Host:</span>
|
||||
<span>{currentRow.smtp.host}</span>
|
||||
<span>{currentRow.smtp?.host}</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-muted-foreground">Port:</span>
|
||||
<span>{currentRow.smtp.port}</span>
|
||||
<span>{currentRow.smtp?.port}</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-muted-foreground">Encryption:</span>
|
||||
<span>{currentRow.smtp.encryption}</span>
|
||||
<span>{currentRow.smtp?.encryption}</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-muted-foreground">Auth:</span>
|
||||
{currentRow.smtp.auth.auth_type === 'OAuth2' ? (
|
||||
{currentRow.smtp?.auth.auth_type === 'OAuth2' ? (
|
||||
<Badge variant="outline" className="bg-blue-100 text-blue-800">
|
||||
OAuth2
|
||||
</Badge>
|
||||
@@ -170,12 +178,14 @@ export function AccountDetailDrawer({ open, onOpenChange, currentRow }: Props) {
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-muted-foreground">Use Proxy:</span>
|
||||
<span>{currentRow.smtp.use_proxy ? "true" : "false"}</span>
|
||||
<span>{currentRow.smtp?.use_proxy ? "true" : "false"}</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div> : <div className="mt-4 text-muted-foreground">
|
||||
No IMAP/SMTP configuration required (using {currentRow.mailer_type}).
|
||||
</div>}
|
||||
</TabsContent>
|
||||
|
||||
{/* Sync Folders Tab */}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Form } from '@/components/ui/form';
|
||||
import { AccountEntity } from '../data/schema';
|
||||
import { AccountEntity, ImapConfig, SmtpConfig } from '../data/schema';
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
@@ -212,9 +212,26 @@ const defaultValues: Account = {
|
||||
incremental_sync_interval_sec: 30,
|
||||
};
|
||||
|
||||
const emptyImap: ImapConfig = {
|
||||
host: "",
|
||||
port: 0,
|
||||
encryption: "None",
|
||||
auth: { auth_type: "Password", password: undefined },
|
||||
use_proxy: undefined,
|
||||
};
|
||||
|
||||
const emptySmtp: SmtpConfig = {
|
||||
host: "",
|
||||
port: 0,
|
||||
encryption: "None",
|
||||
auth: { auth_type: "Password", password: undefined },
|
||||
use_proxy: undefined,
|
||||
};
|
||||
|
||||
|
||||
const mapCurrentRowToFormValues = (currentRow: AccountEntity): Account => {
|
||||
const imap = { ...currentRow.imap };
|
||||
const smtp = { ...currentRow.smtp };
|
||||
const imap = { ...(currentRow.imap ?? emptyImap) };
|
||||
const smtp = { ...(currentRow.smtp ?? emptySmtp) };
|
||||
|
||||
// Handle password and use_proxy conversion
|
||||
imap.auth = { ...imap.auth, password: undefined };
|
||||
@@ -233,9 +250,9 @@ const mapCurrentRowToFormValues = (currentRow: AccountEntity): Account => {
|
||||
imap,
|
||||
smtp,
|
||||
enabled: currentRow.enabled,
|
||||
minimal_sync: currentRow.minimal_sync,
|
||||
minimal_sync: currentRow.minimal_sync ?? false,
|
||||
date_since: currentRow.date_since ?? undefined,
|
||||
full_sync_interval_min: currentRow.full_sync_interval_min,
|
||||
full_sync_interval_min: currentRow.full_sync_interval_min ?? 60,
|
||||
incremental_sync_interval_sec: currentRow.incremental_sync_interval_sec,
|
||||
};
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { Row } from '@tanstack/react-table'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useAccountContext } from '../context'
|
||||
import { AccountEntity } from '../data/schema'
|
||||
import { AccountEntity, MailerType } from '../data/schema'
|
||||
|
||||
interface DataTableRowActionsProps {
|
||||
row: Row<AccountEntity>
|
||||
@@ -16,12 +16,16 @@ interface DataTableRowActionsProps {
|
||||
export function OAuth2Action({ row }: DataTableRowActionsProps) {
|
||||
const { setOpen, setCurrentRow } = useAccountContext()
|
||||
|
||||
return row.original.imap.auth.auth_type === 'OAuth2' ? (
|
||||
return (
|
||||
(row.original.mailer_type === MailerType.ImapSmtp &&
|
||||
row.original.imap?.auth.auth_type === 'OAuth2') ||
|
||||
row.original.mailer_type === MailerType.GmailApi
|
||||
) ? (
|
||||
<Button variant='ghost' onClick={() => {
|
||||
setCurrentRow(row.original)
|
||||
setOpen('oauth2')
|
||||
}}><span className="text-xs text-blue-500 cursor-pointer underline hover:text-blue-700">{row.original.imap.auth.auth_type}</span></Button>
|
||||
}}><span className="text-xs text-blue-500 cursor-pointer underline hover:text-blue-700">OAuth2</span></Button>
|
||||
) : (
|
||||
<span className="text-xs cursor-pointer">{row.original.imap.auth.auth_type}</span>
|
||||
<span className="text-xs cursor-pointer">Password</span>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ interface AuthConfig {
|
||||
password?: string;
|
||||
}
|
||||
|
||||
interface SmtpConfig {
|
||||
export interface SmtpConfig {
|
||||
host: string;
|
||||
port: number; // integer, 0-65535
|
||||
encryption: Encryption;
|
||||
@@ -22,7 +22,7 @@ interface SmtpConfig {
|
||||
use_proxy?: number;
|
||||
}
|
||||
|
||||
interface ImapConfig {
|
||||
export interface ImapConfig {
|
||||
host: string;
|
||||
port: number; // integer, 0-65535
|
||||
encryption: Encryption;
|
||||
@@ -42,18 +42,28 @@ interface DateSelection {
|
||||
|
||||
export interface AccountEntity {
|
||||
id: number;
|
||||
imap: ImapConfig;
|
||||
smtp: SmtpConfig;
|
||||
imap?: ImapConfig;
|
||||
smtp?: SmtpConfig;
|
||||
enabled: boolean;
|
||||
mailer_type: MailerType,
|
||||
deleted: boolean;
|
||||
name?: string,
|
||||
email: string;
|
||||
minimal_sync: boolean;
|
||||
capabilities: string[];
|
||||
minimal_sync?: boolean;
|
||||
capabilities?: string[];
|
||||
date_since?: DateSelection;
|
||||
sync_folders?: string[];
|
||||
full_sync_interval_min: number;
|
||||
full_sync_interval_min?: number;
|
||||
incremental_sync_interval_sec: number;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
}
|
||||
|
||||
|
||||
// Represents the method used to access/manage emails
|
||||
export enum MailerType {
|
||||
/** Use IMAP/SMTP protocol */
|
||||
ImapSmtp = "ImapSmtp",
|
||||
/** Use Gmail API */
|
||||
GmailApi = "GmailApi",
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import AccountProvider, {
|
||||
} from './context'
|
||||
import { Plus } from 'lucide-react'
|
||||
import Logo from '@/assets/logo.svg'
|
||||
import { AccountEntity } from './data/schema'
|
||||
import { AccountEntity, MailerType } from './data/schema'
|
||||
import { AccountDetailDrawer } from './components/account-detail'
|
||||
import { list_accounts } from '@/api/account/api'
|
||||
import { TableSkeleton } from '@/components/table-skeleton'
|
||||
@@ -141,10 +141,14 @@ export default function Accounts() {
|
||||
onOpenChange={() => setOpen('detail')}
|
||||
currentRow={currentRow}
|
||||
/>
|
||||
{currentRow.imap.auth.auth_type === 'OAuth2' && <OAuth2TokensDialog open={open === 'oauth2'}
|
||||
{(
|
||||
(currentRow.mailer_type === MailerType.ImapSmtp &&
|
||||
currentRow.imap?.auth.auth_type === 'OAuth2') ||
|
||||
currentRow.mailer_type === MailerType.GmailApi
|
||||
) && <OAuth2TokensDialog open={open === 'oauth2'}
|
||||
onOpenChange={() => setOpen('oauth2')}
|
||||
currentRow={currentRow}
|
||||
/>}
|
||||
/>}
|
||||
</>
|
||||
)}
|
||||
</AccountProvider>
|
||||
|
||||
Reference in New Issue
Block a user