pub use vta_sdk::contexts::ContextRecord;
use chrono::Utc;
use crate::error::AppError;
use crate::store::KeyspaceHandle;
fn ctx_key(id: &str) -> String {
format!("ctx:{id}")
}
pub async fn get_context(ks: &KeyspaceHandle, id: &str) -> Result<Option<ContextRecord>, AppError> {
ks.get(ctx_key(id)).await
}
pub async fn store_context(ks: &KeyspaceHandle, record: &ContextRecord) -> Result<(), AppError> {
ks.insert(ctx_key(&record.id), record).await
}
pub async fn delete_context(ks: &KeyspaceHandle, id: &str) -> Result<(), AppError> {
ks.remove(ctx_key(id)).await
}
pub async fn list_contexts(ks: &KeyspaceHandle) -> Result<Vec<ContextRecord>, AppError> {
let raw = ks.prefix_iter_raw("ctx:").await?;
let mut records = Vec::with_capacity(raw.len());
for (_key, value) in raw {
let record: ContextRecord = serde_json::from_slice(&value)?;
records.push(record);
}
Ok(records)
}
pub async fn allocate_context_index(ks: &KeyspaceHandle) -> Result<(u32, String), AppError> {
let counter_key = "ctx_counter";
let current: u32 = match ks.get_raw(counter_key).await? {
Some(bytes) => {
let arr: [u8; 4] = bytes
.try_into()
.map_err(|_| AppError::Internal("corrupt context counter".into()))?;
u32::from_le_bytes(arr)
}
None => 0,
};
let base_path = format!("{CONTEXT_KEY_BASE}/{current}'");
ks.insert_raw(counter_key, (current + 1).to_le_bytes().to_vec())
.await?;
Ok((current, base_path))
}
pub async fn create_context(
contexts_ks: &KeyspaceHandle,
id: &str,
name: &str,
) -> Result<ContextRecord, Box<dyn std::error::Error>> {
let (index, base_path) = allocate_context_index(contexts_ks)
.await
.map_err(|e| format!("{e}"))?;
let now = Utc::now();
let record = ContextRecord {
id: id.to_string(),
name: name.to_string(),
did: None,
description: None,
base_path,
index,
created_at: now,
updated_at: now,
};
store_context(contexts_ks, &record)
.await
.map_err(|e| format!("{e}"))?;
Ok(record)
}
pub const CONTEXT_KEY_BASE: &str = "m/26'/2'";