Skip to main content

max / goingson

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