1pub mod agents;
18pub mod attachments;
19pub mod dashboard;
20pub mod deps;
21pub mod export;
22pub mod import;
23pub mod locks;
24pub mod schema;
25pub mod search;
26pub mod state_transitions;
27pub mod stats;
28pub mod tasks;
29pub mod template;
30
31pub use deps::AddDependencyResult;
32pub use search::{AttachmentMatch, SearchResult};
33
34use anyhow::Result;
35use rusqlite::Connection;
36use std::path::Path;
37use std::sync::{Arc, Mutex};
38
39mod embedded {
40 use refinery::embed_migrations;
41 embed_migrations!("migrations");
42}
43
44#[derive(Clone)]
46pub struct Database {
47 conn: Arc<Mutex<Connection>>,
48}
49
50impl Database {
51 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
53 let conn = Connection::open(path)?;
54
55 conn.execute_batch(
57 "PRAGMA journal_mode=WAL;
58 PRAGMA foreign_keys=ON;
59 PRAGMA busy_timeout=5000;",
60 )?;
61
62 let db = Self {
63 conn: Arc::new(Mutex::new(conn)),
64 };
65
66 db.run_migrations()?;
67
68 Ok(db)
69 }
70
71 #[allow(dead_code)]
73 pub fn open_in_memory() -> Result<Self> {
74 let conn = Connection::open_in_memory()?;
75
76 conn.execute_batch("PRAGMA foreign_keys=ON;")?;
77
78 let db = Self {
79 conn: Arc::new(Mutex::new(conn)),
80 };
81
82 db.run_migrations()?;
83
84 Ok(db)
85 }
86
87 fn run_migrations(&self) -> Result<()> {
89 let mut conn = self.conn.lock().unwrap_or_else(|e| e.into_inner());
91 embedded::migrations::runner().run(&mut *conn)?;
92 Ok(())
93 }
94
95 pub fn with_conn<F, T>(&self, f: F) -> Result<T>
100 where
101 F: FnOnce(&Connection) -> Result<T>,
102 {
103 let conn = self.conn.lock().unwrap_or_else(|e| e.into_inner());
104 f(&conn)
105 }
106
107 pub fn with_conn_mut<F, T>(&self, f: F) -> Result<T>
112 where
113 F: FnOnce(&mut Connection) -> Result<T>,
114 {
115 let mut conn = self.conn.lock().unwrap_or_else(|e| e.into_inner());
116 f(&mut conn)
117 }
118}
119
120pub fn now_ms() -> i64 {
122 chrono::Utc::now().timestamp_millis()
123}