use std::ops::ControlFlow;
use auths_id::keri::Event;
use auths_id::keri::types::Prefix;
use auths_verifier::types::IdentityDID;
use serde::{Deserialize, Serialize};
use crate::context::AuthsContext;
use crate::domains::org::audit::list_offboarding_records;
use crate::domains::org::delegation::list_members;
use crate::domains::org::error::OrgError;
use crate::domains::org::offboarding::SignedOffboardingRecord;
pub const AIR_GAPPED_ORG_BUNDLE_SCHEMA_VERSION: u32 = 1;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BundledKel {
pub prefix: Prefix,
pub events: Vec<Event>,
pub attachments: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AirGappedOrgBundle {
pub schema_version: u32,
pub org_did: IdentityDID,
pub org_prefix: Prefix,
pub built_at_org_seq: u128,
pub built_at: String,
pub org_kel: BundledKel,
pub member_kels: Vec<BundledKel>,
pub offboarding_records: Vec<SignedOffboardingRecord>,
pub pinned_roots: Vec<IdentityDID>,
}
impl AirGappedOrgBundle {
pub fn to_canonical_json(&self) -> Result<String, OrgError> {
json_canon::to_string(self)
.map_err(|e| OrgError::Signing(format!("canonicalize org bundle: {e}")))
}
pub fn from_json(json: &str) -> Result<Self, OrgError> {
serde_json::from_str(json).map_err(|e| OrgError::Signing(format!("parse org bundle: {e}")))
}
}
fn collect_bundled_kel(ctx: &AuthsContext, prefix: &Prefix) -> Result<BundledKel, OrgError> {
let mut events = Vec::new();
ctx.registry
.visit_events(prefix, 0, &mut |event| {
events.push(event.clone());
ControlFlow::Continue(())
})
.map_err(OrgError::Storage)?;
let mut attachments = Vec::with_capacity(events.len());
for event in &events {
let seq = event.sequence().value();
let attachment = ctx
.registry
.get_attachment(prefix, seq)
.map_err(OrgError::Storage)?;
attachments.push(attachment.map(hex::encode).unwrap_or_default());
}
Ok(BundledKel {
prefix: prefix.clone(),
events,
attachments,
})
}
pub fn build_org_bundle(
ctx: &AuthsContext,
org_prefix: &Prefix,
) -> Result<AirGappedOrgBundle, OrgError> {
let org_did = IdentityDID::from_prefix(org_prefix.as_str())
.map_err(|e| OrgError::InvalidDid(e.to_string()))?;
let org_kel = collect_bundled_kel(ctx, org_prefix)?;
let built_at_org_seq = org_kel
.events
.last()
.map(|e| e.sequence().value())
.unwrap_or(0);
let mut member_kels = Vec::new();
for member in list_members(ctx, org_prefix)? {
let member_prefix = Prefix::new_unchecked(member.member_prefix.clone());
member_kels.push(collect_bundled_kel(ctx, &member_prefix)?);
}
let offboarding_records = list_offboarding_records(ctx, org_prefix)?;
Ok(AirGappedOrgBundle {
schema_version: AIR_GAPPED_ORG_BUNDLE_SCHEMA_VERSION,
org_did: org_did.clone(),
org_prefix: org_prefix.clone(),
built_at_org_seq,
built_at: ctx.clock.now().to_rfc3339(),
org_kel,
member_kels,
offboarding_records,
pinned_roots: vec![org_did],
})
}