use serde::{Deserialize, Serialize}; use crate::db::{Model, Storable, DB, DbError, DbResult}; use crate::models::mcc::event::Event; use chrono::Utc; /// Contact represents a contact entry in an address book #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Contact { // Database ID pub id: u32, // Database ID (assigned by DBHandler) // Content fields pub created_at: i64, // Unix epoch timestamp pub modified_at: i64, // Unix epoch timestamp pub first_name: String, pub last_name: String, pub email: String, pub group: String, // Reference to a dns name, each group has a globally unique dns pub groups: Vec, // Groups this contact belongs to (references Circle IDs) } impl Contact { /// Create a new contact pub fn new(id: u32, first_name: String, last_name: String, email: String, group: String) -> Self { let now = Utc::now().timestamp(); Self { id, created_at: now, modified_at: now, first_name, last_name, email, group, groups: Vec::new(), } } /// Add a group to this contact pub fn add_group(&mut self, group_id: u32) { if !self.groups.contains(&group_id) { self.groups.push(group_id); } } /// Remove a group from this contact pub fn remove_group(&mut self, group_id: u32) { self.groups.retain(|&id| id != group_id); } /// Filter by groups - returns true if this contact belongs to any of the specified groups pub fn filter_by_groups(&self, groups: &[u32]) -> bool { groups.iter().any(|g| self.groups.contains(g)) } /// Search by name - returns true if the name contains the query (case-insensitive) pub fn search_by_name(&self, query: &str) -> bool { let full_name = self.full_name().to_lowercase(); query.to_lowercase().split_whitespace().all(|word| full_name.contains(word)) } /// Search by email - returns true if the email contains the query (case-insensitive) pub fn search_by_email(&self, query: &str) -> bool { self.email.to_lowercase().contains(&query.to_lowercase()) } /// Get events where this contact is an attendee pub fn get_events(&self, db: &DB) -> DbResult> { let all_events = db.list::()?; let contact_events = all_events .into_iter() .filter(|event| event.attendees.contains(&self.email)) .collect(); Ok(contact_events) } /// Update the contact's information pub fn update(&mut self, first_name: Option, last_name: Option, email: Option, group: Option) { if let Some(first_name) = first_name { self.first_name = first_name; } if let Some(last_name) = last_name { self.last_name = last_name; } if let Some(email) = email { self.email = email; } if let Some(group) = group { self.group = group; } self.modified_at = Utc::now().timestamp(); } /// Update the contact's groups pub fn update_groups(&mut self, groups: Vec) { self.groups = groups; self.modified_at = Utc::now().timestamp(); } /// Get the full name of the contact pub fn full_name(&self) -> String { format!("{} {}", self.first_name, self.last_name) } } // Implement Storable trait (provides default dump/load) impl Storable for Contact {} // Implement SledModel trait impl Model for Contact { fn get_id(&self) -> u32 { self.id } fn db_prefix() -> &'static str { "contact" } }