use std::collections::HashMap;
use anyhow::Result;
use colored::Colorize;
use tsafe_core::{audit::AuditEntry, errors::SafeError};
use crate::helpers::*;
pub(crate) fn cmd_alias(
profile: &str,
target_key: Option<&str>,
alias_name: Option<&str>,
list: bool,
) -> Result<()> {
if list {
let vault = open_vault(profile)?;
let aliases: Vec<(&String, &tsafe_core::vault::SecretEntry)> = vault
.file()
.secrets
.iter()
.filter(|(_, e)| e.tags.get("type").map(|t| t == "alias").unwrap_or(false))
.collect();
if aliases.is_empty() {
println!("{} No aliases in profile '{profile}'", "i".blue());
} else {
for (name, entry) in &aliases {
let target = entry.tags.get("target").map(|s| s.as_str()).unwrap_or("?");
println!("{name} → {target}");
}
}
return Ok(());
}
let target = target_key
.ok_or_else(|| anyhow::anyhow!("provide TARGET_KEY and ALIAS_NAME, or use --list"))?;
let alias = alias_name.ok_or_else(|| anyhow::anyhow!("provide TARGET_KEY and ALIAS_NAME"))?;
let mut vault = open_vault(profile)?;
if !vault.list().contains(&target) {
anyhow::bail!("target key '{target}' does not exist in profile '{profile}'");
}
let mut tags = HashMap::new();
tags.insert("type".into(), "alias".into());
tags.insert("target".into(), target.to_string());
vault.set(alias, &format!("@alias:{target}"), tags)?;
audit(profile)
.append(&AuditEntry::success(profile, "alias", Some(alias)))
.ok();
println!("{} Alias '{alias}' → '{target}' created", "✓".green());
Ok(())
}
pub(crate) fn cmd_history(profile: &str, key: &str) -> Result<()> {
let vault = open_vault(profile)?;
let versions = vault.history(key).map_err(|e| match &e {
SafeError::SecretNotFound { .. } => anyhow::anyhow!("secret '{key}' not found"),
other => anyhow::anyhow!("{other}"),
})?;
if versions.len() <= 1 {
println!("No previous versions for '{key}'");
} else {
println!("Versions for '{key}':");
for (ver, ts) in &versions {
let label = if *ver == 0 { " (current)" } else { "" };
println!(" v{ver} {}{label}", ts.format("%Y-%m-%d %H:%M:%S UTC"));
}
println!(
"\nUse {} to retrieve a specific version.",
format!("tsafe get {key} --version N").cyan()
);
}
Ok(())
}
pub(crate) fn cmd_mv(
profile: &str,
source: &str,
dest: Option<&str>,
to_profile: Option<&str>,
force: bool,
) -> Result<()> {
match to_profile {
None => {
let dest_key = dest.ok_or_else(|| {
anyhow::anyhow!("provide a destination key name, e.g. tsafe mv OLD NEW")
})?;
if source == dest_key {
anyhow::bail!("source and destination are the same key");
}
let mut vault = open_vault(profile)?;
vault
.rename_key(source, dest_key, force)
.map_err(|e| match &e {
SafeError::SecretNotFound { .. } => {
anyhow::anyhow!("secret '{source}' not found")
}
SafeError::SecretAlreadyExists { .. } => anyhow::anyhow!(
"destination '{dest_key}' already exists — use --force to overwrite"
),
other => anyhow::anyhow!("{other}"),
})?;
println!("{} Moved '{}' → '{}'", "✓".green(), source, dest_key);
}
Some(target_profile) => {
let dest_key = dest.unwrap_or(source);
if profile == target_profile && source == dest_key {
anyhow::bail!("source and destination are identical");
}
let value = {
let src_vault = open_vault(profile)?;
let val = src_vault.get(source).map_err(|e| match &e {
SafeError::SecretNotFound { .. } => {
anyhow::anyhow!("secret '{source}' not found in profile '{profile}'")
}
other => anyhow::anyhow!("{other}"),
})?;
(*val).clone()
};
let tags = {
let src_vault = open_vault(profile)?;
src_vault
.file()
.secrets
.get(source)
.map(|e| e.tags.clone())
.unwrap_or_default()
};
{
let mut dst_vault = open_vault(target_profile)?;
if !force && dst_vault.file().secrets.contains_key(dest_key) {
anyhow::bail!(
"'{dest_key}' already exists in profile '{target_profile}' — use --force to overwrite"
);
}
dst_vault.set(dest_key, &value, tags)?;
}
{
let mut src_vault = open_vault(profile)?;
src_vault.delete(source)?;
}
println!(
"{} Moved '{}/{}' → '{}/{}'",
"✓".green(),
profile,
source,
target_profile,
dest_key
);
}
}
Ok(())
}