Skip to main content

chainlink/db/
milestones.rs

1use anyhow::Result;
2use chrono::Utc;
3use rusqlite::params;
4
5use super::{issue_from_row, parse_datetime, Database};
6use crate::models::{Issue, Milestone};
7
8impl Database {
9    pub fn create_milestone(&self, name: &str, description: Option<&str>) -> Result<i64> {
10        let now = Utc::now().to_rfc3339();
11        self.conn.execute(
12            "INSERT INTO milestones (name, description, status, created_at) VALUES (?1, ?2, 'open', ?3)",
13            params![name, description, now],
14        )?;
15        Ok(self.conn.last_insert_rowid())
16    }
17
18    pub fn get_milestone(&self, id: i64) -> Result<Option<Milestone>> {
19        let mut stmt = self.conn.prepare(
20            "SELECT id, name, description, status, created_at, closed_at FROM milestones WHERE id = ?1",
21        )?;
22
23        let milestone = stmt
24            .query_row([id], |row| {
25                Ok(Milestone {
26                    id: row.get(0)?,
27                    name: row.get(1)?,
28                    description: row.get(2)?,
29                    status: row.get(3)?,
30                    created_at: parse_datetime(row.get::<_, String>(4)?),
31                    closed_at: row.get::<_, Option<String>>(5)?.map(parse_datetime),
32                })
33            })
34            .ok();
35
36        Ok(milestone)
37    }
38
39    pub fn list_milestones(&self, status: Option<&str>) -> Result<Vec<Milestone>> {
40        let (sql, params_vec): (&str, Vec<Box<dyn rusqlite::ToSql>>) = if let Some(s) = status {
41            if s == "all" {
42                ("SELECT id, name, description, status, created_at, closed_at FROM milestones ORDER BY id DESC", vec![])
43            } else {
44                ("SELECT id, name, description, status, created_at, closed_at FROM milestones WHERE status = ?1 ORDER BY id DESC",
45                 vec![Box::new(s.to_string())])
46            }
47        } else {
48            ("SELECT id, name, description, status, created_at, closed_at FROM milestones WHERE status = ?1 ORDER BY id DESC",
49             vec![Box::new("open".to_string())])
50        };
51
52        let params_refs: Vec<&dyn rusqlite::ToSql> =
53            params_vec.iter().map(|p| p.as_ref()).collect();
54        let mut stmt = self.conn.prepare(sql)?;
55        let milestones = stmt
56            .query_map(params_refs.as_slice(), |row| {
57                Ok(Milestone {
58                    id: row.get(0)?,
59                    name: row.get(1)?,
60                    description: row.get(2)?,
61                    status: row.get(3)?,
62                    created_at: parse_datetime(row.get::<_, String>(4)?),
63                    closed_at: row.get::<_, Option<String>>(5)?.map(parse_datetime),
64                })
65            })?
66            .collect::<std::result::Result<Vec<_>, _>>()?;
67
68        Ok(milestones)
69    }
70
71    pub fn add_issue_to_milestone(&self, milestone_id: i64, issue_id: i64) -> Result<bool> {
72        let result = self.conn.execute(
73            "INSERT OR IGNORE INTO milestone_issues (milestone_id, issue_id) VALUES (?1, ?2)",
74            params![milestone_id, issue_id],
75        )?;
76        Ok(result > 0)
77    }
78
79    pub fn remove_issue_from_milestone(&self, milestone_id: i64, issue_id: i64) -> Result<bool> {
80        let rows = self.conn.execute(
81            "DELETE FROM milestone_issues WHERE milestone_id = ?1 AND issue_id = ?2",
82            params![milestone_id, issue_id],
83        )?;
84        Ok(rows > 0)
85    }
86
87    pub fn get_milestone_issues(&self, milestone_id: i64) -> Result<Vec<Issue>> {
88        let mut stmt = self.conn.prepare(
89            r#"
90            SELECT i.id, i.title, i.description, i.status, i.priority, i.parent_id, i.created_at, i.updated_at, i.closed_at
91            FROM issues i
92            JOIN milestone_issues mi ON i.id = mi.issue_id
93            WHERE mi.milestone_id = ?1
94            ORDER BY i.id
95            "#,
96        )?;
97
98        let issues = stmt
99            .query_map([milestone_id], issue_from_row)?
100            .collect::<std::result::Result<Vec<_>, _>>()?;
101
102        Ok(issues)
103    }
104
105    pub fn close_milestone(&self, id: i64) -> Result<bool> {
106        let now = Utc::now().to_rfc3339();
107        let rows = self.conn.execute(
108            "UPDATE milestones SET status = 'closed', closed_at = ?1 WHERE id = ?2",
109            params![now, id],
110        )?;
111        Ok(rows > 0)
112    }
113
114    pub fn delete_milestone(&self, id: i64) -> Result<bool> {
115        let rows = self
116            .conn
117            .execute("DELETE FROM milestones WHERE id = ?1", [id])?;
118        Ok(rows > 0)
119    }
120
121    pub fn get_issue_milestone(&self, issue_id: i64) -> Result<Option<Milestone>> {
122        let mut stmt = self.conn.prepare(
123            r#"
124            SELECT m.id, m.name, m.description, m.status, m.created_at, m.closed_at
125            FROM milestones m
126            JOIN milestone_issues mi ON m.id = mi.milestone_id
127            WHERE mi.issue_id = ?1
128            LIMIT 1
129            "#,
130        )?;
131
132        let milestone = stmt
133            .query_row([issue_id], |row| {
134                Ok(Milestone {
135                    id: row.get(0)?,
136                    name: row.get(1)?,
137                    description: row.get(2)?,
138                    status: row.get(3)?,
139                    created_at: parse_datetime(row.get::<_, String>(4)?),
140                    closed_at: row.get::<_, Option<String>>(5)?.map(parse_datetime),
141                })
142            })
143            .ok();
144
145        Ok(milestone)
146    }
147}