Skip to main content

things3_core/database/mutations/
areas.rs

1use crate::{
2    database::{validators, ThingsDatabase},
3    error::{Result as ThingsResult, ThingsError},
4    models::ThingsId,
5};
6use chrono::Utc;
7use tracing::{info, instrument};
8
9impl ThingsDatabase {
10    /// Create a new area
11    ///
12    /// # Errors
13    ///
14    /// Returns an error if the database insert fails
15    #[instrument(skip(self))]
16    pub async fn create_area(
17        &self,
18        request: crate::models::CreateAreaRequest,
19    ) -> ThingsResult<ThingsId> {
20        // Generate ID for new area
21        let id = ThingsId::new_things_native();
22
23        // Get current timestamp for creation/modification dates
24        let now = Utc::now().timestamp() as f64;
25
26        // Calculate next index (max + 1)
27        let max_index: Option<i64> = sqlx::query_scalar("SELECT MAX(`index`) FROM TMArea")
28            .fetch_one(&self.pool)
29            .await
30            .map_err(|e| ThingsError::unknown(format!("Failed to get max area index: {e}")))?;
31
32        let next_index = max_index.unwrap_or(-1) + 1;
33
34        // Insert into TMArea table
35        sqlx::query(
36            r"
37            INSERT INTO TMArea (
38                uuid, title, visible, `index`,
39                creationDate, userModificationDate
40            ) VALUES (?, ?, 1, ?, ?, ?)
41            ",
42        )
43        .bind(id.as_str())
44        .bind(&request.title)
45        .bind(next_index)
46        .bind(now)
47        .bind(now)
48        .execute(&self.pool)
49        .await
50        .map_err(|e| ThingsError::unknown(format!("Failed to create area: {e}")))?;
51
52        info!("Created area with UUID: {}", id);
53        Ok(id)
54    }
55
56    /// Update an existing area
57    ///
58    /// # Errors
59    ///
60    /// Returns an error if the area doesn't exist or if the database update fails
61    #[instrument(skip(self))]
62    pub async fn update_area(&self, request: crate::models::UpdateAreaRequest) -> ThingsResult<()> {
63        // Verify area exists
64        validators::validate_area_exists(&self.pool, &request.uuid).await?;
65
66        let now = Utc::now().timestamp() as f64;
67
68        sqlx::query("UPDATE TMArea SET title = ?, userModificationDate = ? WHERE uuid = ?")
69            .bind(&request.title)
70            .bind(now)
71            .bind(request.uuid.as_str())
72            .execute(&self.pool)
73            .await
74            .map_err(|e| ThingsError::unknown(format!("Failed to update area: {e}")))?;
75
76        info!("Updated area with UUID: {}", request.uuid);
77        Ok(())
78    }
79
80    /// Delete an area
81    ///
82    /// Hard delete (areas don't have a trashed field)
83    /// Orphans all projects in the area by setting their area to NULL
84    ///
85    /// # Errors
86    ///
87    /// Returns an error if the area doesn't exist or if the database delete fails
88    #[instrument(skip(self))]
89    pub async fn delete_area(&self, id: &ThingsId) -> ThingsResult<()> {
90        // Verify area exists
91        validators::validate_area_exists(&self.pool, id).await?;
92
93        let now = Utc::now().timestamp() as f64;
94
95        // Orphan all projects in this area (set area to NULL)
96        sqlx::query(
97            "UPDATE TMTask SET area = NULL, userModificationDate = ? WHERE area = ? AND type = 1 AND trashed = 0",
98        )
99        .bind(now)
100        .bind(id.as_str())
101        .execute(&self.pool)
102        .await
103        .map_err(|e| ThingsError::unknown(format!("Failed to orphan projects in area: {e}")))?;
104
105        // Delete the area (hard delete)
106        sqlx::query("DELETE FROM TMArea WHERE uuid = ?")
107            .bind(id.as_str())
108            .execute(&self.pool)
109            .await
110            .map_err(|e| ThingsError::unknown(format!("Failed to delete area: {e}")))?;
111
112        info!("Deleted area with UUID: {}", id);
113        Ok(())
114    }
115}