use crate::error::StorageError;
use crate::storage::attestation::AttestationSource;
use crate::storage::layout::StorageLayoutConfig;
use auths_index::{AttestationIndex, IndexedAttestation, rebuild_attestations_from_git};
use auths_verifier::core::{Attestation, CommitOid};
use auths_verifier::types::{CanonicalDid, IdentityDID};
use chrono::{DateTime, Utc};
use std::path::{Path, PathBuf};
pub struct IndexedAttestationStorage {
inner: Box<dyn AttestationSource>,
index: AttestationIndex,
repo_path: PathBuf,
config: StorageLayoutConfig,
}
impl IndexedAttestationStorage {
pub fn open(
inner: Box<dyn AttestationSource>,
repo_path: impl AsRef<Path>,
config: StorageLayoutConfig,
) -> Result<Self, StorageError> {
let repo_path = repo_path.as_ref().to_path_buf();
let index_path = repo_path.join(".auths-index.db");
let index = AttestationIndex::open_or_create(&index_path)
.map_err(|e| StorageError::Index(e.to_string()))?;
if index.count().unwrap_or(0) == 0 {
log::info!("Index is empty, rebuilding from Git refs...");
rebuild_attestations_from_git(
&index,
&repo_path,
&config.device_attestation_prefix,
&config.attestation_blob_name,
)
.map_err(|e| StorageError::Index(e.to_string()))?;
}
Ok(Self {
inner,
index,
repo_path,
config,
})
}
pub fn index(&self) -> &AttestationIndex {
&self.index
}
pub fn rebuild_index(&self) -> Result<(), StorageError> {
rebuild_attestations_from_git(
&self.index,
&self.repo_path,
&self.config.device_attestation_prefix,
&self.config.attestation_blob_name,
)
.map_err(|e| StorageError::Index(e.to_string()))?;
Ok(())
}
pub fn update_index(
&self,
att: &Attestation,
git_ref: &str,
commit_oid: &str,
now: DateTime<Utc>,
) -> Result<(), StorageError> {
let indexed = IndexedAttestation {
rid: att.rid.clone(),
#[allow(clippy::disallowed_methods)] issuer_did: IdentityDID::new_unchecked(att.issuer.as_str()),
device_did: att.subject.clone(),
git_ref: git_ref.to_string(),
commit_oid: CommitOid::parse(commit_oid).ok(),
revoked_at: att.revoked_at,
expires_at: att.expires_at,
updated_at: att.timestamp.unwrap_or(now),
};
self.index
.upsert_attestation(&indexed)
.map_err(|e| StorageError::Index(e.to_string()))
}
pub fn load_attestations_indexed(
&self,
device_did: &CanonicalDid,
) -> Result<Vec<Attestation>, StorageError> {
let indexed = self
.index
.query_by_device(device_did.as_str())
.map_err(|e| StorageError::Index(e.to_string()))?;
if indexed.is_empty() {
log::debug!(
"No index entries for device {}, falling back to Git",
device_did
);
return self.inner.load_attestations_for_device(device_did);
}
self.inner.load_attestations_for_device(device_did)
}
}
impl AttestationSource for IndexedAttestationStorage {
fn load_attestations_for_device(
&self,
device_did: &CanonicalDid,
) -> Result<Vec<Attestation>, StorageError> {
self.load_attestations_indexed(device_did)
}
fn load_all_attestations(&self) -> Result<Vec<Attestation>, StorageError> {
self.inner.load_all_attestations()
}
fn discover_device_dids(&self) -> Result<Vec<CanonicalDid>, StorageError> {
let active = self
.index
.query_active()
.map_err(|e| StorageError::Index(e.to_string()))?;
if active.is_empty() {
return self.inner.discover_device_dids();
}
let mut dids: Vec<CanonicalDid> = active
.into_iter()
.filter_map(|a| CanonicalDid::parse(a.device_did.as_str()).ok())
.collect();
dids.sort_by(|a, b| a.as_str().cmp(b.as_str()));
dids.dedup();
Ok(dids)
}
}
#[cfg(test)]
mod tests {
}