use crate::error::GriteError;
use crate::lock::LockPolicy;
use crate::signing::VerificationPolicy;
use crate::types::actor::ActorConfig;
use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct RepoConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub default_actor: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lock_policy: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub verify_signatures: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub snapshot: Option<SnapshotConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SnapshotConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub max_events: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_age_days: Option<u32>,
}
impl Default for SnapshotConfig {
fn default() -> Self {
Self {
max_events: Some(10000),
max_age_days: Some(7),
}
}
}
impl RepoConfig {
pub fn get_lock_policy(&self) -> LockPolicy {
self.lock_policy
.as_ref()
.and_then(|s| LockPolicy::from_str(s))
.unwrap_or(LockPolicy::Warn)
}
pub fn get_verification_policy(&self) -> VerificationPolicy {
self.verify_signatures
.as_ref()
.and_then(|s| VerificationPolicy::from_str(s))
.unwrap_or(VerificationPolicy::Off)
}
}
pub fn load_repo_config(git_dir: &Path) -> Result<Option<RepoConfig>, GriteError> {
let config_path = git_dir.join("grite").join("config.toml");
if !config_path.exists() {
return Ok(None);
}
let content = std::fs::read_to_string(&config_path)?;
let config: RepoConfig = toml::from_str(&content)?;
Ok(Some(config))
}
pub fn save_repo_config(git_dir: &Path, config: &RepoConfig) -> Result<(), GriteError> {
let grit_dir = git_dir.join("grite");
std::fs::create_dir_all(&grit_dir)?;
let config_path = grit_dir.join("config.toml");
let content = toml::to_string_pretty(config)?;
std::fs::write(&config_path, content)?;
Ok(())
}
pub fn load_actor_config(actor_dir: &Path) -> Result<ActorConfig, GriteError> {
let config_path = actor_dir.join("config.toml");
if !config_path.exists() {
return Err(GriteError::NotFound(format!(
"Actor config not found: {}",
config_path.display()
)));
}
let content = std::fs::read_to_string(&config_path)?;
let config: ActorConfig = toml::from_str(&content)?;
Ok(config)
}
pub fn save_actor_config(actor_dir: &Path, config: &ActorConfig) -> Result<(), GriteError> {
std::fs::create_dir_all(actor_dir)?;
let config_path = actor_dir.join("config.toml");
let content = toml::to_string_pretty(config)?;
std::fs::write(&config_path, content)?;
Ok(())
}
pub fn list_actors(git_dir: &Path) -> Result<Vec<ActorConfig>, GriteError> {
let actors_dir = git_dir.join("grite").join("actors");
if !actors_dir.exists() {
return Ok(Vec::new());
}
let mut actors = Vec::new();
for entry in std::fs::read_dir(&actors_dir)? {
let entry = entry?;
if entry.file_type()?.is_dir() {
let actor_dir = entry.path();
match load_actor_config(&actor_dir) {
Ok(config) => actors.push(config),
Err(_) => continue, }
}
}
actors.sort_by(|a, b| a.actor_id.cmp(&b.actor_id));
Ok(actors)
}
pub fn actors_dir(git_dir: &Path) -> std::path::PathBuf {
git_dir.join("grite").join("actors")
}
pub fn actor_dir(git_dir: &Path, actor_id: &str) -> std::path::PathBuf {
actors_dir(git_dir).join(actor_id)
}
pub fn actor_sled_path(git_dir: &Path, actor_id: &str) -> std::path::PathBuf {
actor_dir(git_dir, actor_id).join("sled")
}
pub fn repo_sled_path(git_dir: &Path) -> std::path::PathBuf {
git_dir.join("grite").join("sled")
}
pub fn actor_signing_key_path(git_dir: &Path, actor_id: &str) -> std::path::PathBuf {
actor_dir(git_dir, actor_id).join("signing_key")
}
pub fn load_signing_key(git_dir: &Path, actor_id: &str) -> Option<String> {
let key_path = actor_signing_key_path(git_dir, actor_id);
std::fs::read_to_string(key_path).ok()
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_repo_config_roundtrip() {
let dir = tempdir().unwrap();
let git_dir = dir.path();
let config = RepoConfig {
default_actor: Some("00112233445566778899aabbccddeeff".to_string()),
lock_policy: Some("warn".to_string()),
verify_signatures: Some("warn".to_string()),
snapshot: Some(SnapshotConfig {
max_events: Some(5000),
max_age_days: Some(3),
}),
};
save_repo_config(git_dir, &config).unwrap();
let loaded = load_repo_config(git_dir).unwrap().unwrap();
assert_eq!(loaded.default_actor, config.default_actor);
assert_eq!(loaded.lock_policy, config.lock_policy);
}
#[test]
fn test_actor_config_roundtrip() {
let dir = tempdir().unwrap();
let actor_dir = dir.path().join("test_actor");
let config = ActorConfig {
actor_id: "00112233445566778899aabbccddeeff".to_string(),
label: Some("test-device".to_string()),
created_ts: Some(1700000000000),
public_key: None,
key_scheme: None,
};
save_actor_config(&actor_dir, &config).unwrap();
let loaded = load_actor_config(&actor_dir).unwrap();
assert_eq!(loaded.actor_id, config.actor_id);
assert_eq!(loaded.label, config.label);
}
#[test]
fn test_list_actors() {
let dir = tempdir().unwrap();
let git_dir = dir.path();
let actors = actors_dir(git_dir);
std::fs::create_dir_all(&actors).unwrap();
for i in 0..2 {
let actor_id = format!("{:032x}", i);
let actor_path = actors.join(&actor_id);
let config = ActorConfig {
actor_id: actor_id.clone(),
label: Some(format!("actor-{}", i)),
created_ts: Some(1700000000000 + i),
public_key: None,
key_scheme: None,
};
save_actor_config(&actor_path, &config).unwrap();
}
let found = list_actors(git_dir).unwrap();
assert_eq!(found.len(), 2);
}
}