ricecoder_teams/
manager.rs

1/// Central team manager orchestrating all team operations
2use 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
17/// Central coordinator for all team operations
18pub 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    /// In-memory cache of teams (team_id -> Team)
25    teams_cache: Arc<RwLock<HashMap<String, Team>>>,
26}
27
28impl TeamManager {
29    /// Create a new TeamManager
30    pub fn new() -> Self {
31        // Create SharedRulesManager with mock implementations
32        // TODO: Replace with actual ricecoder-learning implementations
33        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        // Create AccessControlManager with default dependencies
40        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    /// Create a new team with initial members and roles
53    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        // Create team object
60        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        // Store team to persistent storage
70        self.store_team(&team).await?;
71
72        // Update cache
73        let mut cache = self.teams_cache.write().await;
74        cache.insert(team_id.clone(), team.clone());
75
76        // Assign roles to members using access control
77        for member in &members {
78            self.access_control
79                .assign_role(&team.id, &member.id, member.role)
80                .await?;
81        }
82
83        // Initialize team storage and configuration
84        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    /// Add a team member with assigned role
106    pub async fn add_member(&self, team_id: &str, member: TeamMember) -> Result<()> {
107        // Retrieve team from cache or storage
108        let mut team = self.get_team(team_id).await?;
109
110        // Check if member already exists
111        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        // Add member to team
126        team.members.push(member.clone());
127        team.updated_at = Utc::now();
128
129        // Store updated team
130        self.store_team(&team).await?;
131
132        // Update cache
133        let mut cache = self.teams_cache.write().await;
134        cache.insert(team_id.to_string(), team);
135
136        // Assign role using access control
137        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    /// Remove a team member and revoke access
151    pub async fn remove_member(&self, team_id: &str, member_id: &str) -> Result<()> {
152        // Retrieve team from cache or storage
153        let mut team = self.get_team(team_id).await?;
154
155        // Check if member exists
156        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        // Store updated team
172        self.store_team(&team).await?;
173
174        // Update cache
175        let mut cache = self.teams_cache.write().await;
176        cache.insert(team_id.to_string(), team);
177
178        // Revoke access using access control
179        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    /// Get team information and members
193    pub async fn get_team(&self, team_id: &str) -> Result<Team> {
194        // Check cache first
195        {
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        // Load from storage
204        let team = self.load_team(team_id).await?;
205
206        // Update cache
207        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    /// Get reference to config manager
216    pub fn config_manager(&self) -> Arc<TeamConfigManager> {
217        self.config_manager.clone()
218    }
219
220    /// Get reference to rules manager
221    pub fn rules_manager(&self) -> Arc<SharedRulesManager> {
222        self.rules_manager.clone()
223    }
224
225    /// Get reference to access control manager
226    pub fn access_control(&self) -> Arc<AccessControlManager> {
227        self.access_control.clone()
228    }
229
230    /// Get reference to sync service
231    pub fn sync_service(&self) -> Arc<SyncService> {
232        self.sync_service.clone()
233    }
234
235    /// Get reference to analytics dashboard
236    pub fn analytics(&self) -> Arc<AnalyticsDashboard> {
237        self.analytics.clone()
238    }
239
240    // Helper functions
241
242    /// Store team to persistent storage
243    async fn store_team(&self, team: &Team) -> Result<()> {
244        let storage_path = Self::resolve_team_path(&team.id)?;
245
246        // Ensure parent directory exists
247        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        // Serialize team to YAML
254        let yaml_content = serde_yaml::to_string(team).map_err(TeamError::YamlError)?;
255
256        // Write to file
257        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    /// Load team from persistent storage
266    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    /// Resolve the storage path for a team
287    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}