use crate::field_policy::FieldValues;
use crate::store::stateless::{StatelessSchema, StatelessStore};
use crate::store::{
AccountStore, BoxedTransactionValue, MemberStore, SessionStore, TeamStore, TransactionStore,
TransactionWork, UserStore,
};
use crate::types::AddTeamMemberResult;
use crate::user_validation::PreparedUserCreation;
use crate::{
AccountView, AuthError, AuthResult, AuthTransaction, CreateAccount, CreateMember,
CreateSession, CreateUser, Member, SessionView, Team, UserView,
};
use async_trait::async_trait;
use chrono::{DateTime, Utc};
fn merge<T: Clone + PartialEq>(
live: &mut indexmap::IndexMap<String, T>,
base: &indexmap::IndexMap<String, T>,
changed: indexmap::IndexMap<String, T>,
) {
live.retain(|id, _| !base.contains_key(id) || changed.contains_key(id));
for (id, row) in changed {
if base.get(&id) == Some(&row) {
continue;
}
if live.contains_key(&id) || !base.contains_key(&id) {
_ = live.insert(id, row);
}
}
}
struct OrganizationTransaction<'a> {
live: &'a StatelessStore,
snapshot: StatelessStore,
}
#[async_trait]
impl TransactionStore<StatelessSchema> for StatelessStore {
async fn transaction_boxed(
&self,
work: Box<TransactionWork<StatelessSchema>>,
) -> AuthResult<BoxedTransactionValue> {
let base = self.organization_state()?.clone();
let tx = OrganizationTransaction {
live: self,
snapshot: Self {
organizations: std::sync::Mutex::new(base.clone()),
..Self::with_find_many_limit(self.find_many_limit)
},
};
let result = work(&tx).await?;
let changed = tx
.snapshot
.organizations
.into_inner()
.map_err(|_| AuthError::internal("No-database organization state poisoned"))?;
let mut live = self.organization_state()?;
merge(
&mut live.organizations,
&base.organizations,
changed.organizations,
);
merge(&mut live.members, &base.members, changed.members);
merge(
&mut live.invitations,
&base.invitations,
changed.invitations,
);
merge(&mut live.teams, &base.teams, changed.teams);
merge(
&mut live.team_members,
&base.team_members,
changed.team_members,
);
merge(&mut live.roles, &base.roles, changed.roles);
Ok(result)
}
}
#[async_trait]
impl AuthTransaction<StatelessSchema> for OrganizationTransaction<'_> {
async fn get_team(&self, org: &str, id: &str) -> AuthResult<Option<Team>> {
TeamStore::get_team(&self.snapshot, Some(org), id).await
}
async fn add_team_member(
&self,
team: &str,
user: &str,
maximum: Option<f64>,
) -> AuthResult<AddTeamMemberResult> {
TeamStore::add_team_member(&self.snapshot, team, user, maximum).await
}
async fn create_member(&self, data: CreateMember) -> AuthResult<Member> {
MemberStore::create_member(&self.snapshot, data).await
}
async fn prepare_secondary_session_creation(
&self,
input: CreateSession,
persist: bool,
) -> AuthResult<SessionView> {
SessionStore::<StatelessSchema>::prepare_secondary_session_creation(
self.live, input, persist,
)
.await
}
async fn prepare_secondary_session_update(
&self,
session: SessionView,
expires_at: Option<DateTime<Utc>>,
fields: FieldValues,
) -> AuthResult<Option<(SessionView, FieldValues)>> {
SessionStore::<StatelessSchema>::prepare_secondary_session_update(
self.live, session, expires_at, fields,
)
.await
}
async fn complete_secondary_session_update(
&self,
session: SessionView,
expires_at: Option<DateTime<Utc>>,
fields: FieldValues,
persist: bool,
) -> AuthResult<Option<SessionView>> {
SessionStore::<StatelessSchema>::complete_secondary_session_update(
self.live, session, expires_at, fields, persist,
)
.await
}
async fn create_user(&self, data: CreateUser) -> AuthResult<UserView> {
UserStore::<StatelessSchema>::create_user(self.live, data).await
}
async fn create_user_prepared(&self, data: PreparedUserCreation) -> AuthResult<UserView> {
UserStore::<StatelessSchema>::create_user_prepared(self.live, data).await
}
async fn create_account(&self, data: CreateAccount) -> AuthResult<AccountView> {
AccountStore::<StatelessSchema>::create_account(self.live, data).await
}
async fn create_session(&self, data: CreateSession) -> AuthResult<SessionView> {
SessionStore::<StatelessSchema>::create_session(self.live, data).await
}
}