use super::stalwart_client::{ AccountUpdate, AutoResponderConfig, EmailRule, RuleAction, RuleCondition, StalwartClient, }; use anyhow::{Context, Result}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::sync::Arc; use tracing::{info, warn}; use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DistributionListDto { pub id: Uuid, pub bot_id: Uuid, pub owner_id: Uuid, pub name: String, pub email_alias: Option, pub description: Option, pub members: Vec, pub is_public: bool, pub stalwart_principal_id: Option, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NewDistributionList { pub bot_id: Uuid, pub owner_id: Uuid, pub name: String, pub email_alias: String, pub description: Option, pub members: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AutoResponderDto { pub id: Uuid, pub user_id: Uuid, pub bot_id: Uuid, pub responder_type: String, pub subject: String, pub body_html: String, pub body_plain: Option, pub start_date: Option>, pub end_date: Option>, pub send_to_internal_only: bool, pub exclude_addresses: Option, pub is_active: bool, pub stalwart_sieve_id: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NewAutoResponder { pub bot_id: Uuid, pub user_id: Uuid, pub subject: String, pub body_html: String, pub body_plain: Option, pub start_date: Option>, pub end_date: Option>, pub only_contacts: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct EmailRuleDto { pub id: Uuid, pub user_id: Uuid, pub bot_id: Uuid, pub name: String, pub priority: i32, pub is_active: bool, pub conditions: Vec, pub actions: Vec, pub stop_processing: bool, pub stalwart_sieve_id: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NewEmailRule { pub bot_id: Uuid, pub user_id: Uuid, pub name: String, pub priority: i32, pub conditions: Vec, pub actions: Vec, pub stop_processing: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SharedMailboxDto { pub id: Uuid, pub bot_id: Uuid, pub email_address: String, pub display_name: String, pub description: Option, pub stalwart_account_id: Option, pub is_active: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SharedMailboxMemberDto { pub id: Uuid, pub mailbox_id: Uuid, pub user_id: Uuid, pub permission_level: String, pub added_at: DateTime, } pub struct StalwartSyncService { stalwart: Arc, } impl StalwartSyncService { pub fn new(stalwart_client: Arc) -> Self { Self { stalwart: stalwart_client, } } pub fn stalwart(&self) -> &StalwartClient { &self.stalwart } pub async fn create_distribution_list_in_stalwart( &self, list: &NewDistributionList, ) -> Result { info!( "Creating distribution list '{}' with email '{}' in Stalwart", list.name, list.email_alias ); let stalwart_id = self .stalwart .create_distribution_list(&list.name, &list.email_alias, list.members.clone()) .await .context("Failed to create distribution list in Stalwart")?; info!( "Created distribution list in Stalwart with ID: {}", stalwart_id ); Ok(stalwart_id.to_string()) } pub async fn update_distribution_list_in_stalwart( &self, stalwart_id: &str, name: Option<&str>, members: Option<&[String]>, ) -> Result<()> { let mut updates = Vec::new(); if let Some(n) = name { updates.push(AccountUpdate::set("description", n.to_string())); } if let Some(m) = members { updates.push(AccountUpdate::clear("members")); for member in m { updates.push(AccountUpdate::add_item("members", member.clone())); } } if !updates.is_empty() { self.stalwart .update_account(stalwart_id, updates) .await .context("Failed to update distribution list in Stalwart")?; } info!("Updated distribution list {} in Stalwart", stalwart_id); Ok(()) } pub async fn delete_distribution_list_from_stalwart(&self, stalwart_id: &str) -> Result<()> { self.stalwart .delete_account(stalwart_id) .await .context("Failed to delete distribution list from Stalwart")?; info!("Deleted distribution list {} from Stalwart", stalwart_id); Ok(()) } pub async fn set_auto_responder_in_stalwart( &self, account_id: &str, responder: &NewAutoResponder, ) -> Result { info!( "Setting auto-responder for user {} in account {}", responder.user_id, account_id ); let config = AutoResponderConfig { enabled: true, subject: responder.subject.clone(), body_plain: responder.body_plain.clone().unwrap_or_default(), body_html: Some(responder.body_html.clone()), start_date: responder.start_date.map(|dt| dt.date_naive()), end_date: responder.end_date.map(|dt| dt.date_naive()), only_contacts: responder.only_contacts, vacation_days: 1, }; let sieve_id = self .stalwart .set_auto_responder(account_id, &config) .await .context("Failed to set auto-responder in Stalwart")?; info!("Created auto-responder Sieve script: {}", sieve_id); Ok(sieve_id) } pub async fn disable_auto_responder_in_stalwart(&self, account_id: &str) -> Result<()> { self.stalwart .disable_auto_responder(account_id) .await .context("Failed to disable auto-responder in Stalwart")?; info!("Disabled auto-responder for account {}", account_id); Ok(()) } pub async fn set_email_rule_in_stalwart( &self, account_id: &str, rule: &NewEmailRule, rule_id: Uuid, ) -> Result { info!( "Setting email rule '{}' for user {} in account {}", rule.name, rule.user_id, account_id ); let stalwart_rule = EmailRule { id: rule_id.to_string(), name: rule.name.clone(), priority: rule.priority, enabled: true, conditions: rule.conditions.clone(), actions: rule.actions.clone(), stop_processing: rule.stop_processing, }; let sieve_id = self .stalwart .set_filter_rule(account_id, &stalwart_rule) .await .context("Failed to set email rule in Stalwart")?; info!("Created email rule Sieve script: {}", sieve_id); Ok(sieve_id) } pub async fn delete_email_rule_from_stalwart( &self, account_id: &str, rule_id: &str, ) -> Result<()> { self.stalwart .delete_filter_rule(account_id, rule_id) .await .context("Failed to delete email rule from Stalwart")?; info!("Deleted email rule {} from Stalwart", rule_id); Ok(()) } pub async fn create_shared_mailbox_in_stalwart( &self, name: &str, email: &str, initial_members: Vec, ) -> Result { info!("Creating shared mailbox '{}' with email '{}'", name, email); let stalwart_id = self .stalwart .create_shared_mailbox(name, email, initial_members) .await .context("Failed to create shared mailbox in Stalwart")?; info!( "Created shared mailbox in Stalwart with ID: {}", stalwart_id ); Ok(stalwart_id.to_string()) } pub async fn add_shared_mailbox_member_in_stalwart( &self, stalwart_id: &str, member_email: &str, ) -> Result<()> { self.stalwart .add_members(stalwart_id, vec![member_email.to_string()]) .await .context("Failed to add member to shared mailbox in Stalwart")?; info!( "Added member {} to shared mailbox {} in Stalwart", member_email, stalwart_id ); Ok(()) } pub async fn remove_shared_mailbox_member_in_stalwart( &self, stalwart_id: &str, member_email: &str, ) -> Result<()> { self.stalwart .remove_members(stalwart_id, vec![member_email.to_string()]) .await .context("Failed to remove member from shared mailbox in Stalwart")?; info!( "Removed member {} from shared mailbox {} in Stalwart", member_email, stalwart_id ); Ok(()) } pub async fn delete_shared_mailbox_from_stalwart(&self, stalwart_id: &str) -> Result<()> { self.stalwart .delete_account(stalwart_id) .await .context("Failed to delete shared mailbox from Stalwart")?; info!("Deleted shared mailbox {} from Stalwart", stalwart_id); Ok(()) } pub async fn sync_distribution_list_to_stalwart( &self, name: &str, email_alias: &str, members: Vec, ) -> Result { match self .stalwart .create_distribution_list(name, email_alias, members.clone()) .await { Ok(stalwart_id) => { info!( "Synced distribution list '{}' to Stalwart with ID: {}", name, stalwart_id ); Ok(stalwart_id.to_string()) } Err(e) => { warn!( "Failed to sync distribution list '{}' to Stalwart: {}", name, e ); Err(e) } } } pub async fn health_check(&self) -> Result { self.stalwart.health_check().await } }