// // Copyright (c) 2025 rustmailer.com (https://rustmailer.com) // // This file is part of the Bichon Email Archiving Project // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . use crate::modules::account::migration::{AccountV1, AccountV2, AccountV3}; 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::raise_error; use db_type::{KeyOptions, ToKeyDefinition}; use itertools::Itertools; use native_db::*; use serde::Serialize; use std::sync::{Arc, LazyLock}; use transaction::RwTransaction; pub mod manager; pub static META_MODELS: LazyLock = LazyLock::new(|| { let mut adapter = ModelsAdapter::new(); adapter.register_metadata_models(); adapter.models }); pub struct ModelsAdapter { pub models: Models, } impl ModelsAdapter { pub fn new() -> Self { ModelsAdapter { models: Models::new(), } } pub fn register_model(&mut self) { self.models.define::().expect("failed to define model "); } pub fn register_metadata_models(&mut self) { //Starting from version 0.2.0, `AccessToken` is deprecated/no longer used, but its ID must not be reused, otherwise it may cause model errors. //self.register_model::(); self.register_model::(); self.register_model::(); self.register_model::(); self.register_model::(); self.register_model::(); self.register_model::(); self.register_model::(); self.register_model::(); self.register_model::(); self.register_model::(); self.register_model::(); self.register_model::(); self.register_model::(); } } pub async fn insert_impl( database: &Arc>, item: T, ) -> BichonResult<()> { let db = database.clone(); tokio::task::spawn_blocking(move || { let rw_transaction = db .rw_transaction() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; rw_transaction .insert(item) .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; rw_transaction .commit() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; Ok(()) }) .await .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))? } pub async fn batch_insert_impl( database: &Arc>, batch: Vec, ) -> BichonResult<()> { let db = database.clone(); tokio::task::spawn_blocking(move || { let rw_transaction = db .rw_transaction() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; for item in batch { rw_transaction .insert(item) .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; } rw_transaction .commit() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; Ok(()) }) .await .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))? } pub async fn batch_upsert_impl( database: &Arc>, batch: Vec, ) -> BichonResult<()> { let db = database.clone(); tokio::task::spawn_blocking(move || { let rw_transaction = db .rw_transaction() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; for item in batch { rw_transaction .upsert(item) .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; } rw_transaction .commit() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; Ok(()) }) .await .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))? } pub async fn upsert_impl( database: &Arc>, item: T, ) -> BichonResult<()> { let db = database.clone(); tokio::task::spawn_blocking(move || { let rw_transaction = db .rw_transaction() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; rw_transaction .upsert(item) .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; rw_transaction .commit() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; Ok(()) }) .await .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))? } pub async fn update_impl( database: &Arc>, current: impl FnOnce(&RwTransaction) -> BichonResult + Send + 'static, updated: impl FnOnce(&T) -> BichonResult + Send + 'static, ) -> BichonResult { let db = database.clone(); tokio::task::spawn_blocking(move || { let rw = db .rw_transaction() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; let current_item = current(&rw)?; let updated_item = updated(¤t_item)?; rw.update(current_item, updated_item.clone()) .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; rw.commit() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; Ok(updated_item) }) .await .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))? } // pub async fn batch_update_impl( // database: &Arc>, // filter: impl FnOnce(&RwTransaction) -> RustMailerResult> + Send + 'static, // updated: impl FnOnce(&Vec) -> RustMailerResult> + Send + 'static, // ) -> RustMailerResult> { // let db = database.clone(); // tokio::task::spawn_blocking(move || { // let rw = db // .rw_transaction() // .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; // let targets = filter(&rw)?; // let tuples = updated(&targets)?; // for (old, updated) in tuples { // rw.update(old, updated) // .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; // } // rw.commit() // .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; // Ok(targets) // }) // .await // .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))? // } pub async fn async_find_impl( database: &Arc>, key: impl ToKey + Send + 'static, ) -> BichonResult> { let db = database.clone(); tokio::task::spawn_blocking(move || { let r_transaction = db .r_transaction() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; let entity: Option = r_transaction .get() .primary(key) .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; Ok(entity) }) .await .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))? } // pub fn find_impl( // database: &Arc>, // key: &str, // ) -> BichonResult> { // let db = database.clone(); // let r_transaction = db // .r_transaction() // .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; // let entity: Option = r_transaction // .get() // .primary(key) // .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; // Ok(entity) // } pub async fn delete_impl( database: &Arc>, delete: impl FnOnce(&RwTransaction) -> BichonResult + Send + 'static, ) -> BichonResult<()> { let db = database.clone(); tokio::task::spawn_blocking(move || { let rw_transaction = db .rw_transaction() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; let to_delete = delete(&rw_transaction)?; rw_transaction .remove::(to_delete) .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; rw_transaction .commit() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; Ok(()) }) .await .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))? } pub async fn batch_delete_impl( database: &Arc>, delete: impl FnOnce(&RwTransaction) -> BichonResult> + Send + 'static, ) -> BichonResult { let db = database.clone(); tokio::task::spawn_blocking(move || { let rw_transaction = db .rw_transaction() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; let to_delete = delete(&rw_transaction)?; let delete_count = to_delete.len(); for item in to_delete { rw_transaction .remove(item) .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; } rw_transaction .commit() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; Ok(delete_count) }) .await .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))? } pub async fn list_all_impl( database: &Arc>, ) -> BichonResult> { let db = database.clone(); tokio::task::spawn_blocking(move || { let r_transaction = db .r_transaction() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; let entities: Vec = r_transaction .scan() .primary() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))? .all() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))? .try_collect() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; Ok(entities) }) .await .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))? } pub async fn with_transaction( database: &Arc>, f: impl FnOnce(&RwTransaction) -> BichonResult<()> + Send + 'static, ) -> BichonResult<()> { let db: Arc> = database.clone(); tokio::task::spawn_blocking(move || { let rw_transaction = db .rw_transaction() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; f(&rw_transaction)?; rw_transaction .commit() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; Ok(()) }) .await .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))? } // For tables with a creation timestamp, place the creation time at the front of the primary key. // This allows sorting by time, as the data is stored in dictionary order based on the primary key. // If reverse sorting by time is needed, the iterator can be reversed. pub async fn paginate_query_primary_scan_all_impl< T: ToInput + Serialize + std::fmt::Debug + std::marker::Unpin + Send + Sync + 'static, >( database: &Arc>, page: Option, page_size: Option, desc: Option, ) -> BichonResult> { let db = database.clone(); tokio::task::spawn_blocking(move || { let r_transaction = db .r_transaction() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; let total_items = r_transaction .len() .primary::() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; // Validate page and page_size let (offset, total_pages) = if let (Some(p), Some(s)) = (page, page_size) { if p == 0 || s == 0 { return Err(raise_error!( "'page' and 'page_size' must be greater than 0.".into(), ErrorCode::InvalidParameter )); } let offset = (p - 1) * s; let total_pages = if total_items > 0 { (total_items as f64 / s as f64).ceil() as u64 } else { 0 }; (Some(offset), Some(total_pages)) } else { (None, None) }; // Handle empty result early if let Some(offset) = offset { if offset >= total_items { return Ok(Paginated::new( page, page_size, total_items, total_pages, vec![], )); } } let scan = r_transaction .scan() .primary() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; let iter = scan .all() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; // Collect items based on the reverse flag and pagination let items: Vec = match desc { Some(true) => iter .rev() .skip(offset.unwrap_or(0) as usize) .take(page_size.unwrap_or(total_items) as usize) .try_collect() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?, _ => iter .skip(offset.unwrap_or(0) as usize) .take(page_size.unwrap_or(total_items) as usize) .try_collect() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?, }; Ok(Paginated::new( page, page_size, total_items, total_pages, items, )) }) .await .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))? } pub async fn filter_by_secondary_key_impl( database: &Arc>, key_def: impl ToKeyDefinition + Send + 'static, start_with: impl ToKey + Send + 'static, ) -> BichonResult> { let db = database.clone(); tokio::task::spawn_blocking(move || { let r_transaction = db .r_transaction() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; let entities: Vec = r_transaction .scan() .secondary(key_def) .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))? .start_with(start_with) .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))? .try_collect() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; Ok(entities) }) .await .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))? } pub async fn count_by_unique_secondary_key_impl( database: &Arc>, key_def: impl ToKeyDefinition + Send + 'static, ) -> BichonResult { let db = database.clone(); tokio::task::spawn_blocking(move || { let r_transaction = db .r_transaction() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; let count = r_transaction .scan() .secondary::(key_def) .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))? .all() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))? .count(); Ok(count) }) .await .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))? } pub async fn secondary_find_impl( database: &Arc>, key_def: impl ToKeyDefinition + Send + 'static, key: impl ToKey + Send + 'static, ) -> BichonResult> { let db = database.clone(); tokio::task::spawn_blocking(move || { let r_transaction = db .r_transaction() .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; let entities: Option = r_transaction .get() .secondary(key_def, key) .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?; Ok(entities) }) .await .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))? } #[derive(Debug)] pub struct Paginated { pub page: Option, pub page_size: Option, pub total_items: u64, pub total_pages: Option, pub items: Vec, } impl Paginated { pub fn new( page: Option, page_size: Option, total_items: u64, total_pages: Option, items: Vec, ) -> Self { Paginated { page, page_size, total_items, total_pages, items, } } }