track/use_cases/
create_today_task.rs1use crate::db::Database;
2use crate::models::{Task, TaskStatus};
3use crate::services::link_service::ScrapService;
4use crate::services::task_service::TaskService;
5use crate::services::todo_service::TodoService;
6use crate::utils::Result;
7use chrono::{Local, Utc};
8use rusqlite::{params, OptionalExtension};
9
10pub struct CreateTodayTaskUseCase<'a> {
12 db: &'a Database,
13}
14
15impl<'a> CreateTodayTaskUseCase<'a> {
16 pub fn new(db: &'a Database) -> Self {
17 Self { db }
18 }
19
20 pub fn get_or_create(&self) -> Result<Task> {
22 let today = Local::now().format("%Y-%m-%d").to_string();
23 let task_name = format!("Today: {}", today);
24
25 let conn = self.db.get_connection();
26 let active_query = format!(
27 "SELECT id FROM tasks WHERE is_today_task = 1 AND name = ?1 AND status = '{}'",
28 TaskStatus::ACTIVE
29 );
30 let mut stmt = conn.prepare(&active_query)?;
31
32 if let Some(task_id) = stmt
33 .query_row(params![task_name], |row| row.get::<_, i64>(0))
34 .optional()?
35 {
36 return TaskService::new(self.db).get_task(task_id);
37 }
38
39 self.create_with_inheritance(&task_name)
40 }
41
42 pub fn create_with_inheritance(&self, task_name: &str) -> Result<Task> {
44 self.db.with_transaction(|| {
45 let inherit_from = self.find_today_task_to_inherit_from()?;
46
47 let conn = self.db.get_connection();
48 let now = Utc::now().to_rfc3339();
49
50 conn.execute(
51 "UPDATE tasks SET is_today_task = 0 WHERE is_today_task = 1",
52 [],
53 )?;
54
55 conn.execute(
56 "INSERT INTO tasks (name, description, status, is_today_task, created_at) VALUES (?1, ?2, ?3, ?4, ?5)",
57 params![
58 task_name,
59 None::<String>,
60 TaskStatus::Active.as_str(),
61 1,
62 now
63 ],
64 )?;
65
66 let task_id = conn.last_insert_rowid();
67
68 if let Some(from_task_id) = inherit_from {
69 let todo_service = TodoService::new(self.db);
70 let scrap_service = ScrapService::new(self.db);
71 let mapping =
72 todo_service.copy_incomplete_todos_in_tx(from_task_id, task_id)?;
73 scrap_service.copy_linked_scraps_in_tx(from_task_id, task_id, &mapping)?;
74 }
75
76 self.db.set_current_task_id(task_id)?;
77 self.db.increment_rev("task")?;
78 TaskService::new(self.db).get_task(task_id)
79 })
80 }
81
82 fn find_today_task_to_inherit_from(&self) -> Result<Option<i64>> {
83 let conn = self.db.get_connection();
84 let active_query = format!(
85 "SELECT id FROM tasks WHERE is_today_task = 1 AND status = '{}' ORDER BY created_at DESC LIMIT 1",
86 TaskStatus::ACTIVE
87 );
88 let mut stmt = conn.prepare(&active_query)?;
89
90 stmt.query_row([], |row| row.get(0))
91 .optional()
92 .map_err(Into::into)
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use crate::services::{ScrapService, TodoService};
100
101 #[test]
102 fn create_today_task_inherits_pending_todos_and_scraps() {
103 let db = Database::new_in_memory().unwrap();
104 let task_service = TaskService::new(&db);
105 let todo_service = TodoService::new(&db);
106 let scrap_service = ScrapService::new(&db);
107 let use_case = CreateTodayTaskUseCase::new(&db);
108
109 let previous = task_service
110 .create_task("Today: 2026-01-01", None, None, None)
111 .unwrap();
112 db.get_connection()
113 .execute(
114 "UPDATE tasks SET is_today_task = 1 WHERE id = ?1",
115 rusqlite::params![previous.id],
116 )
117 .unwrap();
118 db.set_current_task_id(previous.id).unwrap();
119
120 todo_service
121 .add_todo(previous.id, "Carry over", false)
122 .unwrap();
123 scrap_service.add_scrap(previous.id, "linked note").unwrap();
124
125 let today_task = use_case.get_or_create().unwrap();
126 assert_ne!(today_task.id, previous.id);
127 assert!(today_task.is_today_task);
128
129 let inherited = todo_service.list_todos(today_task.id).unwrap();
130 assert_eq!(inherited.len(), 1);
131 assert_eq!(inherited[0].content, "Carry over");
132
133 let inherited_scraps = scrap_service.list_scraps(today_task.id).unwrap();
134 assert_eq!(inherited_scraps.len(), 1);
135 assert_eq!(inherited_scraps[0].content, "linked note");
136 }
137}