Skip to main content

things3_core/database/queries/
projects.rs

1#![allow(deprecated)]
2
3use crate::{
4    database::{conversions::safe_timestamp_convert, mappers::map_project_row, ThingsDatabase},
5    error::{Result as ThingsResult, ThingsError},
6    models::{Project, TaskStatus, ThingsId},
7};
8use chrono::{DateTime, Utc};
9use sqlx::Row;
10use tracing::{debug, instrument};
11
12impl ThingsDatabase {
13    /// Get all projects (from `TMTask` table where type = 1)
14    ///
15    /// # Errors
16    ///
17    /// Returns an error if the database query fails or if project data is invalid
18    #[instrument]
19    pub async fn get_all_projects(&self) -> ThingsResult<Vec<Project>> {
20        let rows = sqlx::query(
21            r"
22            SELECT 
23                uuid, title, status, 
24                area, notes, 
25                creationDate, userModificationDate,
26                startDate, deadline
27            FROM TMTask
28            WHERE type = 1 AND trashed = 0
29            ORDER BY creationDate DESC
30            ",
31        )
32        .fetch_all(&self.pool)
33        .await
34        .map_err(|e| ThingsError::unknown(format!("Failed to fetch projects: {e}")))?;
35
36        let mut projects = Vec::new();
37        for row in rows {
38            let project = Project {
39                uuid: ThingsId::from_trusted(row.get::<String, _>("uuid")),
40                title: row.get("title"),
41                status: TaskStatus::from_i32(row.get("status")).unwrap_or(TaskStatus::Incomplete),
42                area_uuid: row
43                    .get::<Option<String>, _>("area")
44                    .map(ThingsId::from_trusted),
45                notes: row.get("notes"),
46                deadline: row
47                    .get::<Option<i64>, _>("deadline")
48                    .and_then(|ts| DateTime::from_timestamp(ts, 0))
49                    .map(|dt| dt.date_naive()),
50                start_date: row
51                    .get::<Option<i64>, _>("startDate")
52                    .and_then(|ts| DateTime::from_timestamp(ts, 0))
53                    .map(|dt| dt.date_naive()),
54                tags: Vec::new(),  // TODO: Load tags separately
55                tasks: Vec::new(), // TODO: Load child tasks separately
56                created: {
57                    let ts_f64 = row.get::<f64, _>("creationDate");
58                    let ts = safe_timestamp_convert(ts_f64);
59                    DateTime::from_timestamp(ts, 0).unwrap_or_else(Utc::now)
60                },
61                modified: {
62                    let ts_f64 = row.get::<f64, _>("userModificationDate");
63                    let ts = safe_timestamp_convert(ts_f64);
64                    DateTime::from_timestamp(ts, 0).unwrap_or_else(Utc::now)
65                },
66            };
67            projects.push(project);
68        }
69
70        debug!("Fetched {} projects", projects.len());
71        Ok(projects)
72    }
73
74    /// Get all projects (alias for `get_all_projects` for compatibility)
75    ///
76    /// # Errors
77    ///
78    /// Returns an error if the database query fails or if project data is invalid
79    #[instrument(skip(self))]
80    pub async fn get_projects(&self, limit: Option<usize>) -> ThingsResult<Vec<Project>> {
81        let _ = limit; // Currently unused but kept for API compatibility
82        self.get_all_projects().await
83    }
84
85    /// Get a single project by UUID
86    ///
87    /// Returns `None` if the project doesn't exist or is trashed
88    ///
89    /// # Errors
90    ///
91    /// Returns an error if the database query fails
92    #[instrument(skip(self))]
93    pub async fn get_project_by_uuid(&self, id: &ThingsId) -> ThingsResult<Option<Project>> {
94        let row = sqlx::query(
95            r"
96            SELECT
97                uuid, title, status,
98                area, notes,
99                creationDate, userModificationDate,
100                startDate, deadline,
101                trashed, type
102            FROM TMTask
103            WHERE uuid = ? AND type = 1
104            ",
105        )
106        .bind(id.as_str())
107        .fetch_optional(&self.pool)
108        .await
109        .map_err(|e| ThingsError::unknown(format!("Failed to fetch project: {e}")))?;
110
111        if let Some(row) = row {
112            let trashed: i64 = row.get("trashed");
113            if trashed == 1 {
114                return Ok(None);
115            }
116            Ok(Some(map_project_row(&row)))
117        } else {
118            Ok(None)
119        }
120    }
121}