ricecoder_teams/
config.rs1use crate::error::{Result, TeamError};
3use crate::models::{MergedStandards, StandardsOverride, TeamStandards};
4use chrono::Utc;
5use ricecoder_storage::PathResolver;
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::path::PathBuf;
9use std::sync::Arc;
10use tokio::sync::RwLock;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ChangeHistoryEntry {
15 pub timestamp: chrono::DateTime<Utc>,
16 pub version: u32,
17 pub description: String,
18 pub changed_by: String,
19}
20
21pub struct TeamConfigManager {
23 standards_cache: Arc<RwLock<HashMap<String, TeamStandards>>>,
25 change_history: Arc<RwLock<HashMap<String, Vec<ChangeHistoryEntry>>>>,
27}
28
29impl TeamConfigManager {
30 pub fn new() -> Self {
32 TeamConfigManager {
33 standards_cache: Arc::new(RwLock::new(HashMap::new())),
34 change_history: Arc::new(RwLock::new(HashMap::new())),
35 }
36 }
37
38 pub async fn store_standards(&self, team_id: &str, standards: TeamStandards) -> Result<()> {
40 let storage_path = Self::resolve_team_standards_path(team_id)?;
42
43 if let Some(parent) = storage_path.parent() {
45 std::fs::create_dir_all(parent).map_err(|e| {
46 TeamError::StorageError(format!("Failed to create storage directory: {}", e))
47 })?;
48 }
49
50 let yaml_content = serde_yaml::to_string(&standards).map_err(TeamError::YamlError)?;
52
53 std::fs::write(&storage_path, yaml_content).map_err(|e| {
55 TeamError::StorageError(format!("Failed to write standards file: {}", e))
56 })?;
57
58 let mut cache = self.standards_cache.write().await;
60 cache.insert(team_id.to_string(), standards);
61
62 tracing::info!(
63 team_id = %team_id,
64 path = ?storage_path,
65 "Team standards stored successfully"
66 );
67
68 Ok(())
69 }
70
71 pub async fn get_standards(&self, team_id: &str) -> Result<TeamStandards> {
73 {
75 let cache = self.standards_cache.read().await;
76 if let Some(standards) = cache.get(team_id) {
77 tracing::debug!(team_id = %team_id, "Retrieved standards from cache");
78 return Ok(standards.clone());
79 }
80 }
81
82 let storage_path = Self::resolve_team_standards_path(team_id)?;
84
85 if !storage_path.exists() {
86 return Err(TeamError::TeamNotFound(format!(
87 "Standards not found for team: {}",
88 team_id
89 )));
90 }
91
92 let yaml_content = std::fs::read_to_string(&storage_path).map_err(|e| {
93 TeamError::StorageError(format!("Failed to read standards file: {}", e))
94 })?;
95
96 let standards: TeamStandards =
97 serde_yaml::from_str(&yaml_content).map_err(TeamError::YamlError)?;
98
99 let mut cache = self.standards_cache.write().await;
101 cache.insert(team_id.to_string(), standards.clone());
102
103 tracing::info!(team_id = %team_id, "Retrieved team standards from storage");
104
105 Ok(standards)
106 }
107
108 pub async fn apply_hierarchy(
110 &self,
111 org_id: &str,
112 team_id: &str,
113 project_id: &str,
114 ) -> Result<MergedStandards> {
115 let org_standards = self.get_standards(org_id).await.ok();
117 let team_standards = self.get_standards(team_id).await.ok();
118 let project_standards = self.get_standards(project_id).await.ok();
119
120 let final_standards = Self::merge_standards_hierarchy(
123 org_standards.clone(),
124 team_standards.clone(),
125 project_standards.clone(),
126 )?;
127
128 tracing::info!(
129 org_id = %org_id,
130 team_id = %team_id,
131 project_id = %project_id,
132 "Standards hierarchy applied successfully"
133 );
134
135 Ok(MergedStandards {
136 organization_standards: org_standards,
137 team_standards,
138 project_standards,
139 final_standards,
140 })
141 }
142
143 pub async fn override_standards(
145 &self,
146 project_id: &str,
147 overrides: StandardsOverride,
148 ) -> Result<()> {
149 let mut project_standards = self.get_standards(project_id).await?;
151
152 Self::validate_overrides(&project_standards, &overrides)?;
154
155 for override_id in &overrides.overridden_standards {
157 project_standards
159 .code_review_rules
160 .retain(|r| &r.id != override_id);
161 }
162
163 project_standards.version += 1;
165 project_standards.updated_at = Utc::now();
166
167 self.store_standards(project_id, project_standards).await?;
169
170 self.track_changes(
172 project_id,
173 &format!("Applied {} overrides", overrides.overridden_standards.len()),
174 )
175 .await?;
176
177 tracing::info!(
178 project_id = %project_id,
179 override_count = %overrides.overridden_standards.len(),
180 "Standards overrides applied successfully"
181 );
182
183 Ok(())
184 }
185
186 pub async fn track_changes(&self, team_id: &str, change_description: &str) -> Result<()> {
188 let entry = ChangeHistoryEntry {
189 timestamp: Utc::now(),
190 version: 1, description: change_description.to_string(),
192 changed_by: "system".to_string(), };
194
195 let mut history = self.change_history.write().await;
196 history
197 .entry(team_id.to_string())
198 .or_insert_with(Vec::new)
199 .push(entry);
200
201 tracing::info!(
202 team_id = %team_id,
203 change = %change_description,
204 "Standards change tracked successfully"
205 );
206
207 Ok(())
208 }
209
210 pub async fn get_change_history(&self, team_id: &str) -> Result<Vec<ChangeHistoryEntry>> {
212 let history = self.change_history.read().await;
213 Ok(history.get(team_id).cloned().unwrap_or_default())
214 }
215
216 fn resolve_team_standards_path(team_id: &str) -> Result<PathBuf> {
220 let global_path = PathResolver::resolve_global_path()
221 .map_err(|e| TeamError::StorageError(e.to_string()))?;
222
223 let standards_path = global_path
224 .join("teams")
225 .join(team_id)
226 .join("standards.yaml");
227
228 Ok(standards_path)
229 }
230
231 pub fn merge_standards_hierarchy(
233 org_standards: Option<TeamStandards>,
234 team_standards: Option<TeamStandards>,
235 project_standards: Option<TeamStandards>,
236 ) -> Result<TeamStandards> {
237 let mut merged = org_standards.unwrap_or_else(|| TeamStandards {
239 id: "merged".to_string(),
240 team_id: "merged".to_string(),
241 code_review_rules: Vec::new(),
242 templates: Vec::new(),
243 steering_docs: Vec::new(),
244 compliance_requirements: Vec::new(),
245 version: 1,
246 created_at: Utc::now(),
247 updated_at: Utc::now(),
248 });
249
250 if let Some(team) = team_standards {
252 merged.code_review_rules.extend(team.code_review_rules);
253 merged.templates.extend(team.templates);
254 merged.steering_docs.extend(team.steering_docs);
255 merged
256 .compliance_requirements
257 .extend(team.compliance_requirements);
258 merged.version = team.version;
259 merged.updated_at = team.updated_at;
260 }
261
262 if let Some(project) = project_standards {
264 merged.code_review_rules.extend(project.code_review_rules);
265 merged.templates.extend(project.templates);
266 merged.steering_docs.extend(project.steering_docs);
267 merged
268 .compliance_requirements
269 .extend(project.compliance_requirements);
270 merged.version = project.version;
271 merged.updated_at = project.updated_at;
272 }
273
274 merged.updated_at = Utc::now();
275
276 Ok(merged)
277 }
278
279 fn validate_overrides(standards: &TeamStandards, overrides: &StandardsOverride) -> Result<()> {
281 for override_id in &overrides.overridden_standards {
282 let exists = standards
283 .code_review_rules
284 .iter()
285 .any(|r| &r.id == override_id);
286
287 if !exists {
288 return Err(TeamError::ConfigError(format!(
289 "Override target not found: {}",
290 override_id
291 )));
292 }
293 }
294
295 Ok(())
296 }
297}
298
299impl Default for TeamConfigManager {
300 fn default() -> Self {
301 Self::new()
302 }
303}