alibi_core/store/stateless/
teams.rs1use 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 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}