1use crate::models::Task;
2use rusqlite::Connection;
3
4pub fn create_task(
5 conn: &Connection,
6 title: &str,
7 priority: Option<&str>,
8) -> Result<i64, rusqlite::Error> {
9 let priority = priority.unwrap_or("medium");
10
11 conn.execute(
12 "INSERT INTO tasks (title, priority) VALUES (?1, ?2)",
13 (title, priority),
14 )?;
15
16 Ok(conn.last_insert_rowid())
17}
18
19pub fn update_task_status(
20 conn: &Connection,
21 id: i64,
22 status: &str,
23) -> Result<usize, rusqlite::Error> {
24 conn.execute(
25 "UPDATE tasks SET status = ?1, updated_at = datetime('now') WHERE id = ?2",
26 (status, id),
27 )
28}
29
30pub fn delete_task(conn: &Connection, id: i64) -> Result<usize, rusqlite::Error> {
31 conn.execute("DELETE FROM tasks WHERE id = ?1", (id,))
32}
33
34pub fn edit_task(
35 conn: &Connection,
36 id: i64,
37 title: Option<&str>,
38 priority: Option<&str>,
39 description: Option<&str>,
40) -> Result<usize, rusqlite::Error> {
41 let mut updates = Vec::new();
42 let mut params: Vec<Box<dyn rusqlite::types::ToSql>> = Vec::new();
43
44 if let Some(t) = title {
45 updates.push("title = ?");
46 params.push(Box::new(t.to_string()));
47 }
48
49 if let Some(p) = priority {
50 updates.push("priority = ?");
51 params.push(Box::new(p.to_string()));
52 }
53
54 if let Some(d) = description {
55 updates.push("description = ?");
56 params.push(Box::new(d.to_string()));
57 }
58
59 if updates.is_empty() {
60 return Ok(0);
61 }
62
63 updates.push("updated_at = datetime('now')");
64 params.push(Box::new(id));
65
66 let sql = format!("UPDATE tasks SET {} WHERE id = ?", updates.join(", "));
67
68 conn.execute(&sql, rusqlite::params_from_iter(params))
69}
70
71pub fn list_tasks_filtered(
72 conn: &Connection,
73 status: Option<&str>,
74 priority: Option<&str>,
75) -> Result<Vec<Task>, rusqlite::Error> {
76 let mut conditions = Vec::new();
77 let mut params: Vec<Box<dyn rusqlite::types::ToSql>> = Vec::new();
78
79 if let Some(s) = status {
80 conditions.push("status = ?");
81 params.push(Box::new(s.to_string()));
82 }
83
84 if let Some(p) = priority {
85 conditions.push("priority = ?");
86 params.push(Box::new(p.to_string()));
87 }
88
89 let sql = if conditions.is_empty() {
90 "SELECT id, title, description, status, priority, project_id, due_date, created_at, updated_at FROM tasks".to_string()
91 } else {
92 format!(
93 "SELECT id, title, description, status, priority, project_id, due_date, created_at, updated_at FROM tasks WHERE {}",
94 conditions.join(" AND ")
95 )
96 };
97
98 let mut stmt = conn.prepare(&sql)?;
99
100 let tasks = stmt.query_map(rusqlite::params_from_iter(params), |row| {
101 Ok(Task {
102 id: row.get(0)?,
103 title: row.get(1)?,
104 description: row.get(2)?,
105 status: row.get(3)?,
106 priority: row.get(4)?,
107 project_id: row.get(5)?,
108 due_date: row.get(6)?,
109 created_at: row.get(7)?,
110 updated_at: row.get(8)?,
111 })
112 })?;
113
114 tasks.collect()
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use crate::db::initialize;
121
122 fn setup_db() -> Connection {
123 initialize(":memory:").expect("Failed to initialize test database")
124 }
125
126 #[test]
127 fn test_create_task() {
128 let conn = setup_db();
129 let id = create_task(&conn, "Test task", Some("high")).unwrap();
130 assert_eq!(id, 1);
131 }
132
133 #[test]
134 fn test_create_task_default_priority() {
135 let conn = setup_db();
136 let id = create_task(&conn, "Test task", None).unwrap();
137 let tasks = list_tasks_filtered(&conn, None, None).unwrap();
138 assert_eq!(tasks.len(), 1);
139 assert_eq!(tasks[0].id, id);
140 assert_eq!(tasks[0].priority, "medium");
141 }
142
143 #[test]
144 fn test_list_tasks_empty() {
145 let conn = setup_db();
146 let tasks = list_tasks_filtered(&conn, None, None).unwrap();
147 assert!(tasks.is_empty());
148 }
149
150 #[test]
151 fn test_list_tasks_filtered_by_status() {
152 let conn = setup_db();
153 create_task(&conn, "Task 1", None).unwrap();
154 create_task(&conn, "Task 2", None).unwrap();
155 update_task_status(&conn, 1, "done").unwrap();
156
157 let done = list_tasks_filtered(&conn, Some("done"), None).unwrap();
158 assert_eq!(done.len(), 1);
159 assert_eq!(done[0].title, "Task 1");
160
161 let todo = list_tasks_filtered(&conn, Some("todo"), None).unwrap();
162 assert_eq!(todo.len(), 1);
163 assert_eq!(todo[0].title, "Task 2");
164 }
165
166 #[test]
167 fn test_list_tasks_filtered_by_priority() {
168 let conn = setup_db();
169 create_task(&conn, "Low task", Some("low")).unwrap();
170 create_task(&conn, "High task", Some("high")).unwrap();
171
172 let high = list_tasks_filtered(&conn, None, Some("high")).unwrap();
173 assert_eq!(high.len(), 1);
174 assert_eq!(high[0].title, "High task");
175 }
176
177 #[test]
178 fn test_update_task_status() {
179 let conn = setup_db();
180 create_task(&conn, "Test task", None).unwrap();
181
182 let rows = update_task_status(&conn, 1, "done").unwrap();
183 assert_eq!(rows, 1);
184
185 let tasks = list_tasks_filtered(&conn, Some("done"), None).unwrap();
186 assert_eq!(tasks.len(), 1);
187 }
188
189 #[test]
190 fn test_update_nonexistent_task() {
191 let conn = setup_db();
192 let rows = update_task_status(&conn, 999, "done").unwrap();
193 assert_eq!(rows, 0);
194 }
195
196 #[test]
197 fn test_delete_task() {
198 let conn = setup_db();
199 create_task(&conn, "Test task", None).unwrap();
200
201 let rows = delete_task(&conn, 1).unwrap();
202 assert_eq!(rows, 1);
203
204 let tasks = list_tasks_filtered(&conn, None, None).unwrap();
205 assert!(tasks.is_empty());
206 }
207
208 #[test]
209 fn test_delete_nonexistent_task() {
210 let conn = setup_db();
211 let rows = delete_task(&conn, 999).unwrap();
212 assert_eq!(rows, 0);
213 }
214
215 #[test]
216 fn test_edit_task() {
217 let conn = setup_db();
218 create_task(&conn, "Original", Some("low")).unwrap();
219
220 edit_task(&conn, 1, Some("Updated"), Some("high"), None).unwrap();
221
222 let tasks = list_tasks_filtered(&conn, None, None).unwrap();
223 assert_eq!(tasks[0].title, "Updated");
224 assert_eq!(tasks[0].priority, "high");
225 }
226
227 #[test]
228 fn test_edit_task_partial() {
229 let conn = setup_db();
230 create_task(&conn, "Original", Some("low")).unwrap();
231
232 edit_task(&conn, 1, Some("New title"), None, None).unwrap();
233
234 let tasks = list_tasks_filtered(&conn, None, None).unwrap();
235 assert_eq!(tasks[0].title, "New title");
236 assert_eq!(tasks[0].priority, "low");
237 }
238
239 #[test]
240 fn test_edit_no_fields() {
241 let conn = setup_db();
242 create_task(&conn, "Original", None).unwrap();
243
244 let rows = edit_task(&conn, 1, None, None, None).unwrap();
245 assert_eq!(rows, 0);
246
247 let tasks = list_tasks_filtered(&conn, None, None).unwrap();
248 assert_eq!(tasks[0].title, "Original");
249 }
250}