Skip to main content

max / goingson

6.1 KB · 189 lines History Blame Raw
1 //! Create, update and delete for the contact row itself.
2 //!
3 //! Sub-collection mutations live in `subcollections`; reads live in `query`.
4
5 use goingson_core::{Contact, ContactId, CoreError, NewContact, Result, UpdateContact, UserId};
6
7 use rusqlite::{Connection, params, params_from_iter};
8
9 use crate::utils::{bind_placeholders, escape_like, execute, format_datetime, format_datetime_now};
10
11 use super::query;
12
13 pub(super) fn create(conn: &Connection, user_id: UserId, contact: &NewContact) -> Result<Contact> {
14 let id = ContactId::new();
15 let now = format_datetime_now();
16 let tags_json = serde_json::to_string(&contact.tags).unwrap_or_else(|_| "[]".to_string());
17 let birthday_str = contact.birthday.map(|d| d.format("%Y-%m-%d").to_string());
18
19 execute(
20 conn,
21 r"
22 INSERT INTO contacts (id, user_id, display_name, nickname, company, title, notes, tags, birthday, timezone, is_implicit, created_at, updated_at)
23 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
24 ",
25 params![
26 id.to_string(),
27 user_id.to_string(),
28 &contact.display_name,
29 &contact.nickname,
30 &contact.company,
31 &contact.title,
32 &contact.notes,
33 &tags_json,
34 &birthday_str,
35 &contact.timezone,
36 i32::from(contact.is_implicit),
37 &now,
38 &now
39 ],
40 )?;
41
42 query::get_by_id(conn, id, user_id)?
43 .ok_or_else(|| CoreError::internal("Failed to retrieve created contact"))
44 }
45
46 pub(super) fn restore(conn: &Connection, user_id: UserId, contact: &Contact) -> Result<()> {
47 let tags_json = serde_json::to_string(&contact.tags).unwrap_or_else(|_| "[]".to_string());
48 let birthday_str = contact.birthday.map(|d| d.format("%Y-%m-%d").to_string());
49
50 execute(
51 conn,
52 r"
53 INSERT OR IGNORE INTO contacts (id, user_id, display_name, nickname, company, title, notes, tags, birthday, timezone, external_source, external_id, is_implicit, created_at, updated_at)
54 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
55 ",
56 params![
57 contact.id.to_string(),
58 user_id.to_string(),
59 &contact.display_name,
60 &contact.nickname,
61 &contact.company,
62 &contact.title,
63 &contact.notes,
64 &tags_json,
65 &birthday_str,
66 &contact.timezone,
67 &contact.external_source,
68 &contact.external_id,
69 i32::from(contact.is_implicit),
70 format_datetime(&contact.created_at),
71 format_datetime(&contact.updated_at)
72 ],
73 )?;
74 Ok(())
75 }
76
77 pub(super) fn update(
78 conn: &Connection,
79 id: ContactId,
80 user_id: UserId,
81 contact: &UpdateContact,
82 ) -> Result<Option<Contact>> {
83 let now = format_datetime_now();
84 let tags_json = serde_json::to_string(&contact.tags).unwrap_or_else(|_| "[]".to_string());
85 let birthday_str = contact.birthday.map(|d| d.format("%Y-%m-%d").to_string());
86
87 let result = execute(
88 conn,
89 r"
90 UPDATE contacts
91 SET display_name = ?, nickname = ?, company = ?, title = ?, notes = ?, tags = ?, birthday = ?, timezone = ?, updated_at = ?
92 WHERE id = ? AND user_id = ?
93 ",
94 params![
95 &contact.display_name,
96 &contact.nickname,
97 &contact.company,
98 &contact.title,
99 &contact.notes,
100 &tags_json,
101 &birthday_str,
102 &contact.timezone,
103 &now,
104 id.to_string(),
105 user_id.to_string()
106 ],
107 )?;
108
109 if result > 0 {
110 query::get_by_id(conn, id, user_id)
111 } else {
112 Ok(None)
113 }
114 }
115
116 pub(super) fn delete(conn: &Connection, id: ContactId, user_id: UserId) -> Result<bool> {
117 let result = execute(
118 conn,
119 "DELETE FROM contacts WHERE id = ? AND user_id = ?",
120 params![id.to_string(), user_id.to_string()],
121 )?;
122
123 Ok(result > 0)
124 }
125
126 pub(super) fn set_external_ref(
127 conn: &Connection,
128 id: ContactId,
129 user_id: UserId,
130 source: &str,
131 external_id: &str,
132 ) -> Result<()> {
133 execute(
134 conn,
135 "UPDATE contacts SET external_source = ?, external_id = ? WHERE id = ? AND user_id = ?",
136 params![source, external_id, id.to_string(), user_id.to_string()],
137 )?;
138
139 Ok(())
140 }
141
142 pub(super) fn delete_many(conn: &Connection, ids: &[ContactId], user_id: UserId) -> Result<u64> {
143 if ids.is_empty() {
144 return Ok(0);
145 }
146 let user_id_str = user_id.to_string();
147 let placeholders = bind_placeholders(ids.len());
148 let sql = format!("DELETE FROM contacts WHERE user_id = ? AND id IN ({placeholders})");
149 let mut binds: Vec<String> = Vec::with_capacity(ids.len() + 1);
150 binds.push(user_id_str);
151 binds.extend(ids.iter().map(std::string::ToString::to_string));
152 let result = execute(conn, &sql, params_from_iter(binds))?;
153 Ok(result as u64)
154 }
155
156 pub(super) fn tag_many(
157 conn: &Connection,
158 ids: &[ContactId],
159 user_id: UserId,
160 tag: &str,
161 ) -> Result<u64> {
162 if ids.is_empty() || tag.is_empty() {
163 return Ok(0);
164 }
165 let user_id_str = user_id.to_string();
166 let like_pattern = format!("%\"{}\"%", escape_like(tag));
167 let placeholders = bind_placeholders(ids.len());
168 // Append tag to JSON array where not already present.
169 let sql = format!(
170 r"UPDATE contacts
171 SET tags = CASE
172 WHEN tags IS NULL OR tags = '' OR tags = '[]'
173 THEN json_array(?)
174 ELSE json_insert(tags, '$[#]', ?)
175 END,
176 updated_at = datetime('now')
177 WHERE user_id = ? AND id IN ({placeholders})
178 AND (tags IS NULL OR tags NOT LIKE ? ESCAPE '\')",
179 );
180 let mut binds: Vec<String> = Vec::with_capacity(ids.len() + 4);
181 binds.push(tag.to_string());
182 binds.push(tag.to_string());
183 binds.push(user_id_str);
184 binds.extend(ids.iter().map(std::string::ToString::to_string));
185 binds.push(like_pattern);
186 let result = execute(conn, &sql, params_from_iter(binds))?;
187 Ok(result as u64)
188 }
189