1use crate::error::GriteError;
2use crate::lock::LockPolicy;
3use crate::signing::VerificationPolicy;
4use crate::types::actor::ActorConfig;
5use serde::{Deserialize, Serialize};
6use std::path::Path;
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 repo_sled_path(git_dir: &Path) -> std::path::PathBuf {
151 git_dir.join("grite").join("sled")
152}
153
154pub fn actor_signing_key_path(git_dir: &Path, actor_id: &str) -> std::path::PathBuf {
156 actor_dir(git_dir, actor_id).join("signing_key")
157}
158
159pub fn load_signing_key(git_dir: &Path, actor_id: &str) -> Option<String> {
161 let key_path = actor_signing_key_path(git_dir, actor_id);
162 std::fs::read_to_string(key_path).ok()
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use tempfile::tempdir;
169
170 #[test]
171 fn test_repo_config_roundtrip() {
172 let dir = tempdir().unwrap();
173 let git_dir = dir.path();
174
175 let config = RepoConfig {
176 default_actor: Some("00112233445566778899aabbccddeeff".to_string()),
177 lock_policy: Some("warn".to_string()),
178 verify_signatures: Some("warn".to_string()),
179 snapshot: Some(SnapshotConfig {
180 max_events: Some(5000),
181 max_age_days: Some(3),
182 }),
183 };
184
185 save_repo_config(git_dir, &config).unwrap();
186 let loaded = load_repo_config(git_dir).unwrap().unwrap();
187
188 assert_eq!(loaded.default_actor, config.default_actor);
189 assert_eq!(loaded.lock_policy, config.lock_policy);
190 }
191
192 #[test]
193 fn test_actor_config_roundtrip() {
194 let dir = tempdir().unwrap();
195 let actor_dir = dir.path().join("test_actor");
196
197 let config = ActorConfig {
198 actor_id: "00112233445566778899aabbccddeeff".to_string(),
199 label: Some("test-device".to_string()),
200 created_ts: Some(1700000000000),
201 public_key: None,
202 key_scheme: None,
203 };
204
205 save_actor_config(&actor_dir, &config).unwrap();
206 let loaded = load_actor_config(&actor_dir).unwrap();
207
208 assert_eq!(loaded.actor_id, config.actor_id);
209 assert_eq!(loaded.label, config.label);
210 }
211
212 #[test]
213 fn test_list_actors() {
214 let dir = tempdir().unwrap();
215 let git_dir = dir.path();
216
217 let actors = actors_dir(git_dir);
219 std::fs::create_dir_all(&actors).unwrap();
220
221 for i in 0..2 {
223 let actor_id = format!("{:032x}", i);
224 let actor_path = actors.join(&actor_id);
225 let config = ActorConfig {
226 actor_id: actor_id.clone(),
227 label: Some(format!("actor-{}", i)),
228 created_ts: Some(1700000000000 + i),
229 public_key: None,
230 key_scheme: None,
231 };
232 save_actor_config(&actor_path, &config).unwrap();
233 }
234
235 let found = list_actors(git_dir).unwrap();
236 assert_eq!(found.len(), 2);
237 }
238}