track/use_cases/
archive_task.rs1use crate::db::Database;
2use crate::models::Task;
3use crate::services::{TaskService, WorktreeService};
4use crate::utils::{Result, TrackError};
5
6#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct DirtyWorkspace {
9 pub id: i64,
10 pub path: String,
11}
12
13#[derive(Debug, Clone)]
15pub struct ArchiveTaskOutcome {
16 pub task: Task,
17 pub removed_workspaces: Vec<(i64, String)>,
18 pub workspace_errors: Vec<String>,
19}
20
21pub struct ArchiveTaskUseCase<'a> {
23 db: &'a Database,
24}
25
26impl<'a> ArchiveTaskUseCase<'a> {
27 pub fn new(db: &'a Database) -> Self {
28 Self { db }
29 }
30
31 pub fn resolve_task_id(&self, task_ref: Option<&str>) -> Result<i64> {
32 let task_service = TaskService::new(self.db);
33 match task_ref {
34 Some(r) => task_service.resolve_task_id(r),
35 None => self
36 .db
37 .get_current_task_id()?
38 .ok_or(TrackError::NoActiveTask),
39 }
40 }
41
42 pub fn find_dirty_workspaces(&self, task_id: i64) -> Result<Vec<DirtyWorkspace>> {
43 let worktree_service = WorktreeService::new(self.db);
44 let worktrees = worktree_service.list_worktrees(task_id)?;
45
46 Ok(worktrees
47 .into_iter()
48 .filter(|worktree| {
49 std::path::Path::new(&worktree.path).exists()
50 && worktree_service
51 .has_uncommitted_changes(&worktree.path)
52 .unwrap_or(false)
53 })
54 .map(|worktree| DirtyWorkspace {
55 id: worktree.id,
56 path: worktree.path,
57 })
58 .collect())
59 }
60
61 pub fn execute(&self, task_id: i64, allow_dirty: bool) -> Result<ArchiveTaskOutcome> {
66 let task_service = TaskService::new(self.db);
67 let worktree_service = WorktreeService::new(self.db);
68
69 let task = task_service.get_task(task_id)?;
70 let dirty = self.find_dirty_workspaces(task_id)?;
71
72 if !dirty.is_empty() && !allow_dirty {
73 return Err(TrackError::UncommittedWorkspaces(
74 dirty
75 .into_iter()
76 .map(|ws| format!("#{} {}", ws.id, ws.path))
77 .collect(),
78 ));
79 }
80
81 let worktrees = worktree_service.list_worktrees(task_id)?;
82 let mut removed_workspaces = Vec::new();
83 let mut workspace_errors = Vec::new();
84
85 for worktree in worktrees {
86 match worktree_service.remove_worktree(worktree.id, false) {
87 Ok(()) => removed_workspaces.push((worktree.id, worktree.path)),
88 Err(err) => workspace_errors.push(format!("#{}: {}", worktree.id, err)),
89 }
90 }
91
92 task_service.archive_task(task_id)?;
93
94 Ok(ArchiveTaskOutcome {
95 task,
96 removed_workspaces,
97 workspace_errors,
98 })
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use crate::models::TaskStatus;
106
107 #[test]
108 fn archive_task_marks_task_archived() {
109 let db = Database::new_in_memory().unwrap();
110 let task_service = TaskService::new(&db);
111 let task = task_service
112 .create_task("Archive me", None, None, None)
113 .unwrap();
114
115 let outcome = ArchiveTaskUseCase::new(&db).execute(task.id, true).unwrap();
116
117 assert_eq!(outcome.task.id, task.id);
118 let archived = task_service.get_task(task.id).unwrap();
119 assert_eq!(archived.status, TaskStatus::Archived);
120 }
121}