1use crate::db::DuckDbManager;
2use anyhow::Result;
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::{path::Path, sync::Arc};
6use uuid::Uuid;
7
8#[derive(Debug, Clone)]
10pub struct Database {
11 manager: Arc<DuckDbManager>,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ClientIdentity {
17 pub id: i64,
18 pub client_uuid: Uuid,
19 pub created_at: DateTime<Utc>,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct BackupRecord {
25 pub id: i64,
26 pub file_path: String,
27 pub service_version: String,
28 pub backup_type: BackupType,
29 pub status: BackupStatus,
30 pub created_at: DateTime<Utc>,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub enum BackupType {
36 Manual,
37 PreUpgrade,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
42pub enum BackupStatus {
43 Completed,
44 Failed,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct ScheduledTask {
50 pub id: i64,
51 pub task_type: TaskType,
52 pub target_version: String,
53 pub scheduled_at: DateTime<Utc>,
54 pub status: TaskStatus,
55 pub details: Option<String>,
56 pub created_at: DateTime<Utc>,
57 pub completed_at: Option<DateTime<Utc>>,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62pub enum TaskType {
63 ServiceUpgrade,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68pub enum TaskStatus {
69 Pending,
70 InProgress,
71 Completed,
72 Failed,
73 Cancelled,
74}
75
76impl Database {
77 pub async fn connect<P: AsRef<Path>>(db_path: P) -> Result<Self> {
79 let manager = DuckDbManager::new(db_path).await?;
80 Ok(Database {
81 manager: Arc::new(manager),
82 })
83 }
84
85 pub async fn connect_memory() -> Result<Self> {
87 let manager = DuckDbManager::new_memory().await?;
88 Ok(Database {
89 manager: Arc::new(manager),
90 })
91 }
92
93 pub async fn init_database(&self) -> Result<()> {
95 self.manager.init_database().await?;
96 self.manager.mark_database_initialized().await?;
98 Ok(())
99 }
100
101 pub async fn is_database_initialized(&self) -> Result<bool> {
103 self.manager.is_database_initialized().await
104 }
105
106 pub async fn run_migrations(&self) -> Result<()> {
108 Ok(())
110 }
111
112 pub async fn get_or_create_client_uuid(&self) -> Result<Uuid> {
114 self.manager.get_or_create_client_uuid().await
115 }
116
117 pub async fn get_client_uuid(&self) -> Result<Option<Uuid>> {
119 if let Some(uuid_str) = self.manager.get_config("client_uuid").await? {
120 Ok(Some(Uuid::parse_str(&uuid_str)?))
121 } else {
122 Ok(None)
123 }
124 }
125
126 pub async fn set_client_uuid(&self, uuid: &Uuid) -> Result<()> {
128 self.manager
129 .set_config("client_uuid", &uuid.to_string())
130 .await
131 }
132
133 pub async fn update_client_id(&self, client_id: &str) -> Result<()> {
135 self.manager.set_config("client_id", client_id).await
136 }
137
138 pub async fn get_client_id(&self) -> Result<Option<String>> {
140 self.manager.get_config("client_id").await
141 }
142
143 pub async fn get_api_client_id(&self) -> Result<Option<String>> {
145 self.get_client_id().await
148 }
149
150 pub async fn get_config(&self, key: &str) -> Result<Option<String>> {
152 self.manager.get_config(key).await
153 }
154
155 pub async fn set_config(&self, key: &str, value: &str) -> Result<()> {
157 self.manager.set_config(key, value).await
158 }
159
160 pub async fn get_client_identity(&self) -> Result<Option<ClientIdentity>> {
162 if let Some(uuid) = self.get_client_uuid().await? {
163 let created_at =
165 if let Some(created_at_str) = self.get_config("client_created_at").await? {
166 DateTime::parse_from_rfc3339(&created_at_str)
167 .map(|dt| dt.with_timezone(&Utc))
168 .unwrap_or_else(|_| Utc::now())
169 } else {
170 let now = Utc::now();
171 let _ = self
173 .set_config("client_created_at", &now.to_rfc3339())
174 .await;
175 now
176 };
177
178 Ok(Some(ClientIdentity {
179 id: 1, client_uuid: uuid,
181 created_at,
182 }))
183 } else {
184 Ok(None)
185 }
186 }
187
188 pub async fn create_backup_record(
190 &self,
191 file_path: String,
192 service_version: String,
193 backup_type: BackupType,
194 status: BackupStatus,
195 ) -> Result<i64> {
196 let backup_type_str = match backup_type {
197 BackupType::Manual => "manual",
198 BackupType::PreUpgrade => "pre-upgrade",
199 };
200
201 let status_str = match status {
202 BackupStatus::Completed => "completed",
203 BackupStatus::Failed => "failed",
204 };
205
206 self.manager
207 .create_backup_record(file_path, service_version, backup_type_str, status_str)
208 .await
209 }
210
211 pub async fn get_all_backups(&self) -> Result<Vec<BackupRecord>> {
213 let duckdb_backups = self.manager.get_all_backups().await?;
214
215 let mut backups = Vec::new();
216 for backup in duckdb_backups {
217 let backup_type = match backup.backup_type.as_str() {
218 "manual" => BackupType::Manual,
219 "pre-upgrade" => BackupType::PreUpgrade,
220 _ => BackupType::Manual,
221 };
222
223 let status = match backup.status.as_str() {
224 "completed" => BackupStatus::Completed,
225 "failed" => BackupStatus::Failed,
226 _ => BackupStatus::Failed,
227 };
228
229 backups.push(BackupRecord {
230 id: backup.id,
231 file_path: backup.file_path,
232 service_version: backup.service_version,
233 backup_type,
234 status,
235 created_at: backup.created_at,
236 });
237 }
238
239 Ok(backups)
240 }
241
242 pub async fn get_backup_by_id(&self, id: i64) -> Result<Option<BackupRecord>> {
244 if let Some(backup) = self.manager.get_backup_by_id(id).await? {
245 let backup_type = match backup.backup_type.as_str() {
246 "manual" => BackupType::Manual,
247 "pre-upgrade" => BackupType::PreUpgrade,
248 _ => BackupType::Manual,
249 };
250
251 let status = match backup.status.as_str() {
252 "completed" => BackupStatus::Completed,
253 "failed" => BackupStatus::Failed,
254 _ => BackupStatus::Failed,
255 };
256
257 Ok(Some(BackupRecord {
258 id: backup.id,
259 file_path: backup.file_path,
260 service_version: backup.service_version,
261 backup_type,
262 status,
263 created_at: backup.created_at,
264 }))
265 } else {
266 Ok(None)
267 }
268 }
269
270 pub async fn create_scheduled_task(
272 &self,
273 task_type: TaskType,
274 target_version: String,
275 scheduled_at: DateTime<Utc>,
276 ) -> Result<i64> {
277 let task_type_str = match task_type {
278 TaskType::ServiceUpgrade => "SERVICE_UPGRADE",
279 };
280
281 let id = self
282 .manager
283 .create_scheduled_task(task_type_str, target_version, scheduled_at)
284 .await?;
285
286 Ok(id)
287 }
288
289 pub async fn get_pending_tasks(&self) -> Result<Vec<ScheduledTask>> {
291 let duckdb_tasks = self.manager.get_pending_tasks().await?;
292
293 let mut tasks = Vec::new();
294 for task in duckdb_tasks {
295 let task_type = match task.task_type.as_str() {
296 "SERVICE_UPGRADE" => TaskType::ServiceUpgrade,
297 _ => TaskType::ServiceUpgrade,
298 };
299
300 let status = match task.status.as_str() {
301 "PENDING" => TaskStatus::Pending,
302 "IN_PROGRESS" => TaskStatus::InProgress,
303 "COMPLETED" => TaskStatus::Completed,
304 "FAILED" => TaskStatus::Failed,
305 "CANCELLED" => TaskStatus::Cancelled,
306 _ => TaskStatus::Pending,
307 };
308
309 tasks.push(ScheduledTask {
310 id: task.id,
311 task_type,
312 target_version: task.target_version,
313 scheduled_at: task.scheduled_at,
314 status,
315 details: task.details,
316 created_at: task.created_at,
317 completed_at: task.completed_at,
318 });
319 }
320
321 Ok(tasks)
322 }
323
324 pub async fn update_task_status(
326 &self,
327 task_id: i64,
328 status: TaskStatus,
329 details: Option<String>,
330 ) -> Result<()> {
331 let status_str = match status {
332 TaskStatus::Pending => "PENDING",
333 TaskStatus::InProgress => "IN_PROGRESS",
334 TaskStatus::Completed => "COMPLETED",
335 TaskStatus::Failed => "FAILED",
336 TaskStatus::Cancelled => "CANCELLED",
337 };
338
339 self.manager
340 .update_task_status(task_id, status_str, details)
341 .await
342 }
343
344 pub async fn delete_backup_record(&self, backup_id: i64) -> Result<()> {
346 self.manager.delete_backup_record(backup_id).await
347 }
348
349 pub async fn update_backup_file_path(&self, backup_id: i64, new_path: String) -> Result<()> {
351 self.manager
352 .update_backup_file_path(backup_id, new_path)
353 .await
354 }
355
356 pub async fn update_all_backup_paths(&self, old_prefix: &str, new_prefix: &str) -> Result<()> {
358 let backups = self.get_all_backups().await?;
359
360 for backup in backups {
361 if backup.file_path.starts_with(old_prefix) {
362 let new_path = backup.file_path.replace(old_prefix, new_prefix);
363 self.update_backup_file_path(backup.id, new_path).await?;
364 }
365 }
366
367 Ok(())
368 }
369}