Rust backend: SQLite (WAL mode, 8 tables), vault encryption (Argon2id + AES-256-GCM), settings/connections/credentials services, 19 Tauri command wrappers. 46/46 tests passing. Vue 3 frontend: unlock/create vault flow, Pinia app store, Tailwind CSS v4 dark theme with Wraith branding. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
50 lines
1.6 KiB
Rust
50 lines
1.6 KiB
Rust
use rusqlite::Connection;
|
|
use std::path::Path;
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
/// Cheap-to-clone handle to a single SQLite connection protected by a mutex.
|
|
///
|
|
/// Using a single shared connection (rather than a pool) is correct for
|
|
/// desktop use: SQLite's WAL mode allows concurrent reads from the OS level,
|
|
/// and the mutex ensures we never issue overlapping writes from Tauri's
|
|
/// async command threads.
|
|
#[derive(Clone)]
|
|
pub struct Database {
|
|
conn: Arc<Mutex<Connection>>,
|
|
}
|
|
|
|
impl Database {
|
|
/// Open (or create) the SQLite database at `path` and apply PRAGMAs.
|
|
pub fn open(path: &Path) -> Result<Self, Box<dyn std::error::Error>> {
|
|
let conn = Connection::open(path)?;
|
|
|
|
conn.execute_batch(
|
|
"PRAGMA journal_mode=WAL;
|
|
PRAGMA busy_timeout=5000;
|
|
PRAGMA foreign_keys=ON;",
|
|
)?;
|
|
|
|
Ok(Self {
|
|
conn: Arc::new(Mutex::new(conn)),
|
|
})
|
|
}
|
|
|
|
/// Acquire a lock on the underlying connection.
|
|
///
|
|
/// Panics if the mutex was poisoned (which only happens if a thread
|
|
/// panicked while holding the lock — a non-recoverable situation anyway).
|
|
pub fn conn(&self) -> std::sync::MutexGuard<'_, Connection> {
|
|
self.conn.lock().unwrap()
|
|
}
|
|
|
|
/// Run all embedded SQL migrations.
|
|
///
|
|
/// The migration file is compiled into the binary via `include_str!`,
|
|
/// so there is no runtime file-system dependency on it.
|
|
pub fn migrate(&self) -> Result<(), Box<dyn std::error::Error>> {
|
|
let conn = self.conn();
|
|
conn.execute_batch(include_str!("migrations/001_initial.sql"))?;
|
|
Ok(())
|
|
}
|
|
}
|