Skip to main content

max / goingson

8.4 KB · 279 lines History Blame Raw
1 //! SQLite implementation of the ContactRepository.
2 //!
3 //! Manages contacts with sub-collections (emails, phones, social handles).
4 //! Sub-collections are stored in separate tables and batch-loaded for list operations.
5
6 mod crud;
7 mod hydrate;
8 mod query;
9 mod row;
10 mod subcollections;
11
12 use goingson_core::{
13 Contact, ContactCustomField, ContactEmail, ContactEmailEntry, ContactEmailId, ContactId,
14 ContactPhone, ContactPhoneId, ContactRepository, CustomFieldId, NewContact,
15 NewContactCustomField, NewContactEmail, NewContactPhone, NewSocialHandle, Result, SocialHandle,
16 SocialHandleId, UpdateContact, UserId,
17 };
18 use std::collections::HashSet;
19
20 use crate::Db;
21
22 /// SQLite-backed implementation of [`ContactRepository`].
23 pub struct SqliteContactRepository {
24 db: Db,
25 }
26
27 impl SqliteContactRepository {
28 /// Creates a new repository instance with the given connection pool.
29 #[tracing::instrument(skip_all)]
30 pub fn new(db: Db) -> Self {
31 Self { db }
32 }
33 }
34
35 impl ContactRepository for SqliteContactRepository {
36 #[tracing::instrument(skip_all)]
37 fn list_all(&self, user_id: UserId) -> Result<Vec<Contact>> {
38 let conn = self.db.conn()?;
39 query::list_all(&conn, user_id)
40 }
41
42 fn list_all_for_backup(&self, user_id: UserId) -> Result<Vec<Contact>> {
43 let conn = self.db.conn()?;
44 query::list_all_for_backup(&conn, user_id)
45 }
46
47 #[tracing::instrument(skip_all)]
48 fn get_by_id(&self, id: ContactId, user_id: UserId) -> Result<Option<Contact>> {
49 let conn = self.db.conn()?;
50 query::get_by_id(&conn, id, user_id)
51 }
52
53 #[tracing::instrument(skip_all)]
54 fn create(&self, user_id: UserId, contact: NewContact) -> Result<Contact> {
55 let conn = self.db.conn()?;
56 crud::create(&conn, user_id, &contact)
57 }
58
59 #[tracing::instrument(skip_all)]
60 fn restore(&self, user_id: UserId, contact: &Contact) -> Result<()> {
61 let conn = self.db.conn()?;
62 crud::restore(&conn, user_id, contact)
63 }
64
65 #[tracing::instrument(skip_all)]
66 fn update(
67 &self,
68 id: ContactId,
69 user_id: UserId,
70 contact: UpdateContact,
71 ) -> Result<Option<Contact>> {
72 let conn = self.db.conn()?;
73 crud::update(&conn, id, user_id, &contact)
74 }
75
76 #[tracing::instrument(skip_all)]
77 fn delete(&self, id: ContactId, user_id: UserId) -> Result<bool> {
78 let conn = self.db.conn()?;
79 crud::delete(&conn, id, user_id)
80 }
81
82 #[tracing::instrument(skip_all)]
83 fn set_external_ref(
84 &self,
85 id: ContactId,
86 user_id: UserId,
87 source: &str,
88 external_id: &str,
89 ) -> Result<()> {
90 let conn = self.db.conn()?;
91 crud::set_external_ref(&conn, id, user_id, source, external_id)
92 }
93
94 #[tracing::instrument(skip_all)]
95 fn delete_many(&self, ids: &[ContactId], user_id: UserId) -> Result<u64> {
96 let conn = self.db.conn()?;
97 crud::delete_many(&conn, ids, user_id)
98 }
99
100 #[tracing::instrument(skip_all)]
101 fn tag_many(&self, ids: &[ContactId], user_id: UserId, tag: &str) -> Result<u64> {
102 let conn = self.db.conn()?;
103 crud::tag_many(&conn, ids, user_id, tag)
104 }
105
106 #[tracing::instrument(skip_all)]
107 fn list_by_tag(&self, user_id: UserId, tag: &str) -> Result<Vec<Contact>> {
108 let conn = self.db.conn()?;
109 query::list_by_tag(&conn, user_id, tag)
110 }
111
112 #[tracing::instrument(skip_all)]
113 fn list_filtered(
114 &self,
115 user_id: UserId,
116 search: Option<&str>,
117 tag: Option<&str>,
118 include_implicit: bool,
119 ) -> Result<Vec<Contact>> {
120 let conn = self.db.conn()?;
121 query::list_filtered(&conn, user_id, search, tag, include_implicit)
122 }
123
124 #[tracing::instrument(skip_all)]
125 fn list_email_directory(
126 &self,
127 user_id: UserId,
128 include_implicit: bool,
129 ) -> Result<Vec<ContactEmailEntry>> {
130 let conn = self.db.conn()?;
131 query::list_email_directory(&conn, user_id, include_implicit)
132 }
133
134 #[tracing::instrument(skip_all)]
135 fn find_by_email(&self, user_id: UserId, email: &str) -> Result<Option<Contact>> {
136 let conn = self.db.conn()?;
137 query::find_by_email(&conn, user_id, email)
138 }
139
140 #[tracing::instrument(skip_all)]
141 fn find_emails_in_contacts(
142 &self,
143 user_id: UserId,
144 addresses: &[&str],
145 ) -> Result<HashSet<String>> {
146 let conn = self.db.conn()?;
147 query::find_emails_in_contacts(&conn, user_id, addresses)
148 }
149
150 #[tracing::instrument(skip_all)]
151 fn promote_contact(&self, id: ContactId, user_id: UserId) -> Result<Option<Contact>> {
152 let conn = self.db.conn()?;
153 query::promote_contact(&conn, id, user_id)
154 }
155
156 #[tracing::instrument(skip_all)]
157 fn find_by_external_id(
158 &self,
159 source: &str,
160 ext_id: &str,
161 user_id: UserId,
162 ) -> Result<Option<Contact>> {
163 let conn = self.db.conn()?;
164 query::find_by_external_id(&conn, source, ext_id, user_id)
165 }
166
167 #[tracing::instrument(skip_all)]
168 fn add_email(
169 &self,
170 contact_id: ContactId,
171 user_id: UserId,
172 email: NewContactEmail,
173 ) -> Result<ContactEmail> {
174 let conn = self.db.conn()?;
175 subcollections::add_email(&conn, contact_id, user_id, email)
176 }
177
178 #[tracing::instrument(skip_all)]
179 fn remove_email(&self, email_id: ContactEmailId, user_id: UserId) -> Result<bool> {
180 let conn = self.db.conn()?;
181 subcollections::remove_email(&conn, email_id, user_id)
182 }
183
184 #[tracing::instrument(skip_all)]
185 fn add_phone(
186 &self,
187 contact_id: ContactId,
188 user_id: UserId,
189 phone: NewContactPhone,
190 ) -> Result<ContactPhone> {
191 let conn = self.db.conn()?;
192 subcollections::add_phone(&conn, contact_id, user_id, phone)
193 }
194
195 #[tracing::instrument(skip_all)]
196 fn remove_phone(&self, phone_id: ContactPhoneId, user_id: UserId) -> Result<bool> {
197 let conn = self.db.conn()?;
198 subcollections::remove_phone(&conn, phone_id, user_id)
199 }
200
201 #[tracing::instrument(skip_all)]
202 fn add_social_handle(
203 &self,
204 contact_id: ContactId,
205 user_id: UserId,
206 handle: NewSocialHandle,
207 ) -> Result<SocialHandle> {
208 let conn = self.db.conn()?;
209 subcollections::add_social_handle(&conn, contact_id, user_id, handle)
210 }
211
212 #[tracing::instrument(skip_all)]
213 fn remove_social_handle(&self, handle_id: SocialHandleId, user_id: UserId) -> Result<bool> {
214 let conn = self.db.conn()?;
215 subcollections::remove_social_handle(&conn, handle_id, user_id)
216 }
217
218 #[tracing::instrument(skip_all)]
219 fn add_custom_field(
220 &self,
221 contact_id: ContactId,
222 user_id: UserId,
223 field: NewContactCustomField,
224 ) -> Result<ContactCustomField> {
225 let conn = self.db.conn()?;
226 subcollections::add_custom_field(&conn, contact_id, user_id, field)
227 }
228
229 #[tracing::instrument(skip_all)]
230 fn remove_custom_field(&self, field_id: CustomFieldId, user_id: UserId) -> Result<bool> {
231 let conn = self.db.conn()?;
232 subcollections::remove_custom_field(&conn, field_id, user_id)
233 }
234
235 #[tracing::instrument(skip_all)]
236 fn update_email(
237 &self,
238 email_id: ContactEmailId,
239 user_id: UserId,
240 email: NewContactEmail,
241 ) -> Result<Option<ContactEmail>> {
242 let conn = self.db.conn()?;
243 subcollections::update_email(&conn, email_id, user_id, &email)
244 }
245
246 #[tracing::instrument(skip_all)]
247 fn update_phone(
248 &self,
249 phone_id: ContactPhoneId,
250 user_id: UserId,
251 phone: NewContactPhone,
252 ) -> Result<Option<ContactPhone>> {
253 let conn = self.db.conn()?;
254 subcollections::update_phone(&conn, phone_id, user_id, &phone)
255 }
256
257 #[tracing::instrument(skip_all)]
258 fn update_social_handle(
259 &self,
260 handle_id: SocialHandleId,
261 user_id: UserId,
262 handle: NewSocialHandle,
263 ) -> Result<Option<SocialHandle>> {
264 let conn = self.db.conn()?;
265 subcollections::update_social_handle(&conn, handle_id, user_id, &handle)
266 }
267
268 #[tracing::instrument(skip_all)]
269 fn update_custom_field(
270 &self,
271 field_id: CustomFieldId,
272 user_id: UserId,
273 field: NewContactCustomField,
274 ) -> Result<Option<ContactCustomField>> {
275 let conn = self.db.conn()?;
276 subcollections::update_custom_field(&conn, field_id, user_id, &field)
277 }
278 }
279