//! Binary blob storage in the message-log `SQLite` database (e.g. images, //! attachments), keyed by session id and an arbitrary blob key. use rusqlite::{Connection, params}; use anyhow::Result; /// Insert or overwrite a blob for a session under `blob_key`. /// /// Flow: compute current timestamp → `INSERT OR REPLACE` into `blobs` /// keyed on `(session_id, blob_key)`. /// /// Return: `Ok(())` on success, or the underlying `SQLite` error. pub fn store_blob(conn: &Connection, session_id: &str, blob_key: &str, data: &[u8], mime_type: Option<&str>) -> Result<()> { let created_at = chrono::Utc::now().timestamp_millis(); conn.execute( "INSERT OR REPLACE INTO blobs (session_id, blob_key, data, mime_type, created_at) VALUES (?1, ?2, ?3, ?4, ?5)", params![session_id, blob_key, data, mime_type, created_at], )?; Ok(()) } /// Fetch a blob's bytes for a session by key. /// /// Return: `Ok(Some(data))` if found, `Ok(None)` if no matching row /// exists, `Err` for any other `SQLite` failure. pub fn retrieve_blob(conn: &Connection, session_id: &str, blob_key: &str) -> Result>> { let result = conn.query_row( "SELECT data FROM blobs WHERE session_id = ?1 AND blob_key = ?2", params![session_id, blob_key], |row| row.get(0), ); match result { Ok(data) => Ok(Some(data)), Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None), Err(e) => Err(e.into()), } } /// Delete a blob for a session by key. /// /// Return: `Ok(true)` if a row was deleted, `Ok(false)` if no matching /// row existed. #[allow(dead_code)] pub fn delete_blob(conn: &Connection, session_id: &str, blob_key: &str) -> Result { let rows = conn.execute( "DELETE FROM blobs WHERE session_id = ?1 AND blob_key = ?2", params![session_id, blob_key], )?; Ok(rows > 0) } /// List all blob keys stored for a session, oldest first. /// /// Return: `Ok(Vec)` of keys ordered by `created_at`, or the /// underlying `SQLite` error. pub fn list_blob_keys(conn: &Connection, session_id: &str) -> Result> { let mut stmt = conn.prepare( "SELECT blob_key FROM blobs WHERE session_id = ?1 ORDER BY created_at ASC" )?; let rows = stmt.query_map(params![session_id], |row| { row.get::<_, String>(0) })?; let mut keys = Vec::new(); for row in rows { keys.push(row?); } Ok(keys) }