alibi_core/store/stateless/
invitations.rs1use super::*;
2
3#[async_trait]
4impl InvitationStore for StatelessStore {
5 async fn create_invitation(&self, data: CreateInvitation) -> AuthResult<Invitation> {
6 self.create_invitation_with_options(data, InvitationCreateOptions::default())
7 .await
8 }
9 async fn create_invitation_with_options(
10 &self,
11 data: CreateInvitation,
12 options: InvitationCreateOptions,
13 ) -> AuthResult<Invitation> {
14 let row = Invitation {
15 id: options
16 .id
17 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string()),
18 organization_id: data.organization_id,
19 email: data.email,
20 role: Some(data.role),
21 team_id: data.team_id,
22 status: options.status.unwrap_or_default(),
23 inviter_id: data.inviter_id,
24 expires_at: data.expires_at,
25 created_at: options.created_at.unwrap_or_else(Utc::now),
26 };
27 drop(
28 self.organization_state()?
29 .invitations
30 .insert(row.id.clone(), row.clone()),
31 );
32 Ok(row)
33 }
34 async fn get_invitation_by_id(&self, id: &str) -> AuthResult<Option<Invitation>> {
35 Ok(self.organization_state()?.invitations.get(id).cloned())
36 }
37 async fn get_pending_invitation(
38 &self,
39 org_id: &str,
40 email: &str,
41 ) -> AuthResult<Option<Invitation>> {
42 Ok(self
43 .organization_state()?
44 .invitations
45 .values()
46 .find(|row| {
47 row.organization_id == org_id
48 && row.email == email.to_lowercase()
49 && row.status == InvitationStatus::Pending
50 && row.expires_at > Utc::now()
51 })
52 .cloned())
53 }
54 async fn pending_invitation_page(
55 &self,
56 org_id: &str,
57 email: Option<&str>,
58 ) -> AuthResult<Vec<Invitation>> {
59 let email = email.map(str::to_lowercase);
60 Ok(self
61 .organization_state()?
62 .invitations
63 .values()
64 .filter(|row| {
65 row.organization_id == org_id
66 && row.status == InvitationStatus::Pending
67 && email.as_ref().is_none_or(|email| row.email == *email)
68 })
69 .take(self.find_many_limit)
70 .cloned()
71 .collect())
72 }
73 async fn update_invitation_expiry(
74 &self,
75 id: &str,
76 expires_at: DateTime<Utc>,
77 ) -> AuthResult<Invitation> {
78 let mut state = self.organization_state()?;
79 let row = state
80 .invitations
81 .get_mut(id)
82 .ok_or_else(|| AuthError::not_found("Invitation not found"))?;
83 row.expires_at = expires_at;
84 Ok(row.clone())
85 }
86 async fn update_invitation_team_ids(
87 &self,
88 id: &str,
89 team_ids: Option<String>,
90 ) -> AuthResult<Invitation> {
91 let mut state = self.organization_state()?;
92 let row = state
93 .invitations
94 .get_mut(id)
95 .ok_or_else(|| AuthError::not_found("Invitation not found"))?;
96 row.team_id = team_ids;
97 Ok(row.clone())
98 }
99 async fn update_invitation_status(
100 &self,
101 id: &str,
102 status: InvitationStatus,
103 ) -> AuthResult<Invitation> {
104 let mut state = self.organization_state()?;
105 let row = state
106 .invitations
107 .get_mut(id)
108 .ok_or_else(|| AuthError::not_found("Invitation not found"))?;
109 row.status = status;
110 Ok(row.clone())
111 }
112 async fn update_invitation_status_if_status(
113 &self,
114 id: &str,
115 expected: InvitationStatus,
116 status: InvitationStatus,
117 ) -> AuthResult<Option<Invitation>> {
118 let mut state = self.organization_state()?;
119 let Some(row) = state
120 .invitations
121 .get_mut(id)
122 .filter(|row| row.status == expected)
123 else {
124 return Ok(None);
125 };
126 row.status = status;
127 Ok(Some(row.clone()))
128 }
129 async fn list_organization_invitations(&self, org_id: &str) -> AuthResult<Vec<Invitation>> {
130 Ok(self
131 .organization_state()?
132 .invitations
133 .values()
134 .filter(|row| row.organization_id == org_id)
135 .take(self.find_many_limit)
136 .cloned()
137 .collect())
138 }
139 async fn count_pending_organization_invitations(&self, org_id: &str) -> AuthResult<i64> {
140 i64::try_from(
141 self.organization_state()?
142 .invitations
143 .values()
144 .filter(|row| {
145 row.organization_id == org_id
146 && row.status == InvitationStatus::Pending
147 && row.expires_at > Utc::now()
148 })
149 .count(),
150 )
151 .map_err(|_| AuthError::internal("Invitation count overflow"))
152 }
153 async fn list_user_invitations(&self, email: &str) -> AuthResult<Vec<Invitation>> {
154 let email = email.to_lowercase();
155 Ok(self
156 .organization_state()?
157 .invitations
158 .values()
159 .filter(|row| row.email == email)
160 .take(self.find_many_limit)
161 .cloned()
162 .collect())
163 }
164 }