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}