chainlink/db/
time_entries.rs1use anyhow::Result;
2use chrono::{DateTime, Utc};
3use rusqlite::params;
4
5use super::{parse_datetime, Database};
6
7impl Database {
8 pub fn start_timer(&self, issue_id: i64) -> Result<i64> {
9 let now = Utc::now().to_rfc3339();
10 self.conn.execute(
11 "INSERT INTO time_entries (issue_id, started_at) VALUES (?1, ?2)",
12 params![issue_id, now],
13 )?;
14 Ok(self.conn.last_insert_rowid())
15 }
16
17 pub fn stop_timer(&self, issue_id: i64) -> Result<bool> {
18 let now = Utc::now();
19 let now_str = now.to_rfc3339();
20
21 let started_at: Option<String> = self
22 .conn
23 .query_row(
24 "SELECT started_at FROM time_entries WHERE issue_id = ?1 AND ended_at IS NULL",
25 [issue_id],
26 |row| row.get(0),
27 )
28 .ok();
29
30 if let Some(started) = started_at {
31 let start_dt = DateTime::parse_from_rfc3339(&started)
32 .map(|dt| dt.with_timezone(&Utc))
33 .unwrap_or(now);
34 let duration = now.signed_duration_since(start_dt).num_seconds();
35
36 let rows = self.conn.execute(
37 "UPDATE time_entries SET ended_at = ?1, duration_seconds = ?2 WHERE issue_id = ?3 AND ended_at IS NULL",
38 params![now_str, duration, issue_id],
39 )?;
40 Ok(rows > 0)
41 } else {
42 Ok(false)
43 }
44 }
45
46 pub fn get_active_timer(&self) -> Result<Option<(i64, DateTime<Utc>)>> {
47 let result: Option<(i64, String)> = self
48 .conn
49 .query_row(
50 "SELECT issue_id, started_at FROM time_entries WHERE ended_at IS NULL ORDER BY id DESC LIMIT 1",
51 [],
52 |row| Ok((row.get(0)?, row.get(1)?)),
53 )
54 .ok();
55
56 Ok(result.map(|(id, started)| (id, parse_datetime(started))))
57 }
58
59 pub fn get_total_time(&self, issue_id: i64) -> Result<i64> {
60 let total: i64 = self
61 .conn
62 .query_row(
63 "SELECT COALESCE(SUM(duration_seconds), 0) FROM time_entries WHERE issue_id = ?1 AND duration_seconds IS NOT NULL",
64 [issue_id],
65 |row| row.get(0),
66 )
67 .unwrap_or(0);
68 Ok(total)
69 }
70}