alibi_core/store/stateless/
members.rs1use super::*;
2use std::cmp::Ordering;
3
4fn field<'a>(row: &'a Member, name: &str) -> Option<&'a str> {
5 match name {
6 "id" => Some(&row.id),
7 "organizationId" => Some(&row.organization_id),
8 "userId" => Some(&row.user_id),
9 "role" => Some(&row.role),
10 _ => None,
11 }
12}
13fn comparison(order: Ordering, operator: &str) -> bool {
14 match operator {
15 "ne" => order != Ordering::Equal,
16 "gt" => order == Ordering::Greater,
17 "gte" => order != Ordering::Less,
18 "lt" => order == Ordering::Less,
19 "lte" => order != Ordering::Greater,
20 _ => order == Ordering::Equal,
21 }
22}
23fn matches(row: &Member, query: &MemberPageQuery) -> bool {
24 let (Some(name), Some(value)) = (query.filter_field.as_deref(), query.filter_value.as_deref())
25 else {
26 return true;
27 };
28 let operator = query.filter_operator.as_deref().unwrap_or("eq");
29 if name == "createdAt" {
30 return chrono::DateTime::parse_from_rfc3339(value).is_ok_and(|value| {
31 comparison(row.created_at.cmp(&value.with_timezone(&Utc)), operator)
32 });
33 }
34 field(row, name).is_some_and(|stored| {
35 if operator == "contains" {
36 stored.contains(value)
37 } else {
38 comparison(stored.encode_utf16().cmp(value.encode_utf16()), operator)
39 }
40 })
41}
42pub(super) fn slice_index(value: f64, length: usize) -> usize {
44 if value.is_nan() {
45 return 0;
46 }
47 let value = value.trunc();
48 if value < 0.0 {
49 (length as f64 + value.trunc()).max(0.0) as usize
50 } else {
51 value.min(length as f64) as usize
52 }
53}
54
55#[async_trait]
56impl MemberStore for StatelessStore {
57 async fn create_member(&self, data: CreateMember) -> AuthResult<Member> {
58 let row = Member {
59 id: uuid::Uuid::new_v4().to_string(),
60 organization_id: data.organization_id,
61 user_id: data.user_id,
62 role: data.role,
63 created_at: Utc::now(),
64 };
65 drop(
66 self.organization_state()?
67 .members
68 .insert(row.id.clone(), row.clone()),
69 );
70 Ok(row)
71 }
72 async fn get_member(&self, organization_id: &str, user_id: &str) -> AuthResult<Option<Member>> {
73 Ok(self
74 .organization_state()?
75 .members
76 .values()
77 .find(|row| row.organization_id == organization_id && row.user_id == user_id)
78 .cloned())
79 }
80 async fn get_member_by_id(&self, id: &str) -> AuthResult<Option<Member>> {
81 Ok(self.organization_state()?.members.get(id).cloned())
82 }
83 async fn update_member_role(&self, id: &str, role: &str) -> AuthResult<Member> {
84 self.update_member_role_if_present(id, role)
85 .await?
86 .ok_or_else(|| AuthError::not_found("Member not found"))
87 }
88 async fn update_member_role_if_present(
89 &self,
90 id: &str,
91 role: &str,
92 ) -> AuthResult<Option<Member>> {
93 let mut state = self.organization_state()?;
94 let Some(row) = state.members.get_mut(id) else {
95 return Ok(None);
96 };
97 row.role = role.to_owned();
98 Ok(Some(row.clone()))
99 }
100 async fn delete_member(&self, id: &str) -> AuthResult<()> {
101 drop(self.organization_state()?.members.shift_remove(id));
102 Ok(())
103 }
104 async fn delete_member_with_context(
105 &self,
106 id: &str,
107 organization_id: &str,
108 user_id: &str,
109 remove_team_members: bool,
110 ) -> AuthResult<()> {
111 let mut state = self.organization_state()?;
112 drop(state.members.shift_remove(id));
113 if remove_team_members {
114 let teams: Vec<_> = state
115 .teams
116 .values()
117 .filter(|row| row.organization_id == organization_id)
118 .take(self.find_many_limit)
119 .map(|row| row.id.clone())
120 .collect();
121 for team in teams {
122 let _removed = state.remove_team_members(&team, user_id);
123 }
124 }
125 Ok(())
126 }
127 async fn list_organization_members_page(
128 &self,
129 organization_id: &str,
130 limit: usize,
131 ) -> AuthResult<Vec<Member>> {
132 Ok(self
133 .organization_state()?
134 .members
135 .values()
136 .filter(|row| row.organization_id == organization_id)
137 .take(limit)
138 .cloned()
139 .collect())
140 }
141 async fn list_organization_members(&self, org_id: &str) -> AuthResult<Vec<Member>> {
142 let mut rows = self
143 .list_organization_members_page(org_id, usize::MAX)
144 .await?;
145 rows.sort_by_key(|row| row.created_at);
146 Ok(rows)
147 }
148 async fn query_organization_members(
149 &self,
150 params: &ListOrganizationMembersParams,
151 ) -> AuthResult<(Vec<Member>, usize)> {
152 self.query_organization_members_page(&MemberPageQuery {
153 organization_id: params.organization_id.clone(),
154 limit: params.limit.map(|v| v as f64),
155 offset: params.offset.map(|v| v as f64),
156 sort_by: Some(params.sort_by.clone().unwrap_or_else(|| "createdAt".into())),
157 sort_direction: Some(
158 params
159 .sort_direction
160 .clone()
161 .unwrap_or_else(|| "asc".into()),
162 ),
163 filter_field: params.filter_field.clone(),
164 filter_value: params.filter_value.clone(),
165 filter_operator: params.filter_operator.clone(),
166 })
167 .await
168 }
169 async fn query_organization_members_page(
170 &self,
171 query: &MemberPageQuery,
172 ) -> AuthResult<(Vec<Member>, usize)> {
173 let mut rows: Vec<_> = self
174 .organization_state()?
175 .members
176 .values()
177 .filter(|row| row.organization_id == query.organization_id && matches(row, query))
178 .cloned()
179 .collect();
180 let total = rows.len();
181 if let Some(name) = query.sort_by.as_deref() {
182 rows.sort_by(|a, b| {
183 let order = if name == "createdAt" {
184 a.created_at.cmp(&b.created_at)
185 } else {
186 field(a, name).cmp(&field(b, name))
187 };
188 if query.sort_direction.as_deref() == Some("desc") {
189 order.reverse()
190 } else {
191 order
192 }
193 });
194 }
195 let start = query
196 .offset
197 .map_or(0, |value| slice_index(value, rows.len()));
198 drop(rows.drain(..start));
199 rows.truncate(slice_index(
200 query.limit.unwrap_or(self.find_many_limit as f64),
201 rows.len(),
202 ));
203 Ok((rows, total))
204 }
205 async fn count_organization_members(&self, org_id: &str) -> AuthResult<i64> {
206 i64::try_from(
207 self.organization_state()?
208 .members
209 .values()
210 .filter(|row| row.organization_id == org_id)
211 .count(),
212 )
213 .map_err(|_| AuthError::internal("Member count overflow"))
214 }
215 async fn count_organization_owners(&self, org_id: &str) -> AuthResult<i64> {
216 i64::try_from(
217 self.organization_state()?
218 .members
219 .values()
220 .filter(|row| row.organization_id == org_id && row.role == "owner")
221 .count(),
222 )
223 .map_err(|_| AuthError::internal("Owner count overflow"))
224 }
225}