Adds the canonical post-mutation funnel: save_manifest_raw + groups.cache refresh in one method. Converts nine commands/*.rs mutation callsites from the manual save_manifest + refresh_groups_cache pair to a single vault.after_manifest_change(&manifest)?. save_manifest renamed to save_manifest_raw (pub(crate)) so future commands cannot accidentally bypass the cache refresh. Four of the nine sites (attach.rs add/detach, import.rs LastPass, trash.rs cmd_trash_empty's per-item save) previously skipped the cache refresh — the wrapper fixes them. refresh_groups_cache moves from main.rs to helpers.rs so the read-side warmup callers in get.rs/list.rs still reach it.
104 lines
3.6 KiB
Rust
104 lines
3.6 KiB
Rust
//! `relicario list` and `relicario history` — both read-only browse paths.
|
|
|
|
use anyhow::Result;
|
|
|
|
pub fn cmd_list(
|
|
type_filter: Option<String>,
|
|
group_filter: Option<String>,
|
|
tag_filter: Option<String>,
|
|
trashed: bool,
|
|
) -> Result<()> {
|
|
use relicario_core::ItemType;
|
|
|
|
let vault = crate::session::UnlockedVault::unlock_interactive()?;
|
|
let manifest = vault.load_manifest()?;
|
|
crate::helpers::refresh_groups_cache(vault.root(), &manifest);
|
|
|
|
let parsed_type: Option<ItemType> = match type_filter.as_deref() {
|
|
None => None,
|
|
Some("login") => Some(ItemType::Login),
|
|
Some("secure_note") | Some("note") => Some(ItemType::SecureNote),
|
|
Some("identity") => Some(ItemType::Identity),
|
|
Some("card") => Some(ItemType::Card),
|
|
Some("key") => Some(ItemType::Key),
|
|
Some("document") => Some(ItemType::Document),
|
|
Some("totp") => Some(ItemType::Totp),
|
|
Some(other) => anyhow::bail!("unknown type filter: {other}"),
|
|
};
|
|
|
|
let mut entries: Vec<_> = manifest.items.values()
|
|
.filter(|e| {
|
|
if trashed { e.trashed_at.is_some() } else { e.trashed_at.is_none() }
|
|
})
|
|
.filter(|e| match parsed_type {
|
|
Some(t) => e.r#type == t,
|
|
None => true,
|
|
})
|
|
.filter(|e| group_filter.as_ref().is_none_or(|g| e.group.as_deref() == Some(g.as_str())))
|
|
.filter(|e| tag_filter.as_ref().is_none_or(|t| e.tags.iter().any(|x| x == t)))
|
|
.collect();
|
|
entries.sort_by(|a, b| a.title.to_lowercase().cmp(&b.title.to_lowercase()));
|
|
|
|
if entries.is_empty() {
|
|
eprintln!("(no items match)");
|
|
return Ok(());
|
|
}
|
|
|
|
println!("{:<16} {:<14} {:<6} TITLE", "ID", "TYPE", "FAV");
|
|
for e in entries {
|
|
let fav = if e.favorite { " *" } else { "" };
|
|
println!("{:<16} {:<14} {:<6} {}", e.id.as_str(), format!("{:?}", e.r#type), fav, e.title);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn cmd_history(query: String, show: bool, field: Option<String>) -> Result<()> {
|
|
let vault = crate::session::UnlockedVault::unlock_interactive()?;
|
|
let manifest = vault.load_manifest()?;
|
|
let entry = super::resolve_query(&manifest, &query)?;
|
|
let item = vault.load_item(&entry.id)?;
|
|
|
|
println!("History for {} ({})", item.title, item.id.as_str());
|
|
println!();
|
|
|
|
// Filter and sort the field-id keys so output is deterministic.
|
|
let mut keys: Vec<&relicario_core::FieldId> = item.field_history.keys().collect();
|
|
keys.sort_by(|a, b| a.0.cmp(&b.0));
|
|
|
|
let mut printed_any = false;
|
|
for fid in keys {
|
|
let display_name = fid.0.strip_prefix("core:").unwrap_or(&fid.0);
|
|
if let Some(filter) = &field {
|
|
if display_name != filter && fid.0 != *filter { continue; }
|
|
}
|
|
let entries = &item.field_history[fid];
|
|
if entries.is_empty() { continue; }
|
|
printed_any = true;
|
|
|
|
println!("{display_name} ({} {})",
|
|
entries.len(),
|
|
if entries.len() == 1 { "entry" } else { "entries" });
|
|
for (i, e) in entries.iter().enumerate() {
|
|
let ts = crate::helpers::iso8601(e.replaced_at);
|
|
if show {
|
|
println!(" [{i}] {ts} {}", e.value.as_str());
|
|
} else {
|
|
println!(" [{i}] {ts} ********");
|
|
}
|
|
}
|
|
println!();
|
|
}
|
|
|
|
if !printed_any {
|
|
if field.is_some() {
|
|
println!("no history for the requested field");
|
|
} else {
|
|
println!("no history captured for this item");
|
|
}
|
|
} else if !show {
|
|
println!("(use --show to reveal values)");
|
|
}
|
|
|
|
Ok(())
|
|
}
|