use rusqlite::params;
use uuid::Uuid;
use super::{
EnvId, SecretId, Vault,
error::{DbError, map_rusqlite_error, not_found_or},
};
#[derive(Debug, Clone)]
pub struct SecretRecord {
pub id: SecretId,
pub environment_id: EnvId,
pub key: String,
pub value_encrypted: Vec<u8>,
pub value_nonce: Vec<u8>,
pub created_at: i64,
pub updated_at: i64,
}
impl Vault {
pub fn upsert_secret(
&self,
env_id: &EnvId,
key: &str,
value_encrypted: &[u8],
value_nonce: &[u8],
) -> Result<SecretId, DbError> {
if value_nonce.len() != 12 {
return Err(DbError::ConstraintViolation(
"nonce must be exactly 12 bytes".into(),
));
}
let id = Uuid::new_v4().to_string();
self.conn
.execute(
"INSERT OR REPLACE INTO secrets
(id, environment_id, key, value_encrypted, value_nonce,
created_at, updated_at)
VALUES (?1, ?2, ?3, ?4, ?5, strftime('%s','now'), strftime('%s','now'))",
params![id, env_id.as_str(), key, value_encrypted, value_nonce],
)
.map_err(map_rusqlite_error)?;
Ok(SecretId(id))
}
pub fn get_secret(&self, env_id: &EnvId, key: &str) -> Result<SecretRecord, DbError> {
self.conn
.query_row(
"SELECT id, environment_id, key, value_encrypted, value_nonce,
created_at, updated_at
FROM secrets
WHERE environment_id = ?1 AND key = ?2",
params![env_id.as_str(), key],
row_to_secret,
)
.map_err(not_found_or)
}
pub fn list_secrets(&self, env_id: &EnvId) -> Result<Vec<SecretRecord>, DbError> {
let mut stmt = self
.conn
.prepare(
"SELECT id, environment_id, key, value_encrypted, value_nonce,
created_at, updated_at
FROM secrets
WHERE environment_id = ?1
ORDER BY key ASC",
)
.map_err(map_rusqlite_error)?;
let rows = stmt
.query_map(params![env_id.as_str()], row_to_secret)
.map_err(map_rusqlite_error)?;
rows.map(|r| r.map_err(map_rusqlite_error))
.collect::<Result<Vec<SecretRecord>, DbError>>()
}
pub fn delete_secret(&self, env_id: &EnvId, key: &str) -> Result<(), DbError> {
let changed = self
.conn
.execute(
"DELETE FROM secrets WHERE environment_id = ?1 AND key = ?2",
params![env_id.as_str(), key],
)
.map_err(map_rusqlite_error)?;
if changed == 0 {
return Err(DbError::NotFound);
}
Ok(())
}
}
fn row_to_secret(row: &rusqlite::Row<'_>) -> rusqlite::Result<SecretRecord> {
Ok(SecretRecord {
id: SecretId(row.get(0)?),
environment_id: EnvId(row.get(1)?),
key: row.get(2)?,
value_encrypted: row.get(3)?,
value_nonce: row.get(4)?,
created_at: row.get(5)?,
updated_at: row.get(6)?,
})
}