use std::path::Path;
use std::process::Command;
use chrono::Utc;
use sha2::{Digest, Sha256};
use crate::adapters::audit::json_audit_logger::JsonAuditLogger;
use crate::cli::output;
use crate::config::app_config::AppConfig;
use crate::core::models::audit_entry::{AuditAction, AuditEntry};
use crate::core::traits::audit::AuditLogger;
pub fn git_author() -> (String, Option<String>) {
let name = Command::new("git")
.args(["config", "user.name"])
.output()
.ok()
.and_then(|o| {
if o.status.success() {
Some(String::from_utf8_lossy(&o.stdout).trim().to_string())
} else {
None
}
})
.unwrap_or_else(|| "unknown".to_string());
let email = Command::new("git")
.args(["config", "user.email"])
.output()
.ok()
.and_then(|o| {
if o.status.success() {
let val = String::from_utf8_lossy(&o.stdout).trim().to_string();
if val.is_empty() { None } else { Some(val) }
} else {
None
}
});
(name, email)
}
pub fn compute_file_hash(path: &Path) -> Option<String> {
let data = std::fs::read(path).ok()?;
let hash = Sha256::digest(&data);
Some(format!("{hash:x}"))
}
pub fn log_audit(action: AuditAction, files: Vec<String>, detail: Option<String>) {
log_audit_with_hash(action, files, detail, None);
}
pub fn log_audit_with_hash(
action: AuditAction,
files: Vec<String>,
detail: Option<String>,
state_hash: Option<String>,
) {
let vaultic_dir = crate::cli::context::vaultic_dir();
let config = AppConfig::load(vaultic_dir).ok();
let audit_section = config.as_ref().and_then(|c| c.audit.as_ref());
if !JsonAuditLogger::is_enabled(audit_section) {
return;
}
let logger = JsonAuditLogger::from_config(vaultic_dir, audit_section);
let (author, email) = git_author();
let entry = AuditEntry {
timestamp: Utc::now(),
author,
email,
action,
files,
detail,
state_hash,
};
if let Err(e) = logger.log_event(&entry) {
output::warning(&format!("Could not write audit log: {e}"));
}
}
pub fn log_audit_init() {
let vaultic_dir = crate::cli::context::vaultic_dir();
let logger = JsonAuditLogger::new(vaultic_dir, "audit.log");
let (author, email) = git_author();
let entry = AuditEntry {
timestamp: Utc::now(),
author,
email,
action: AuditAction::Init,
files: vec![],
detail: Some("project initialized".to_string()),
state_hash: None,
};
if let Err(e) = logger.log_event(&entry) {
output::warning(&format!("Could not write audit log: {e}"));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compute_file_hash_returns_hex_string() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("test.txt");
std::fs::write(&file, "hello world").unwrap();
let hash = compute_file_hash(&file).unwrap();
assert_eq!(
hash,
"b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
);
}
#[test]
fn compute_file_hash_nonexistent_returns_none() {
let result = compute_file_hash(Path::new("/nonexistent/file.txt"));
assert!(result.is_none());
}
}