1use rusqlite::params;
2
3use crate::error::{Error, Result};
4use crate::store::Store;
5use crate::types::Session;
6
7impl Store {
8 pub fn session_start(&self, project: &str, directory: Option<&str>) -> Result<Session> {
9 let existing: Option<Session> = self
11 .conn()
12 .query_row(
13 "SELECT id, project, directory, started_at, ended_at, summary, status
14 FROM sessions WHERE project = ?1 AND directory IS ?2 AND status = 'active'
15 ORDER BY started_at DESC LIMIT 1",
16 params![project, directory],
17 |row| {
18 Ok(Session {
19 id: row.get(0)?,
20 project: row.get(1)?,
21 directory: row.get(2)?,
22 started_at: row.get(3)?,
23 ended_at: row.get(4)?,
24 summary: row.get(5)?,
25 status: row.get(6)?,
26 })
27 },
28 )
29 .ok();
30
31 if let Some(session) = existing {
32 return Ok(session);
33 }
34
35 let completed: Option<String> = self
37 .conn()
38 .query_row(
39 "SELECT id FROM sessions
40 WHERE project = ?1 AND directory IS ?2 AND status = 'completed'
41 ORDER BY started_at DESC LIMIT 1",
42 params![project, directory],
43 |row| row.get(0),
44 )
45 .ok();
46
47 if let Some(existing_id) = completed {
48 self.conn().execute(
49 "UPDATE sessions SET status = 'active', ended_at = NULL, summary = NULL
50 WHERE id = ?1",
51 params![existing_id],
52 )?;
53 return self
54 .conn()
55 .query_row(
56 "SELECT id, project, directory, started_at, ended_at, summary, status
57 FROM sessions WHERE id = ?1",
58 params![existing_id],
59 |row| {
60 Ok(Session {
61 id: row.get(0)?,
62 project: row.get(1)?,
63 directory: row.get(2)?,
64 started_at: row.get(3)?,
65 ended_at: row.get(4)?,
66 summary: row.get(5)?,
67 status: row.get(6)?,
68 })
69 },
70 )
71 .map_err(Error::Database);
72 }
73
74 let id = generate_session_id();
75 self.conn().execute(
76 "INSERT INTO sessions (id, project, directory) VALUES (?1, ?2, ?3)",
77 params![id, project, directory],
78 )?;
79
80 self.conn()
81 .query_row(
82 "SELECT id, project, directory, started_at, ended_at, summary, status
83 FROM sessions WHERE id = ?1",
84 params![id],
85 |row| {
86 Ok(Session {
87 id: row.get(0)?,
88 project: row.get(1)?,
89 directory: row.get(2)?,
90 started_at: row.get(3)?,
91 ended_at: row.get(4)?,
92 summary: row.get(5)?,
93 status: row.get(6)?,
94 })
95 },
96 )
97 .map_err(Error::Database)
98 }
99
100 pub fn session_end(&self, session_id: &str, summary: Option<&str>) -> Result<Session> {
101 let session = self
102 .conn()
103 .query_row(
104 "SELECT id, status FROM sessions WHERE id = ?1",
105 params![session_id],
106 |row| Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?)),
107 )
108 .map_err(|_| Error::SessionNotFound(session_id.to_string()))?;
109
110 if session.1 != "active" {
111 return Err(Error::SessionAlreadyEnded(session_id.to_string()));
112 }
113
114 self.conn().execute(
115 "UPDATE sessions SET ended_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
116 summary = ?1, status = 'completed'
117 WHERE id = ?2",
118 params![summary, session_id],
119 )?;
120
121 self.conn()
122 .query_row(
123 "SELECT id, project, directory, started_at, ended_at, summary, status
124 FROM sessions WHERE id = ?1",
125 params![session_id],
126 |row| {
127 Ok(Session {
128 id: row.get(0)?,
129 project: row.get(1)?,
130 directory: row.get(2)?,
131 started_at: row.get(3)?,
132 ended_at: row.get(4)?,
133 summary: row.get(5)?,
134 status: row.get(6)?,
135 })
136 },
137 )
138 .map_err(Error::Database)
139 }
140
141 pub fn recent_sessions(&self, limit: i32) -> Result<Vec<Session>> {
142 let mut stmt = self.conn().prepare(
143 "SELECT id, project, directory, started_at, ended_at, summary, status
144 FROM sessions ORDER BY started_at DESC LIMIT ?1",
145 )?;
146
147 let results = stmt
148 .query_map(params![limit], |row| {
149 Ok(Session {
150 id: row.get(0)?,
151 project: row.get(1)?,
152 directory: row.get(2)?,
153 started_at: row.get(3)?,
154 ended_at: row.get(4)?,
155 summary: row.get(5)?,
156 status: row.get(6)?,
157 })
158 })?
159 .collect::<std::result::Result<Vec<_>, _>>()?;
160
161 Ok(results)
162 }
163
164 pub fn end_active_sessions(
167 &self,
168 project: Option<&str>,
169 summary: Option<&str>,
170 ) -> Result<usize> {
171 let active = self.recent_sessions(10000)?;
172 let mut ended = 0;
173 for session in &active {
174 if session.status != "active" {
175 continue;
176 }
177 if let Some(proj) = project {
178 if session.project != proj {
179 continue;
180 }
181 }
182 self.session_end(&session.id, summary)?;
183 ended += 1;
184 }
185 Ok(ended)
186 }
187}
188
189fn generate_session_id() -> String {
190 use std::time::{SystemTime, UNIX_EPOCH};
191 let ts = SystemTime::now()
192 .duration_since(UNIX_EPOCH)
193 .unwrap()
194 .as_nanos();
195 format!("ses_{ts:x}")
196}