kaizen/core_loop/
review.rs1use crate::core_loop::{ReviewItem, ReviewStatus};
3use crate::store::Store;
4use anyhow::{Result, anyhow};
5use rusqlite::{OptionalExtension, params};
6
7pub fn create(
8 store: &Store,
9 source_key: &str,
10 session_id: &str,
11 title: &str,
12 now_ms: u64,
13) -> Result<ReviewItem> {
14 let id = uuid::Uuid::now_v7().to_string();
15 store.conn().execute(
16 "INSERT OR IGNORE INTO review_items
17 (id, source_key, session_id, title, status, created_at_ms, resolved_at_ms)
18 VALUES (?1, ?2, ?3, ?4, 'open', ?5, NULL)",
19 params![id, source_key, session_id, title, now_ms as i64],
20 )?;
21 by_source(store, source_key)
22}
23
24pub fn list(store: &Store, status: Option<ReviewStatus>) -> Result<Vec<ReviewItem>> {
25 let sql = "SELECT id, source_key, session_id, title, status, created_at_ms, resolved_at_ms FROM review_items WHERE (?1 IS NULL OR status = ?1) ORDER BY created_at_ms DESC";
26 let mut stmt = store.conn().prepare(sql)?;
27 let rows = stmt.query_map(params![status.map(|s| s.as_str().to_string())], row)?;
28 rows.map(|r| r.map_err(anyhow::Error::from)).collect()
29}
30
31pub fn get(store: &Store, id: &str) -> Result<ReviewItem> {
32 let sql = "SELECT id, source_key, session_id, title, status, created_at_ms, resolved_at_ms FROM review_items WHERE id = ?1";
33 store
34 .conn()
35 .query_row(sql, params![id], row)
36 .optional()?
37 .ok_or_else(|| anyhow!("review not found: {id}"))
38}
39
40pub fn set_status(store: &Store, id: &str, status: ReviewStatus, now_ms: u64) -> Result<()> {
41 store.conn().execute(
42 "UPDATE review_items SET status = ?2, resolved_at_ms = ?3 WHERE id = ?1",
43 params![id, status.as_str(), now_ms as i64],
44 )?;
45 Ok(())
46}
47
48fn by_source(store: &Store, source_key: &str) -> Result<ReviewItem> {
49 let sql = "SELECT id, source_key, session_id, title, status, created_at_ms, resolved_at_ms FROM review_items WHERE source_key = ?1";
50 store
51 .conn()
52 .query_row(sql, params![source_key], row)
53 .map_err(Into::into)
54}
55
56fn row(r: &rusqlite::Row<'_>) -> rusqlite::Result<ReviewItem> {
57 Ok(ReviewItem {
58 id: r.get(0)?,
59 source_key: r.get(1)?,
60 session_id: r.get(2)?,
61 title: r.get(3)?,
62 status: status(r.get::<_, String>(4)?.as_str()),
63 created_at_ms: r.get::<_, i64>(5)? as u64,
64 resolved_at_ms: r.get::<_, Option<i64>>(6)?.map(|v| v as u64),
65 })
66}
67
68fn status(s: &str) -> ReviewStatus {
69 match s {
70 "resolved" => ReviewStatus::Resolved,
71 "dismissed" => ReviewStatus::Dismissed,
72 _ => ReviewStatus::Open,
73 }
74}