Skip to main content

max / goingson

5.9 KB · 215 lines History Blame Raw
1 //! Row structs for the five contact tables and their conversions to core models.
2 //!
3 //! Leaf module: it knows rusqlite and the core models, and nothing else in the crate.
4
5 use goingson_core::{
6 Contact, ContactCustomField, ContactEmail, ContactPhone, CoreError, Result, SocialHandle,
7 };
8
9 use crate::utils::{parse_datetime, parse_tags, parse_uuid};
10
11 // Row Structs
12
13 #[derive(Debug, Clone)]
14 pub(super) struct ContactRow {
15 pub id: String,
16 pub display_name: String,
17 pub nickname: Option<String>,
18 pub company: Option<String>,
19 pub title: Option<String>,
20 pub notes: String,
21 pub tags: String,
22 pub birthday: Option<String>,
23 pub timezone: Option<String>,
24 pub external_source: Option<String>,
25 pub external_id: Option<String>,
26 pub is_implicit: i32,
27 pub created_at: String,
28 pub updated_at: String,
29 }
30
31 impl ContactRow {
32 pub(super) fn from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<Self> {
33 Ok(Self {
34 id: row.get("id")?,
35 display_name: row.get("display_name")?,
36 nickname: row.get("nickname")?,
37 company: row.get("company")?,
38 title: row.get("title")?,
39 notes: row.get("notes")?,
40 tags: row.get("tags")?,
41 birthday: row.get("birthday")?,
42 timezone: row.get("timezone")?,
43 external_source: row.get("external_source")?,
44 external_id: row.get("external_id")?,
45 is_implicit: row.get("is_implicit")?,
46 created_at: row.get("created_at")?,
47 updated_at: row.get("updated_at")?,
48 })
49 }
50 }
51
52 #[derive(Debug, Clone)]
53 pub(super) struct ContactEmailRow {
54 pub id: String,
55 pub contact_id: String,
56 pub address: String,
57 pub label: String,
58 pub is_primary: i32,
59 }
60
61 impl ContactEmailRow {
62 pub(super) fn from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<Self> {
63 Ok(Self {
64 id: row.get("id")?,
65 contact_id: row.get("contact_id")?,
66 address: row.get("address")?,
67 label: row.get("label")?,
68 is_primary: row.get("is_primary")?,
69 })
70 }
71 }
72
73 #[derive(Debug, Clone)]
74 pub(super) struct ContactPhoneRow {
75 pub id: String,
76 pub contact_id: String,
77 pub number: String,
78 pub label: String,
79 pub is_primary: i32,
80 }
81
82 impl ContactPhoneRow {
83 pub(super) fn from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<Self> {
84 Ok(Self {
85 id: row.get("id")?,
86 contact_id: row.get("contact_id")?,
87 number: row.get("number")?,
88 label: row.get("label")?,
89 is_primary: row.get("is_primary")?,
90 })
91 }
92 }
93
94 #[derive(Debug, Clone)]
95 pub(super) struct SocialHandleRow {
96 pub id: String,
97 pub contact_id: String,
98 pub platform: String,
99 pub handle: String,
100 pub url: Option<String>,
101 }
102
103 impl SocialHandleRow {
104 pub(super) fn from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<Self> {
105 Ok(Self {
106 id: row.get("id")?,
107 contact_id: row.get("contact_id")?,
108 platform: row.get("platform")?,
109 handle: row.get("handle")?,
110 url: row.get("url")?,
111 })
112 }
113 }
114
115 #[derive(Debug, Clone)]
116 pub(super) struct CustomFieldRow {
117 pub id: String,
118 pub contact_id: String,
119 pub label: String,
120 pub value: String,
121 pub url: Option<String>,
122 }
123
124 impl CustomFieldRow {
125 pub(super) fn from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<Self> {
126 Ok(Self {
127 id: row.get("id")?,
128 contact_id: row.get("contact_id")?,
129 label: row.get("label")?,
130 value: row.get("value")?,
131 url: row.get("url")?,
132 })
133 }
134 }
135
136 // Row Conversions
137
138 pub(super) fn contact_email_from_row(row: ContactEmailRow) -> Result<ContactEmail> {
139 Ok(ContactEmail {
140 id: parse_uuid(&row.id)?.into(),
141 contact_id: parse_uuid(&row.contact_id)?.into(),
142 address: row.address,
143 label: row.label,
144 is_primary: row.is_primary != 0,
145 })
146 }
147
148 pub(super) fn contact_phone_from_row(row: ContactPhoneRow) -> Result<ContactPhone> {
149 Ok(ContactPhone {
150 id: parse_uuid(&row.id)?.into(),
151 contact_id: parse_uuid(&row.contact_id)?.into(),
152 number: row.number,
153 label: row.label,
154 is_primary: row.is_primary != 0,
155 })
156 }
157
158 pub(super) fn social_handle_from_row(row: SocialHandleRow) -> Result<SocialHandle> {
159 Ok(SocialHandle {
160 id: parse_uuid(&row.id)?.into(),
161 contact_id: parse_uuid(&row.contact_id)?.into(),
162 platform: row.platform,
163 handle: row.handle,
164 url: row.url,
165 })
166 }
167
168 pub(super) fn custom_field_from_row(row: CustomFieldRow) -> Result<ContactCustomField> {
169 Ok(ContactCustomField {
170 id: parse_uuid(&row.id)?.into(),
171 contact_id: parse_uuid(&row.contact_id)?.into(),
172 label: row.label,
173 value: row.value,
174 url: row.url,
175 })
176 }
177
178 pub(super) fn contact_from_row(
179 row: ContactRow,
180 emails: Vec<ContactEmail>,
181 phones: Vec<ContactPhone>,
182 social_handles: Vec<SocialHandle>,
183 custom_fields: Vec<ContactCustomField>,
184 ) -> Result<Contact> {
185 let birthday = row
186 .birthday
187 .as_deref()
188 .map(|s| {
189 chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d")
190 .map_err(|e| CoreError::database_msg(format!("Invalid birthday: {e}")))
191 })
192 .transpose()?;
193
194 Ok(Contact {
195 id: parse_uuid(&row.id)?.into(),
196 display_name: row.display_name,
197 nickname: row.nickname,
198 company: row.company,
199 title: row.title,
200 notes: row.notes,
201 tags: parse_tags(&row.tags),
202 birthday,
203 timezone: row.timezone,
204 external_source: row.external_source,
205 external_id: row.external_id,
206 is_implicit: row.is_implicit != 0,
207 emails,
208 phones,
209 social_handles,
210 custom_fields,
211 created_at: parse_datetime(&row.created_at)?,
212 updated_at: parse_datetime(&row.updated_at)?,
213 })
214 }
215