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