Skip to main content

alibi_core/store/stateless/
members.rs

1use 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}
42// Array.slice applies ToIntegerOrInfinity, supports negative indices, and treats NaN as zero.
43pub(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}