Skip to main content

max / goingson

11.1 KB · 422 lines History Blame Raw
1 //! Mutations over the four contact sub-collection tables.
2 //!
3 //! Twelve functions that are three shapes (add, remove, update) replicated over
4 //! emails, phones, social handles and custom fields. They stay in one module so
5 //! the repetition stays visible rather than spread over four files that each say
6 //! the same thing.
7
8 use goingson_core::{
9 ContactCustomField, ContactEmail, ContactEmailId, ContactId, ContactPhone, ContactPhoneId,
10 CoreError, CustomFieldId, NewContactCustomField, NewContactEmail, NewContactPhone,
11 NewSocialHandle, Result, SocialHandle, SocialHandleId, UserId,
12 };
13
14 use rusqlite::{Connection, params};
15
16 use crate::utils::execute;
17
18 pub(super) fn add_email(
19 conn: &Connection,
20 contact_id: ContactId,
21 user_id: UserId,
22 email: NewContactEmail,
23 ) -> Result<ContactEmail> {
24 // Verify contact ownership
25 let exists: i64 = conn
26 .query_row(
27 "SELECT COUNT(*) FROM contacts WHERE id = ? AND user_id = ?",
28 params![contact_id.to_string(), user_id.to_string()],
29 |row| row.get(0),
30 )
31 .map_err(CoreError::database)?;
32
33 if exists == 0 {
34 return Err(CoreError::not_found("contact", contact_id));
35 }
36
37 let id = ContactEmailId::new();
38 execute(
39 conn,
40 "INSERT INTO contact_emails (id, contact_id, address, label, is_primary) VALUES (?, ?, ?, ?, ?)",
41 params![
42 id.to_string(),
43 contact_id.to_string(),
44 &email.address,
45 &email.label,
46 email.is_primary as i32
47 ],
48 )?;
49
50 Ok(ContactEmail {
51 id,
52 contact_id,
53 address: email.address,
54 label: email.label,
55 is_primary: email.is_primary,
56 })
57 }
58
59 pub(super) fn remove_email(
60 conn: &Connection,
61 email_id: ContactEmailId,
62 user_id: UserId,
63 ) -> Result<bool> {
64 // Verify ownership via JOIN
65 let result = execute(
66 conn,
67 r"
68 DELETE FROM contact_emails
69 WHERE id = ? AND contact_id IN (SELECT id FROM contacts WHERE user_id = ?)
70 ",
71 params![email_id.to_string(), user_id.to_string()],
72 )?;
73
74 Ok(result > 0)
75 }
76
77 pub(super) fn update_email(
78 conn: &Connection,
79 email_id: ContactEmailId,
80 user_id: UserId,
81 email: &NewContactEmail,
82 ) -> Result<Option<ContactEmail>> {
83 let result = execute(
84 conn,
85 r"
86 UPDATE contact_emails
87 SET address = ?, label = ?, is_primary = ?
88 WHERE id = ? AND contact_id IN (SELECT id FROM contacts WHERE user_id = ?)
89 ",
90 params![
91 &email.address,
92 &email.label,
93 email.is_primary as i32,
94 email_id.to_string(),
95 user_id.to_string()
96 ],
97 )?;
98
99 if result == 0 {
100 return Ok(None);
101 }
102
103 let row: (String, String, String, i32) = conn
104 .query_row(
105 "SELECT contact_id, address, label, is_primary FROM contact_emails WHERE id = ?",
106 params![email_id.to_string()],
107 |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
108 )
109 .map_err(CoreError::database)?;
110
111 Ok(Some(ContactEmail {
112 id: email_id,
113 contact_id: crate::utils::parse_uuid(&row.0)?.into(),
114 address: row.1,
115 label: row.2,
116 is_primary: row.3 != 0,
117 }))
118 }
119
120 pub(super) fn add_phone(
121 conn: &Connection,
122 contact_id: ContactId,
123 user_id: UserId,
124 phone: NewContactPhone,
125 ) -> Result<ContactPhone> {
126 // Verify contact ownership
127 let exists: i64 = conn
128 .query_row(
129 "SELECT COUNT(*) FROM contacts WHERE id = ? AND user_id = ?",
130 params![contact_id.to_string(), user_id.to_string()],
131 |row| row.get(0),
132 )
133 .map_err(CoreError::database)?;
134
135 if exists == 0 {
136 return Err(CoreError::not_found("contact", contact_id));
137 }
138
139 let id = ContactPhoneId::new();
140 execute(
141 conn,
142 "INSERT INTO contact_phones (id, contact_id, number, label, is_primary) VALUES (?, ?, ?, ?, ?)",
143 params![
144 id.to_string(),
145 contact_id.to_string(),
146 &phone.number,
147 &phone.label,
148 phone.is_primary as i32
149 ],
150 )?;
151
152 Ok(ContactPhone {
153 id,
154 contact_id,
155 number: phone.number,
156 label: phone.label,
157 is_primary: phone.is_primary,
158 })
159 }
160
161 pub(super) fn remove_phone(
162 conn: &Connection,
163 phone_id: ContactPhoneId,
164 user_id: UserId,
165 ) -> Result<bool> {
166 let result = execute(
167 conn,
168 r"
169 DELETE FROM contact_phones
170 WHERE id = ? AND contact_id IN (SELECT id FROM contacts WHERE user_id = ?)
171 ",
172 params![phone_id.to_string(), user_id.to_string()],
173 )?;
174
175 Ok(result > 0)
176 }
177
178 pub(super) fn update_phone(
179 conn: &Connection,
180 phone_id: ContactPhoneId,
181 user_id: UserId,
182 phone: &NewContactPhone,
183 ) -> Result<Option<ContactPhone>> {
184 let result = execute(
185 conn,
186 r"
187 UPDATE contact_phones
188 SET number = ?, label = ?, is_primary = ?
189 WHERE id = ? AND contact_id IN (SELECT id FROM contacts WHERE user_id = ?)
190 ",
191 params![
192 &phone.number,
193 &phone.label,
194 phone.is_primary as i32,
195 phone_id.to_string(),
196 user_id.to_string()
197 ],
198 )?;
199
200 if result == 0 {
201 return Ok(None);
202 }
203
204 let row: (String, String, String, i32) = conn
205 .query_row(
206 "SELECT contact_id, number, label, is_primary FROM contact_phones WHERE id = ?",
207 params![phone_id.to_string()],
208 |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
209 )
210 .map_err(CoreError::database)?;
211
212 Ok(Some(ContactPhone {
213 id: phone_id,
214 contact_id: crate::utils::parse_uuid(&row.0)?.into(),
215 number: row.1,
216 label: row.2,
217 is_primary: row.3 != 0,
218 }))
219 }
220
221 pub(super) fn add_social_handle(
222 conn: &Connection,
223 contact_id: ContactId,
224 user_id: UserId,
225 handle: NewSocialHandle,
226 ) -> Result<SocialHandle> {
227 // Verify contact ownership
228 let exists: i64 = conn
229 .query_row(
230 "SELECT COUNT(*) FROM contacts WHERE id = ? AND user_id = ?",
231 params![contact_id.to_string(), user_id.to_string()],
232 |row| row.get(0),
233 )
234 .map_err(CoreError::database)?;
235
236 if exists == 0 {
237 return Err(CoreError::not_found("contact", contact_id));
238 }
239
240 let id = SocialHandleId::new();
241 execute(
242 conn,
243 "INSERT INTO contact_social_handles (id, contact_id, platform, handle, url) VALUES (?, ?, ?, ?, ?)",
244 params![
245 id.to_string(),
246 contact_id.to_string(),
247 &handle.platform,
248 &handle.handle,
249 &handle.url
250 ],
251 )?;
252
253 Ok(SocialHandle {
254 id,
255 contact_id,
256 platform: handle.platform,
257 handle: handle.handle,
258 url: handle.url,
259 })
260 }
261
262 pub(super) fn remove_social_handle(
263 conn: &Connection,
264 handle_id: SocialHandleId,
265 user_id: UserId,
266 ) -> Result<bool> {
267 let result = execute(
268 conn,
269 r"
270 DELETE FROM contact_social_handles
271 WHERE id = ? AND contact_id IN (SELECT id FROM contacts WHERE user_id = ?)
272 ",
273 params![handle_id.to_string(), user_id.to_string()],
274 )?;
275
276 Ok(result > 0)
277 }
278
279 pub(super) fn update_social_handle(
280 conn: &Connection,
281 handle_id: SocialHandleId,
282 user_id: UserId,
283 handle: &NewSocialHandle,
284 ) -> Result<Option<SocialHandle>> {
285 let result = execute(
286 conn,
287 r"
288 UPDATE contact_social_handles
289 SET platform = ?, handle = ?, url = ?
290 WHERE id = ? AND contact_id IN (SELECT id FROM contacts WHERE user_id = ?)
291 ",
292 params![
293 &handle.platform,
294 &handle.handle,
295 &handle.url,
296 handle_id.to_string(),
297 user_id.to_string()
298 ],
299 )?;
300
301 if result == 0 {
302 return Ok(None);
303 }
304
305 let row: (String, String, String, Option<String>) = conn
306 .query_row(
307 "SELECT contact_id, platform, handle, url FROM contact_social_handles WHERE id = ?",
308 params![handle_id.to_string()],
309 |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
310 )
311 .map_err(CoreError::database)?;
312
313 Ok(Some(SocialHandle {
314 id: handle_id,
315 contact_id: crate::utils::parse_uuid(&row.0)?.into(),
316 platform: row.1,
317 handle: row.2,
318 url: row.3,
319 }))
320 }
321
322 pub(super) fn add_custom_field(
323 conn: &Connection,
324 contact_id: ContactId,
325 user_id: UserId,
326 field: NewContactCustomField,
327 ) -> Result<ContactCustomField> {
328 // Verify contact ownership
329 let exists: i64 = conn
330 .query_row(
331 "SELECT COUNT(*) FROM contacts WHERE id = ? AND user_id = ?",
332 params![contact_id.to_string(), user_id.to_string()],
333 |row| row.get(0),
334 )
335 .map_err(CoreError::database)?;
336
337 if exists == 0 {
338 return Err(CoreError::not_found("contact", contact_id));
339 }
340
341 let id = CustomFieldId::new();
342 execute(
343 conn,
344 "INSERT INTO contact_custom_fields (id, contact_id, label, value, url) VALUES (?, ?, ?, ?, ?)",
345 params![
346 id.to_string(),
347 contact_id.to_string(),
348 &field.label,
349 &field.value,
350 &field.url
351 ],
352 )?;
353
354 Ok(ContactCustomField {
355 id,
356 contact_id,
357 label: field.label,
358 value: field.value,
359 url: field.url,
360 })
361 }
362
363 pub(super) fn remove_custom_field(
364 conn: &Connection,
365 field_id: CustomFieldId,
366 user_id: UserId,
367 ) -> Result<bool> {
368 let result = execute(
369 conn,
370 r"
371 DELETE FROM contact_custom_fields
372 WHERE id = ? AND contact_id IN (SELECT id FROM contacts WHERE user_id = ?)
373 ",
374 params![field_id.to_string(), user_id.to_string()],
375 )?;
376
377 Ok(result > 0)
378 }
379
380 pub(super) fn update_custom_field(
381 conn: &Connection,
382 field_id: CustomFieldId,
383 user_id: UserId,
384 field: &NewContactCustomField,
385 ) -> Result<Option<ContactCustomField>> {
386 let result = execute(
387 conn,
388 r"
389 UPDATE contact_custom_fields
390 SET label = ?, value = ?, url = ?
391 WHERE id = ? AND contact_id IN (SELECT id FROM contacts WHERE user_id = ?)
392 ",
393 params![
394 &field.label,
395 &field.value,
396 &field.url,
397 field_id.to_string(),
398 user_id.to_string()
399 ],
400 )?;
401
402 if result == 0 {
403 return Ok(None);
404 }
405
406 let row: (String, String, String, Option<String>) = conn
407 .query_row(
408 "SELECT contact_id, label, value, url FROM contact_custom_fields WHERE id = ?",
409 params![field_id.to_string()],
410 |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
411 )
412 .map_err(CoreError::database)?;
413
414 Ok(Some(ContactCustomField {
415 id: field_id,
416 contact_id: crate::utils::parse_uuid(&row.0)?.into(),
417 label: row.1,
418 value: row.2,
419 url: row.3,
420 }))
421 }
422