Skip to main content

alibi_core/store/stateless/
teams.rs

1use super::*;
2
3impl OrganizationState {
4    pub(super) fn remove_team_members(&mut self, team_id: &str, user_id: &str) -> usize {
5        let before = self.team_members.len();
6        self.team_members
7            .retain(|_, row| row.team_id != team_id || row.user_id != user_id);
8        let removed = before - self.team_members.len();
9        if let (Some(team), Ok(removed)) = (self.teams.get_mut(team_id), i64::try_from(removed))
10            && team.member_count >= removed
11        {
12            team.member_count -= removed;
13        }
14        removed
15    }
16}
17#[async_trait]
18impl TeamStore for StatelessStore {
19    async fn create_team(&self, data: CreateTeam) -> AuthResult<Team> {
20        let row = Team {
21            id: uuid::Uuid::new_v4().to_string(),
22            name: data.name,
23            organization_id: data.organization_id,
24            created_at: data.updated_at.unwrap_or_else(Utc::now),
25            updated_at: data.updated_at,
26            member_count: 0,
27        };
28        drop(
29            self.organization_state()?
30                .teams
31                .insert(row.id.clone(), row.clone()),
32        );
33        Ok(row)
34    }
35    async fn get_team(&self, organization_id: Option<&str>, id: &str) -> AuthResult<Option<Team>> {
36        Ok(self
37            .organization_state()?
38            .teams
39            .get(id)
40            .filter(|row| organization_id.is_none_or(|org| org == row.organization_id))
41            .cloned())
42    }
43    async fn list_teams(&self, organization_id: &str) -> AuthResult<Vec<Team>> {
44        Ok(self
45            .organization_state()?
46            .teams
47            .values()
48            .filter(|row| row.organization_id == organization_id)
49            .take(self.find_many_limit)
50            .cloned()
51            .collect())
52    }
53    async fn update_team(
54        &self,
55        organization_id: &str,
56        id: &str,
57        update: UpdateTeam,
58    ) -> AuthResult<Team> {
59        let mut state = self.organization_state()?;
60        let row = state
61            .teams
62            .get_mut(id)
63            .filter(|row| row.organization_id == organization_id)
64            .ok_or_else(|| AuthError::bad_request("Team not found"))?;
65        if let Some(name) = update.name {
66            row.name = name;
67        }
68        row.updated_at = Some(Utc::now());
69        Ok(row.clone())
70    }
71    async fn delete_team(&self, organization_id: &str, id: &str) -> AuthResult<bool> {
72        let mut state = self.organization_state()?;
73        if state
74            .teams
75            .get(id)
76            .is_none_or(|row| row.organization_id != organization_id)
77        {
78            return Ok(false);
79        }
80        drop(state.teams.shift_remove(id));
81        state.team_members.retain(|_, row| row.team_id != id);
82        for invite in state
83            .invitations
84            .values_mut()
85            .filter(|row| {
86                row.organization_id == organization_id && row.status == InvitationStatus::Pending
87            })
88            .take(self.find_many_limit)
89        {
90            if invite.expires_at <= Utc::now() {
91                continue;
92            }
93            if let Some(ids) = invite
94                .team_id
95                .as_deref()
96                .filter(|ids| ids.split(',').any(|part| part == id))
97            {
98                let remaining = ids
99                    .split(',')
100                    .filter(|part| *part != id)
101                    .collect::<Vec<_>>()
102                    .join(",");
103                invite.team_id = (!remaining.is_empty()).then_some(remaining);
104            }
105        }
106        Ok(true)
107    }
108    async fn get_team_member(
109        &self,
110        team_id: &str,
111        user_id: &str,
112    ) -> AuthResult<Option<TeamMember>> {
113        Ok(self
114            .organization_state()?
115            .team_members
116            .values()
117            .find(|row| row.team_id == team_id && row.user_id == user_id)
118            .cloned())
119    }
120    async fn add_team_member(
121        &self,
122        team_id: &str,
123        user_id: &str,
124        maximum: Option<f64>,
125    ) -> AuthResult<AddTeamMemberResult> {
126        let key = team_membership_key(team_id, user_id)?;
127        let mut state = self.organization_state()?;
128        if let Some(row) = state
129            .team_members
130            .values()
131            .find(|row| row.team_id == team_id && row.user_id == user_id)
132        {
133            return Ok(AddTeamMemberResult::Existing(row.clone()));
134        }
135        let count = i64::try_from(
136            state
137                .team_members
138                .values()
139                .filter(|row| row.team_id == team_id)
140                .count(),
141        )
142        .map_err(|_| AuthError::internal("Team membership count overflow"))?;
143        let team = state
144            .teams
145            .get_mut(team_id)
146            .ok_or_else(|| AuthError::bad_request("Team not found"))?;
147        team.member_count = team.member_count.max(count);
148        // Keep the raw Number predicate, including fractions, NaN and infinities.
149        if maximum.is_some_and(|maximum| {
150            (team.member_count as f64).partial_cmp(&maximum) != Some(std::cmp::Ordering::Less)
151        }) {
152            return Ok(AddTeamMemberResult::LimitReached);
153        }
154        team.member_count = team
155            .member_count
156            .checked_add(1)
157            .ok_or_else(|| AuthError::internal("Team membership count overflow"))?;
158        let row = TeamMember {
159            id: uuid::Uuid::new_v4().to_string(),
160            team_id: team_id.into(),
161            user_id: user_id.into(),
162            created_at: Utc::now(),
163            membership_key: Some(key),
164        };
165        drop(state.team_members.insert(row.id.clone(), row.clone()));
166        Ok(AddTeamMemberResult::Added(row))
167    }
168    async fn remove_team_member(&self, team_id: &str, user_id: &str) -> AuthResult<usize> {
169        Ok(self
170            .organization_state()?
171            .remove_team_members(team_id, user_id))
172    }
173    async fn list_team_members(&self, team_id: &str) -> AuthResult<Vec<TeamMember>> {
174        Ok(self
175            .organization_state()?
176            .team_members
177            .values()
178            .filter(|row| row.team_id == team_id)
179            .take(self.find_many_limit)
180            .cloned()
181            .collect())
182    }
183    async fn list_user_teams(&self, user_id: &str) -> AuthResult<Vec<Team>> {
184        let state = self.organization_state()?;
185        Ok(state
186            .team_members
187            .values()
188            .filter(|row| row.user_id == user_id)
189            .take(self.find_many_limit)
190            .filter_map(|row| state.teams.get(&row.team_id).cloned())
191            .collect())
192    }
193}