//! SQLite implementation of the ContactRepository. //! //! Manages contacts with sub-collections (emails, phones, social handles). //! Sub-collections are stored in separate tables and batch-loaded for list operations. mod crud; mod hydrate; mod query; mod row; mod subcollections; use goingson_core::{ Contact, ContactCustomField, ContactEmail, ContactEmailEntry, ContactEmailId, ContactId, ContactPhone, ContactPhoneId, ContactRepository, CustomFieldId, NewContact, NewContactCustomField, NewContactEmail, NewContactPhone, NewSocialHandle, Result, SocialHandle, SocialHandleId, UpdateContact, UserId, }; use std::collections::HashSet; use crate::Db; /// SQLite-backed implementation of [`ContactRepository`]. pub struct SqliteContactRepository { db: Db, } impl SqliteContactRepository { /// Creates a new repository instance with the given connection pool. #[tracing::instrument(skip_all)] pub fn new(db: Db) -> Self { Self { db } } } impl ContactRepository for SqliteContactRepository { #[tracing::instrument(skip_all)] fn list_all(&self, user_id: UserId) -> Result> { let conn = self.db.conn()?; query::list_all(&conn, user_id) } fn list_all_for_backup(&self, user_id: UserId) -> Result> { let conn = self.db.conn()?; query::list_all_for_backup(&conn, user_id) } #[tracing::instrument(skip_all)] fn get_by_id(&self, id: ContactId, user_id: UserId) -> Result> { let conn = self.db.conn()?; query::get_by_id(&conn, id, user_id) } #[tracing::instrument(skip_all)] fn create(&self, user_id: UserId, contact: NewContact) -> Result { let conn = self.db.conn()?; crud::create(&conn, user_id, &contact) } #[tracing::instrument(skip_all)] fn restore(&self, user_id: UserId, contact: &Contact) -> Result<()> { let conn = self.db.conn()?; crud::restore(&conn, user_id, contact) } #[tracing::instrument(skip_all)] fn update( &self, id: ContactId, user_id: UserId, contact: UpdateContact, ) -> Result> { let conn = self.db.conn()?; crud::update(&conn, id, user_id, &contact) } #[tracing::instrument(skip_all)] fn delete(&self, id: ContactId, user_id: UserId) -> Result { let conn = self.db.conn()?; crud::delete(&conn, id, user_id) } #[tracing::instrument(skip_all)] fn set_external_ref( &self, id: ContactId, user_id: UserId, source: &str, external_id: &str, ) -> Result<()> { let conn = self.db.conn()?; crud::set_external_ref(&conn, id, user_id, source, external_id) } #[tracing::instrument(skip_all)] fn delete_many(&self, ids: &[ContactId], user_id: UserId) -> Result { let conn = self.db.conn()?; crud::delete_many(&conn, ids, user_id) } #[tracing::instrument(skip_all)] fn tag_many(&self, ids: &[ContactId], user_id: UserId, tag: &str) -> Result { let conn = self.db.conn()?; crud::tag_many(&conn, ids, user_id, tag) } #[tracing::instrument(skip_all)] fn list_by_tag(&self, user_id: UserId, tag: &str) -> Result> { let conn = self.db.conn()?; query::list_by_tag(&conn, user_id, tag) } #[tracing::instrument(skip_all)] fn list_filtered( &self, user_id: UserId, search: Option<&str>, tag: Option<&str>, include_implicit: bool, ) -> Result> { let conn = self.db.conn()?; query::list_filtered(&conn, user_id, search, tag, include_implicit) } #[tracing::instrument(skip_all)] fn list_email_directory( &self, user_id: UserId, include_implicit: bool, ) -> Result> { let conn = self.db.conn()?; query::list_email_directory(&conn, user_id, include_implicit) } #[tracing::instrument(skip_all)] fn find_by_email(&self, user_id: UserId, email: &str) -> Result> { let conn = self.db.conn()?; query::find_by_email(&conn, user_id, email) } #[tracing::instrument(skip_all)] fn find_emails_in_contacts( &self, user_id: UserId, addresses: &[&str], ) -> Result> { let conn = self.db.conn()?; query::find_emails_in_contacts(&conn, user_id, addresses) } #[tracing::instrument(skip_all)] fn promote_contact(&self, id: ContactId, user_id: UserId) -> Result> { let conn = self.db.conn()?; query::promote_contact(&conn, id, user_id) } #[tracing::instrument(skip_all)] fn find_by_external_id( &self, source: &str, ext_id: &str, user_id: UserId, ) -> Result> { let conn = self.db.conn()?; query::find_by_external_id(&conn, source, ext_id, user_id) } #[tracing::instrument(skip_all)] fn add_email( &self, contact_id: ContactId, user_id: UserId, email: NewContactEmail, ) -> Result { let conn = self.db.conn()?; subcollections::add_email(&conn, contact_id, user_id, email) } #[tracing::instrument(skip_all)] fn remove_email(&self, email_id: ContactEmailId, user_id: UserId) -> Result { let conn = self.db.conn()?; subcollections::remove_email(&conn, email_id, user_id) } #[tracing::instrument(skip_all)] fn add_phone( &self, contact_id: ContactId, user_id: UserId, phone: NewContactPhone, ) -> Result { let conn = self.db.conn()?; subcollections::add_phone(&conn, contact_id, user_id, phone) } #[tracing::instrument(skip_all)] fn remove_phone(&self, phone_id: ContactPhoneId, user_id: UserId) -> Result { let conn = self.db.conn()?; subcollections::remove_phone(&conn, phone_id, user_id) } #[tracing::instrument(skip_all)] fn add_social_handle( &self, contact_id: ContactId, user_id: UserId, handle: NewSocialHandle, ) -> Result { let conn = self.db.conn()?; subcollections::add_social_handle(&conn, contact_id, user_id, handle) } #[tracing::instrument(skip_all)] fn remove_social_handle(&self, handle_id: SocialHandleId, user_id: UserId) -> Result { let conn = self.db.conn()?; subcollections::remove_social_handle(&conn, handle_id, user_id) } #[tracing::instrument(skip_all)] fn add_custom_field( &self, contact_id: ContactId, user_id: UserId, field: NewContactCustomField, ) -> Result { let conn = self.db.conn()?; subcollections::add_custom_field(&conn, contact_id, user_id, field) } #[tracing::instrument(skip_all)] fn remove_custom_field(&self, field_id: CustomFieldId, user_id: UserId) -> Result { let conn = self.db.conn()?; subcollections::remove_custom_field(&conn, field_id, user_id) } #[tracing::instrument(skip_all)] fn update_email( &self, email_id: ContactEmailId, user_id: UserId, email: NewContactEmail, ) -> Result> { let conn = self.db.conn()?; subcollections::update_email(&conn, email_id, user_id, &email) } #[tracing::instrument(skip_all)] fn update_phone( &self, phone_id: ContactPhoneId, user_id: UserId, phone: NewContactPhone, ) -> Result> { let conn = self.db.conn()?; subcollections::update_phone(&conn, phone_id, user_id, &phone) } #[tracing::instrument(skip_all)] fn update_social_handle( &self, handle_id: SocialHandleId, user_id: UserId, handle: NewSocialHandle, ) -> Result> { let conn = self.db.conn()?; subcollections::update_social_handle(&conn, handle_id, user_id, &handle) } #[tracing::instrument(skip_all)] fn update_custom_field( &self, field_id: CustomFieldId, user_id: UserId, field: NewContactCustomField, ) -> Result> { let conn = self.db.conn()?; subcollections::update_custom_field(&conn, field_id, user_id, &field) } }