intent_engine/
workspace.rs

1use crate::db::models::Task;
2use crate::error::{IntentError, Result};
3use serde::Serialize;
4use sqlx::SqlitePool;
5
6#[derive(Debug, Serialize)]
7pub struct CurrentTaskResponse {
8    pub current_task_id: Option<i64>,
9    #[serde(skip_serializing_if = "Option::is_none")]
10    pub task: Option<Task>,
11}
12
13pub struct WorkspaceManager<'a> {
14    pool: &'a SqlitePool,
15}
16
17impl<'a> WorkspaceManager<'a> {
18    pub fn new(pool: &'a SqlitePool) -> Self {
19        Self { pool }
20    }
21
22    /// Get the current task
23    pub async fn get_current_task(&self) -> Result<CurrentTaskResponse> {
24        let current_task_id: Option<String> =
25            sqlx::query_scalar("SELECT value FROM workspace_state WHERE key = 'current_task_id'")
26                .fetch_optional(self.pool)
27                .await?;
28
29        let current_task_id = current_task_id.and_then(|id| id.parse::<i64>().ok());
30
31        let task = if let Some(id) = current_task_id {
32            sqlx::query_as::<_, Task>(
33                r#"
34                SELECT id, parent_id, name, spec, status, complexity, priority, first_todo_at, first_doing_at, first_done_at
35                FROM tasks
36                WHERE id = ?
37                "#,
38            )
39            .bind(id)
40            .fetch_optional(self.pool)
41            .await?
42        } else {
43            None
44        };
45
46        Ok(CurrentTaskResponse {
47            current_task_id,
48            task,
49        })
50    }
51
52    /// Set the current task
53    pub async fn set_current_task(&self, task_id: i64) -> Result<CurrentTaskResponse> {
54        // Check if task exists
55        let task_exists: bool =
56            sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM tasks WHERE id = ?)")
57                .bind(task_id)
58                .fetch_one(self.pool)
59                .await?;
60
61        if !task_exists {
62            return Err(IntentError::TaskNotFound(task_id));
63        }
64
65        // Set current task
66        sqlx::query(
67            r#"
68            INSERT OR REPLACE INTO workspace_state (key, value)
69            VALUES ('current_task_id', ?)
70            "#,
71        )
72        .bind(task_id.to_string())
73        .execute(self.pool)
74        .await?;
75
76        self.get_current_task().await
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    use crate::tasks::TaskManager;
84    use crate::test_utils::test_helpers::TestContext;
85
86    #[tokio::test]
87    async fn test_get_current_task_none() {
88        let ctx = TestContext::new().await;
89        let workspace_mgr = WorkspaceManager::new(ctx.pool());
90
91        let response = workspace_mgr.get_current_task().await.unwrap();
92
93        assert!(response.current_task_id.is_none());
94        assert!(response.task.is_none());
95    }
96
97    #[tokio::test]
98    async fn test_set_current_task() {
99        let ctx = TestContext::new().await;
100        let task_mgr = TaskManager::new(ctx.pool());
101        let workspace_mgr = WorkspaceManager::new(ctx.pool());
102
103        let task = task_mgr.add_task("Test task", None, None).await.unwrap();
104
105        let response = workspace_mgr.set_current_task(task.id).await.unwrap();
106
107        assert_eq!(response.current_task_id, Some(task.id));
108        assert!(response.task.is_some());
109        assert_eq!(response.task.unwrap().id, task.id);
110    }
111
112    #[tokio::test]
113    async fn test_set_current_task_nonexistent() {
114        let ctx = TestContext::new().await;
115        let workspace_mgr = WorkspaceManager::new(ctx.pool());
116
117        let result = workspace_mgr.set_current_task(999).await;
118        assert!(matches!(result, Err(IntentError::TaskNotFound(999))));
119    }
120
121    #[tokio::test]
122    async fn test_update_current_task() {
123        let ctx = TestContext::new().await;
124        let task_mgr = TaskManager::new(ctx.pool());
125        let workspace_mgr = WorkspaceManager::new(ctx.pool());
126
127        let task1 = task_mgr.add_task("Task 1", None, None).await.unwrap();
128        let task2 = task_mgr.add_task("Task 2", None, None).await.unwrap();
129
130        // Set task1 as current
131        workspace_mgr.set_current_task(task1.id).await.unwrap();
132
133        // Update to task2
134        let response = workspace_mgr.set_current_task(task2.id).await.unwrap();
135
136        assert_eq!(response.current_task_id, Some(task2.id));
137        assert_eq!(response.task.unwrap().id, task2.id);
138    }
139
140    #[tokio::test]
141    async fn test_get_current_task_after_set() {
142        let ctx = TestContext::new().await;
143        let task_mgr = TaskManager::new(ctx.pool());
144        let workspace_mgr = WorkspaceManager::new(ctx.pool());
145
146        let task = task_mgr.add_task("Test task", None, None).await.unwrap();
147        workspace_mgr.set_current_task(task.id).await.unwrap();
148
149        let response = workspace_mgr.get_current_task().await.unwrap();
150
151        assert_eq!(response.current_task_id, Some(task.id));
152        assert!(response.task.is_some());
153    }
154}