//
// Copyright (c) 2025-2026 rustmailer.com (https://rustmailer.com)
//
// This file is part of the Bichon Email Archiving Project
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
use tracing::info;
use crate::{
account::{
entity::ImapConfig,
payload::{AccountCreateRequest, AccountUpdateRequest, MinimalAccount},
since::{DateSince, RelativeDate},
state::DownloadState,
},
cache::imap::{mailbox::MailBox, task::SYNC_TASKS},
common::paginated::DataPage,
context::controller::DOWNLOAD_CONTROLLER,
database::{
count_impl, delete_impl, find_impl, insert_impl, list_all_impl, manager::DB_MANAGER,
paginate_impl, update_impl, MemDbModel,
},
encrypt,
error::{code::ErrorCode, BichonResult},
id,
oauth2::token::OAuth2AccessToken,
raise_error,
store::tantivy::{attachment::ATTACHMENT_MANAGER, envelope::ENVELOPE_MANAGER},
users::{payload::UserUpdateRequest, role::DEFAULT_ACCOUNT_MANAGER_ROLE_ID, UserModel},
utc_now,
};
pub type AccountModel = Account;
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Enum))]
pub enum AccountType {
#[default]
IMAP,
NoSync,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Enum))]
pub enum QuotaWindow {
Hourly,
#[default]
Daily,
Weekly,
Monthly,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct Account {
pub id: u64,
pub imap: Option,
pub enabled: bool,
#[cfg_attr(
feature = "web-api",
oai(validator(custom = "crate::common::validator::EmailValidator"))
)]
pub email: String,
pub account_name: Option,
pub login_name: Option,
pub capabilities: Option>,
pub date_since: Option,
pub date_before: Option,
pub download_folders: Option>,
pub account_type: AccountType,
pub download_interval_min: Option,
pub download_batch_size: Option,
#[serde(default)]
pub max_email_size_bytes: Option,
pub known_folders: Option>,
pub created_at: i64,
pub updated_at: i64,
pub created_by: u64, //user id
pub use_proxy: Option,
pub use_dangerous: bool,
pub pgp_key: Option,
pub imap_quota_bytes: Option,
pub imap_quota_window: Option,
pub auto_download_new_mailboxes: Option,
pub download_schedule: Option,
#[serde(default)]
pub deleting: bool,
}
impl MemDbModel for Account {
fn collection() -> &'static str {
"accounts"
}
fn key(&self) -> String {
self.id.to_string()
}
}
impl Account {
pub fn new(user_id: u64, request: AccountCreateRequest) -> BichonResult {
Ok(Self {
id: id!(64),
email: request.email,
login_name: request.login_name,
account_name: request.account_name,
imap: request.imap.map(|i| i.try_encrypt_password()).transpose()?,
enabled: request.enabled,
capabilities: None,
date_since: request.date_since,
download_folders: None,
known_folders: None,
account_type: request.account_type,
download_interval_min: request.download_interval_min,
created_at: utc_now!(),
updated_at: utc_now!(),
use_proxy: request.use_proxy,
use_dangerous: request.use_dangerous,
pgp_key: request.pgp_key,
created_by: user_id,
download_batch_size: request.download_batch_size,
max_email_size_bytes: request.max_email_size_bytes,
date_before: request.date_before,
auto_download_new_mailboxes: request.auto_download_new_mailboxes,
imap_quota_bytes: request.imap_quota_bytes,
imap_quota_window: request.imap_quota_window,
download_schedule: request.download_schedule,
deleting: false,
})
}
pub fn check_account_exists(account_id: u64) -> BichonResult {
Self::get(account_id)
}
pub fn get(account_id: u64) -> BichonResult {
let result: AccountModel = Self::find(account_id)?.ok_or_else(|| {
raise_error!(
format!("Account with ID '{account_id}' not found"),
ErrorCode::ResourceNotFound
)
})?;
Ok(result)
}
pub fn find(account_id: u64) -> BichonResult