dx_forge/context/
annotations.rs1use anyhow::Result;
2use chrono::{DateTime, Utc};
3use rusqlite::params;
4use serde::{Deserialize, Serialize};
5use std::path::Path;
6use uuid::Uuid;
7
8use crate::storage::Database;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct Annotation {
12 pub id: Uuid,
13 pub file_path: String,
14 pub anchor_id: Option<Uuid>,
15 pub line: usize,
16 pub content: String,
17 pub author: String,
18 pub created_at: DateTime<Utc>,
19 pub is_ai: bool,
20}
21
22impl Annotation {
23 pub fn new(file_path: String, line: usize, content: String, is_ai: bool) -> Self {
24 let author = if is_ai {
25 "AI Agent".to_string()
26 } else {
27 whoami::username()
28 };
29
30 Self {
31 id: Uuid::new_v4(),
32 file_path,
33 anchor_id: None,
34 line,
35 content,
36 author,
37 created_at: Utc::now(),
38 is_ai,
39 }
40 }
41}
42
43pub fn store_annotation(db: &Database, annotation: &Annotation) -> Result<()> {
44 let conn = db.conn.lock();
45
46 conn.execute(
47 "INSERT INTO annotations (id, file_path, anchor_id, line, content, author, created_at, is_ai)
48 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
49 params![
50 annotation.id.to_string(),
51 annotation.file_path,
52 annotation.anchor_id.map(|id| id.to_string()),
53 annotation.line as i64,
54 annotation.content,
55 annotation.author,
56 annotation.created_at.to_rfc3339(),
57 annotation.is_ai,
58 ],
59 )?;
60
61 Ok(())
62}
63
64pub fn get_annotations(db: &Database, file: &Path, line: Option<usize>) -> Result<Vec<Annotation>> {
65 let conn = db.conn.lock();
66
67 let query = if let Some(l) = line {
68 format!(
69 "SELECT id, file_path, anchor_id, line, content, author, created_at, is_ai
70 FROM annotations
71 WHERE file_path = '{}' AND line = {}
72 ORDER BY created_at DESC",
73 file.display(),
74 l
75 )
76 } else {
77 format!(
78 "SELECT id, file_path, anchor_id, line, content, author, created_at, is_ai
79 FROM annotations
80 WHERE file_path = '{}'
81 ORDER BY created_at DESC",
82 file.display()
83 )
84 };
85
86 let mut stmt = conn.prepare(&query)?;
87 let annotations = stmt.query_map([], |row| {
88 let id: String = row.get(0)?;
89 let file_path: String = row.get(1)?;
90 let anchor_id: Option<String> = row.get(2)?;
91 let line: i64 = row.get(3)?;
92 let content: String = row.get(4)?;
93 let author: String = row.get(5)?;
94 let created_at: String = row.get(6)?;
95 let is_ai: bool = row.get(7)?;
96
97 Ok(Annotation {
98 id: Uuid::parse_str(&id).unwrap(),
99 file_path,
100 anchor_id: anchor_id.as_ref().and_then(|s| Uuid::parse_str(s).ok()),
101 line: line as usize,
102 content,
103 author,
104 created_at: chrono::DateTime::parse_from_rfc3339(&created_at)
105 .unwrap()
106 .into(),
107 is_ai,
108 })
109 })?;
110
111 Ok(annotations.collect::<Result<Vec<_>, _>>()?)
112}