ricecoder_teams/
manager.rs1use crate::access::AccessControlManager;
3use crate::analytics::AnalyticsDashboard;
4use crate::config::TeamConfigManager;
5use crate::error::{Result, TeamError};
6use crate::models::{Team, TeamMember, TeamStandards};
7use crate::rules::SharedRulesManager;
8use crate::sync::SyncService;
9use chrono::Utc;
10use ricecoder_storage::PathResolver;
11use std::collections::HashMap;
12use std::path::PathBuf;
13use std::sync::Arc;
14use tokio::sync::RwLock;
15use uuid::Uuid;
16
17pub struct TeamManager {
19 config_manager: Arc<TeamConfigManager>,
20 rules_manager: Arc<SharedRulesManager>,
21 access_control: Arc<AccessControlManager>,
22 sync_service: Arc<SyncService>,
23 analytics: Arc<AnalyticsDashboard>,
24 teams_cache: Arc<RwLock<HashMap<String, Team>>>,
26}
27
28impl TeamManager {
29 pub fn new() -> Self {
31 let rules_manager = SharedRulesManager::new(
34 Arc::new(crate::rules::mocks::MockRulePromoter),
35 Arc::new(crate::rules::mocks::MockRuleValidator),
36 Arc::new(crate::rules::mocks::MockAnalyticsEngine),
37 );
38
39 let access_control = AccessControlManager::default();
41
42 TeamManager {
43 config_manager: Arc::new(TeamConfigManager::new()),
44 rules_manager: Arc::new(rules_manager),
45 access_control: Arc::new(access_control),
46 sync_service: Arc::new(SyncService::new()),
47 analytics: Arc::new(AnalyticsDashboard::new()),
48 teams_cache: Arc::new(RwLock::new(HashMap::new())),
49 }
50 }
51
52 pub async fn create_team(&self, name: &str, members: Vec<TeamMember>) -> Result<Team> {
54 let team_id = Uuid::new_v4().to_string();
55 let now = Utc::now();
56
57 tracing::info!(team_id = %team_id, team_name = %name, "Creating team");
58
59 let team = Team {
61 id: team_id.clone(),
62 name: name.to_string(),
63 organization_id: None,
64 members: members.clone(),
65 created_at: now,
66 updated_at: now,
67 };
68
69 self.store_team(&team).await?;
71
72 let mut cache = self.teams_cache.write().await;
74 cache.insert(team_id.clone(), team.clone());
75
76 for member in &members {
78 self.access_control
79 .assign_role(&team.id, &member.id, member.role)
80 .await?;
81 }
82
83 let team_standards = TeamStandards {
85 id: Uuid::new_v4().to_string(),
86 team_id: team.id.clone(),
87 code_review_rules: Vec::new(),
88 templates: Vec::new(),
89 steering_docs: Vec::new(),
90 compliance_requirements: Vec::new(),
91 version: 1,
92 created_at: now,
93 updated_at: now,
94 };
95
96 self.config_manager
97 .store_standards(&team.id, team_standards)
98 .await?;
99
100 tracing::info!(team_id = %team.id, member_count = %members.len(), "Team created successfully");
101
102 Ok(team)
103 }
104
105 pub async fn add_member(&self, team_id: &str, member: TeamMember) -> Result<()> {
107 let mut team = self.get_team(team_id).await?;
109
110 if team.members.iter().any(|m| m.id == member.id) {
112 return Err(TeamError::Internal(format!(
113 "Member {} already exists in team",
114 member.id
115 )));
116 }
117
118 tracing::info!(
119 team_id = %team_id,
120 member_id = %member.id,
121 role = %member.role.as_str(),
122 "Adding team member"
123 );
124
125 team.members.push(member.clone());
127 team.updated_at = Utc::now();
128
129 self.store_team(&team).await?;
131
132 let mut cache = self.teams_cache.write().await;
134 cache.insert(team_id.to_string(), team);
135
136 self.access_control
138 .assign_role(team_id, &member.id, member.role)
139 .await?;
140
141 tracing::info!(
142 team_id = %team_id,
143 member_id = %member.id,
144 "Team member added successfully"
145 );
146
147 Ok(())
148 }
149
150 pub async fn remove_member(&self, team_id: &str, member_id: &str) -> Result<()> {
152 let mut team = self.get_team(team_id).await?;
154
155 let initial_count = team.members.len();
157 team.members.retain(|m| m.id != member_id);
158
159 if team.members.len() == initial_count {
160 return Err(TeamError::MemberNotFound(member_id.to_string()));
161 }
162
163 tracing::info!(
164 team_id = %team_id,
165 member_id = %member_id,
166 "Removing team member"
167 );
168
169 team.updated_at = Utc::now();
170
171 self.store_team(&team).await?;
173
174 let mut cache = self.teams_cache.write().await;
176 cache.insert(team_id.to_string(), team);
177
178 self.access_control
180 .revoke_access(team_id, member_id)
181 .await?;
182
183 tracing::info!(
184 team_id = %team_id,
185 member_id = %member_id,
186 "Team member removed successfully"
187 );
188
189 Ok(())
190 }
191
192 pub async fn get_team(&self, team_id: &str) -> Result<Team> {
194 {
196 let cache = self.teams_cache.read().await;
197 if let Some(team) = cache.get(team_id) {
198 tracing::debug!(team_id = %team_id, "Retrieved team from cache");
199 return Ok(team.clone());
200 }
201 }
202
203 let team = self.load_team(team_id).await?;
205
206 let mut cache = self.teams_cache.write().await;
208 cache.insert(team_id.to_string(), team.clone());
209
210 tracing::info!(team_id = %team_id, "Retrieved team from storage");
211
212 Ok(team)
213 }
214
215 pub fn config_manager(&self) -> Arc<TeamConfigManager> {
217 self.config_manager.clone()
218 }
219
220 pub fn rules_manager(&self) -> Arc<SharedRulesManager> {
222 self.rules_manager.clone()
223 }
224
225 pub fn access_control(&self) -> Arc<AccessControlManager> {
227 self.access_control.clone()
228 }
229
230 pub fn sync_service(&self) -> Arc<SyncService> {
232 self.sync_service.clone()
233 }
234
235 pub fn analytics(&self) -> Arc<AnalyticsDashboard> {
237 self.analytics.clone()
238 }
239
240 async fn store_team(&self, team: &Team) -> Result<()> {
244 let storage_path = Self::resolve_team_path(&team.id)?;
245
246 if let Some(parent) = storage_path.parent() {
248 std::fs::create_dir_all(parent).map_err(|e| {
249 TeamError::StorageError(format!("Failed to create storage directory: {}", e))
250 })?;
251 }
252
253 let yaml_content = serde_yaml::to_string(team).map_err(TeamError::YamlError)?;
255
256 std::fs::write(&storage_path, yaml_content)
258 .map_err(|e| TeamError::StorageError(format!("Failed to write team file: {}", e)))?;
259
260 tracing::debug!(team_id = %team.id, path = ?storage_path, "Team stored successfully");
261
262 Ok(())
263 }
264
265 async fn load_team(&self, team_id: &str) -> Result<Team> {
267 let storage_path = Self::resolve_team_path(team_id)?;
268
269 if !storage_path.exists() {
270 return Err(TeamError::TeamNotFound(format!(
271 "Team not found: {}",
272 team_id
273 )));
274 }
275
276 let yaml_content = std::fs::read_to_string(&storage_path)
277 .map_err(|e| TeamError::StorageError(format!("Failed to read team file: {}", e)))?;
278
279 let team: Team = serde_yaml::from_str(&yaml_content).map_err(TeamError::YamlError)?;
280
281 tracing::debug!(team_id = %team_id, path = ?storage_path, "Team loaded successfully");
282
283 Ok(team)
284 }
285
286 fn resolve_team_path(team_id: &str) -> Result<PathBuf> {
288 let global_path = PathResolver::resolve_global_path()
289 .map_err(|e| TeamError::StorageError(e.to_string()))?;
290
291 let team_path = global_path.join("teams").join(team_id).join("team.yaml");
292
293 Ok(team_path)
294 }
295}
296
297impl Default for TeamManager {
298 fn default() -> Self {
299 Self::new()
300 }
301}