Skip to main content

max / goingson

5.3 KB · 155 lines History Blame Raw
1 //! SQLite implementation of the SyncAccountRepository.
2
3 use async_trait::async_trait;
4 use sqlx::SqlitePool;
5 use goingson_core::{
6 CoreError, Result, SyncAccount, SyncAccountId, SyncAccountRepository, UserId,
7 };
8
9 use crate::utils::{format_datetime_now, parse_datetime, parse_uuid};
10
11 #[derive(Debug, Clone, sqlx::FromRow)]
12 struct SyncAccountRow {
13 pub id: String,
14 pub user_id: String,
15 pub provider: String,
16 pub account_name: String,
17 pub email: Option<String>,
18 pub sync_calendars: i32,
19 pub sync_contacts: i32,
20 pub calendar_ids: String,
21 pub last_calendar_sync: Option<String>,
22 pub last_contact_sync: Option<String>,
23 pub sync_interval_minutes: i32,
24 pub enabled: i32,
25 pub created_at: String,
26 }
27
28 fn sync_account_from_row(row: SyncAccountRow) -> Result<SyncAccount> {
29 let calendar_ids: Vec<String> = serde_json::from_str(&row.calendar_ids)
30 .unwrap_or_default();
31 let last_calendar_sync = row.last_calendar_sync
32 .as_ref()
33 .map(|s| parse_datetime(s))
34 .transpose()?;
35 let last_contact_sync = row.last_contact_sync
36 .as_ref()
37 .map(|s| parse_datetime(s))
38 .transpose()?;
39
40 Ok(SyncAccount {
41 id: parse_uuid(&row.id)?.into(),
42 user_id: parse_uuid(&row.user_id)?.into(),
43 provider: row.provider,
44 account_name: row.account_name,
45 email: row.email,
46 sync_calendars: row.sync_calendars != 0,
47 sync_contacts: row.sync_contacts != 0,
48 calendar_ids,
49 last_calendar_sync,
50 last_contact_sync,
51 sync_interval_minutes: row.sync_interval_minutes,
52 enabled: row.enabled != 0,
53 created_at: parse_datetime(&row.created_at)?,
54 })
55 }
56
57 /// SQLite-backed implementation of [`SyncAccountRepository`].
58 pub struct SqliteSyncAccountRepository {
59 pool: SqlitePool,
60 }
61
62 impl SqliteSyncAccountRepository {
63 #[tracing::instrument(skip_all)]
64 pub fn new(pool: SqlitePool) -> Self {
65 Self { pool }
66 }
67 }
68
69 #[async_trait]
70 impl SyncAccountRepository for SqliteSyncAccountRepository {
71 #[tracing::instrument(skip_all)]
72 async fn list_all(&self, user_id: UserId) -> Result<Vec<SyncAccount>> {
73 let rows = sqlx::query_as::<_, SyncAccountRow>(
74 "SELECT id, user_id, provider, account_name, email, sync_calendars, sync_contacts, calendar_ids, last_calendar_sync, last_contact_sync, sync_interval_minutes, enabled, created_at FROM sync_accounts WHERE user_id = ? ORDER BY created_at ASC"
75 )
76 .bind(user_id.to_string())
77 .fetch_all(&self.pool)
78 .await
79 .map_err(CoreError::database)?;
80
81 rows.into_iter().map(sync_account_from_row).collect()
82 }
83
84 #[tracing::instrument(skip_all)]
85 async fn get_by_id(&self, id: SyncAccountId, user_id: UserId) -> Result<Option<SyncAccount>> {
86 let row = sqlx::query_as::<_, SyncAccountRow>(
87 "SELECT id, user_id, provider, account_name, email, sync_calendars, sync_contacts, calendar_ids, last_calendar_sync, last_contact_sync, sync_interval_minutes, enabled, created_at FROM sync_accounts WHERE id = ? AND user_id = ?"
88 )
89 .bind(id.to_string())
90 .bind(user_id.to_string())
91 .fetch_optional(&self.pool)
92 .await
93 .map_err(CoreError::database)?;
94
95 row.map(sync_account_from_row).transpose()
96 }
97
98 #[tracing::instrument(skip_all)]
99 async fn create(&self, user_id: UserId, provider: &str, account_name: &str, email: Option<&str>) -> Result<SyncAccount> {
100 let id = SyncAccountId::new();
101 let now = format_datetime_now();
102
103 sqlx::query(
104 "INSERT INTO sync_accounts (id, user_id, provider, account_name, email, created_at) VALUES (?, ?, ?, ?, ?, ?)"
105 )
106 .bind(id.to_string())
107 .bind(user_id.to_string())
108 .bind(provider)
109 .bind(account_name)
110 .bind(email)
111 .bind(&now)
112 .execute(&self.pool)
113 .await
114 .map_err(CoreError::database)?;
115
116 self.get_by_id(id, user_id)
117 .await?
118 .ok_or_else(|| CoreError::internal("Failed to retrieve created sync account"))
119 }
120
121 #[tracing::instrument(skip_all)]
122 async fn update(&self, id: SyncAccountId, user_id: UserId, account_name: &str, sync_calendars: bool, sync_contacts: bool, enabled: bool) -> Result<Option<SyncAccount>> {
123 let result = sqlx::query(
124 "UPDATE sync_accounts SET account_name = ?, sync_calendars = ?, sync_contacts = ?, enabled = ? WHERE id = ? AND user_id = ?"
125 )
126 .bind(account_name)
127 .bind(sync_calendars as i32)
128 .bind(sync_contacts as i32)
129 .bind(enabled as i32)
130 .bind(id.to_string())
131 .bind(user_id.to_string())
132 .execute(&self.pool)
133 .await
134 .map_err(CoreError::database)?;
135
136 if result.rows_affected() > 0 {
137 self.get_by_id(id, user_id).await
138 } else {
139 Ok(None)
140 }
141 }
142
143 #[tracing::instrument(skip_all)]
144 async fn delete(&self, id: SyncAccountId, user_id: UserId) -> Result<bool> {
145 let result = sqlx::query("DELETE FROM sync_accounts WHERE id = ? AND user_id = ?")
146 .bind(id.to_string())
147 .bind(user_id.to_string())
148 .execute(&self.pool)
149 .await
150 .map_err(CoreError::database)?;
151
152 Ok(result.rows_affected() > 0)
153 }
154 }
155