//
// Copyright (c) 2025 rustmailer.com (https://rustmailer.com)
//
// This file is part of the Bichon Email Archiving Project
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
use native_db::*;
use native_model::{native_model, Model};
use poem_openapi::{Enum, Object};
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
use tracing::info;
use crate::{
encrypt,
modules::{
account::{
entity::ImapConfig,
since::{DateSince, RelativeDate},
state::AccountRunningState,
},
cache::imap::mailbox::MailBox,
database::{list_all_impl, secondary_find_impl, with_transaction},
error::BichonResult,
indexer::manager::{EML_INDEX_MANAGER, ENVELOPE_INDEX_MANAGER},
users::{role::DEFAULT_ACCOUNT_MANAGER_ROLE_ID, UserModel, DEFAULT_ADMIN_USER_ID},
},
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::task::SYNC_TASKS;
use crate::modules::context::controller::SYNC_CONTROLLER;
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::{
async_secondary_find_impl, paginate_query_primary_scan_all_impl, update_impl,
};
use crate::modules::error::code::ErrorCode;
use crate::modules::oauth2::token::OAuth2AccessToken;
use crate::modules::rest::response::DataPage;
use crate::raise_error;
pub type AccountModel = AccountV4;
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Enum)]
pub enum AccountType {
#[default]
IMAP,
NoSync,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[native_model(id = 4, version = 1)]
#[native_db(primary_key(pk -> String))]
pub struct AccountV1 {
#[secondary_key(unique)]
pub id: u64,
pub imap: Option,
pub enabled: bool,
#[oai(validator(custom = "crate::modules::common::validator::EmailValidator"))]
pub email: String,
pub name: Option,
pub capabilities: Option>,
pub date_since: Option,
pub folder_limit: Option,
pub sync_folders: Option>,
pub account_type: AccountType,
pub sync_interval_min: Option,
pub known_folders: Option>,
pub created_at: i64,
pub updated_at: i64,
pub use_proxy: Option,
}
impl AccountV1 {
fn pk(&self) -> String {
format!("{}_{}", self.created_at, self.id)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[native_model(id = 4, version = 2, from = AccountV1)]
#[native_db(primary_key(pk -> String))]
pub struct AccountV2 {
#[secondary_key(unique)]
pub id: u64,
pub imap: Option,
pub enabled: bool,
#[oai(validator(custom = "crate::modules::common::validator::EmailValidator"))]
pub email: String,
pub name: Option,
pub capabilities: Option>,
pub date_since: Option,
pub folder_limit: Option,
pub sync_folders: Option>,
pub account_type: AccountType,
pub sync_interval_min: Option,
pub known_folders: Option>,
pub created_at: i64,
pub updated_at: i64,
pub use_proxy: Option,
pub use_dangerous: bool,
pub pgp_key: Option,
}
impl AccountV2 {
fn pk(&self) -> String {
format!("{}_{}", self.created_at, self.id)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[native_model(id = 4, version = 3, from = AccountV2)]
#[native_db(primary_key(pk -> String))]
pub struct AccountV3 {
#[secondary_key(unique)]
pub id: u64,
pub imap: Option,
pub enabled: bool,
#[oai(validator(custom = "crate::modules::common::validator::EmailValidator"))]
pub email: String,
pub name: Option,
pub capabilities: Option>,
pub date_since: Option,
pub date_before: Option,
pub folder_limit: Option,
pub sync_folders: Option>,
pub account_type: AccountType,
pub sync_interval_min: Option,
pub sync_batch_size: 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,
}
impl AccountV3 {
fn pk(&self) -> String {
format!("{}_{}", self.created_at, self.id)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[native_model(id = 4, version = 4, from = AccountV3)]
#[native_db(primary_key(pk -> String))]
pub struct AccountV4 {
#[secondary_key(unique)]
pub id: u64,
pub imap: Option,
pub enabled: bool,
#[oai(validator(custom = "crate::modules::common::validator::EmailValidator"))]
pub email: String,
pub name: Option,
pub capabilities: Option>,
pub date_since: Option,
pub date_before: Option,
pub folder_limit: Option,
pub sync_folders: Option>,
pub account_type: AccountType,
pub sync_interval_min: Option,
pub sync_batch_size: 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_daily_quota_bytes: Option,
pub auto_sync_new_mailboxes: Option,
}
impl AccountV4 {
fn pk(&self) -> String {
format!("{}_{}", self.created_at, self.id)
}
pub fn new(user_id: u64, request: AccountCreateRequest) -> BichonResult {
Ok(Self {
id: id!(64),
email: request.email,
name: request.name,
imap: request.imap.map(|i| i.try_encrypt_password()).transpose()?,
enabled: request.enabled,
capabilities: None,
date_since: request.date_since,
sync_folders: None,
known_folders: None,
account_type: request.account_type,
sync_interval_min: request.sync_interval_min,
created_at: utc_now!(),
updated_at: utc_now!(),
use_proxy: request.use_proxy,
folder_limit: request.folder_limit,
use_dangerous: request.use_dangerous,
pgp_key: request.pgp_key,
created_by: user_id,
sync_batch_size: request.sync_batch_size,
date_before: request.date_before,
imap_daily_quota_bytes: request.imap_daily_quota_bytes,
auto_sync_new_mailboxes: request.auto_sync_new_mailboxes,
})
}
pub async fn check_account_exists(account_id: u64) -> BichonResult {
let account = async_secondary_find_impl::(
DB_MANAGER.meta_db(),
AccountV4Key::id,
account_id,
)
.await?
.ok_or_else(|| {
raise_error!(
format!("Account id='{account_id}' not found"),
ErrorCode::ResourceNotFound
)
})?;
Ok(account)
}
/// Fetches an `AccountEntity` by its `id`.
pub async fn async_get(account_id: u64) -> BichonResult {
let result: AccountModel = Self::async_find(account_id).await?.ok_or_else(|| {
raise_error!(
format!("Account with ID '{account_id}' not found"),
ErrorCode::ResourceNotFound
)
})?;
Ok(result)
}
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 async fn async_find(account_id: u64) -> BichonResult