Skip to main content

max / goingson

5.6 KB · 106 lines History Blame Raw
1 use super::*;
2
3 /// Repository for contact CRUD operations and sub-collection management.
4 ///
5 /// Contacts have sub-collections (emails, phones, social handles) stored in
6 /// separate tables to enable querying by email address for future integrations.
7 #[async_trait]
8 pub trait ContactRepository: Send + Sync {
9 /// Lists all contacts for a user.
10 async fn list_all(&self, user_id: UserId) -> Result<Vec<Contact>>;
11
12 /// Retrieves a contact by ID, returning `None` if not found.
13 async fn get_by_id(&self, id: ContactId, user_id: UserId) -> Result<Option<Contact>>;
14
15 /// Creates a new contact.
16 async fn create(&self, user_id: UserId, contact: NewContact) -> Result<Contact>;
17
18 /// Restores a contact verbatim from a backup, preserving its original ID,
19 /// `created_at`, and `updated_at`. Idempotent (`INSERT OR IGNORE`).
20 /// Sub-collections (emails, phones, social handles, custom fields) are
21 /// restored separately by the caller.
22 async fn restore(&self, user_id: UserId, contact: &Contact) -> Result<()>;
23
24 /// Updates an existing contact, returning `None` if not found.
25 async fn update(&self, id: ContactId, user_id: UserId, contact: UpdateContact) -> Result<Option<Contact>>;
26
27 /// Deletes a contact (CASCADE removes sub-entities), returning `true` if deleted.
28 async fn delete(&self, id: ContactId, user_id: UserId) -> Result<bool>;
29
30 /// Records the external source/id for a contact (e.g. after a vCard import),
31 /// used to dedup on re-import.
32 async fn set_external_ref(
33 &self,
34 id: ContactId,
35 user_id: UserId,
36 source: &str,
37 external_id: &str,
38 ) -> Result<()>;
39
40 /// Deletes multiple contacts by ID, returning the number deleted.
41 async fn delete_many(&self, ids: &[ContactId], user_id: UserId) -> Result<u64>;
42
43 /// Adds a tag to multiple contacts (skips contacts that already have the tag).
44 async fn tag_many(&self, ids: &[ContactId], user_id: UserId, tag: &str) -> Result<u64>;
45
46 /// Lists contacts matching a tag.
47 async fn list_by_tag(&self, user_id: UserId, tag: &str) -> Result<Vec<Contact>>;
48
49 /// Lists contacts matching a search query and/or tag filter.
50 /// Searches across display_name, nickname, company, title, notes, and email addresses.
51 async fn list_filtered(&self, user_id: UserId, search: Option<&str>, tag: Option<&str>, include_implicit: bool) -> Result<Vec<Contact>>;
52
53 /// Flat (name, email) directory for compose autocomplete — one JOIN, no
54 /// per-contact sub-collection hydration. One row per contact email address.
55 async fn list_email_directory(&self, user_id: UserId, include_implicit: bool) -> Result<Vec<ContactEmailEntry>>;
56
57 /// Finds a contact by email address.
58 async fn find_by_email(&self, user_id: UserId, email: &str) -> Result<Option<Contact>>;
59
60 /// Batch check which email addresses belong to known contacts.
61 /// Returns the set of addresses (lowercased) that match at least one contact.
62 async fn find_emails_in_contacts(&self, user_id: UserId, addresses: &[&str]) -> Result<HashSet<String>>;
63
64 /// Promotes an implicit contact to explicit by setting is_implicit = 0.
65 async fn promote_contact(&self, id: ContactId, user_id: UserId) -> Result<Option<Contact>>;
66
67 /// Finds a contact by external source and ID (for dedup during import).
68 async fn find_by_external_id(&self, source: &str, ext_id: &str, user_id: UserId) -> Result<Option<Contact>>;
69
70 /// Adds an email address to a contact.
71 async fn add_email(&self, contact_id: ContactId, user_id: UserId, email: NewContactEmail) -> Result<ContactEmail>;
72
73 /// Removes an email address from a contact.
74 async fn remove_email(&self, email_id: ContactEmailId, user_id: UserId) -> Result<bool>;
75
76 /// Adds a phone number to a contact.
77 async fn add_phone(&self, contact_id: ContactId, user_id: UserId, phone: NewContactPhone) -> Result<ContactPhone>;
78
79 /// Removes a phone number from a contact.
80 async fn remove_phone(&self, phone_id: ContactPhoneId, user_id: UserId) -> Result<bool>;
81
82 /// Adds a social handle to a contact.
83 async fn add_social_handle(&self, contact_id: ContactId, user_id: UserId, handle: NewSocialHandle) -> Result<SocialHandle>;
84
85 /// Removes a social handle from a contact.
86 async fn remove_social_handle(&self, handle_id: SocialHandleId, user_id: UserId) -> Result<bool>;
87
88 /// Adds a custom field to a contact.
89 async fn add_custom_field(&self, contact_id: ContactId, user_id: UserId, field: NewContactCustomField) -> Result<ContactCustomField>;
90
91 /// Removes a custom field from a contact.
92 async fn remove_custom_field(&self, field_id: CustomFieldId, user_id: UserId) -> Result<bool>;
93
94 /// Updates a contact email row (address/label/is_primary). Returns the updated row, or `None` if not found.
95 async fn update_email(&self, email_id: ContactEmailId, user_id: UserId, email: NewContactEmail) -> Result<Option<ContactEmail>>;
96
97 /// Updates a contact phone row (number/label/is_primary). Returns the updated row, or `None` if not found.
98 async fn update_phone(&self, phone_id: ContactPhoneId, user_id: UserId, phone: NewContactPhone) -> Result<Option<ContactPhone>>;
99
100 /// Updates a social handle row (platform/handle/url). Returns the updated row, or `None` if not found.
101 async fn update_social_handle(&self, handle_id: SocialHandleId, user_id: UserId, handle: NewSocialHandle) -> Result<Option<SocialHandle>>;
102
103 /// Updates a custom field row (label/value/url). Returns the updated row, or `None` if not found.
104 async fn update_custom_field(&self, field_id: CustomFieldId, user_id: UserId, field: NewContactCustomField) -> Result<Option<ContactCustomField>>;
105 }
106