use chrono::{DateTime, NaiveDate, Utc}; use uuid::Uuid; #[derive(Debug, Clone)] pub struct AddressBook { id: Uuid, name: String, owner_id: String, description: Option, color: Option, is_public: bool, created_at: DateTime, updated_at: DateTime, } impl AddressBook { /// Creates a new AddressBook with generated id and timestamps pub fn new( name: String, owner_id: String, description: Option, color: Option, is_public: bool, ) -> Self { let now = Utc::now(); Self { id: Uuid::new_v4(), name, owner_id, description, color, is_public, created_at: now, updated_at: now, } } #[allow(clippy::too_many_arguments)] pub fn from_raw( id: Uuid, name: String, owner_id: String, description: Option, color: Option, is_public: bool, created_at: DateTime, updated_at: DateTime, ) -> Self { Self { id, name, owner_id, description, color, is_public, created_at, updated_at, } } // --- Getters --- pub fn id(&self) -> &Uuid { &self.id } pub fn name(&self) -> &str { &self.name } pub fn owner_id(&self) -> &str { &self.owner_id } pub fn description(&self) -> Option<&str> { self.description.as_deref() } pub fn color(&self) -> Option<&str> { self.color.as_deref() } pub fn is_public(&self) -> bool { self.is_public } pub fn created_at(&self) -> &DateTime { &self.created_at } pub fn updated_at(&self) -> &DateTime { &self.updated_at } // --- Setters for mutable operations --- pub fn set_name(&mut self, name: String) { self.name = name; self.updated_at = Utc::now(); } pub fn set_description(&mut self, description: Option) { self.description = description; self.updated_at = Utc::now(); } pub fn set_color(&mut self, color: Option) { self.color = color; self.updated_at = Utc::now(); } pub fn set_is_public(&mut self, is_public: bool) { self.is_public = is_public; self.updated_at = Utc::now(); } pub fn set_updated_at(&mut self, updated_at: DateTime) { self.updated_at = updated_at; } } impl Default for AddressBook { fn default() -> Self { Self::new( "Default Address Book".to_string(), "default".to_string(), None, None, false, ) } } #[derive(Debug, Clone)] pub struct Email { pub email: String, pub r#type: String, // home, work, other pub is_primary: bool, } #[derive(Debug, Clone)] pub struct Phone { pub number: String, pub r#type: String, // mobile, home, work, fax, other pub is_primary: bool, } #[derive(Debug, Clone)] pub struct Address { pub street: Option, pub city: Option, pub state: Option, pub postal_code: Option, pub country: Option, pub r#type: String, // home, work, other pub is_primary: bool, } #[derive(Debug, Clone)] pub struct Contact { id: Uuid, address_book_id: Uuid, uid: String, full_name: Option, first_name: Option, last_name: Option, nickname: Option, email: Vec, phone: Vec, address: Vec
, organization: Option, title: Option, notes: Option, photo_url: Option, birthday: Option, anniversary: Option, vcard: String, etag: String, created_at: DateTime, updated_at: DateTime, } impl Contact { /// Creates a new Contact with generated id, uid, etag and timestamps #[allow(clippy::too_many_arguments)] pub fn new( address_book_id: Uuid, full_name: Option, first_name: Option, last_name: Option, nickname: Option, email: Vec, phone: Vec, address: Vec
, organization: Option, title: Option, notes: Option, photo_url: Option, birthday: Option, anniversary: Option, vcard: String, ) -> Self { let now = Utc::now(); Self { id: Uuid::new_v4(), address_book_id, uid: format!("{}@oxicloud", Uuid::new_v4()), full_name, first_name, last_name, nickname, email, phone, address, organization, title, notes, photo_url, birthday, anniversary, vcard, etag: Uuid::new_v4().to_string(), created_at: now, updated_at: now, } } /// Reconstructs from persistence (no validation) #[allow(clippy::too_many_arguments)] pub fn from_raw( id: Uuid, address_book_id: Uuid, uid: String, full_name: Option, first_name: Option, last_name: Option, nickname: Option, email: Vec, phone: Vec, address: Vec
, organization: Option, title: Option, notes: Option, photo_url: Option, birthday: Option, anniversary: Option, vcard: String, etag: String, created_at: DateTime, updated_at: DateTime, ) -> Self { Self { id, address_book_id, uid, full_name, first_name, last_name, nickname, email, phone, address, organization, title, notes, photo_url, birthday, anniversary, vcard, etag, created_at, updated_at, } } // --- Getters --- pub fn id(&self) -> &Uuid { &self.id } pub fn address_book_id(&self) -> &Uuid { &self.address_book_id } pub fn uid(&self) -> &str { &self.uid } pub fn full_name(&self) -> Option<&str> { self.full_name.as_deref() } pub fn first_name(&self) -> Option<&str> { self.first_name.as_deref() } pub fn last_name(&self) -> Option<&str> { self.last_name.as_deref() } pub fn nickname(&self) -> Option<&str> { self.nickname.as_deref() } pub fn email(&self) -> &[Email] { &self.email } pub fn phone(&self) -> &[Phone] { &self.phone } pub fn address(&self) -> &[Address] { &self.address } pub fn organization(&self) -> Option<&str> { self.organization.as_deref() } pub fn title(&self) -> Option<&str> { self.title.as_deref() } pub fn notes(&self) -> Option<&str> { self.notes.as_deref() } pub fn photo_url(&self) -> Option<&str> { self.photo_url.as_deref() } pub fn birthday(&self) -> Option<&NaiveDate> { self.birthday.as_ref() } pub fn anniversary(&self) -> Option<&NaiveDate> { self.anniversary.as_ref() } pub fn vcard(&self) -> &str { &self.vcard } pub fn etag(&self) -> &str { &self.etag } pub fn created_at(&self) -> &DateTime { &self.created_at } pub fn updated_at(&self) -> &DateTime { &self.updated_at } // --- Owned getters for persistence layer bind() calls --- pub fn full_name_owned(&self) -> Option { self.full_name.clone() } pub fn first_name_owned(&self) -> Option { self.first_name.clone() } pub fn last_name_owned(&self) -> Option { self.last_name.clone() } pub fn nickname_owned(&self) -> Option { self.nickname.clone() } pub fn organization_owned(&self) -> Option { self.organization.clone() } pub fn title_owned(&self) -> Option { self.title.clone() } pub fn notes_owned(&self) -> Option { self.notes.clone() } pub fn photo_url_owned(&self) -> Option { self.photo_url.clone() } // --- Setters for mutable operations (contact_service.rs needs these) --- pub fn set_full_name(&mut self, v: Option) { self.full_name = v; } pub fn set_first_name(&mut self, v: Option) { self.first_name = v; } pub fn set_last_name(&mut self, v: Option) { self.last_name = v; } pub fn set_nickname(&mut self, v: Option) { self.nickname = v; } pub fn set_organization(&mut self, v: Option) { self.organization = v; } pub fn set_title(&mut self, v: Option) { self.title = v; } pub fn set_notes(&mut self, v: Option) { self.notes = v; } pub fn set_photo_url(&mut self, v: Option) { self.photo_url = v; } pub fn set_birthday(&mut self, v: Option) { self.birthday = v; } pub fn set_anniversary(&mut self, v: Option) { self.anniversary = v; } pub fn set_vcard(&mut self, vcard: String) { self.vcard = vcard; } pub fn set_etag(&mut self, etag: String) { self.etag = etag; } pub fn set_updated_at(&mut self, updated_at: DateTime) { self.updated_at = updated_at; } pub fn set_address_book_id(&mut self, id: Uuid) { self.address_book_id = id; } pub fn set_uid(&mut self, uid: String) { self.uid = uid; } // --- Collection mutators --- pub fn push_email(&mut self, e: Email) { self.email.push(e); } pub fn push_phone(&mut self, p: Phone) { self.phone.push(p); } pub fn set_email(&mut self, email: Vec) { self.email = email; } pub fn set_phone(&mut self, phone: Vec) { self.phone = phone; } pub fn set_address(&mut self, address: Vec
) { self.address = address; } pub fn email_is_empty(&self) -> bool { self.email.is_empty() } pub fn phone_is_empty(&self) -> bool { self.phone.is_empty() } // --- Consuming methods for ownership transfer --- pub fn into_email(self) -> Vec { self.email } pub fn into_parts(self) -> ContactParts { ContactParts { id: self.id, address_book_id: self.address_book_id, uid: self.uid, full_name: self.full_name, first_name: self.first_name, last_name: self.last_name, nickname: self.nickname, email: self.email, phone: self.phone, address: self.address, organization: self.organization, title: self.title, notes: self.notes, photo_url: self.photo_url, birthday: self.birthday, anniversary: self.anniversary, vcard: self.vcard, etag: self.etag, created_at: self.created_at, updated_at: self.updated_at, } } } /// Holds all Contact fields by value, for when ownership transfer is needed pub struct ContactParts { pub id: Uuid, pub address_book_id: Uuid, pub uid: String, pub full_name: Option, pub first_name: Option, pub last_name: Option, pub nickname: Option, pub email: Vec, pub phone: Vec, pub address: Vec
, pub organization: Option, pub title: Option, pub notes: Option, pub photo_url: Option, pub birthday: Option, pub anniversary: Option, pub vcard: String, pub etag: String, pub created_at: DateTime, pub updated_at: DateTime, } impl Default for Contact { fn default() -> Self { let now = Utc::now(); Self { id: Uuid::new_v4(), address_book_id: Uuid::new_v4(), uid: format!("{}@oxicloud", Uuid::new_v4()), full_name: None, first_name: None, last_name: None, nickname: None, email: Vec::new(), phone: Vec::new(), address: Vec::new(), organization: None, title: None, notes: None, photo_url: None, birthday: None, anniversary: None, vcard: "BEGIN:VCARD\nVERSION:3.0\nEND:VCARD".to_string(), etag: Uuid::new_v4().to_string(), created_at: now, updated_at: now, } } } #[derive(Debug, Clone)] pub struct ContactGroup { id: Uuid, address_book_id: Uuid, name: String, created_at: DateTime, updated_at: DateTime, } impl ContactGroup { /// Creates a new ContactGroup with generated id and timestamps pub fn new(address_book_id: Uuid, name: String) -> Self { let now = Utc::now(); Self { id: Uuid::new_v4(), address_book_id, name, created_at: now, updated_at: now, } } /// Reconstructs from persistence pub fn from_raw( id: Uuid, address_book_id: Uuid, name: String, created_at: DateTime, updated_at: DateTime, ) -> Self { Self { id, address_book_id, name, created_at, updated_at, } } // --- Getters --- pub fn id(&self) -> &Uuid { &self.id } pub fn address_book_id(&self) -> &Uuid { &self.address_book_id } pub fn name(&self) -> &str { &self.name } pub fn created_at(&self) -> &DateTime { &self.created_at } pub fn updated_at(&self) -> &DateTime { &self.updated_at } // --- Setters --- pub fn set_name(&mut self, name: String) { self.name = name; self.updated_at = Utc::now(); } pub fn set_updated_at(&mut self, updated_at: DateTime) { self.updated_at = updated_at; } } impl Default for ContactGroup { fn default() -> Self { ContactGroup::new(Uuid::new_v4(), "New Group".to_string()) } }