1use std::path::Path;
2use serde::{Deserialize, Serialize};
3use crate::error::GriteError;
4use crate::lock::LockPolicy;
5use crate::signing::VerificationPolicy;
6use crate::types::actor::ActorConfig;
7
8#[derive(Debug, Clone, Default, Serialize, Deserialize)]
10pub struct RepoConfig {
11 #[serde(skip_serializing_if = "Option::is_none")]
13 pub default_actor: Option<String>,
14 #[serde(skip_serializing_if = "Option::is_none")]
16 pub lock_policy: Option<String>,
17 #[serde(skip_serializing_if = "Option::is_none")]
19 pub verify_signatures: Option<String>,
20 #[serde(skip_serializing_if = "Option::is_none")]
22 pub snapshot: Option<SnapshotConfig>,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct SnapshotConfig {
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub max_events: Option<u32>,
31 #[serde(skip_serializing_if = "Option::is_none")]
33 pub max_age_days: Option<u32>,
34}
35
36impl Default for SnapshotConfig {
37 fn default() -> Self {
38 Self {
39 max_events: Some(10000),
40 max_age_days: Some(7),
41 }
42 }
43}
44
45impl RepoConfig {
46 pub fn get_lock_policy(&self) -> LockPolicy {
48 self.lock_policy
49 .as_ref()
50 .and_then(|s| LockPolicy::from_str(s))
51 .unwrap_or(LockPolicy::Warn)
52 }
53
54 pub fn get_verification_policy(&self) -> VerificationPolicy {
56 self.verify_signatures
57 .as_ref()
58 .and_then(|s| VerificationPolicy::from_str(s))
59 .unwrap_or(VerificationPolicy::Off)
60 }
61}
62
63pub fn load_repo_config(git_dir: &Path) -> Result<Option<RepoConfig>, GriteError> {
65 let config_path = git_dir.join("grite").join("config.toml");
66 if !config_path.exists() {
67 return Ok(None);
68 }
69 let content = std::fs::read_to_string(&config_path)?;
70 let config: RepoConfig = toml::from_str(&content)?;
71 Ok(Some(config))
72}
73
74pub fn save_repo_config(git_dir: &Path, config: &RepoConfig) -> Result<(), GriteError> {
76 let grit_dir = git_dir.join("grite");
77 std::fs::create_dir_all(&grit_dir)?;
78 let config_path = grit_dir.join("config.toml");
79 let content = toml::to_string_pretty(config)?;
80 std::fs::write(&config_path, content)?;
81 Ok(())
82}
83
84pub fn load_actor_config(actor_dir: &Path) -> Result<ActorConfig, GriteError> {
86 let config_path = actor_dir.join("config.toml");
87 if !config_path.exists() {
88 return Err(GriteError::NotFound(format!(
89 "Actor config not found: {}",
90 config_path.display()
91 )));
92 }
93 let content = std::fs::read_to_string(&config_path)?;
94 let config: ActorConfig = toml::from_str(&content)?;
95 Ok(config)
96}
97
98pub fn save_actor_config(actor_dir: &Path, config: &ActorConfig) -> Result<(), GriteError> {
100 std::fs::create_dir_all(actor_dir)?;
101 let config_path = actor_dir.join("config.toml");
102 let content = toml::to_string_pretty(config)?;
103 std::fs::write(&config_path, content)?;
104 Ok(())
105}
106
107pub fn list_actors(git_dir: &Path) -> Result<Vec<ActorConfig>, GriteError> {
109 let actors_dir = git_dir.join("grite").join("actors");
110 if !actors_dir.exists() {
111 return Ok(Vec::new());
112 }
113
114 let mut actors = Vec::new();
115 for entry in std::fs::read_dir(&actors_dir)? {
116 let entry = entry?;
117 if entry.file_type()?.is_dir() {
118 let actor_dir = entry.path();
119 match load_actor_config(&actor_dir) {
120 Ok(config) => actors.push(config),
121 Err(_) => continue, }
123 }
124 }
125
126 actors.sort_by(|a, b| a.actor_id.cmp(&b.actor_id));
128 Ok(actors)
129}
130
131pub fn actors_dir(git_dir: &Path) -> std::path::PathBuf {
133 git_dir.join("grite").join("actors")
134}
135
136pub fn actor_dir(git_dir: &Path, actor_id: &str) -> std::path::PathBuf {
138 actors_dir(git_dir).join(actor_id)
139}
140
141pub fn actor_sled_path(git_dir: &Path, actor_id: &str) -> std::path::PathBuf {
143 actor_dir(git_dir, actor_id).join("sled")
144}
145
146pub fn actor_signing_key_path(git_dir: &Path, actor_id: &str) -> std::path::PathBuf {
148 actor_dir(git_dir, actor_id).join("signing_key")
149}
150
151pub fn load_signing_key(git_dir: &Path, actor_id: &str) -> Option<String> {
153 let key_path = actor_signing_key_path(git_dir, actor_id);
154 std::fs::read_to_string(key_path).ok()
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160 use tempfile::tempdir;
161
162 #[test]
163 fn test_repo_config_roundtrip() {
164 let dir = tempdir().unwrap();
165 let git_dir = dir.path();
166
167 let config = RepoConfig {
168 default_actor: Some("00112233445566778899aabbccddeeff".to_string()),
169 lock_policy: Some("warn".to_string()),
170 verify_signatures: Some("warn".to_string()),
171 snapshot: Some(SnapshotConfig {
172 max_events: Some(5000),
173 max_age_days: Some(3),
174 }),
175 };
176
177 save_repo_config(git_dir, &config).unwrap();
178 let loaded = load_repo_config(git_dir).unwrap().unwrap();
179
180 assert_eq!(loaded.default_actor, config.default_actor);
181 assert_eq!(loaded.lock_policy, config.lock_policy);
182 }
183
184 #[test]
185 fn test_actor_config_roundtrip() {
186 let dir = tempdir().unwrap();
187 let actor_dir = dir.path().join("test_actor");
188
189 let config = ActorConfig {
190 actor_id: "00112233445566778899aabbccddeeff".to_string(),
191 label: Some("test-device".to_string()),
192 created_ts: Some(1700000000000),
193 public_key: None,
194 key_scheme: None,
195 };
196
197 save_actor_config(&actor_dir, &config).unwrap();
198 let loaded = load_actor_config(&actor_dir).unwrap();
199
200 assert_eq!(loaded.actor_id, config.actor_id);
201 assert_eq!(loaded.label, config.label);
202 }
203
204 #[test]
205 fn test_list_actors() {
206 let dir = tempdir().unwrap();
207 let git_dir = dir.path();
208
209 let actors = actors_dir(git_dir);
211 std::fs::create_dir_all(&actors).unwrap();
212
213 for i in 0..2 {
215 let actor_id = format!("{:032x}", i);
216 let actor_path = actors.join(&actor_id);
217 let config = ActorConfig {
218 actor_id: actor_id.clone(),
219 label: Some(format!("actor-{}", i)),
220 created_ts: Some(1700000000000 + i),
221 public_key: None,
222 key_scheme: None,
223 };
224 save_actor_config(&actor_path, &config).unwrap();
225 }
226
227 let found = list_actors(git_dir).unwrap();
228 assert_eq!(found.len(), 2);
229 }
230}