valis_core/modules/projects/agile/
core.rs

1use std::fmt::Error;
2
3use chrono::{DateTime, Utc};
4use rusqlite::{Connection, params, Result, Row};
5use rusqlite::types::{FromSql, ValueRef};
6use serde::{Deserialize, Serialize};
7use termion::{color, style};
8use uuid::Uuid;
9
10use db::DatabaseOperations;
11
12use crate::modules::db;
13use crate::modules::db::{get_connection, serializers};
14use crate::modules::db::serializers::SerializableDateTime;
15use crate::modules::tasks::todoist::core::Task as TodoisTask;
16
17#[derive(Serialize, Deserialize)]
18pub struct Project {
19    pub id: Uuid,
20    pub name: String,
21    pub description: String,
22    pub created_at: SerializableDateTime,
23    pub updated_at: SerializableDateTime,
24}
25
26impl DatabaseOperations<String> for Project {
27    /// Create a new project and save it
28    fn save(&self, db: &str) -> Result<(), Error> {
29        let conn = Connection::open(db).ok().unwrap();
30        let id = Uuid::new_v4();
31        let now = Utc::now().to_string();
32
33        conn.execute(
34            "INSERT INTO project (id, name, description, created_at, updated_at) VALUES (?1, ?2, ?3, ?4, ?5)",
35            params![id.to_string(), self.name, self.description, now, now],
36        ).ok().unwrap();
37
38        Ok(())
39    }
40
41    fn get(id: String, db: &str) -> Result<Self, rusqlite::Error> where Self: Sized {
42        todo!()
43    }
44
45    // List All projects
46    fn get_all(db: &str) -> Result<Vec<Project>, rusqlite::Error> {
47        let conn = Connection::open(db).ok().unwrap();
48        let mut stmt = conn.prepare("SELECT * FROM project").ok().unwrap();
49        let rows = stmt
50            .query_map((), |row| {
51                let id: Vec<u8> = row.get(0).ok().unwrap();
52                Ok(Project {
53                    id: Uuid::from_slice(&id).unwrap(),
54                    name: row.get(1).ok().unwrap(),
55                    description: row.get(2).ok().unwrap(),
56                    created_at: row.get(3).ok().unwrap(),
57                    updated_at: row.get(4).ok().unwrap(),
58                })
59            })
60            .ok()
61            .unwrap();
62
63        let mut projects = Vec::new();
64        for project in rows {
65            projects.push(project.ok().unwrap());
66        }
67
68        Ok(projects)
69    }
70
71    fn map(row: &Row<'_>) -> std::result::Result<Self, rusqlite::Error> where Self: Sized {
72        todo!()
73    }
74}
75
76impl Default for Project {
77    fn default() -> Self {
78        let now = SerializableDateTime::now();
79        Self {
80            id: Uuid::new_v4(),
81            name: "Default name".to_string(),
82            description: "Default description".to_string(),
83            created_at: now.clone(),
84            updated_at: now.clone(),
85        }
86    }
87}
88
89#[derive(Debug, Serialize, Deserialize)]
90pub struct Sprint {
91    pub id: Uuid,
92    pub project_id: Uuid,
93    pub name: String,
94    pub start_date: SerializableDateTime,
95    pub end_date: SerializableDateTime,
96    pub created_at: SerializableDateTime,
97    pub updated_at: SerializableDateTime,
98}
99
100impl DatabaseOperations<String> for Sprint {
101    /// Save a Sprint
102    fn save(&self, db: &str) -> Result<(), Error> {
103        let conn = Connection::open(db).ok().unwrap();
104
105        let now = Utc::now().to_rfc3339().to_string();
106
107        conn.execute(
108            "INSERT INTO sprint (id, project_id, name, start_date, end_date, created_at, updated_at) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
109            params![self.id.to_string(), &self.project_id.to_string(), &self.name, &self.start_date.with_timezone(&Utc).to_string(), &self.end_date.with_timezone(&Utc).to_string(), now, now],
110        ).ok().unwrap();
111
112        Ok(())
113    }
114
115    fn get(id: String, db: &str) -> Result<Self, rusqlite::Error> where Self: Sized {
116        let conn = get_connection(db);
117        let mut query = conn.prepare("SELECT * FROM sprint WHERE id = ?")?;
118
119        query.query_row(params![&id], Sprint::map)
120    }
121
122    /// List all sprints
123    fn get_all(db: &str) -> Result<Vec<Sprint>, rusqlite::Error> {
124        let conn = Connection::open(db)?;
125        let mut stmt = conn.prepare("SELECT * FROM sprint")?;
126        let rows = stmt
127            .query_map((), Sprint::map)?;
128
129        let mut sprints = Vec::new();
130        for sprint_res in rows {
131            sprints.push(sprint_res?);
132        }
133
134        Ok(sprints)
135    }
136    fn map(row: &Row<'_>) -> Result<Self, rusqlite::Error> where Self: Sized {
137        let id: String = row.get(0)?;
138        let project_id: String = row.get(1)?;
139        let start_date: String = row.get(3)?;
140        let end_date: String = row.get(4)?;
141        let created_at: String = row.get(5)?;
142        let updated_at: String = row.get(6)?;
143        Ok(Sprint {
144            id: Uuid::parse_str(&id).unwrap(),
145            project_id: Uuid::parse_str(&project_id).unwrap(),
146            name: row.get(2)?,
147            start_date: SerializableDateTime::parse_from_rfc3339(&start_date)
148                .unwrap()
149                .with_timezone(&Utc),
150            end_date: SerializableDateTime::parse_from_rfc3339(&end_date)
151                .unwrap()
152                .with_timezone(&Utc),
153            created_at: SerializableDateTime::parse_from_rfc3339(&created_at)
154                .unwrap()
155                .with_timezone(&Utc),
156            updated_at: SerializableDateTime::parse_from_rfc3339(&updated_at)
157                .unwrap()
158                .with_timezone(&Utc),
159        })
160    }
161}
162
163pub fn sprint_get_all(db: &str) -> Result<Vec<Sprint>, rusqlite::Error> {
164    Sprint::get_all(db)
165}
166
167impl Default for Sprint {
168    fn default() -> Self {
169        let now = SerializableDateTime::now();
170        Self {
171            id: Uuid::new_v4(),
172            project_id: Uuid::new_v4(),
173            name: "Default name".to_string(),
174            start_date: now.clone(),
175            end_date: now.add_weeks(3),
176            created_at: now.clone(),
177            updated_at: now.clone(),
178        }
179    }
180}
181
182impl Sprint {
183    fn is_active(&self) -> bool {
184        let now = Utc::now();
185        self.start_date.get_utc() <= now && now <= self.end_date.get_utc()
186    }
187}
188
189// Delete a project, given the project id
190pub fn delete_project_by_id(conn: &Connection, id: Uuid) -> Result<()> {
191    conn.execute("DELETE FROM project WHERE id = ?1", params![id.to_string()])?;
192
193    Ok(())
194}
195
196// Delete a project, given the project name
197pub fn delete_project_by_name(conn: &Connection, name: &str) -> Result<()> {
198    conn.execute("DELETE FROM project WHERE name = ?1", params![name])?;
199
200    Ok(())
201}
202
203// Create a Sprint
204pub fn create_sprint(
205    conn: &Connection,
206    project_id: Uuid,
207    name: &str,
208    start_date: DateTime<Utc>,
209    end_date: DateTime<Utc>,
210) -> Result<()> {
211    let id = Uuid::new_v4();
212    let now = Utc::now().to_string();
213
214    conn.execute(
215        "INSERT INTO sprint (id, project_id, name, start_date, end_date, created_at, updated_at) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
216        params![id.to_string(), project_id.to_string(), name, start_date.to_string(), end_date.to_string(), now, now],
217    )?;
218
219    Ok(())
220}
221
222// Delete a Sprint
223pub fn delete_sprint(conn: &Connection, id: Uuid) -> Result<()> {
224    conn.execute("DELETE FROM sprint WHERE id = ?1", params![id.to_string()])?;
225
226    Ok(())
227}
228
229// List all sprints for a project
230pub fn list_sprints_for_project(conn: &Connection, project_id: Uuid) -> Result<Vec<Sprint>> {
231    let mut stmt = conn.prepare("SELECT * FROM sprint WHERE project_id = ?1")?;
232    let rows = stmt.query_map(params![project_id.to_string()], Sprint::map)?;
233
234    let mut sprints = Vec::new();
235    for sprint_res in rows {
236        sprints.push(sprint_res?);
237    }
238
239    Ok(sprints)
240}
241
242pub fn print_sprint_info(db: &str, sprint_id: Uuid) -> Result<()> {
243    let conn = get_connection(db);
244    let sprint_info = Sprint::get(sprint_id.to_string(), db).ok().unwrap();
245
246    println!("\n{}{}Sprint Information{}", style::Bold, color::Fg(color::Blue), style::Reset);
247    println!("{}ID:{} {}", color::Fg(color::Green), style::Reset, sprint_info.id.to_string());
248    println!("{}Name:{} {}", color::Fg(color::Green), style::Reset, sprint_info.name);
249    println!("{}Start Date:{} {}", color::Fg(color::Green), style::Reset, sprint_info.start_date.get_utc().to_string());
250    println!("{}End Date:{} {}", color::Fg(color::Green), style::Reset, sprint_info.end_date.get_utc().to_string());
251
252    let now = Utc::now();
253    let days_to_finish = sprint_info.end_date.get_utc().signed_duration_since(now).num_days();
254    println!("{}Days to Sprint Finish:{} {}", color::Fg(color::Green), style::Reset, days_to_finish.to_string());
255
256    let mut task_query = conn.prepare("
257        SELECT task.* FROM task
258        INNER JOIN sprint_todoist_task ON task.id = sprint_todoist_task.todoist_task_id
259        WHERE sprint_todoist_task.sprint_id = ?
260    ")?;
261    let task_rows = task_query.query_map(params![sprint_id.to_string()], |row| {
262        Ok(TodoisTask {
263            id: row.get(0)?,
264            content: row.get(1)?,
265            // TODO: Fetch all the labels
266            labels: vec![],
267        })
268    })?;
269
270    let tasks: Result<Vec<TodoisTask>, _> = task_rows.collect();
271    match tasks {
272        Ok(tasks) => {
273            println!("\n{}{}Tasks{}", style::Bold, color::Fg(color::Blue), style::Reset);
274            for task in tasks {
275                println!("{}Task ID:{} {}", color::Fg(color::Green), style::Reset, task.id);
276                println!("{}Content:{} {}", color::Fg(color::Green), style::Reset, task.content.unwrap_or_else(|| "None".to_string()));
277                println!("{}Labels:{} {:?}", color::Fg(color::Green), style::Reset, task.labels);
278            }
279        }
280        Err(e) => println!("Failed to get tasks: {}", e),
281    }
282
283    Ok(())
284}