Skip to main content

alibi_sqlx/store/
accounts.rs

1use super::SqlxStore;
2use crate::error::record_not_updated;
3use crate::model::{self, SqlxModel};
4use crate::pool::{Exec, SqlxTransaction};
5use crate::schema::{AuthSchema, SqlxAccountModel};
6use crate::sql::Sql;
7use alibi_core::error::AuthResult;
8use alibi_core::store::AccountStore;
9use alibi_core::store::adapter::cancelled_by_hook;
10use alibi_core::types::{CreateAccount, UpdateAccount};
11use alibi_core::{AuthError, DatabaseError};
12use async_trait::async_trait;
13use chrono::Utc;
14
15impl<S> SqlxStore<S>
16where
17    S: AuthSchema,
18    S::Account: SqlxAccountModel,
19{
20    async fn create_account_with_connection(
21        &self,
22        exec: Exec<'_>,
23        tx: Option<&SqlxTransaction>,
24        mut create_account: CreateAccount,
25    ) -> AuthResult<S::Account> {
26        let hook_context = self.hook_context(tx);
27        for hook in self.hooks() {
28            if hook
29                .before_create_account(&mut create_account, &hook_context)
30                .await?
31                .is_cancelled()
32            {
33                return Err(cancelled_by_hook("account creation"));
34            }
35        }
36        let now = Utc::now();
37        let mut fields = std::mem::take(&mut create_account.additional_fields);
38        fields.apply_adapter_transforms_async().await?;
39        let generated_id = self
40            .generated_id(
41                exec,
42                "account",
43                <S::Account as SqlxModel>::TABLE,
44                S::Account::id_column(),
45            )
46            .await?;
47        let id = generated_id
48            .as_deref()
49            .map(S::Account::parse_id)
50            .transpose()?;
51        let mut active = S::Account::new_active(id, create_account, now);
52        let backend = exec.engine();
53        for (column, value) in S::Account::additional_field_bindings(&fields, backend)? {
54            let value = crate::additional_fields::prepare_value(
55                exec,
56                <S::Account as SqlxModel>::column_kind(column),
57                value,
58            )
59            .await?;
60            S::Account::set_additional_field(&mut active, column, value, backend)?;
61        }
62        let account = model::insert::<S::Account>(exec, &active).await?;
63        if tx.is_none() {
64            for hook in self.hooks() {
65                hook.after_create_account(&account, &hook_context).await?;
66            }
67        }
68        Ok(account)
69    }
70
71    pub(crate) async fn create_account_in_tx(
72        &self,
73        tx: &SqlxTransaction,
74        create_account: CreateAccount,
75    ) -> AuthResult<S::Account> {
76        self.create_account_with_connection(Exec::tx(tx), Some(tx), create_account)
77            .await
78    }
79
80    async fn find_account_by_id(&self, id: &str) -> AuthResult<Option<S::Account>> {
81        let account_id = <S::Account as SqlxAccountModel>::parse_id(id)?;
82        let mut sql = model::select_model::<S::Account>(self.exec());
83        sql.push(" WHERE ");
84        sql.compare_model::<S::Account>(
85            <S::Account as SqlxModel>::TABLE,
86            S::Account::id_column(),
87            " = ",
88            account_id,
89        );
90        sql.push(" LIMIT 1");
91        self.exec().fetch_optional(sql).await
92    }
93}
94
95#[async_trait]
96impl<S> AccountStore<S> for SqlxStore<S>
97where
98    S: AuthSchema + Send + Sync,
99    S::Account: SqlxAccountModel,
100{
101    async fn provider_token_text(&self, value: &serde_json::Value) -> AuthResult<Option<String>> {
102        if value.is_object() || value.is_array() {
103            return Err(alibi_core::AuthError::internal(
104                "Unsupported provider token SQL parameter",
105            ));
106        }
107        let value = crate::additional_fields::raw_value(&alibi_core::utils::json::JsValue::from(
108            value.clone(),
109        ))?;
110        crate::additional_fields::prepare_string_value(self.exec(), value).await
111    }
112
113    async fn create_account(&self, create_account: CreateAccount) -> AuthResult<S::Account> {
114        self.create_account_with_connection(self.exec(), None, create_account)
115            .await
116    }
117
118    async fn get_account(
119        &self,
120        provider: &str,
121        provider_account_id: &str,
122    ) -> AuthResult<Option<S::Account>> {
123        let table = <S::Account as SqlxModel>::TABLE;
124        let mut sql = model::select_model::<S::Account>(self.exec());
125        sql.push(" WHERE ");
126        sql.compare_model::<S::Account>(table, S::Account::provider_id_column(), " = ", provider);
127        sql.push(" AND ");
128        sql.compare_model::<S::Account>(
129            table,
130            S::Account::account_id_column(),
131            " = ",
132            provider_account_id,
133        );
134        sql.push(" LIMIT ");
135        sql.bind(2_i64);
136        let mut accounts: Vec<S::Account> = self.exec().fetch_all(sql).await?;
137        if accounts.len() > 1 {
138            return Err(AuthError::Database(DatabaseError::AmbiguousAccount {
139                provider: provider.to_owned(),
140            }));
141        }
142        Ok(accounts.pop())
143    }
144
145    async fn get_user_accounts(&self, user_id: &str) -> AuthResult<Vec<S::Account>> {
146        let user_id = <S::Account as SqlxAccountModel>::parse_user_id(user_id)?;
147        let mut sql = model::select_model::<S::Account>(self.exec());
148        sql.push(" WHERE ");
149        sql.compare_model::<S::Account>(
150            <S::Account as SqlxModel>::TABLE,
151            S::Account::user_id_column(),
152            " = ",
153            user_id,
154        );
155        self.exec().fetch_all(sql).await
156    }
157
158    async fn update_account(&self, id: &str, mut update: UpdateAccount) -> AuthResult<S::Account> {
159        _ = <S::Account as SqlxAccountModel>::parse_id(id)?;
160        let hook_context = self.hook_context(None);
161        for hook in self.hooks() {
162            if hook
163                .before_update_account(id, &mut update, &hook_context)
164                .await?
165                .is_cancelled()
166            {
167                return Err(cancelled_by_hook("account update"));
168            }
169        }
170        let Some(model) = self.find_account_by_id(id).await? else {
171            return Err(alibi_core::error::AuthError::not_found("Account not found"));
172        };
173
174        let mut active = model.into_active();
175        let mut fields = std::mem::take(&mut update.additional_fields);
176        fields.apply_adapter_transforms_async().await?;
177        S::Account::apply_update(&mut active, update, Utc::now());
178        let backend = self.exec().engine();
179        for (column, value) in S::Account::additional_field_bindings(&fields, backend)? {
180            let value = crate::additional_fields::prepare_value(
181                self.exec(),
182                <S::Account as SqlxModel>::column_kind(column),
183                value,
184            )
185            .await?;
186            S::Account::set_additional_field(&mut active, column, value, backend)?;
187        }
188
189        let account = model::update::<S::Account>(self.exec(), &active)
190            .await?
191            .ok_or_else(record_not_updated)?;
192        for hook in self.hooks() {
193            hook.after_update_account(&account, &hook_context).await?;
194        }
195        Ok(account)
196    }
197
198    async fn delete_account(&self, id: &str) -> AuthResult<()> {
199        let account_id = <S::Account as SqlxAccountModel>::parse_id(id)?;
200        let Some(account_model) = self.find_account_by_id(id).await? else {
201            return Err(alibi_core::error::AuthError::not_found("Account not found"));
202        };
203        let hook_context = self.hook_context(None);
204        for hook in self.hooks() {
205            if hook
206                .before_delete_account(&account_model, &hook_context)
207                .await?
208                .is_cancelled()
209            {
210                return Ok(());
211            }
212        }
213        let table = <S::Account as SqlxModel>::TABLE;
214        let mut sql = Sql::with(self.exec().engine(), "DELETE FROM ");
215        sql.ident(table);
216        sql.push(" WHERE ");
217        sql.compare_model::<S::Account>(table, S::Account::id_column(), " = ", account_id);
218        _ = self.exec().execute(sql).await?;
219        for hook in self.hooks() {
220            hook.after_delete_account(&account_model, &hook_context)
221                .await?;
222        }
223        Ok(())
224    }
225}