feat(cli/org): org rm/restore/purge trash lifecycle (collection-scoped)

This commit is contained in:
adlee-was-taken
2026-06-20 14:39:18 -04:00
parent 057a7defe5
commit 6123d8b033
4 changed files with 350 additions and 4 deletions

View File

@@ -1044,6 +1044,94 @@ pub fn run_edit(
Ok(())
}
/// Resolve a query to (collection, item) with grant enforcement. Used by the
/// trash-lifecycle commands.
fn open_org_item(
vault: &crate::org_session::UnlockedOrgVault,
caller: &relicario_core::OrgMember,
query: &str,
) -> Result<(String, relicario_core::Item)> {
let manifest = vault.load_manifest()?;
let visible = manifest.filter_for_member(caller);
let entry = resolve_org_query(&visible, query)?;
let collection = entry.collection.clone();
let id = entry.id.clone();
crate::org_session::UnlockedOrgVault::ensure_grant(caller, &collection)?;
let item = vault.load_item(&collection, &id)?;
Ok((collection, item))
}
pub fn run_rm(dir: &Path, query: &str) -> Result<()> {
let vault = crate::org_session::open_org_vault(Some(dir))?;
let caller = vault.current_member()?;
let (collection, mut item) = open_org_item(&vault, &caller, query)?;
item.soft_delete();
let item_rel = vault.save_item(&collection, &item)?;
let mut manifest = vault.load_manifest()?;
upsert_org_entry(&mut manifest, &item, &collection);
vault.save_manifest(&manifest)?;
let commit_msg = format!(
"org trash: {} ({})\n\nRelicario-Actor: {} {}\nRelicario-Action: item-delete\nRelicario-Collection: {}\nRelicario-Item: {}",
crate::helpers::sanitize_for_commit(&item.title), item.id.as_str(),
caller.display_name, caller.member_id.as_str(), collection, item.id.as_str()
);
crate::org_session::org_git_run(&vault.root, &["add", &item_rel, "manifest.enc"], "org rm: git add")?;
crate::org_session::org_git_run(&vault.root, &["commit", "-m", &commit_msg], "org rm: git commit")?;
println!("Moved to trash: {}", item.title);
Ok(())
}
pub fn run_restore(dir: &Path, query: &str) -> Result<()> {
let vault = crate::org_session::open_org_vault(Some(dir))?;
let caller = vault.current_member()?;
let (collection, mut item) = open_org_item(&vault, &caller, query)?;
item.restore();
let item_rel = vault.save_item(&collection, &item)?;
let mut manifest = vault.load_manifest()?;
upsert_org_entry(&mut manifest, &item, &collection);
vault.save_manifest(&manifest)?;
let commit_msg = format!(
"org restore: {} ({})\n\nRelicario-Actor: {} {}\nRelicario-Action: item-restore\nRelicario-Collection: {}\nRelicario-Item: {}",
crate::helpers::sanitize_for_commit(&item.title), item.id.as_str(),
caller.display_name, caller.member_id.as_str(), collection, item.id.as_str()
);
crate::org_session::org_git_run(&vault.root, &["add", &item_rel, "manifest.enc"], "org restore: git add")?;
crate::org_session::org_git_run(&vault.root, &["commit", "-m", &commit_msg], "org restore: git commit")?;
println!("Restored: {}", item.title);
Ok(())
}
pub fn run_purge(dir: &Path, query: &str) -> Result<()> {
let vault = crate::org_session::open_org_vault(Some(dir))?;
let caller = vault.current_member()?;
let (collection, item) = open_org_item(&vault, &caller, query)?;
let title = item.title.clone();
let id = item.id.clone();
// Remove the blob from disk, drop the manifest entry, stage with git rm.
vault.remove_item(&collection, &id)?;
let mut manifest = vault.load_manifest()?;
manifest.entries.retain(|e| e.id != id);
vault.save_manifest(&manifest)?;
let item_rel = format!("items/{}/{}.enc", collection, id.as_str());
crate::helpers::git_rm(&vault.root, &[item_rel], "org purge: git rm")?;
crate::org_session::org_git_run(&vault.root, &["add", "manifest.enc"], "org purge: git add manifest")?;
let commit_msg = format!(
"org purge: {} ({})\n\nRelicario-Actor: {} {}\nRelicario-Action: item-purge\nRelicario-Collection: {}\nRelicario-Item: {}",
crate::helpers::sanitize_for_commit(&title), id.as_str(),
caller.display_name, caller.member_id.as_str(), collection, id.as_str()
);
crate::org_session::org_git_run(&vault.root, &["commit", "-m", &commit_msg], "org purge: git commit")?;
println!("Purged: {title}");
Ok(())
}
/// Insert-or-replace an `OrgManifestEntry` mirroring the personal-vault
/// `Manifest::upsert`. Keyed by item id.
fn upsert_org_entry(