use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditProfile {
pub name: String,
pub before: String,
pub after: String,
pub definition: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ignore_file: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_used_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ProfileStore {
#[serde(default)]
pub profiles: Vec<AuditProfile>,
}
impl ProfileStore {
pub fn load() -> anyhow::Result<Self> {
let path = profile_path()?;
if !path.exists() {
return Ok(Self::default());
}
let text = std::fs::read_to_string(&path)?;
Ok(serde_yaml::from_str(&text)?)
}
pub fn save(&self) -> anyhow::Result<()> {
let path = profile_path()?;
let yaml = serde_yaml::to_string(self)?;
std::fs::write(&path, yaml)?;
Ok(())
}
pub fn add(&mut self, profile: AuditProfile) {
if let Some(pos) = self.profiles.iter().position(|p| p.name == profile.name) {
self.profiles[pos] = profile;
} else {
self.profiles.push(profile);
}
}
pub fn remove(&mut self, name: &str) {
self.profiles.retain(|p| p.name != name);
}
pub fn touch(&mut self, name: &str) -> anyhow::Result<bool> {
if let Some(p) = self.profiles.iter_mut().find(|p| p.name == name) {
p.last_used_at = Some(Utc::now());
self.save()?;
Ok(true)
} else {
Ok(false)
}
}
pub fn sorted_by_recent(&self) -> Vec<&AuditProfile> {
let mut v: Vec<&AuditProfile> = self.profiles.iter().collect();
v.sort_by(|a, b| b.last_used_at.cmp(&a.last_used_at));
v
}
}
fn profile_path() -> anyhow::Result<PathBuf> {
let base = dirs::config_dir()
.ok_or_else(|| anyhow::anyhow!("Cannot determine OS config directory"))?
.join("aaai");
std::fs::create_dir_all(&base)?;
Ok(base.join("profiles.yaml"))
}
#[cfg(test)]
mod tests;