refactor(workspace): decompose project into multiple crates

This commit is contained in:
rustmailer
2026-04-23 21:45:34 +08:00
parent 5b884125f7
commit 0b866c81ff
171 changed files with 2203 additions and 2042 deletions
Generated
+502 -457
View File
File diff suppressed because it is too large Load Diff
+15 -49
View File
@@ -1,48 +1,20 @@
[package]
name = "bichon"
[workspace]
members = ["crates/core", "crates/server", "crates/cli", "crates/admin"]
resolver = "2"
[workspace.package]
version = "1.0.0"
edition = "2021"
[[bin]]
name = "bichon"
path = "src/main.rs"
[[bin]]
name = "bichonctl"
path = "src/bin/bichonctl.rs"
[[bin]]
name = "bichon-admin"
path = "src/bin/bichon_admin.rs"
[features]
default = []
vendored-openssl = ["openssl-sys"]
[profile.release]
strip = true
lto = true
opt-level = 3
codegen-units = 1
[dependencies]
[workspace.dependencies]
chrono = "0.4.44"
clap = { version = "4.6.1", features = ["derive", "env"] }
mimalloc = "0.1.49"
native_db = "0.8.2"
itertools = "0.14.0"
native_model = "0.4.20"
poem = { version = "3.1.12", features = ["embed", "compression", "rustls"] }
poem-derive = "3.1.12"
poem-openapi = { version = "5.1.16", features = [
"openapi-explorer",
"rapidoc",
"scalar",
"redoc",
"swagger-ui",
"email",
] }
ring = { version = "0.17.14", features = ["std"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149"
@@ -74,7 +46,6 @@ tokio-rustls = { version = "0.26.4", default-features = false, features = [
"tls12",
] }
timeago = "0.6.0"
ahash = "0.8.12"
oauth2 = { version = "5.0.0", features = ["reqwest-blocking"] }
url = { version = "2.5.8", features = ["serde"] }
sysinfo = "0.38.4"
@@ -104,19 +75,12 @@ murmur3 = "0.5.2"
autoconfig = "0.4.0"
urlencoding = "2.1.3"
dashmap = "6.1.0"
# Statically links OpenSSL by compiling from source, avoiding system library dependencies
openssl-sys = { version = "0.9.114", optional = true, features = ["vendored"] }
gethostname = "1.1.0"
itoa = "1.0.18"
html2text = "0.17.1"
bytes = "1.11.1"
dialoguer = "0.12.0"
console = "0.16.3"
toml = "0.9.8"
memmap2 = "0.9.10"
outlook-pst = { git = "https://github.com/rustmailer/outlook-pst-rs.git", branch = "main" }
compressed-rtf = "1.0.1"
codepage-strings = "1.0.2"
mail-send = "0.6.0"
rcgen = "0.14.7"
rustls-pemfile = "2.2.0"
@@ -126,8 +90,10 @@ fjall = { version = "3.1.4", features = ["lz4", "metrics", "bytes_1"] }
tantivy = { version = "0.26.0", features = ["zstd-compression"] }
tracing-log = "0.2.0"
tokio-util = "0.7.18"
[dev-dependencies]
#bincode = "1.3.3"
#secret-lib = "1.0.0"
tempfile = "3.27.0"
lettre = "0.11.21"
[profile.release]
strip = true
lto = true
opt-level = 3
codegen-units = 1
+11
View File
@@ -0,0 +1,11 @@
[package]
name = "bichon-admin"
version.workspace = true
edition.workspace = true
[dependencies]
bichon-core = { path = "../core" }
tokio.workspace = true
dialoguer.workspace = true
console.workspace = true
@@ -16,22 +16,19 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use std::{
fs,
path::{Path, PathBuf},
rc::Rc,
};
use bichon::modules::{
cli::admin::meta::{find_admin, init_meta_database, update_admin_password},
use bichon_core::{
admin::meta::{find_admin, init_meta_database, update_admin_password},
error::BichonError,
utils::encrypt::internal_decrypt_string,
};
use console::{style, Emoji};
use dialoguer::Confirm;
use dialoguer::{theme::ColorfulTheme, Input, Password, Select};
use native_db::Database;
#[tokio::main]
async fn main() {
@@ -75,7 +72,7 @@ async fn main() {
let root_path = PathBuf::from(&root_dir_str);
let database: Rc<Database<'static>> = match init_meta_database(&root_path.join("meta.db")) {
let database = match init_meta_database(&root_path.join("meta.db")) {
Ok(database) => database,
Err(e) => match e {
BichonError::Generic {
+24
View File
@@ -0,0 +1,24 @@
[package]
name = "bichon-cli"
version.workspace = true
edition.workspace = true
[dependencies]
bichon-core = { path = "../core" }
tokio.workspace = true
serde.workspace = true
clap.workspace = true
dialoguer.workspace = true
console.workspace = true
mail-parser.workspace = true
reqwest.workspace = true
toml = "0.9.8"
memmap2 = "0.9.10"
outlook-pst = { git = "https://github.com/rustmailer/outlook-pst-rs.git", branch = "main" }
compressed-rtf = "1.0.1"
chrono.workspace = true
mail-send.workspace = true
base64.workspace = true
codepage-strings = "1.0.2"
hex = "0.4.3"
@@ -16,19 +16,19 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use std::process;
use console::style;
use dialoguer::{theme::ColorfulTheme, Select};
use reqwest::Client;
use crate::modules::{
use bichon_core::{
account::payload::MinimalAccount,
cli::BichonCtlConfig,
users::{permissions::Permission, view::UserView},
};
use crate::BichonCtlConfig;
pub async fn verify_user_and_get_account(config: &BichonCtlConfig, theme: &ColorfulTheme) -> u64 {
let client = Client::new();
let url = format!("{}/api/v1/current-user", config.base_url);
@@ -16,7 +16,6 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use std::{
collections::HashMap,
fs,
@@ -28,10 +27,9 @@ use dialoguer::{theme::ColorfulTheme, Input};
use mail_parser::MessageParser;
use reqwest::Client;
use crate::{
base64_encode_url_safe,
modules::cli::{sender::send_batch_request, BichonCtlConfig},
};
use bichon_core::base64_encode_url_safe;
use crate::{sender::send_batch_request, BichonCtlConfig};
pub async fn handle_eml_directory_import(
config: &BichonCtlConfig,
@@ -16,17 +16,51 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use bichon::modules::cli::{
auth::verify_user_and_get_account, eml::handle_eml_directory_import,
mbox::handle_mbox_single_file_import, pst::handle_pst_import,
thunderbird::handle_thunderbird_import, BichonCli, BichonCtlConfig,
};
use bichon_core::bichon_version;
use clap::Parser;
use console::style;
use dialoguer::{theme::ColorfulTheme, Confirm, Input, Select};
use serde::{Deserialize, Serialize};
use std::fs;
use crate::{
auth::verify_user_and_get_account, eml::handle_eml_directory_import,
mbox::handle_mbox_single_file_import, pst::handle_pst_import,
thunderbird::handle_thunderbird_import,
};
pub mod auth;
pub mod eml;
pub mod mbox;
pub mod pst;
pub mod sender;
pub mod thunderbird;
#[derive(Parser, Debug)]
#[command(
name = "bichonctl",
author = "rustmailer",
version = bichon_version!(),
about = "A CLI tool to import email data into Bichon service"
)]
pub struct BichonCli {
/// Path to the configuration file
#[arg(
short,
long,
default_value = "config.toml",
value_name = "FILE",
help = "Sets a custom config file"
)]
pub config: std::path::PathBuf,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BichonCtlConfig {
pub base_url: String,
pub api_token: String,
}
#[tokio::main]
async fn main() {
let cli = BichonCli::parse();
@@ -19,11 +19,11 @@
use std::collections::HashMap;
use std::path::PathBuf;
use crate::base64_encode_url_safe;
use crate::modules::cli::mbox::gmail::determine_folder;
use crate::modules::cli::mbox::reader::MboxFile;
use crate::modules::cli::sender::send_batch_request;
use crate::modules::cli::BichonCtlConfig;
use crate::mbox::gmail::determine_folder;
use crate::mbox::reader::MboxFile;
use crate::sender::send_batch_request;
use crate::BichonCtlConfig;
use bichon_core::base64_encode_url_safe;
use console::style;
use dialoguer::{theme::ColorfulTheme, Input};
use dialoguer::{Confirm, Select};
@@ -16,7 +16,6 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use memmap2::Mmap;
use std::fs;
use std::io;
@@ -130,7 +129,7 @@ impl<'a> Iterator for MboxReader<'a> {
mod tests {
use mail_parser::MessageParser;
use crate::modules::cli::mbox::gmail::determine_folder;
use crate::mbox::gmail::determine_folder;
use super::*;
@@ -221,7 +220,6 @@ mod tests {
labels,
determine_folder(labels)
)
}
}
}
@@ -16,7 +16,6 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use chrono::{DateTime, TimeZone, Utc};
use dialoguer::theme::ColorfulTheme;
use dialoguer::Input;
@@ -24,10 +23,10 @@ use mail_send::mail_builder::headers::text::Text;
use mail_send::mail_builder::MessageBuilder;
use outlook_pst::ltp::prop_context::PropertyValue;
use crate::base64_encode_url_safe;
use crate::modules::cli::pst::encoding::decode_subject;
use crate::modules::cli::sender::send_batch_request;
use crate::modules::cli::BichonCtlConfig;
use crate::BichonCtlConfig;
use crate::pst::encoding::decode_subject;
use crate::sender::send_batch_request;
use bichon_core::base64_encode_url_safe;
use dialoguer::Confirm;
use outlook_pst::messaging::attachment::AttachmentProperties;
use outlook_pst::messaging::folder::Folder;
@@ -16,11 +16,12 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use console::style;
use reqwest::Client;
use crate::modules::{cli::BichonCtlConfig, import::BatchEmlRequest};
use bichon_core::import::BatchEmlRequest;
use crate::BichonCtlConfig;
pub async fn send_batch_request(
client: &Client,
@@ -16,10 +16,9 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use std::{collections::HashMap, path::PathBuf};
use crate::modules::cli::{mbox::run_import, BichonCtlConfig};
use crate::{mbox::run_import, BichonCtlConfig};
use console::style;
use dialoguer::{theme::ColorfulTheme, Confirm, Input};
+67
View File
@@ -0,0 +1,67 @@
[package]
name = "bichon-core"
version.workspace = true
edition.workspace = true
[features]
default = ["web-api"]
web-api = ["dep:poem-openapi"]
[dependencies]
poem-openapi = { version = "5.1.16", features = [
"openapi-explorer",
"rapidoc",
"scalar",
"redoc",
"swagger-ui",
"email",
], optional = true }
chrono.workspace = true
clap.workspace = true
native_db.workspace = true
itertools.workspace = true
native_model.workspace = true
ring.workspace = true
serde.workspace = true
serde_json.workspace = true
tokio.workspace = true
tracing.workspace = true
tracing-appender.workspace = true
tracing-subscriber.workspace = true
base64.workspace = true
snafu.workspace = true
reqwest.workspace = true
tokio-socks.workspace = true
regex.workspace = true
email_address.workspace = true
futures.workspace = true
utf7-imap.workspace = true
mail-parser.workspace = true
tokio-rustls.workspace = true
oauth2.workspace = true
sysinfo.workspace = true
num_cpus.workspace = true
rand.workspace = true
encoding_rs.workspace = true
async-imap.workspace = true
webpki-roots.workspace = true
rustls.workspace = true
rustls-pki-types.workspace = true
tokio-io-timeout.workspace = true
governor.workspace = true
lru.workspace = true
time.workspace = true
murmur3.workspace = true
autoconfig.workspace = true
dashmap.workspace = true
itoa.workspace = true
html2text.workspace = true
bytes.workspace = true
mail-send.workspace = true
blake3.workspace = true
uuid.workspace = true
fjall.workspace = true
tantivy.workspace = true
tracing-log.workspace = true
tokio-util.workspace = true
@@ -16,19 +16,19 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::{encrypt, error::BichonResult};
use crate::{encrypt, modules::error::BichonResult};
use poem_openapi::{Enum, Object};
//use poem_openapi::{Enum, Object};
use serde::{Deserialize, Serialize};
#[derive(Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize, Object)]
#[derive(Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct ImapConfig {
/// IMAP server hostname or IP address
#[oai(validator(max_length = 253, pattern = r"^[a-zA-Z0-9\-\.]+$"))]
//#[oai(validator(max_length = 253, pattern = r"^[a-zA-Z0-9\-\.]+$"))]
pub host: String,
/// IMAP server port number
#[oai(validator(minimum(value = "1"), maximum(value = "65535")))]
//#[oai(validator(minimum(value = "1"), maximum(value = "65535")))]
pub port: u16,
/// Connection encryption method
pub encryption: Encryption,
@@ -52,8 +52,8 @@ impl ImapConfig {
}
}
#[derive(Enum, Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Enum))]
pub enum AuthType {
/// Standard password authentication (PLAIN/LOGIN)
#[default]
@@ -62,7 +62,8 @@ pub enum AuthType {
OAuth2,
}
#[derive(Object, Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct AuthConfig {
///Authentication method to use
pub auth_type: AuthType,
@@ -70,7 +71,7 @@ pub struct AuthConfig {
///
/// Users should provide a plaintext password (1 to 256 characters).
/// The server will encrypt the password using AES-256-GCM and securely store it.
#[oai(validator(max_length = 256, min_length = 1))]
//#[oai(validator(max_length = 256, min_length = 1))]
pub password: Option<String>,
}
@@ -97,7 +98,8 @@ impl AuthConfig {
}
}
#[derive(Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize, Enum)]
#[derive(Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Enum))]
pub enum Encryption {
/// SSL/TLS encrypted connection
#[default]
@@ -16,11 +16,12 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use poem_openapi::Object;
//use poem_openapi::Object;
use serde::{Deserialize, Serialize};
use crate::{
modules::{
raise_error, utc_now,
{
account::migration::AccountModel,
common::auth::ClientContext,
database::{manager::DB_MANAGER, with_transaction},
@@ -31,10 +32,10 @@ use crate::{
UserModel,
},
},
raise_error, utc_now,
};
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct BatchAccountRoleRequest {
pub account_ids: Vec<u64>,
pub user_ids: Vec<u64>,
@@ -18,56 +18,54 @@
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::DownloadState,
},
cache::imap::mailbox::MailBox,
database::{list_all_impl, secondary_find_impl, with_transaction},
error::BichonResult,
store::tantivy::{attachment::ATTACHMENT_MANAGER, envelope::ENVELOPE_MANAGER},
users::{role::DEFAULT_ACCOUNT_MANAGER_ROLE_ID, UserModel, DEFAULT_ADMIN_USER_ID},
account::{
entity::ImapConfig,
since::{DateSince, RelativeDate},
state::DownloadState,
},
cache::imap::mailbox::MailBox,
common::paginated::DataPage,
database::{list_all_impl, secondary_find_impl, with_transaction},
encrypt,
error::BichonResult,
store::tantivy::{attachment::ATTACHMENT_MANAGER, envelope::ENVELOPE_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::DOWNLOAD_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::{
use crate::account::payload::AccountCreateRequest;
use crate::account::payload::AccountUpdateRequest;
use crate::account::payload::MinimalAccount;
use crate::cache::imap::task::SYNC_TASKS;
use crate::context::controller::DOWNLOAD_CONTROLLER;
use crate::database::count_by_unique_secondary_key_impl;
use crate::database::delete_impl;
use crate::database::manager::DB_MANAGER;
use crate::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::error::code::ErrorCode;
use crate::id;
use crate::oauth2::token::OAuth2AccessToken;
use crate::raise_error;
pub type AccountModel = AccountV4;
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Enum)]
#[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, Enum)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Enum))]
pub enum QuotaWindow {
Hourly,
#[default]
@@ -76,7 +74,7 @@ pub enum QuotaWindow {
Monthly,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[native_model(id = 4, version = 1)]
#[native_db(primary_key(pk -> String))]
pub struct AccountV1 {
@@ -84,7 +82,7 @@ pub struct AccountV1 {
pub id: u64,
pub imap: Option<ImapConfig>,
pub enabled: bool,
#[oai(validator(custom = "crate::modules::common::validator::EmailValidator"))]
//#[oai(validator(custom = "crate::common::validator::EmailValidator"))]
pub email: String,
pub name: Option<String>,
pub capabilities: Option<Vec<String>>,
@@ -104,7 +102,7 @@ impl AccountV1 {
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[native_model(id = 4, version = 2, from = AccountV1)]
#[native_db(primary_key(pk -> String))]
pub struct AccountV2 {
@@ -112,7 +110,7 @@ pub struct AccountV2 {
pub id: u64,
pub imap: Option<ImapConfig>,
pub enabled: bool,
#[oai(validator(custom = "crate::modules::common::validator::EmailValidator"))]
//#[oai(validator(custom = "crate::common::validator::EmailValidator"))]
pub email: String,
pub name: Option<String>,
pub capabilities: Option<Vec<String>>,
@@ -135,7 +133,7 @@ impl AccountV2 {
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[native_model(id = 4, version = 3, from = AccountV2)]
#[native_db(primary_key(pk -> String))]
pub struct AccountV3 {
@@ -143,7 +141,7 @@ pub struct AccountV3 {
pub id: u64,
pub imap: Option<ImapConfig>,
pub enabled: bool,
#[oai(validator(custom = "crate::modules::common::validator::EmailValidator"))]
//#[oai(validator(custom = "crate::common::validator::EmailValidator"))]
pub email: String,
pub name: Option<String>,
pub capabilities: Option<Vec<String>>,
@@ -169,7 +167,8 @@ impl AccountV3 {
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
#[native_model(id = 4, version = 4, from = AccountV3)]
#[native_db(primary_key(pk -> String))]
pub struct AccountV4 {
@@ -177,7 +176,7 @@ pub struct AccountV4 {
pub id: u64,
pub imap: Option<ImapConfig>,
pub enabled: bool,
#[oai(validator(custom = "crate::modules::common::validator::EmailValidator"))]
//#[oai(validator(custom = "crate::common::validator::EmailValidator"))]
pub email: String,
pub account_name: Option<String>,
pub login_name: Option<String>,
@@ -16,26 +16,18 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// use crate::{
// modules::{
// database::{async_find_impl, delete_impl, manager::DB_MANAGER, update_impl, upsert_impl},
// error::{code::ErrorCode, BichonResult},
// },
// raise_error, utc_now,
// };
use native_db::*;
use native_model::{native_model, Model};
use poem_openapi::Object;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
pub struct MailboxBatchProgress {
pub total_batches: u32,
pub current_batch: u32,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[native_model(id = 2, version = 1)]
#[native_db]
pub struct AccountRunningState {
@@ -51,7 +43,7 @@ pub struct AccountRunningState {
pub initial_sync_failed_time: Option<i64>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
pub struct AccountError {
pub error: String,
pub at: i64,
@@ -16,18 +16,18 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::account::entity::ImapConfig;
use crate::modules::account::migration::{AccountModel, AccountType, QuotaWindow};
use crate::modules::account::since::{DateSince, RelativeDate};
use crate::modules::error::code::ErrorCode;
use crate::modules::error::BichonResult;
use crate::account::entity::ImapConfig;
use crate::account::migration::{AccountModel, AccountType, QuotaWindow};
use crate::account::since::{DateSince, RelativeDate};
use crate::error::code::ErrorCode;
use crate::error::BichonResult;
use crate::{raise_error, validate_email};
use poem_openapi::Object;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct AccountCreateRequest {
#[oai(validator(custom = "crate::modules::common::validator::EmailValidator"))]
//#[oai(validator(custom = "crate::common::validator::EmailValidator"))]
pub email: String,
pub login_name: Option<String>,
pub account_name: Option<String>,
@@ -36,11 +36,11 @@ pub struct AccountCreateRequest {
pub date_since: Option<DateSince>,
pub date_before: Option<RelativeDate>,
pub account_type: AccountType,
#[oai(validator(minimum(value = "100")))]
//#[oai(validator(minimum(value = "100")))]
pub folder_limit: Option<u32>,
#[oai(validator(minimum(value = "10")))]
//#[oai(validator(minimum(value = "10")))]
pub download_interval_min: Option<i64>,
#[oai(validator(minimum(value = "10"), maximum(value = "200")))]
//#[oai(validator(minimum(value = "10"), maximum(value = "200")))]
pub download_batch_size: Option<u32>,
pub use_proxy: Option<u64>,
pub use_dangerous: bool,
@@ -107,7 +107,8 @@ impl AccountCreateRequest {
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct AccountUpdateRequest {
pub email: Option<String>,
/// Represents the account activation status.
@@ -135,7 +136,7 @@ pub struct AccountUpdateRequest {
/// Max emails to sync for this folder.
/// If not set, sync all emails.
/// otherwise sync up to `n` most recent emails (min 10).
#[oai(validator(minimum(value = "100")))]
//#[oai(validator(minimum(value = "100")))]
pub folder_limit: Option<u32>,
pub clear_folder_limit: Option<bool>,
/// Configuration for selective folder (mailbox/label) synchronization
@@ -153,9 +154,9 @@ pub struct AccountUpdateRequest {
/// Modified folders will be automatically synced on the next update.
pub sync_folders: Option<Vec<String>>,
/// Incremental sync interval (seconds)
#[oai(validator(minimum(value = "10")))]
//#[oai(validator(minimum(value = "10")))]
pub download_interval_min: Option<i64>,
#[oai(validator(minimum(value = "10"), maximum(value = "200")))]
//#[oai(validator(minimum(value = "10"), maximum(value = "200")))]
pub download_batch_size: Option<u32>,
/// Optional proxy ID for establishing the connection to external APIs (e.g., Gmail, Outlook).
/// - If `None` or not provided, the client will connect directly to the API server.
@@ -225,7 +226,8 @@ impl AccountUpdateRequest {
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct MinimalAccount {
pub id: u64,
@@ -17,14 +17,14 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::{
modules::error::{code::ErrorCode, BichonResult},
error::{code::ErrorCode, BichonResult},
raise_error,
};
use chrono::{Datelike, Days, Local, Months, NaiveDate, Utc};
use poem_openapi::{Enum, Object};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct DateSince {
/// Absolute date boundary in ISO 8601 format (YYYY-MM-DD)
///
@@ -38,7 +38,7 @@ pub struct DateSince {
/// "fixed": "2025-05-01"
/// }
/// ```
#[oai(validator(pattern = r"^\d{4}-\d{2}-\d{2}$"))]
//#[oai(validator(pattern = r"^\d{4}-\d{2}-\d{2}$"))]
pub fixed: Option<String>,
/// Relative time period from current date
///
@@ -58,7 +58,8 @@ pub struct DateSince {
pub relative: Option<RelativeDate>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Enum)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Enum))]
pub enum Unit {
#[default]
Days,
@@ -66,12 +67,13 @@ pub enum Unit {
Years,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct RelativeDate {
/// The time unit to use for the offset (days, months, or years)
pub unit: Unit,
/// The quantity of time units to offset (must be a positive integer)
#[oai(validator(minimum(value = "1")))]
//#[oai(validator(minimum(value = "1")))]
pub value: u32,
}
@@ -255,7 +257,7 @@ impl DateSince {
#[cfg(test)]
mod test {
use crate::modules::account::since::{DateSince, RelativeDate, Unit};
use crate::account::since::{DateSince, RelativeDate, Unit};
#[test]
fn test1() {
@@ -17,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::{
modules::{
{
database::{async_find_impl, delete_impl, manager::DB_MANAGER, update_impl, upsert_impl},
error::{code::ErrorCode, BichonResult},
},
@@ -25,11 +25,11 @@ use crate::{
};
use native_db::*;
use native_model::{native_model, Model};
use poem_openapi::{Enum, Object};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Enum)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Enum))]
pub enum DownloadStatus {
Running,
Success,
@@ -38,14 +38,16 @@ pub enum DownloadStatus {
Cancelled,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Enum)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Enum))]
pub enum TriggerType {
Manual,
#[default]
Scheduled,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Enum)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Enum))]
pub enum FolderStatus {
#[default]
Pending,
@@ -55,7 +57,8 @@ pub enum FolderStatus {
Cancelled,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct FolderProgress {
pub folder_name: String,
pub planned: u64,
@@ -64,7 +67,8 @@ pub struct FolderProgress {
pub message: Option<String>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct DownloadSession {
pub start_time: i64,
pub end_time: Option<i64>,
@@ -76,7 +80,8 @@ pub struct DownloadSession {
pub errors: Vec<AccountError>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
#[native_model(id = 3, version = 1)]
#[native_db]
pub struct DownloadState {
@@ -89,7 +94,8 @@ pub struct DownloadState {
pub global_errors: Vec<AccountError>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct AccountError {
pub error: String,
pub at: i64,
@@ -17,11 +17,9 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use std::collections::{BTreeSet, HashMap};
use poem_openapi::Object;
use serde::{Deserialize, Serialize};
use crate::modules::{
use crate::{
account::{
entity::ImapConfig,
migration::{AccountModel, AccountType, QuotaWindow},
@@ -30,7 +28,8 @@ use crate::modules::{
users::UserModel,
};
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct AccountResp {
pub id: u64,
pub imap: Option<ImapConfig>,
@@ -23,7 +23,7 @@ use std::{path::Path, rc::Rc};
use native_db::{Builder, Database};
use crate::{
modules::{
{
database::META_MODELS,
error::{code::ErrorCode, BichonResult},
token::{AccessTokenModel, AccessTokenModelKey, TokenType},
@@ -18,12 +18,12 @@
use autoconfig::config::OAuth2Config as XOAuth2Config;
use poem_openapi::Object;
use serde::{Deserialize, Serialize};
use crate::modules::account::entity::Encryption;
use crate::account::entity::Encryption;
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)]
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct ServerConfig {
/// server hostname or IP address
pub host: String,
@@ -43,7 +43,8 @@ impl ServerConfig {
}
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)]
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct OAuth2Config {
/// The authorization server's issuer identifier URL
pub issuer: String,
@@ -66,7 +67,8 @@ impl From<&XOAuth2Config> for OAuth2Config {
}
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)]
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct MailServerConfig {
/// IMAP server configuration
pub imap: ServerConfig,
@@ -17,10 +17,10 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::autoconfig::entity::{MailServerConfig, ServerConfig};
use crate::modules::error::code::ErrorCode;
use crate::autoconfig::entity::{MailServerConfig, ServerConfig};
use crate::error::code::ErrorCode;
use crate::{
modules::{
{
account::entity::Encryption, autoconfig::CachedMailSettings, error::BichonResult,
},
raise_error,
@@ -17,12 +17,12 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::database::manager::DB_MANAGER;
use crate::modules::database::{delete_impl, async_find_impl, upsert_impl};
use crate::modules::error::code::ErrorCode;
use crate::database::manager::DB_MANAGER;
use crate::database::{delete_impl, async_find_impl, upsert_impl};
use crate::error::code::ErrorCode;
use crate::raise_error;
use crate::{
modules::autoconfig::entity::MailServerConfig, modules::error::BichonResult, utc_now,
autoconfig::entity::MailServerConfig, error::BichonResult, utc_now,
};
use native_db::*;
use native_model::{native_model, Model};
@@ -20,7 +20,7 @@ use std::collections::BTreeSet;
use crate::{
decode_mailbox_name,
modules::{
{
account::migration::{AccountModel, AccountType},
cache::imap::mailbox::{AttributeEnum, MailBox},
error::{code::ErrorCode, BichonResult},
@@ -17,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::{
modules::{
{
account::{
migration::AccountModel,
state::{DownloadState, TriggerType},
@@ -17,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::{
modules::{
{
account::{
migration::AccountModel,
state::{DownloadState, DownloadStatus, FolderStatus},
@@ -16,7 +16,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::{
use crate::{
account::{
migration::{AccountModel, AccountType},
state::{DownloadState, DownloadStatus},
@@ -17,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::{
modules::{
{
account::{
migration::AccountModel,
state::{DownloadState, DownloadStatus, FolderStatus},
@@ -18,7 +18,7 @@
use crate::{
decode_mailbox_name, encode_mailbox_name,
modules::{
{
database::{
async_filter_by_secondary_key_impl, async_find_impl, batch_delete_impl,
batch_insert_impl, batch_upsert_impl, delete_impl, filter_by_secondary_key_impl,
@@ -32,10 +32,10 @@ use async_imap::types::{Name, NameAttribute};
use itertools::Itertools;
use native_db::*;
use native_model::{native_model, Model};
use poem_openapi::{Enum, Object};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
#[native_model(id = 1, version = 1)]
#[native_db]
pub struct MailBox {
@@ -145,7 +145,8 @@ impl MailBox {
}
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct Attribute {
pub attr: AttributeEnum,
pub extension: Option<String>,
@@ -157,7 +158,8 @@ impl Attribute {
}
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize, Enum)]
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Enum))]
pub enum AttributeEnum {
NoInferiors,
NoSelect,
@@ -16,18 +16,20 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use std::sync::LazyLock;
use std::{
collections::{HashMap, HashSet},
sync::LazyLock,
};
use crate::modules::{
account::{state::DownloadState, old_state::AccountRunningState},
use crate::{
account::{old_state::AccountRunningState, state::DownloadState},
database::ModelsAdapter,
};
use ahash::{AHashMap, AHashSet};
use mailbox::MailBox;
use native_db::Models;
pub mod mailbox;
pub mod download;
pub mod mailbox;
pub mod task;
pub static MAILBOX_MODELS: LazyLock<Models> = LazyLock::new(|| {
@@ -42,7 +44,7 @@ pub fn find_missing_mailboxes(
local_mailboxes: &[MailBox],
server_mailboxes: &[MailBox],
) -> Vec<MailBox> {
let local_names: AHashSet<_> = local_mailboxes.iter().map(|m| &m.name).collect();
let local_names: HashSet<_> = local_mailboxes.iter().map(|m| &m.name).collect();
server_mailboxes
.iter()
.filter(|m| !local_names.contains(&m.name))
@@ -54,7 +56,7 @@ pub fn find_intersecting_mailboxes(
local_mailboxes: &[MailBox],
remote_mailboxes: &[MailBox],
) -> Vec<(MailBox, MailBox)> {
let local_map: AHashMap<_, _> = local_mailboxes
let local_map: HashMap<_, _> = local_mailboxes
.iter()
.map(|m| (m.name.clone(), m.clone()))
.collect();
@@ -16,12 +16,12 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::account::entity::AuthType;
use crate::modules::account::state::DownloadState;
use crate::modules::cache::imap::download::process_imap_download;
use crate::modules::common::periodic::{PeriodicTask, TaskHandle};
use crate::modules::oauth2::token::OAuth2AccessToken;
use crate::modules::{account::migration::AccountModel, error::BichonResult};
use crate::account::entity::AuthType;
use crate::account::state::DownloadState;
use crate::cache::imap::download::process_imap_download;
use crate::common::periodic::{PeriodicTask, TaskHandle};
use crate::oauth2::token::OAuth2AccessToken;
use crate::{account::migration::AccountModel, error::BichonResult};
use crate::utc_now;
use std::collections::HashMap;
use std::sync::atomic::{AtomicI64, Ordering};
+1 -1
View File
@@ -17,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::settings::cli::SETTINGS;
use crate::settings::cli::SETTINGS;
use std::sync::{Arc, LazyLock};
use tokio::sync::Semaphore;
+143
View File
@@ -0,0 +1,143 @@
use std::{
collections::{BTreeSet, HashSet},
net::IpAddr,
};
use crate::{
error::{code::ErrorCode, BichonResult},
raise_error,
users::{permissions::Permission, role::UserRole, UserModel},
};
#[derive(Clone, Debug)]
pub struct ClientContext {
pub ip_addr: Option<IpAddr>,
pub user: UserModel,
}
impl ClientContext {
pub async fn require_any_permission(
&self,
requirements: Vec<(Option<u64>, &str)>,
) -> BichonResult<()> {
for (account_id, permission) in requirements {
if self.has_permission(account_id, permission).await {
return Ok(());
}
}
Err(raise_error!(
"Access denied: Insufficient permissions to perform this action.".into(),
ErrorCode::Forbidden
))
}
pub async fn check_has_permission(
user: &UserModel,
account_id: Option<u64>,
permission: &str,
) -> bool {
if user.is_admin().await {
return true;
}
let mut global_perms = HashSet::new();
for rid in &user.global_roles {
if let Some(role) = UserRole::find(*rid).await.ok().flatten() {
global_perms.extend(role.permissions);
}
}
if Self::check_global_logic(&global_perms, permission) {
return true;
}
if let Some(aid) = account_id {
if let Some(role_id) = user.account_access_map.get(&aid) {
if let Some(role) = UserRole::find(*role_id).await.ok().flatten() {
if role.permissions.contains(&permission.to_string())
|| Self::check_account_logic(&role.permissions, permission)
{
return true;
}
}
}
}
false
}
pub async fn has_permission(&self, account_id: Option<u64>, permission: &str) -> bool {
if self.user.is_admin().await {
return true;
}
let mut global_perms = HashSet::new();
for rid in &self.user.global_roles {
if let Some(role) = UserRole::find(*rid).await.ok().flatten() {
global_perms.extend(role.permissions);
}
}
if Self::check_global_logic(&global_perms, permission) {
return true;
}
if let Some(aid) = account_id {
if let Some(role_id) = self.user.account_access_map.get(&aid) {
if let Some(role) = UserRole::find(*role_id).await.ok().flatten() {
if role.permissions.contains(&permission.to_string())
|| Self::check_account_logic(&role.permissions, permission)
{
return true;
}
}
}
}
false
}
fn check_global_logic(global: &HashSet<String>, perm: &str) -> bool {
if global.contains(perm) {
return true;
}
match perm {
Permission::DATA_READ => global.contains(Permission::DATA_READ_ALL),
Permission::DATA_DELETE => global.contains(Permission::DATA_DELETE_ALL),
Permission::DATA_RAW_DOWNLOAD => global.contains(Permission::DATA_RAW_DOWNLOAD_ALL),
Permission::DATA_EXPORT_BATCH => global.contains(Permission::DATA_EXPORT_BATCH_ALL),
Permission::ACCOUNT_MANAGE | Permission::ACCOUNT_READ_DETAILS => {
global.contains(Permission::ACCOUNT_MANAGE_ALL)
}
_ => false,
}
}
fn check_account_logic(scoped_perms: &BTreeSet<String>, perm: &str) -> bool {
if scoped_perms.contains(perm) {
return true;
}
match perm {
Permission::DATA_READ | Permission::ACCOUNT_READ_DETAILS => {
scoped_perms.contains(Permission::ACCOUNT_MANAGE)
}
_ => false,
}
}
pub async fn require_permission(
&self,
account_id: Option<u64>,
permission: &str,
) -> BichonResult<()> {
if self.has_permission(account_id, permission).await {
Ok(())
} else {
Err(raise_error!(
format!("Access Denied: Missing permission '{}'", permission),
ErrorCode::Forbidden
))
}
}
}
@@ -16,29 +16,17 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use super::error::code::ErrorCode;
use super::error::BichonError;
use mail_parser::{Addr as ImapAddr, Address as ImapAddress};
use poem::error::ResponseError;
use poem::Body;
use poem::{http::StatusCode, Error, Response};
use poem_openapi::Object;
use serde::{Deserialize, Serialize};
use std::ops::Deref;
use tracing::error;
use mail_parser::{Addr as ImapAddr, Address as ImapAddress};
use serde::{Deserialize, Serialize};
pub mod auth;
pub mod error;
pub mod log;
pub mod paginated;
pub mod periodic;
pub mod rustls;
pub mod signal;
pub mod timeout;
pub mod tls;
pub mod validator;
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Object)]
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct Addr {
/// The optional display name associated with the email address (e.g., "John Doe").
/// If `None`, no display name is specified.
@@ -91,57 +79,3 @@ impl<'x> From<&ImapAddress<'x>> for AddrVec {
AddrVec(vec)
}
}
#[inline]
fn create_rust_mailer_error(message: &str, code: ErrorCode) -> BichonError {
BichonError::Generic {
message: message.into(),
location: snafu::location!(),
code,
}
}
#[inline]
pub fn create_api_error_response(message: &str, code: ErrorCode) -> Error {
let rust_mailer_error = create_rust_mailer_error(message, code);
rust_mailer_error.into()
}
impl ResponseError for BichonError {
fn status(&self) -> StatusCode {
match self {
BichonError::Generic {
message: _,
location: _,
code,
} => code.status(),
}
}
fn as_response(&self) -> Response
where
Self: std::error::Error + Send + Sync + 'static,
{
match self {
BichonError::Generic {
message,
location,
code,
} => {
error!(
error_code = *code as u32,
error_message = %message,
error_location = ?location
);
let body = Body::from_json(serde_json::json!({
"code": *code as u32,
"message": message.to_string(),
}))
.unwrap();
Response::builder().status(self.status()).body(body)
}
}
}
}
+180
View File
@@ -0,0 +1,180 @@
//
// 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 <http://www.gnu.org/licenses/>.
use crate::{
error::{code::ErrorCode, BichonResult},
raise_error,
};
use serde::{Deserialize, Serialize};
use std::cmp::min;
pub fn paginate_vec<T: Clone>(
items: &Vec<T>,
page: Option<u64>,
page_size: Option<u64>,
) -> BichonResult<Paginated<T>> {
let total_items = items.len() as u64;
let (offset, total_pages) = match (page, page_size) {
(Some(p), Some(s)) if p > 0 && s > 0 => {
let offset = (p - 1) * s;
let total_pages = if total_items > 0 {
(total_items + s - 1) / s
} else {
0
};
(Some(offset), Some(total_pages))
}
(Some(0), _) | (_, Some(0)) => {
return Err(raise_error!(
"'page' and 'page_size' must be greater than 0.".into(),
ErrorCode::InvalidParameter
));
}
_ => (None, None),
};
let data = match offset {
Some(offset) if offset >= total_items => vec![],
Some(offset) => {
let end = min(offset + page_size.unwrap_or(total_items), total_items) as usize;
items[offset as usize..end].to_vec()
}
None => items.clone(),
};
Ok(Paginated::new(
page,
page_size,
total_items,
total_pages,
data,
))
}
#[cfg(not(feature = "web-api"))]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DataPage<S>
where
S: Serialize + std::fmt::Debug + std::marker::Unpin + Send + Sync,
{
/// The current page number (starting from 1).
pub current_page: Option<u64>,
/// The number of items per page.
pub page_size: Option<u64>,
/// The total number of items across all pages.
pub total_items: u64,
/// The list of items returned on the current page.
pub items: Vec<S>,
/// The total number of pages. This is optional and may not be set if not calculated.
pub total_pages: Option<u64>,
}
#[cfg(not(feature = "web-api"))]
impl<S: Serialize + std::fmt::Debug + std::marker::Unpin + Send + Sync> From<Paginated<S>>
for DataPage<S>
{
fn from(paginated: Paginated<S>) -> Self {
DataPage {
current_page: paginated.page,
page_size: paginated.page_size,
total_items: paginated.total_items,
total_pages: paginated.total_pages,
items: paginated.items,
}
}
}
#[cfg(feature = "web-api")]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, poem_openapi::Object)]
pub struct DataPage<S>
where
S: Serialize
+ std::fmt::Debug
+ std::marker::Unpin
+ Send
+ Sync
+ poem_openapi::types::Type
+ poem_openapi::types::ParseFromJSON
+ poem_openapi::types::ToJSON,
{
/// The current page number (starting from 1).
pub current_page: Option<u64>,
/// The number of items per page.
pub page_size: Option<u64>,
/// The total number of items across all pages.
pub total_items: u64,
/// The list of items returned on the current page.
pub items: Vec<S>,
/// The total number of pages. This is optional and may not be set if not calculated.
pub total_pages: Option<u64>,
}
#[cfg(feature = "web-api")]
impl<
S: Serialize
+ std::fmt::Debug
+ std::marker::Unpin
+ Send
+ Sync
+ poem_openapi::types::Type
+ poem_openapi::types::ParseFromJSON
+ poem_openapi::types::ToJSON,
> From<Paginated<S>> for DataPage<S>
{
fn from(paginated: Paginated<S>) -> Self {
DataPage {
current_page: paginated.page,
page_size: paginated.page_size,
total_items: paginated.total_items,
total_pages: paginated.total_pages,
items: paginated.items,
}
}
}
#[derive(Debug)]
pub struct Paginated<T> {
pub page: Option<u64>,
pub page_size: Option<u64>,
pub total_items: u64,
pub total_pages: Option<u64>,
pub items: Vec<T>,
}
impl<T> Paginated<T> {
pub fn new(
page: Option<u64>,
page_size: Option<u64>,
total_items: u64,
total_pages: Option<u64>,
items: Vec<T>,
) -> Self {
Paginated {
page,
page_size,
total_items,
total_pages,
items,
}
}
}
@@ -16,7 +16,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::{common::signal::SIGNAL_MANAGER, error::BichonResult};
use crate::{common::signal::SIGNAL_MANAGER, error::BichonResult};
use std::{future::Future, time::Duration};
use tokio::{sync::oneshot, task::JoinHandle, time::MissedTickBehavior};
use tracing::{info, warn};
@@ -17,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::{
modules::{
{
context::Initialize,
error::{code::ErrorCode, BichonResult},
},
@@ -18,7 +18,7 @@
use std::sync::LazyLock;
use crate::modules::{context::Initialize, error::BichonResult, utils::shutdown::shutdown_signal};
use crate::{context::Initialize, error::BichonResult, utils::shutdown::shutdown_signal};
use tokio::sync::broadcast;
pub static SIGNAL_MANAGER: LazyLock<SignalManager> = LazyLock::new(SignalManager::new);
@@ -16,7 +16,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::{cache::imap::task::SYNC_TASKS, error::BichonResult};
use crate::{cache::imap::task::SYNC_TASKS, error::BichonResult};
use std::{sync::LazyLock, time::Duration};
use tokio::sync::mpsc;
use tracing::{error, info};
@@ -16,10 +16,10 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::account::migration::AccountType;
use crate::modules::context::Initialize;
use crate::account::migration::AccountType;
use crate::context::Initialize;
use crate::{
modules::{
{
account::migration::AccountModel, context::controller::DOWNLOAD_CONTROLLER, error::BichonResult,
},
utc_now,
@@ -16,11 +16,10 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::{common::periodic::TaskHandle, error::BichonResult};
use crate::{common::periodic::TaskHandle, error::BichonResult};
pub mod controller;
pub mod executors;
pub mod status;
#[allow(async_fn_in_trait)]
pub trait Initialize {
@@ -16,7 +16,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::{
use crate::{
store::tantivy::{
attachment::ATTACHMENT_MANAGER,
envelope::ENVELOPE_MANAGER,
@@ -25,24 +25,24 @@ use crate::modules::{
},
users::permissions::Permission,
};
use poem_openapi::Object;
//use poem_openapi::Object;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use tantivy::{schema::Value, TantivyDocument};
use crate::{
bichon_version,
modules::{
bichon_version, raise_error,
{
account::migration::AccountModel,
common::auth::ClientContext,
error::{code::ErrorCode, BichonResult},
settings::dir::DATA_DIR_MANAGER,
utils::get_total_size,
},
raise_error,
};
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct DashboardStats {
pub account_count: usize, // Number of accounts
pub email_count: u64, // Total number of emails
@@ -57,7 +57,6 @@ pub struct DashboardStats {
pub top_largest_emails: Vec<LargestEmail>, // Top 10 largest emails
pub top_largest_attachments: Vec<LargestAttachment>, // Top 10 largest attachments
pub system_version: String, // The semantic version string of the currently running backend service
pub commit_hash: String, // Git commit hash used to build this system version
}
impl DashboardStats {
@@ -103,25 +102,27 @@ impl DashboardStats {
}
stat.system_version = bichon_version!().to_string();
stat.commit_hash = env!("GIT_HASH").to_string();
Ok(stat)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct TimeBucket {
pub timestamp_ms: i64, // Timestamp in milliseconds
pub count: u64, // Number of emails in this time bucket
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct Group {
pub key: String,
pub count: u64, // Number of emails from this sender
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct LargestEmail {
pub subject: String, // Email subject
pub size_bytes: u64, // Email size in bytes
@@ -173,7 +174,8 @@ impl LargestEmail {
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct LargestAttachment {
pub name: String, // Attachment name
pub size_bytes: u64, // Attachment size in bytes
@@ -16,13 +16,13 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::account::migration::AccountModel;
use crate::modules::cache::imap::MAILBOX_MODELS;
use crate::modules::error::{code::ErrorCode, BichonError};
use crate::modules::settings::cli::SETTINGS;
use crate::modules::settings::dir::DATA_DIR_MANAGER;
use crate::modules::users::UserModel;
use crate::modules::{database::META_MODELS, error::BichonResult};
use crate::account::migration::AccountModel;
use crate::cache::imap::MAILBOX_MODELS;
use crate::error::{code::ErrorCode, BichonError};
use crate::settings::cli::SETTINGS;
use crate::settings::dir::DATA_DIR_MANAGER;
use crate::users::UserModel;
use crate::{database::META_MODELS, error::BichonResult};
use crate::raise_error;
use native_db::{Builder, Database};
use std::sync::{Arc, LazyLock};
@@ -16,18 +16,19 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::account::migration::{AccountV1, AccountV2, AccountV3, AccountV4};
use crate::modules::autoconfig::CachedMailSettings;
use crate::modules::error::code::ErrorCode;
use crate::modules::error::BichonResult;
use crate::modules::oauth2::entity::OAuth2;
use crate::modules::oauth2::pending::OAuth2PendingEntity;
use crate::modules::oauth2::token::OAuth2AccessToken;
use crate::modules::settings::proxy::Proxy;
use crate::modules::settings::system::SystemSetting;
use crate::modules::token::AccessTokenModel;
use crate::modules::users::role::UserRole;
use crate::modules::users::{BichonUser, BichonUserV2};
use crate::account::migration::{AccountV1, AccountV2, AccountV3, AccountV4};
use crate::autoconfig::CachedMailSettings;
use crate::common::paginated::Paginated;
use crate::error::code::ErrorCode;
use crate::error::BichonResult;
use crate::oauth2::entity::OAuth2;
use crate::oauth2::pending::OAuth2PendingEntity;
use crate::oauth2::token::OAuth2AccessToken;
use crate::settings::proxy::Proxy;
use crate::settings::system::SystemSetting;
use crate::token::AccessTokenModel;
use crate::users::role::UserRole;
use crate::users::{BichonUser, BichonUserV2};
use crate::raise_error;
use db_type::{KeyOptions, ToKeyDefinition};
use itertools::Itertools;
@@ -503,30 +504,3 @@ pub fn secondary_find_impl<T: ToInput + Clone + Send + 'static>(
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
Ok(entities)
}
#[derive(Debug)]
pub struct Paginated<T> {
pub page: Option<u64>,
pub page_size: Option<u64>,
pub total_items: u64,
pub total_pages: Option<u64>,
pub items: Vec<T>,
}
impl<T> Paginated<T> {
pub fn new(
page: Option<u64>,
page_size: Option<u64>,
total_items: u64,
total_pages: Option<u64>,
items: Vec<T>,
) -> Self {
Paginated {
page,
page_size,
total_items,
total_pages,
items,
}
}
}
@@ -16,18 +16,18 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::common::AddrVec;
use crate::modules::envelope::utils::normalize_subject;
use crate::modules::error::code::ErrorCode;
use crate::modules::error::BichonResult;
use crate::modules::message::content::AttachmentInfo;
use crate::modules::store::storage::{DetachedEmail, BLOB_MANAGER};
use crate::modules::store::tantivy::attachment::ATTACHMENT_MANAGER;
use crate::modules::store::tantivy::envelope::ENVELOPE_MANAGER;
use crate::modules::store::tantivy::model::{AttachmentModel, EnvelopeWithAttachments};
use crate::modules::utils::html::extract_text;
use crate::modules::utils::{compute_content_hash, hex_hash};
use crate::{id, modules::store::envelope::Envelope};
use crate::common::AddrVec;
use crate::envelope::utils::normalize_subject;
use crate::error::code::ErrorCode;
use crate::error::BichonResult;
use crate::message::content::AttachmentInfo;
use crate::store::storage::{DetachedEmail, BLOB_MANAGER};
use crate::store::tantivy::attachment::ATTACHMENT_MANAGER;
use crate::store::tantivy::envelope::ENVELOPE_MANAGER;
use crate::store::tantivy::model::{AttachmentModel, EnvelopeWithAttachments};
use crate::utils::html::extract_text;
use crate::utils::{compute_content_hash, hex_hash};
use crate::{id, store::envelope::Envelope};
use crate::{raise_error, utc_now};
use async_imap::types::Fetch;
use bytes::Bytes;
@@ -90,7 +90,7 @@ pub fn normalize_subject(raw_subject: Option<&str>) -> String {
#[cfg(test)]
mod tests {
use crate::modules::envelope::utils::merge_contiguous_encoded_words;
use crate::envelope::utils::merge_contiguous_encoded_words;
#[tokio::test]
+44
View File
@@ -0,0 +1,44 @@
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(u32)]
pub enum ErrorCode {
// Client-side errors (1000010999)
InvalidParameter = 10000,
MissingConfiguration = 10020,
Incompatible = 10030,
PayloadTooLarge = 10070,
RequestTimeout = 10080,
MethodNotAllowed = 10090,
// Authentication and authorization errors (2000020999)
PermissionDenied = 20000,
AccountDisabled = 20010,
Forbidden = 20020,
OAuth2ItemDisabled = 20050,
MissingRefreshToken = 20060,
// Resource errors (3000030999)
ResourceNotFound = 30000,
TooManyRequest = 30020,
AlreadyExists = 30030,
// Network connection errors (4000040999)
NetworkError = 40000,
ConnectionTimeout = 40010,
ConnectionPoolTimeout = 40020,
HttpResponseError = 40030,
// Mail service errors (5000050999)
ImapCommandFailed = 50000,
ImapAuthenticationFailed = 50010,
ImapUnexpectedResult = 50020,
AutoconfigFetchFailed = 50060,
// Internal system errors (7000070999)
InternalError = 70000,
UnhandledPoemError = 70010,
}
impl ErrorCode {
pub fn to_u32(&self) -> u32 {
*self as u32
}
}
+19
View File
@@ -0,0 +1,19 @@
use snafu::{Location, Snafu};
use crate::error::code::ErrorCode;
pub mod code;
#[derive(Debug, Snafu)]
#[snafu(visibility(pub))]
pub enum BichonError {
#[snafu(display("{message}"))]
Generic {
message: String,
#[snafu(implicit)]
location: Location,
code: ErrorCode,
},
}
pub type BichonResult<T, E = BichonError> = std::result::Result<T, E>;
@@ -16,9 +16,9 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::error::code::ErrorCode;
use crate::modules::imap::session::SessionStream;
use crate::{modules::error::BichonResult, raise_error};
use crate::error::code::ErrorCode;
use crate::imap::session::SessionStream;
use crate::{error::BichonResult, raise_error};
use async_imap::types::Capability;
use async_imap::{types::Capabilities, Session};
@@ -16,14 +16,14 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::account::entity::Encryption;
use crate::modules::error::code::ErrorCode;
use crate::modules::error::BichonResult;
use crate::modules::imap::session::SessionStream;
use crate::modules::imap::stats::StatsWrapper;
use crate::modules::utils::net::establish_tcp_connection_with_timeout;
use crate::modules::utils::net::establish_tls_connection;
use crate::modules::utils::tls::establish_tls_stream;
use crate::account::entity::Encryption;
use crate::error::code::ErrorCode;
use crate::error::BichonResult;
use crate::imap::session::SessionStream;
use crate::imap::stats::StatsWrapper;
use crate::utils::net::establish_tcp_connection_with_timeout;
use crate::utils::net::establish_tls_connection;
use crate::utils::tls::establish_tls_stream;
use crate::raise_error;
use async_imap::Client as ImapClient;
use async_imap::Session as ImapSession;
@@ -16,14 +16,14 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::account::migration::AccountModel;
use crate::modules::account::state::{DownloadState, FolderStatus};
use crate::modules::cache::imap::mailbox::MailBox;
use crate::modules::cache::imap::download::flow::{generate_uid_sequence_hashset, DEFAULT_BATCH_SIZE};
use crate::modules::envelope::extractor::extract_envelope_and_store_it;
use crate::modules::error::code::ErrorCode;
use crate::modules::imap::session::SessionStream;
use crate::modules::{error::BichonResult, imap::manager::ImapConnectionManager};
use crate::account::migration::AccountModel;
use crate::account::state::{DownloadState, FolderStatus};
use crate::cache::imap::mailbox::MailBox;
use crate::cache::imap::download::flow::{generate_uid_sequence_hashset, DEFAULT_BATCH_SIZE};
use crate::envelope::extractor::extract_envelope_and_store_it;
use crate::error::code::ErrorCode;
use crate::imap::session::SessionStream;
use crate::{error::BichonResult, imap::manager::ImapConnectionManager};
use crate::raise_error;
use async_imap::types::Name;
use async_imap::Session;
@@ -16,17 +16,17 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::account::entity::AuthType;
use crate::modules::account::migration::{AccountModel, AccountType};
use crate::modules::error::code::ErrorCode;
use crate::modules::error::BichonResult;
use crate::modules::imap::capabilities::{
use crate::account::entity::AuthType;
use crate::account::migration::{AccountModel, AccountType};
use crate::error::code::ErrorCode;
use crate::error::BichonResult;
use crate::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;
use crate::modules::oauth2::token::OAuth2AccessToken;
use crate::imap::client::Client;
use crate::imap::oauth2::OAuth2;
use crate::imap::session::SessionStream;
use crate::oauth2::token::OAuth2AccessToken;
use crate::{bichon_version, decrypt, raise_error};
use async_imap::Session;
use tracing::{error, warn};
@@ -23,7 +23,7 @@ use std::task::{Context, Poll};
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use crate::modules::imap::session::SessionStream;
use crate::imap::session::SessionStream;
pub struct StatsWrapper<T> {
inner: T,
@@ -20,7 +20,7 @@ use mail_parser::{parsers::MessageStream, MessageParser, MimeHeaders};
use crate::{
base64_encode_url_safe,
modules::{account::entity::Encryption, imap::client::Client},
{account::entity::Encryption, imap::client::Client},
};
#[tokio::test]
@@ -17,12 +17,12 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use poem_openapi::Object;
//use poem_openapi::Object;
use serde::{Deserialize, Serialize};
use crate::{
base64_decode_url_safe,
modules::{
{
account::migration::{AccountModel, AccountType},
cache::imap::mailbox::{Attribute, AttributeEnum, MailBox},
envelope::extractor::extract_envelope_from_eml,
@@ -32,7 +32,8 @@ use crate::{
raise_error,
};
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)]
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct BatchEmlRequest {
pub account_id: u64,
pub mail_folder: String,
@@ -40,7 +41,8 @@ pub struct BatchEmlRequest {
pub emls: Vec<String>,
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)]
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct FailedEmlDetail {
/// The 0-based index of the failed EML in the request list
pub index: usize,
@@ -48,7 +50,8 @@ pub struct FailedEmlDetail {
pub error_message: String,
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)]
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct BatchEmlResult {
/// Total number of emails processed
pub total: usize,
+23
View File
@@ -0,0 +1,23 @@
pub mod account;
pub mod admin;
pub mod autoconfig;
pub mod cache;
pub mod common;
pub mod context;
pub mod dashboard;
pub mod database;
pub mod envelope;
pub mod error;
pub mod imap;
pub mod import;
pub mod logger;
pub mod mailbox;
pub mod message;
pub mod oauth2;
pub mod settings;
pub mod store;
pub mod tasks;
pub mod token;
pub mod users;
pub mod utils;
pub mod version;
@@ -16,9 +16,9 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::logger::LocalTimer;
use crate::modules::settings::cli::SETTINGS;
use crate::modules::settings::dir::DATA_DIR_MANAGER;
use crate::logger::LocalTimer;
use crate::settings::cli::SETTINGS;
use crate::settings::dir::DATA_DIR_MANAGER;
use std::sync::OnceLock;
use tracing::level_filters::LevelFilter;
use tracing::Level;
@@ -16,8 +16,8 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::logger::file::setup_file_logger;
use crate::modules::settings::cli::SETTINGS;
use crate::logger::file::setup_file_logger;
use crate::settings::cli::SETTINGS;
use chrono::Local;
use std::process;
use tracing::Level;
@@ -16,7 +16,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::{
use crate::{
cache::imap::mailbox::MailBox,
error::BichonResult,
store::tantivy::{attachment::ATTACHMENT_MANAGER, envelope::ENVELOPE_MANAGER},
@@ -16,13 +16,13 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::account::migration::{AccountModel, AccountType};
use crate::modules::cache::imap::mailbox::{Attribute, AttributeEnum, MailBox};
use crate::modules::error::code::ErrorCode;
use crate::modules::error::BichonResult;
use crate::modules::imap::executor::ImapExecutor;
use crate::modules::imap::session::SessionStream;
use crate::modules::utils::create_hash;
use crate::account::migration::{AccountModel, AccountType};
use crate::cache::imap::mailbox::{Attribute, AttributeEnum, MailBox};
use crate::error::code::ErrorCode;
use crate::error::BichonResult;
use crate::imap::executor::ImapExecutor;
use crate::imap::session::SessionStream;
use crate::utils::create_hash;
use crate::raise_error;
use async_imap::types::Name;
use async_imap::Session;
@@ -1,6 +1,6 @@
use crate::{
encode_mailbox_name,
modules::{
{
account::migration::{AccountModel, AccountType},
envelope::extractor::reattach_eml_content,
error::{code::ErrorCode, BichonResult},
@@ -8,12 +8,13 @@ use crate::{
},
raise_error,
};
use poem_openapi::Object;
//use poem_openapi::Object;
use serde::{Deserialize, Serialize};
const MAX_RESTORE_COUNT: usize = 100;
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct RestoreMessagesRequest {
/// envelope IDs to restore (max 100)
pub envelope_ids: Vec<String>,
@@ -1,7 +1,7 @@
use std::io::Cursor;
use crate::{
modules::{
{
dashboard::Group,
envelope::extractor::reattach_eml_content,
error::{code::ErrorCode, BichonResult},
@@ -11,10 +11,11 @@ use crate::{
};
use bytes::Bytes;
use mail_parser::MessageParser;
use poem_openapi::Object;
//use poem_openapi::Object;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct AttachmentMetadata {
/// Statistics of attachment file extensions (key + count).
/// Each item represents a file extension and its occurrence count.
@@ -1,7 +1,7 @@
use poem_openapi::Object;
//use poem_openapi::Object;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
pub struct Contact {
pub email: String,
pub name: Option<String>,
@@ -17,23 +17,24 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::base64_encode;
use crate::modules::account::migration::AccountModel;
use crate::modules::envelope::extractor::{
use crate::account::migration::AccountModel;
use crate::envelope::extractor::{
extract_envelope_from_nested_message, reattach_eml_content,
};
use crate::modules::error::code::ErrorCode;
use crate::modules::store::envelope::Envelope;
use crate::modules::utils::compute_content_hash;
use crate::{modules::error::BichonResult, raise_error};
use crate::error::code::ErrorCode;
use crate::store::envelope::Envelope;
use crate::utils::compute_content_hash;
use crate::{error::BichonResult, raise_error};
use mail_parser::{MessageParser, MimeHeaders};
use poem_openapi::Object;
//use poem_openapi::Object;
use serde::{Deserialize, Serialize};
/// Represents metadata of an attachment in a Gmail message.
///
/// This struct stores information required to identify, download,
/// and render an attachment, including inline images embedded
/// in HTML emails.
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct AttachmentInfo {
/// MIME content type of the attachment (e.g., `image/png`, `application/pdf`).
pub file_type: String,
@@ -134,7 +135,8 @@ impl AttachmentInfo {
///
/// - `plain`: The plain text version of the message, if available.
/// - `html`: The HTML version of the message, if available.
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct FullMessageContent {
/// Optional plain text version of the message.
pub text: Option<String>,
@@ -144,7 +146,8 @@ pub struct FullMessageContent {
pub attachments: Option<Vec<AttachmentInfo>>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct FullNestedMessageContent {
/// Optional plain text version of the message.
pub text: Option<String>,
@@ -16,9 +16,9 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::error::BichonResult;
use crate::modules::store::tantivy::attachment::ATTACHMENT_MANAGER;
use crate::modules::store::tantivy::envelope::ENVELOPE_MANAGER;
use crate::error::BichonResult;
use crate::store::tantivy::attachment::ATTACHMENT_MANAGER;
use crate::store::tantivy::envelope::ENVELOPE_MANAGER;
use std::collections::HashMap;
pub async fn delete_messages_impl(request: HashMap<u64, Vec<String>>) -> BichonResult<()> {
@@ -16,11 +16,8 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::{
account::migration::AccountModel,
error::BichonResult,
rest::response::DataPage,
store::{envelope::Envelope, tantivy::envelope::ENVELOPE_MANAGER},
use crate::{
account::migration::AccountModel, common::paginated::DataPage, error::BichonResult, store::{envelope::Envelope, tantivy::envelope::ENVELOPE_MANAGER}
};
pub async fn get_thread_messages(
@@ -16,25 +16,21 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use poem_openapi::{Enum, Object};
//use poem_openapi::{Enum, Object};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use crate::{
modules::{
error::{code::ErrorCode, BichonResult},
rest::response::DataPage,
store::{
common::paginated::DataPage, error::{BichonResult, code::ErrorCode}, raise_error, store::{
envelope::Envelope,
tantivy::{
attachment::ATTACHMENT_MANAGER, envelope::ENVELOPE_MANAGER, model::AttachmentModel,
},
},
},
raise_error,
}
};
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)]
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct EmailSearchFilter {
pub text: Option<String>,
pub subject: Option<String>,
@@ -59,14 +55,16 @@ pub struct EmailSearchFilter {
pub attachment_content_type: Option<String>,
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Enum)]
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Enum))]
pub enum SortBy {
#[default]
DATE,
SIZE,
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)]
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct EmailSearchRequest {
filter: EmailSearchFilter,
page: u64,
@@ -110,7 +108,8 @@ pub async fn search_messages_impl(
.await
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)]
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct AttachmentSearchFilter {
pub id: Option<String>,
pub text: Option<String>,
@@ -139,7 +138,8 @@ pub struct AttachmentSearchFilter {
pub max_page_count: Option<u64>,
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)]
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct AttachmentSearchRequest {
filter: AttachmentSearchFilter,
page: u64,
@@ -18,23 +18,26 @@
use std::collections::HashMap;
use poem_openapi::{Enum, Object};
//use poem_openapi::{Enum, Object};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)]
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct TagsRequest {
pub updates: HashMap<u64, Vec<String>>,
pub tags: Vec<String>,
pub action: TagAction,
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)]
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct TagCount {
pub tag: String,
pub count: u64,
}
#[derive(Enum, Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Enum))]
pub enum TagAction {
Add,
Remove,
@@ -17,25 +17,24 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::{
encrypt, id,
modules::{
database::{
async_secondary_find_impl, delete_impl, insert_impl, manager::DB_MANAGER,
paginate_query_primary_scan_all_impl, update_impl,
},
error::{code::ErrorCode, BichonResult},
rest::response::DataPage,
common::paginated::DataPage,
database::{
async_secondary_find_impl, delete_impl, insert_impl, manager::DB_MANAGER,
paginate_query_primary_scan_all_impl, update_impl,
},
raise_error, utc_now,
encrypt,
error::{code::ErrorCode, BichonResult},
id, raise_error, utc_now,
};
use native_db::*;
use native_model::{native_model, Model};
use poem_openapi::Object;
//use poem_openapi::Object;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
/// Represents the OAuth2 configuration for a client, including initialization and runtime values.
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
#[native_model(id = 5, version = 1)]
#[native_db(primary_key(pk -> String))]
pub struct OAuth2 {
@@ -175,7 +174,8 @@ impl OAuth2 {
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct OAuth2CreateRequest {
/// A description of what this configuration is used for.
pub description: Option<String>,
@@ -225,7 +225,8 @@ impl OAuth2CreateRequest {
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct OAuth2UpdateRequest {
/// A description of what this configuration is used for.
pub description: Option<String>,
@@ -17,18 +17,18 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::error::code::ErrorCode;
use crate::modules::error::BichonResult;
use crate::modules::oauth2::{
use crate::error::code::ErrorCode;
use crate::error::BichonResult;
use crate::oauth2::{
entity::OAuth2, pending::OAuth2PendingEntity, token::OAuth2AccessToken,
};
use crate::modules::settings::proxy::Proxy;
use crate::settings::proxy::Proxy;
use crate::{decrypt, encrypt, raise_error};
use oauth2::{
basic::BasicClient, AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken,
PkceCodeChallenge, PkceCodeVerifier, RedirectUrl, RefreshToken, Scope, TokenResponse, TokenUrl,
};
use poem_openapi::Object;
//use poem_openapi::Object;
use serde::{Deserialize, Serialize};
pub type OAuth2Client = oauth2::Client<
@@ -47,7 +47,8 @@ pub type OAuth2Client = oauth2::Client<
oauth2::EndpointSet,
>;
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)]
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct AuthorizeUrlRequest {
/// The ID of the account for which the authorization URL is generated.
pub account_id: u64,
@@ -18,7 +18,7 @@
use crate::{
modules::{
{
database::{
batch_delete_impl, delete_impl, async_find_impl, insert_impl, manager::DB_MANAGER,
},
@@ -16,10 +16,10 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::common::periodic::{PeriodicTask, TaskHandle};
use crate::modules::context::BichonTask;
use crate::modules::oauth2::token::EXTERNAL_OAUTH_APP_ID;
use crate::modules::oauth2::{flow::OAuth2Flow, token::OAuth2AccessToken};
use crate::common::periodic::{PeriodicTask, TaskHandle};
use crate::context::BichonTask;
use crate::oauth2::token::EXTERNAL_OAUTH_APP_ID;
use crate::oauth2::{flow::OAuth2Flow, token::OAuth2AccessToken};
use crate::utc_now;
use std::time::Duration;
use tracing::{debug, error, info};
@@ -16,7 +16,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::{
use crate::{
common::periodic::{PeriodicTask, TaskHandle},
context::BichonTask,
oauth2::pending::OAuth2PendingEntity,
@@ -19,7 +19,7 @@
use crate::{
decrypt, encrypt,
modules::{
{
database::{
async_find_impl, delete_impl, insert_impl, list_all_impl, manager::DB_MANAGER,
update_impl, upsert_impl,
@@ -31,12 +31,13 @@ use crate::{
};
use native_db::*;
use native_model::{native_model, Model};
use poem_openapi::Object;
//use poem_openapi::Object;
use serde::{Deserialize, Serialize};
pub const EXTERNAL_OAUTH_APP_ID: u64 = 0;
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
#[native_model(id = 7, version = 1)]
#[native_db]
pub struct OAuth2AccessToken {
@@ -195,7 +196,8 @@ impl OAuth2AccessToken {
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct ExternalOAuth2Request {
/// The id of the OAuth2 configuration associated with this access token.
pub oauth2_id: Option<u64>,
@@ -245,7 +247,7 @@ impl ExternalOAuth2Request {
#[cfg(test)]
mod tests {
use crate::modules::oauth2::token::OAuth2AccessToken;
use crate::oauth2::token::OAuth2AccessToken;
#[tokio::test]
async fn test1() {
@@ -16,7 +16,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::settings::io::check_dir_read_write;
use crate::settings::io::check_dir_read_write;
use clap::{builder::ValueParser, Parser, ValueEnum};
use std::{collections::HashSet, env, fmt, path::PathBuf, sync::LazyLock};
@@ -16,10 +16,10 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::context::Initialize;
use crate::modules::settings::cli::SETTINGS;
use crate::context::Initialize;
use crate::settings::cli::SETTINGS;
use crate::{
modules::error::{code::ErrorCode, BichonResult},
error::{code::ErrorCode, BichonResult},
raise_error,
};
use std::path::PathBuf;
@@ -16,8 +16,8 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::settings::cli::Settings;
use poem_openapi::Object;
use crate::settings::cli::Settings;
//use poem_openapi::Object;
use serde::{Deserialize, Serialize};
pub mod cli;
@@ -25,7 +25,8 @@ pub mod dir;
pub mod io;
pub mod proxy;
pub mod system;
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct SystemConfigurations {
pub bichon_log_level: String,
pub bichon_http_port: i32,
@@ -18,12 +18,12 @@
use native_db::*;
use native_model::{native_model, Model};
use poem_openapi::Object;
//use poem_openapi::Object;
use serde::{Deserialize, Serialize};
use crate::{
id,
modules::{
{
database::{
async_find_impl, delete_impl, insert_impl, list_all_impl, manager::DB_MANAGER,
update_impl,
@@ -34,7 +34,8 @@ use crate::{
raise_error, utc_now,
};
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
#[native_model(id = 8, version = 1)]
#[native_db]
pub struct Proxy {
@@ -16,11 +16,6 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// use crate::modules::database::manager::DB_MANAGER;
// use crate::modules::database::{find_impl, upsert_impl};
// use crate::modules::error::BichonResult;
// use crate::utc_now;
use native_db::*;
use native_model::{native_model, Model};
use serde::{Deserialize, Serialize};
@@ -16,11 +16,12 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use poem_openapi::Object;
//use poem_openapi::Object;
use serde::{Deserialize, Serialize};
use tantivy::doc;
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "web-api", derive(poem_openapi::Object))]
pub struct Envelope {
pub id: String,
pub message_id: String,
@@ -16,7 +16,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::{
use crate::{
common::signal::SIGNAL_MANAGER,
envelope::extractor::reattach_eml_content,
error::{code::ErrorCode, BichonResult},
@@ -25,28 +25,19 @@ use std::{
};
use crate::{
modules::{
common::signal::SIGNAL_MANAGER,
dashboard::{Group, LargestAttachment},
error::{code::ErrorCode, BichonResult},
message::{
common::{paginated::DataPage, signal::SIGNAL_MANAGER}, dashboard::{Group, LargestAttachment}, error::{BichonResult, code::ErrorCode}, message::{
attachment::AttachmentMetadata,
search::{AttachmentSearchFilter, SortBy},
tags::{TagAction, TagCount, TagsRequest},
},
rest::response::DataPage,
settings::dir::DATA_DIR_MANAGER,
store::tantivy::{
}, raise_error, settings::dir::DATA_DIR_MANAGER, store::tantivy::{
fatal_commit,
fields::{
F_ATTACHMENT_CATEGORY, F_ATTACHMENT_CONTENT_TYPE, F_ATTACHMENT_EXT, F_DATE, F_SIZE,
F_TAGS,
},
model::{extract_senders, AttachmentModel},
model::{AttachmentModel, extract_senders},
schema::SchemaTools,
},
},
raise_error,
}
};
use serde_json::json;
@@ -25,18 +25,10 @@ use std::{
};
use crate::{
modules::{
account::migration::AccountModel,
common::signal::SIGNAL_MANAGER,
dashboard::{DashboardStats, Group, LargestEmail, TimeBucket},
error::{code::ErrorCode, BichonResult},
message::{
account::migration::AccountModel, common::{paginated::DataPage, signal::SIGNAL_MANAGER}, dashboard::{DashboardStats, Group, LargestEmail, TimeBucket}, error::{BichonResult, code::ErrorCode}, message::{
search::{EmailSearchFilter, SortBy},
tags::{TagAction, TagCount, TagsRequest},
},
rest::response::DataPage,
settings::dir::DATA_DIR_MANAGER,
store::{
}, raise_error, settings::dir::DATA_DIR_MANAGER, store::{
envelope::Envelope,
storage::BLOB_MANAGER,
tantivy::{
@@ -45,12 +37,10 @@ use crate::{
F_ACCOUNT_ID, F_DATE, F_FROM, F_REGULAR_ATTACHMENT_COUNT, F_SIZE, F_TAGS,
F_THREAD_ID, F_UID,
},
model::{extract_contacts, EnvelopeWithAttachments},
model::{EnvelopeWithAttachments, extract_contacts},
schema::SchemaTools,
},
},
},
raise_error, utc_now,
}, utc_now
};
use chrono::Utc;

Some files were not shown because too many files have changed in this diff Show More