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}