valis_core/modules/tasks/todoist/
core.rs

1use std::error::Error;
2use std::result::Result;
3
4use rusqlite::{Connection, params, Row};
5use serde::{Deserialize, Serialize};
6use uuid::Uuid;
7
8use db::DatabaseOperations;
9
10use crate::modules::db;
11use crate::modules::db::get_connection;
12use crate::modules::projects::agile::core::Sprint;
13
14#[derive(Debug, Deserialize, Serialize)]
15pub struct Task {
16    pub id: String,
17    pub content: Option<String>,
18    pub labels: Vec<String>,
19    // add other fields as necessary
20}
21
22#[derive(Debug, Serialize, Deserialize)]
23pub struct SprintTask {
24    pub sprint_id: Uuid,
25    pub todoist_task_id: String,
26}
27
28impl Task {
29    pub fn add_to_sprint(&self, db: &str, sprint: Sprint) -> Result<(), rusqlite::Error> {
30        add_task_to_sprint(db, &sprint.id, self.id.to_string())
31    }
32}
33
34impl DatabaseOperations<String> for Task {
35    fn save(&self, db: &str) -> Result<(), std::fmt::Error> {
36        todo!()
37    }
38
39    fn get(id: String, db: &str) -> Result<Self, rusqlite::Error> where Self: Sized {
40        todo!()
41    }
42
43    fn get_all(db: &str) -> Result<Vec<Task>, rusqlite::Error> {
44        let conn = get_connection(db);
45
46        let mut stmt = conn.prepare("SELECT * FROM todoist_tasks").ok().unwrap();
47        let tasks_iter = stmt
48            .query_map([], |row| {
49                Ok(Task {
50                    id: row.get(0).ok().unwrap(),
51                    content: row.get(1).ok().unwrap(),
52                    labels: vec![], // We'll get the tags later
53                })
54            })
55            .ok()
56            .unwrap();
57
58        let mut tasks = Vec::new();
59        for task_result in tasks_iter {
60            let mut task = task_result.ok().unwrap();
61            let mut stmt = conn
62                .prepare(
63                    "
64            SELECT todoist_labels.label FROM todoist_labels
65            JOIN todoist_task_labels ON todoist_labels.id = todoist_task_labels.todoist_label_id
66            WHERE todoist_task_labels.todoist_task_id = ?
67        ",
68                )
69                .ok()
70                .unwrap();
71            let labels_iter = stmt
72                .query_map(params![task.id], |row| row.get(0))
73                .ok()
74                .unwrap();
75
76            for label_result in labels_iter {
77                task.labels.push(label_result.ok().unwrap());
78            }
79
80            tasks.push(task);
81        }
82
83        Ok(tasks)
84    }
85
86    fn map(row: &Row<'_>) -> Result<Self, rusqlite::Error> where Self: Sized {
87        todo!()
88    }
89}
90
91fn get_task_by_id(db: &str, id: String) -> Result<Task, std::fmt::Error> {
92    let conn = get_connection(db);
93
94    let mut stmt = conn
95        .prepare("SELECT * FROM tasks WHERE id = ?")
96        .ok()
97        .unwrap();
98    let mut task = stmt
99        .query_row(params![id], |row| {
100            Ok(Task {
101                id: row.get(0).ok().unwrap(),
102                content: row.get(1).ok().unwrap(),
103                labels: vec![], // We'll get the tags later
104            })
105        })
106        .ok()
107        .unwrap();
108
109    let mut stmt = conn
110        .prepare(
111            "
112        SELECT todoist_labels.label FROM todoist_labels
113        JOIN todoist_task_labels ON todoist_labels.id = todoist_task_labels.label_id
114        WHERE todoist_task_labels.task_id = ?
115    ",
116        )
117        .ok()
118        .unwrap();
119    let labels_iter = stmt.query_map(params![id], |row| row.get(0)).ok().unwrap();
120
121    for label_result in labels_iter {
122        task.labels.push(label_result.ok().unwrap());
123    }
124
125    Ok(task)
126}
127
128pub fn get_all_tasks(db: &str) -> Result<Vec<Task>, rusqlite::Error> {
129    let conn = get_connection(db);
130
131    let mut stmt = conn.prepare("SELECT * FROM todoist_tasks").ok().unwrap();
132    let tasks_iter = stmt
133        .query_map([], |row| {
134            Ok(Task {
135                id: row.get(0).ok().unwrap(),
136                content: row.get(1).ok().unwrap(),
137                labels: vec![], // We'll get the tags later
138            })
139        })
140        .ok()
141        .unwrap();
142
143    let mut tasks = Vec::new();
144    for task_result in tasks_iter {
145        let mut task = task_result.ok().unwrap();
146        let mut stmt = conn
147            .prepare(
148                "
149            SELECT todoist_labels.label FROM todoist_labels
150            JOIN todoist_task_labels ON todoist_labels.id = todoist_task_labels.todoist_label_id
151            WHERE todoist_task_labels.todoist_task_id = ?
152        ",
153            )
154            .ok()
155            .unwrap();
156        let labels_iter = stmt
157            .query_map(params![task.id], |row| row.get(0))
158            .ok()
159            .unwrap();
160
161        for label_result in labels_iter {
162            task.labels.push(label_result.ok().unwrap());
163        }
164
165        tasks.push(task);
166    }
167
168    Ok(tasks)
169}
170
171async fn get_todoist_tasks(todoist_token: &str) -> Result<Vec<Task>, reqwest::Error> {
172    let client = reqwest::Client::new();
173    let response = client
174        .get("https://api.todoist.com/rest/v2/tasks")
175        .header("Authorization", format!("Bearer {}", todoist_token))
176        .send()
177        .await?
178        .text()
179        .await?;
180
181    Ok(serde_json::from_str::<Vec<Task>>(&response).ok().unwrap())
182}
183
184fn sync_to_db(tasks: &Vec<Task>, db: &str) -> rusqlite::Result<()> {
185    let conn = Connection::open(db)?;
186
187    for task in tasks {
188        let result: Result<String, rusqlite::Error> = conn.query_row(
189            "SELECT id FROM todoist_tasks WHERE id = ?1",
190            params![task.id],
191            |row| row.get(0),
192        );
193
194        match result {
195            Ok(_) => {
196                // task exists in the database, update it
197                conn.execute(
198                    "UPDATE todoist_tasks SET content = ?1 WHERE id = ?2",
199                    params![task.content, task.id],
200                )?;
201            }
202            Err(_) => {
203                // task doesn't exist in the database, insert it
204                conn.execute(
205                    "INSERT INTO todoist_tasks (id, content) VALUES (?1, ?2)",
206                    params![task.id, task.content],
207                )?;
208            }
209        }
210        for label in &task.labels {
211            // Insert tag if it doesn't exist
212            conn.execute(
213                "INSERT OR IGNORE INTO todoist_labels (label) VALUES (?)",
214                [label],
215            )?;
216
217            // Get tag id
218            let label_id: i32 = conn.query_row(
219                "SELECT id FROM todoist_labels WHERE label = ?",
220                [label],
221                |row| row.get(0),
222            )?;
223
224            // Link task and tag
225            conn.execute(
226                "INSERT OR IGNORE INTO todoist_task_labels (todoist_task_id, todoist_label_id) VALUES (?, ?)",
227                [&task.id, &(label_id.to_string())],
228            )?;
229        }
230    }
231
232    // delete tasks from database that are not in Todoist
233    let db_tasks: Vec<String> = conn
234        .prepare("SELECT id FROM todoist_tasks")?
235        .query_map([], |row| row.get(0))?
236        .collect::<Result<_, _>>()?;
237
238    for db_task in db_tasks {
239        if !tasks.iter().any(|task| task.id == db_task) {
240            conn.execute("DELETE FROM todoist_tasks WHERE id = ?1", params![db_task])?;
241        }
242    }
243
244    Ok(())
245}
246
247pub fn add_task_to_sprint(db: &str, sprint_id: &Uuid, task_id: String) -> Result<(), rusqlite::Error> {
248    let conn = get_connection(db);
249    conn.execute(
250        "INSERT INTO sprint_todoist_task (sprint_id, todoist_task_id) VALUES (?1, ?2)",
251        [&sprint_id.to_string(), &task_id],
252    )?;
253
254    Ok(())
255}
256
257pub async fn sync(token: &str, db: &str) -> Result<(), std::fmt::Error> {
258    let tasks = get_todoist_tasks(token).await.ok().unwrap();
259    sync_to_db(&tasks, db).ok().unwrap();
260
261    Ok(())
262}