mod audit;
mod environments;
mod error;
mod projects;
mod schema;
mod secrets;
mod sync_markers;
pub use audit::AuditLogRecord;
pub use environments::Environment;
pub use error::DbError;
use error::map_rusqlite_error;
pub use projects::Project;
pub use secrets::SecretRecord;
pub use sync_markers::EnvironmentStatus;
use std::path::Path;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProjectId(pub String);
impl ProjectId {
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnvId(pub String);
impl EnvId {
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SecretId(pub String);
impl SecretId {
pub fn as_str(&self) -> &str {
&self.0
}
}
pub struct Vault {
conn: rusqlite::Connection,
}
impl std::fmt::Debug for Vault {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Vault").finish_non_exhaustive()
}
}
impl Vault {
pub fn open(vault_path: &Path, master_key: &[u8]) -> Result<Self, DbError> {
let conn =
rusqlite::Connection::open(vault_path).map_err(|e| DbError::IoError(e.to_string()))?;
let hex_key = bytes_to_hex(master_key);
conn.execute_batch(&format!("PRAGMA key = \"x'{}'\"", hex_key))
.map_err(|e| DbError::Internal(format!("failed to set vault key: {e}")))?;
conn.pragma_update(None, "foreign_keys", 1i64)
.map_err(map_rusqlite_error)?;
conn.execute_batch("PRAGMA journal_mode = WAL;")
.map_err(map_rusqlite_error)?;
schema::run_migrations(&conn)?;
Ok(Self { conn })
}
pub fn close(self) -> Result<(), DbError> {
self.conn
.execute_batch("PRAGMA wal_checkpoint(TRUNCATE);")
.map_err(|e| DbError::Internal(e.to_string()))?;
Ok(())
}
pub fn pragma_int(&self, name: &str) -> Result<i64, DbError> {
self.conn
.pragma_query_value(None, name, |row| row.get::<_, i64>(0))
.map_err(map_rusqlite_error)
}
pub fn pragma_str(&self, name: &str) -> Result<String, DbError> {
self.conn
.pragma_query_value(None, name, |row| row.get::<_, String>(0))
.map_err(map_rusqlite_error)
}
pub fn table_exists(&self, table_name: &str) -> Result<bool, DbError> {
let count: i64 = self
.conn
.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = ?1",
rusqlite::params![table_name],
|row| row.get(0),
)
.map_err(map_rusqlite_error)?;
Ok(count > 0)
}
}
fn bytes_to_hex(bytes: &[u8]) -> String {
use std::fmt::Write as _;
let mut hex = String::with_capacity(bytes.len() * 2);
for b in bytes {
write!(hex, "{:02x}", b).expect("fmt::Write for String is infallible");
}
hex
}