vector_core/db/
wrappers.rs1use nostr_sdk::prelude::{EventId, Timestamp};
4
5pub const TRANSPORT_NIP17: i64 = 0;
8pub const TRANSPORT_CONCORD: i64 = 1;
9
10pub fn save_processed_wrapper(wrapper_id_bytes: &[u8; 32], wrapper_created_at: u64, transport: i64) -> Result<(), String> {
13 let conn = super::get_write_connection_guard_static()?;
14 conn.execute(
15 "INSERT OR IGNORE INTO processed_wrappers (wrapper_id, wrapper_created_at, transport) VALUES (?1, ?2, ?3)",
16 rusqlite::params![&wrapper_id_bytes[..], wrapper_created_at as i64, transport],
17 ).map_err(|e| format!("Failed to save processed wrapper: {}", e))?;
18 Ok(())
19}
20
21pub fn processed_wrapper_exists(wrapper_id_bytes: &[u8; 32]) -> bool {
25 let conn = match super::get_db_connection_guard_static() {
26 Ok(c) => c,
27 Err(_) => return false,
28 };
29 conn.prepare_cached("SELECT EXISTS(SELECT 1 FROM processed_wrappers WHERE wrapper_id = ?1)")
30 .and_then(|mut stmt| stmt.query_row(rusqlite::params![&wrapper_id_bytes[..]], |row| row.get(0)))
31 .unwrap_or(false)
32}
33
34pub fn update_wrapper_timestamp(wrapper_id_bytes: &[u8; 32], wrapper_created_at: u64) -> Result<(), String> {
39 let conn = super::get_write_connection_guard_static()?;
40 conn.execute(
41 "UPDATE processed_wrappers SET wrapper_created_at = ?2 \
42 WHERE wrapper_id = ?1 AND wrapper_created_at = 0",
43 rusqlite::params![&wrapper_id_bytes[..], wrapper_created_at as i64],
44 ).map_err(|e| format!("Failed to backfill wrapper timestamp: {}", e))?;
45 Ok(())
46}
47
48pub fn load_processed_wrappers() -> Result<Vec<[u8; 32]>, String> {
50 let conn = match super::get_db_connection_guard_static() {
51 Ok(c) => c,
52 Err(_) => return Ok(Vec::new()),
53 };
54 let mut stmt = conn.prepare("SELECT wrapper_id FROM processed_wrappers WHERE transport = 0")
57 .map_err(|e| format!("Failed to prepare processed_wrappers query: {}", e))?;
58 let rows = stmt.query_map([], |row| {
59 let blob: Vec<u8> = row.get(0)?;
60 if blob.len() == 32 {
61 let mut arr = [0u8; 32];
62 arr.copy_from_slice(&blob);
63 Ok(arr)
64 } else {
65 Err(rusqlite::Error::InvalidParameterCount(blob.len(), 32))
66 }
67 }).map_err(|e| format!("Failed to query processed_wrappers: {}", e))?;
68
69 Ok(rows.flatten().collect())
70}
71
72pub fn load_processed_wrappers_since(since_secs: u64) -> Result<Vec<[u8; 32]>, String> {
76 let conn = match super::get_db_connection_guard_static() {
77 Ok(c) => c,
78 Err(_) => return Ok(Vec::new()),
79 };
80 let mut stmt = conn.prepare(
81 "SELECT wrapper_id FROM processed_wrappers WHERE transport = 0 AND wrapper_created_at >= ?1",
82 ).map_err(|e| format!("Failed to prepare processed_wrappers query: {}", e))?;
83 let rows = stmt.query_map(rusqlite::params![since_secs as i64], |row| {
84 let blob: Vec<u8> = row.get(0)?;
85 if blob.len() == 32 {
86 let mut arr = [0u8; 32];
87 arr.copy_from_slice(&blob);
88 Ok(arr)
89 } else {
90 Err(rusqlite::Error::InvalidParameterCount(blob.len(), 32))
91 }
92 }).map_err(|e| format!("Failed to query processed_wrappers: {}", e))?;
93
94 Ok(rows.flatten().collect())
95}
96
97pub fn load_recent_wrapper_ids(days: u64) -> Result<Vec<[u8; 32]>, String> {
99 let conn = match super::get_db_connection_guard_static() {
100 Ok(c) => c,
101 Err(_) => return Ok(Vec::new()),
102 };
103
104 let cutoff_secs = web_time::SystemTime::now()
105 .duration_since(web_time::UNIX_EPOCH).unwrap()
106 .as_secs()
107 .saturating_sub(days * 24 * 60 * 60);
108
109 let mut stmt = conn.prepare(
114 "SELECT e.wrapper_event_id FROM events e \
115 JOIN chats c ON e.chat_id = c.id \
116 WHERE e.wrapper_event_id IS NOT NULL AND e.wrapper_event_id != '' \
117 AND e.created_at >= ?1 AND c.chat_type != 2"
118 ).map_err(|e| format!("Failed to prepare wrapper_id query: {}", e))?;
119
120 let mut rows = stmt
124 .query(rusqlite::params![cutoff_secs as i64])
125 .map_err(|e| format!("Failed to query wrapper_ids: {}", e))?;
126
127 let mut result: Vec<[u8; 32]> = Vec::new();
128 while let Some(row) = rows
129 .next()
130 .map_err(|e| format!("Failed to read wrapper_id row: {}", e))?
131 {
132 let Ok(hex) = row.get_ref(0).and_then(|v| v.as_str().map_err(Into::into)) else {
133 continue;
134 };
135 if hex.len() == 64 {
136 result.push(crate::simd::hex::hex_to_bytes_32(hex));
137 }
138 }
139 Ok(result)
140}
141
142pub fn load_negentropy_items() -> Result<Vec<(EventId, Timestamp)>, String> {
144 load_negentropy_items_inner(None)
145}
146
147pub fn load_negentropy_items_since(
154 since_secs: u64,
155) -> Result<Vec<(EventId, Timestamp)>, String> {
156 load_negentropy_items_inner(Some(since_secs))
157}
158
159fn load_negentropy_items_inner(
160 since_secs: Option<u64>,
161) -> Result<Vec<(EventId, Timestamp)>, String> {
162 let conn = super::get_db_connection_guard_static()
163 .map_err(|_| "No DB connection".to_string())?;
164
165 let sql = if since_secs.is_some() {
174 "SELECT wrapper_id, wrapper_created_at FROM processed_wrappers \
175 WHERE transport = 0 AND wrapper_created_at >= ?1 \
176 ORDER BY wrapper_created_at, wrapper_id"
177 } else {
178 "SELECT wrapper_id, wrapper_created_at FROM processed_wrappers \
179 WHERE transport = 0 \
180 ORDER BY wrapper_created_at, wrapper_id"
181 };
182
183 let mut stmt = conn
184 .prepare(sql)
185 .map_err(|e| format!("Failed to prepare negentropy query: {}", e))?;
186
187 let mut rows = match since_secs {
190 Some(since) => stmt.query(rusqlite::params![since as i64]),
191 None => stmt.query([]),
192 }
193 .map_err(|e| format!("Failed to query processed_wrappers: {}", e))?;
194
195 let mut items: Vec<(EventId, Timestamp)> = Vec::new();
196 while let Some(row) = rows
197 .next()
198 .map_err(|e| format!("Failed to read processed_wrappers row: {}", e))?
199 {
200 let Ok(blob) = row.get_ref(0).and_then(|v| v.as_blob().map_err(Into::into)) else {
201 continue;
202 };
203 if blob.len() != 32 {
204 continue;
205 }
206 let mut arr = [0u8; 32];
207 arr.copy_from_slice(blob);
208 let Ok(created_at) = row.get::<_, i64>(1) else {
212 continue;
213 };
214 items.push((
215 EventId::from_byte_array(arr),
216 Timestamp::from_secs(created_at as u64),
217 ));
218 }
219
220 Ok(items)
221}