alibi_core/store/stateless/
transaction.rs1use crate::field_policy::FieldValues;
2use crate::store::stateless::{StatelessSchema, StatelessStore};
3use crate::store::{
4 AccountStore, BoxedTransactionValue, MemberStore, SessionStore, TeamStore, TransactionStore,
5 TransactionWork, UserStore,
6};
7use crate::types::AddTeamMemberResult;
8use crate::user_validation::PreparedUserCreation;
9use crate::{
10 AccountView, AuthError, AuthResult, AuthTransaction, CreateAccount, CreateMember,
11 CreateSession, CreateUser, Member, SessionView, Team, UserView,
12};
13use async_trait::async_trait;
14use chrono::{DateTime, Utc};
15
16fn merge<T: Clone + PartialEq>(
20 live: &mut indexmap::IndexMap<String, T>,
21 base: &indexmap::IndexMap<String, T>,
22 changed: indexmap::IndexMap<String, T>,
23) {
24 live.retain(|id, _| !base.contains_key(id) || changed.contains_key(id));
25 for (id, row) in changed {
26 if base.get(&id) == Some(&row) {
27 continue;
28 }
29 if live.contains_key(&id) || !base.contains_key(&id) {
32 _ = live.insert(id, row);
33 }
34 }
35}
36
37struct OrganizationTransaction<'a> {
38 live: &'a StatelessStore,
39 snapshot: StatelessStore,
40}
41
42#[async_trait]
43impl TransactionStore<StatelessSchema> for StatelessStore {
44 async fn transaction_boxed(
45 &self,
46 work: Box<TransactionWork<StatelessSchema>>,
47 ) -> AuthResult<BoxedTransactionValue> {
48 let base = self.organization_state()?.clone();
49 let tx = OrganizationTransaction {
50 live: self,
51 snapshot: Self {
52 organizations: std::sync::Mutex::new(base.clone()),
53 ..Self::with_find_many_limit(self.find_many_limit)
54 },
55 };
56 let result = work(&tx).await?;
57 let changed = tx
58 .snapshot
59 .organizations
60 .into_inner()
61 .map_err(|_| AuthError::internal("No-database organization state poisoned"))?;
62 let mut live = self.organization_state()?;
63 merge(
64 &mut live.organizations,
65 &base.organizations,
66 changed.organizations,
67 );
68 merge(&mut live.members, &base.members, changed.members);
69 merge(
70 &mut live.invitations,
71 &base.invitations,
72 changed.invitations,
73 );
74 merge(&mut live.teams, &base.teams, changed.teams);
75 merge(
76 &mut live.team_members,
77 &base.team_members,
78 changed.team_members,
79 );
80 merge(&mut live.roles, &base.roles, changed.roles);
81 Ok(result)
82 }
83}
84
85#[async_trait]
86impl AuthTransaction<StatelessSchema> for OrganizationTransaction<'_> {
87 async fn get_team(&self, org: &str, id: &str) -> AuthResult<Option<Team>> {
88 TeamStore::get_team(&self.snapshot, Some(org), id).await
89 }
90 async fn add_team_member(
91 &self,
92 team: &str,
93 user: &str,
94 maximum: Option<f64>,
95 ) -> AuthResult<AddTeamMemberResult> {
96 TeamStore::add_team_member(&self.snapshot, team, user, maximum).await
97 }
98 async fn create_member(&self, data: CreateMember) -> AuthResult<Member> {
99 MemberStore::create_member(&self.snapshot, data).await
100 }
101 async fn prepare_secondary_session_creation(
102 &self,
103 input: CreateSession,
104 persist: bool,
105 ) -> AuthResult<SessionView> {
106 SessionStore::<StatelessSchema>::prepare_secondary_session_creation(
107 self.live, input, persist,
108 )
109 .await
110 }
111 async fn prepare_secondary_session_update(
112 &self,
113 session: SessionView,
114 expires_at: Option<DateTime<Utc>>,
115 fields: FieldValues,
116 ) -> AuthResult<Option<(SessionView, FieldValues)>> {
117 SessionStore::<StatelessSchema>::prepare_secondary_session_update(
118 self.live, session, expires_at, fields,
119 )
120 .await
121 }
122 async fn complete_secondary_session_update(
123 &self,
124 session: SessionView,
125 expires_at: Option<DateTime<Utc>>,
126 fields: FieldValues,
127 persist: bool,
128 ) -> AuthResult<Option<SessionView>> {
129 SessionStore::<StatelessSchema>::complete_secondary_session_update(
130 self.live, session, expires_at, fields, persist,
131 )
132 .await
133 }
134 async fn create_user(&self, data: CreateUser) -> AuthResult<UserView> {
137 UserStore::<StatelessSchema>::create_user(self.live, data).await
138 }
139 async fn create_user_prepared(&self, data: PreparedUserCreation) -> AuthResult<UserView> {
140 UserStore::<StatelessSchema>::create_user_prepared(self.live, data).await
141 }
142 async fn create_account(&self, data: CreateAccount) -> AuthResult<AccountView> {
143 AccountStore::<StatelessSchema>::create_account(self.live, data).await
144 }
145 async fn create_session(&self, data: CreateSession) -> AuthResult<SessionView> {
146 SessionStore::<StatelessSchema>::create_session(self.live, data).await
147 }
148}