Skip to main content

max / goingson

5.6 KB · 169 lines History Blame Raw
1 //! SQLite implementation of backup settings repository.
2 //!
3 //! Manages automated backup configuration including frequency,
4 //! retention policy, and last backup tracking.
5
6 use async_trait::async_trait;
7 use chrono::{DateTime, Utc};
8 use sqlx::SqlitePool;
9 use uuid::Uuid;
10
11 use goingson_core::{BackupSettings, BackupSettingsRepository, CoreError, NewBackupSettings, Result, UserId};
12
13 use crate::utils::{format_datetime, format_datetime_now, parse_datetime, parse_uuid};
14
15 #[derive(Debug, Clone, sqlx::FromRow)]
16 struct BackupSettingsRow {
17 pub id: String,
18 pub user_id: String,
19 pub auto_backup_enabled: i32,
20 pub backup_frequency_minutes: i32,
21 pub max_backups_to_keep: i32,
22 pub last_backup_at: Option<String>,
23 pub created_at: String,
24 pub updated_at: String,
25 }
26
27 impl TryFrom<BackupSettingsRow> for BackupSettings {
28 type Error = CoreError;
29
30 fn try_from(row: BackupSettingsRow) -> std::result::Result<Self, Self::Error> {
31 let last_backup_at = row
32 .last_backup_at
33 .as_ref()
34 .map(|s| parse_datetime(s))
35 .transpose()?;
36
37 Ok(BackupSettings {
38 id: parse_uuid(&row.id)?,
39 user_id: parse_uuid(&row.user_id)?,
40 auto_backup_enabled: row.auto_backup_enabled != 0,
41 backup_frequency_minutes: row.backup_frequency_minutes,
42 max_backups_to_keep: row.max_backups_to_keep,
43 last_backup_at,
44 created_at: parse_datetime(&row.created_at)?,
45 updated_at: parse_datetime(&row.updated_at)?,
46 })
47 }
48 }
49
50 /// SQLite-backed implementation of [`BackupSettingsRepository`].
51 ///
52 /// Manages per-user backup configuration with automatic scheduling
53 /// and retention policies.
54 pub struct SqliteBackupSettingsRepository {
55 pool: SqlitePool,
56 }
57
58 impl SqliteBackupSettingsRepository {
59 /// Creates a new repository instance with the given connection pool.
60 #[tracing::instrument(skip_all)]
61 pub fn new(pool: SqlitePool) -> Self {
62 Self { pool }
63 }
64 }
65
66 #[async_trait]
67 impl BackupSettingsRepository for SqliteBackupSettingsRepository {
68 #[tracing::instrument(skip_all)]
69 async fn get(&self, user_id: UserId) -> Result<Option<BackupSettings>> {
70 let row = sqlx::query_as::<_, BackupSettingsRow>(
71 r#"SELECT id, user_id, auto_backup_enabled, backup_frequency_minutes,
72 max_backups_to_keep, last_backup_at, created_at, updated_at
73 FROM backup_settings WHERE user_id = ?"#,
74 )
75 .bind(user_id.to_string())
76 .fetch_optional(&self.pool)
77 .await
78 .map_err(CoreError::database)?;
79
80 row.map(BackupSettings::try_from).transpose()
81 }
82
83 #[tracing::instrument(skip_all)]
84 async fn upsert(&self, user_id: UserId, settings: NewBackupSettings) -> Result<BackupSettings> {
85 let now = format_datetime_now();
86
87 // Check if settings exist
88 let existing = self.get(user_id).await?;
89
90 if existing.is_some() {
91 // Update
92 sqlx::query(
93 r#"UPDATE backup_settings SET
94 auto_backup_enabled = ?, backup_frequency_minutes = ?,
95 max_backups_to_keep = ?, updated_at = ?
96 WHERE user_id = ?"#,
97 )
98 .bind(if settings.auto_backup_enabled { 1 } else { 0 })
99 .bind(settings.backup_frequency_minutes)
100 .bind(settings.max_backups_to_keep)
101 .bind(&now)
102 .bind(user_id.to_string())
103 .execute(&self.pool)
104 .await
105 .map_err(CoreError::database)?;
106
107 self.get(user_id)
108 .await?
109 .ok_or_else(|| CoreError::internal("Failed to retrieve updated backup settings"))
110 } else {
111 // Insert
112 let id = Uuid::new_v4();
113 sqlx::query(
114 r#"INSERT INTO backup_settings
115 (id, user_id, auto_backup_enabled, backup_frequency_minutes,
116 max_backups_to_keep, last_backup_at, created_at, updated_at)
117 VALUES (?, ?, ?, ?, ?, NULL, ?, ?)"#,
118 )
119 .bind(id.to_string())
120 .bind(user_id.to_string())
121 .bind(if settings.auto_backup_enabled { 1 } else { 0 })
122 .bind(settings.backup_frequency_minutes)
123 .bind(settings.max_backups_to_keep)
124 .bind(&now)
125 .bind(&now)
126 .execute(&self.pool)
127 .await
128 .map_err(CoreError::database)?;
129
130 self.get(user_id)
131 .await?
132 .ok_or_else(|| CoreError::internal("Failed to retrieve created backup settings"))
133 }
134 }
135
136 #[tracing::instrument(skip_all)]
137 async fn update_last_backup_at(&self, user_id: UserId, timestamp: DateTime<Utc>) -> Result<()> {
138 let now = format_datetime_now();
139 let backup_time = format_datetime(&timestamp);
140
141 // Ensure settings exist with defaults first
142 let existing = self.get(user_id).await?;
143 if existing.is_none() {
144 // Create default settings
145 self.upsert(
146 user_id,
147 NewBackupSettings {
148 auto_backup_enabled: true,
149 backup_frequency_minutes: 15,
150 max_backups_to_keep: 1,
151 },
152 )
153 .await?;
154 }
155
156 sqlx::query(
157 r#"UPDATE backup_settings SET last_backup_at = ?, updated_at = ? WHERE user_id = ?"#,
158 )
159 .bind(&backup_time)
160 .bind(&now)
161 .bind(user_id.to_string())
162 .execute(&self.pool)
163 .await
164 .map_err(CoreError::database)?;
165
166 Ok(())
167 }
168 }
169