1use crate::model::{self, ActiveRow, SqlxModel};
5use crate::pool::Exec;
6use crate::sql::Sql;
7use crate::store::entities::{invitation, member, organization};
8use crate::value::{ColumnKind, SqlValue};
9use alibi_core::{AuthError, AuthResult};
10use async_trait::async_trait;
11use std::{marker::PhantomData, sync::Arc};
12
13#[derive(Clone)]
16pub struct OrganizationModels {
17 pub(crate) organization: Binding<organization::Model>,
18 pub(crate) member: Binding<member::Model>,
19 pub(crate) invitation: Binding<invitation::Model>,
20}
21
22impl OrganizationModels {
23 #[must_use]
27 pub fn new<O: SqlxModel, M: SqlxModel, I: SqlxModel>(
28 organization_id: fn() -> String,
29 member_id: fn() -> String,
30 invitation_id: fn() -> String,
31 ) -> Self {
32 Self {
33 organization: Binding::new::<O>(organization_id),
34 member: Binding::new::<M>(member_id),
35 invitation: Binding::new::<I>(invitation_id),
36 }
37 }
38}
39
40impl Default for OrganizationModels {
41 fn default() -> Self {
42 fn id() -> String {
43 uuid::Uuid::new_v4().to_string()
44 }
45 Self::new::<organization::Model, member::Model, invitation::Model>(id, id, id)
46 }
47}
48
49#[async_trait]
50trait Backend: Send + Sync {
51 fn table(&self) -> &'static str;
52 fn columns(&self) -> &'static [&'static str];
53 fn fields(&self) -> &'static [(&'static str, &'static str)];
54 fn kind(&self, physical: &str) -> ColumnKind;
55 async fn fetch(&self, exec: Exec<'_>, sql: Sql) -> AuthResult<Vec<ActiveRow>>;
56 async fn insert(&self, exec: Exec<'_>, row: &ActiveRow) -> AuthResult<ActiveRow>;
57 async fn update(&self, exec: Exec<'_>, row: &ActiveRow) -> AuthResult<Option<ActiveRow>>;
58}
59struct TypedBackend<M>(PhantomData<M>);
60#[async_trait]
61impl<M: SqlxModel> Backend for TypedBackend<M> {
62 fn table(&self) -> &'static str {
63 M::TABLE
64 }
65 fn columns(&self) -> &'static [&'static str] {
66 M::COLUMN_NAMES
67 }
68 fn fields(&self) -> &'static [(&'static str, &'static str)] {
69 M::FIELD_COLUMNS
70 }
71 fn kind(&self, physical: &str) -> ColumnKind {
72 M::column_kind(physical)
73 }
74 async fn fetch(&self, exec: Exec<'_>, sql: Sql) -> AuthResult<Vec<ActiveRow>> {
75 Ok(exec
76 .fetch_all::<M>(sql)
77 .await?
78 .into_iter()
79 .map(SqlxModel::into_active)
80 .collect())
81 }
82 async fn insert(&self, exec: Exec<'_>, row: &ActiveRow) -> AuthResult<ActiveRow> {
83 Ok(model::insert::<M>(exec, row).await?.into_active())
84 }
85 async fn update(&self, exec: Exec<'_>, row: &ActiveRow) -> AuthResult<Option<ActiveRow>> {
86 Ok(model::update::<M>(exec, row)
87 .await?
88 .map(SqlxModel::into_active))
89 }
90}
91
92#[derive(Clone)]
93pub(crate) struct Binding<T> {
94 backend: Arc<dyn Backend>,
95 generate_id: fn() -> String,
96 _canonical: PhantomData<T>,
97}
98
99pub(crate) struct Row<T> {
102 value: T,
103 active: ActiveRow,
104}
105impl<T> std::ops::Deref for Row<T> {
106 type Target = T;
107 fn deref(&self) -> &T {
108 &self.value
109 }
110}
111impl<T> Row<T> {
112 pub(crate) fn active_role(&self) -> SqlValue {
113 self.active
114 .get("role")
115 .and_then(crate::ActiveValue::value)
116 .cloned()
117 .unwrap_or(SqlValue::Text(None))
118 }
119 pub(crate) fn into_active(self) -> ActiveRow {
120 self.active
121 }
122}
123
124impl<T: SqlxModel> Binding<T> {
125 fn new<M: SqlxModel>(generate_id: fn() -> String) -> Self {
126 Self {
127 backend: Arc::new(TypedBackend::<M>(PhantomData)),
128 generate_id,
129 _canonical: PhantomData,
130 }
131 }
132 pub(crate) async fn set_fields(
133 &self,
134 exec: Exec<'_>,
135 active: &mut ActiveRow,
136 fields: &mut alibi_core::field_policy::FieldValues,
137 ) -> AuthResult<()> {
138 fields.apply_adapter_transforms_async().await?;
139 for (name, value) in &*fields {
140 let requested = fields.binding_name(name);
141 let column = self.physical(requested).ok_or_else(|| {
142 AuthError::config(format!(
143 "configured organization field {name} has no model column"
144 ))
145 })?;
146 let field = self.logical(column);
147 if T::COLUMN_NAMES.contains(&field) {
148 return Err(AuthError::config(
149 "additional organization fields cannot overwrite plugin fields",
150 ));
151 }
152 let value = crate::additional_fields::prepare_value(
153 exec,
154 self.backend.kind(column),
155 crate::additional_fields::raw_value(value)?,
156 )
157 .await?;
158 active.set(field, value);
159 }
160 Ok(())
161 }
162 pub(crate) fn new_id(&self) -> String {
163 (self.generate_id)()
164 }
165 pub(crate) fn table(&self) -> &'static str {
166 self.backend.table()
167 }
168 fn physical(&self, field: &str) -> Option<&'static str> {
169 self.backend
170 .fields()
171 .iter()
172 .find(|(logical, _)| *logical == field)
173 .map(|(_, physical)| *physical)
174 .or_else(|| {
175 self.backend
176 .columns()
177 .iter()
178 .copied()
179 .find(|column| *column == field)
180 })
181 }
182 fn logical(&self, physical: &'static str) -> &'static str {
183 self.backend
184 .fields()
185 .iter()
186 .find(|(_, column)| *column == physical)
187 .map_or(physical, |(logical, _)| *logical)
188 }
189 fn value(&self, field: &str, value: SqlValue) -> AuthResult<SqlValue> {
190 let column = self.physical(field).ok_or_else(|| {
191 AuthError::internal(format!("organization model has no {field} column"))
192 })?;
193 Ok(match (self.backend.kind(column), value) {
194 (ColumnKind::BpChar, SqlValue::Text(value)) => SqlValue::BpChar(value),
195 (ColumnKind::NaiveTimestamp, SqlValue::Timestamp(value)) => {
196 SqlValue::NaiveTimestamp(value.map(|time| time.naive_utc()))
197 }
198 (ColumnKind::Text, SqlValue::Json(value)) => SqlValue::Text(
199 value
200 .map(|value| alibi_core::utils::json::to_string(&*value))
201 .transpose()?,
202 ),
203 (_, value) => value,
204 })
205 }
206 fn stage(&self, active: &ActiveRow) -> AuthResult<ActiveRow> {
207 let mut physical = ActiveRow::new();
208 for (field, value) in active.present() {
209 let Some(column) = self.physical(field) else {
210 if field == "updated_at" || (field == "team_id" && value.is_null()) {
211 continue;
212 }
213 return Err(AuthError::internal(format!(
214 "organization model has no {field} column"
215 )));
216 };
217 let value = self.value(field, value.clone())?;
218 if active.get(field).is_some_and(crate::ActiveValue::is_set) {
219 physical.set(column, value);
220 } else {
221 physical.unchanged(column, value);
222 }
223 }
224 Ok(physical)
225 }
226 fn row(&self, physical: ActiveRow) -> AuthResult<Row<T>> {
227 let mut active = ActiveRow::new();
228 for (column, value) in physical.present() {
229 let field = self.logical(column);
230 let value = match value.clone() {
231 SqlValue::NaiveTimestamp(value) => {
232 SqlValue::Timestamp(value.map(|time| time.and_utc()))
233 }
234 SqlValue::BpChar(value) => SqlValue::Text(value),
235 value => value,
236 };
237 active.unchanged(field, value);
238 }
239 let mut canonical = active.clone();
240 if T::TABLE == organization::Model::TABLE {
241 if self.physical("updated_at").is_none()
242 && let Some(time) = active.get("created_at").and_then(crate::ActiveValue::value)
243 {
244 canonical.unchanged("updated_at", time.clone());
245 }
246 if canonical
247 .get("metadata")
248 .and_then(crate::ActiveValue::value)
249 .is_some_and(SqlValue::is_null)
250 {
251 canonical.unchanged("metadata", SqlValue::Json(None));
252 }
253 }
254 if T::TABLE == invitation::Model::TABLE {
255 if self.physical("team_id").is_none() {
256 canonical.unchanged("team_id", None::<String>);
257 }
258 if canonical
261 .get("role")
262 .and_then(crate::ActiveValue::value)
263 .is_some_and(SqlValue::is_null)
264 {
265 canonical.unchanged("role", "");
266 }
267 }
268 Ok(Row {
269 value: T::from_active(canonical)?,
270 active,
271 })
272 }
273 pub(crate) fn select(&self, exec: Exec<'_>) -> Sql {
274 crate::sql::select(exec.engine(), self.table(), self.backend.columns())
275 }
276 pub(crate) fn by_id(&self, exec: Exec<'_>, id: impl Into<SqlValue>) -> AuthResult<Sql> {
277 let mut sql = self.select(exec);
278 sql.push(" WHERE ");
279 self.compare(&mut sql, "id", " = ", id)?;
280 Ok(sql)
281 }
282 pub(crate) fn delete_for_organization(&self, exec: Exec<'_>, id: &str) -> AuthResult<Sql> {
283 let mut sql = Sql::with(exec.engine(), "DELETE FROM ");
284 sql.ident(self.table());
285 sql.push(" WHERE ");
286 self.compare(&mut sql, "organization_id", " = ", id)?;
287 Ok(sql)
288 }
289 pub(crate) fn delete_by_id(&self, exec: Exec<'_>, id: impl Into<SqlValue>) -> AuthResult<Sql> {
290 let mut sql = Sql::with(exec.engine(), "DELETE FROM ");
291 sql.ident(self.table());
292 sql.push(" WHERE ");
293 self.compare(&mut sql, "id", " = ", id)?;
294 Ok(sql)
295 }
296 pub(crate) fn column(&self, sql: &mut Sql, field: &str) -> AuthResult<()> {
297 let column = self.physical(field).ok_or_else(|| {
298 AuthError::internal(format!("organization model has no {field} column"))
299 })?;
300 sql.column(self.table(), column);
301 Ok(())
302 }
303 pub(crate) fn compare(
304 &self,
305 sql: &mut Sql,
306 field: &str,
307 operator: &str,
308 value: impl Into<SqlValue>,
309 ) -> AuthResult<()> {
310 self.column(sql, field)?;
311 sql.push(operator);
312 sql.bind(self.value(field, value.into())?);
313 Ok(())
314 }
315 pub(crate) fn assign(
316 &self,
317 sql: &mut Sql,
318 field: &str,
319 value: impl Into<SqlValue>,
320 ) -> AuthResult<()> {
321 let column = self.physical(field).ok_or_else(|| {
322 AuthError::internal(format!("organization model has no {field} column"))
323 })?;
324 sql.assign(column, self.value(field, value.into())?);
325 Ok(())
326 }
327 pub(crate) fn bind_list<I: IntoIterator<Item = String>>(
328 &self,
329 sql: &mut Sql,
330 field: &str,
331 values: I,
332 ) -> AuthResult<()> {
333 sql.bind_list(
334 values
335 .into_iter()
336 .map(|value| self.value(field, value.into()))
337 .collect::<AuthResult<Vec<_>>>()?,
338 );
339 Ok(())
340 }
341 pub(crate) fn returning(&self, sql: &mut Sql) {
342 sql.push(" RETURNING ");
343 sql.column_list(self.backend.columns());
344 }
345 pub(crate) async fn fetch_all(&self, exec: Exec<'_>, sql: Sql) -> AuthResult<Vec<Row<T>>> {
346 self.backend
347 .fetch(exec, sql)
348 .await?
349 .into_iter()
350 .map(|active| self.row(active))
351 .collect()
352 }
353 pub(crate) async fn fetch_optional(
354 &self,
355 exec: Exec<'_>,
356 sql: Sql,
357 ) -> AuthResult<Option<Row<T>>> {
358 Ok(self.fetch_all(exec, sql).await?.into_iter().next())
359 }
360 pub(crate) async fn insert(&self, exec: Exec<'_>, active: &ActiveRow) -> AuthResult<Row<T>> {
361 self.row(self.backend.insert(exec, &self.stage(active)?).await?)
362 }
363 pub(crate) async fn update(
364 &self,
365 exec: Exec<'_>,
366 active: &ActiveRow,
367 ) -> AuthResult<Option<Row<T>>> {
368 self.backend
369 .update(exec, &self.stage(active)?)
370 .await?
371 .map(|active| self.row(active))
372 .transpose()
373 }
374}
375
376impl From<&Row<organization::Model>> for alibi_core::Organization {
377 fn from(row: &Row<organization::Model>) -> Self {
378 let mut organization = Self::from(&row.value);
379 for (field, value) in row.active.present() {
380 if organization::Model::COLUMN_NAMES.contains(&field) {
381 continue;
382 }
383 if let Some(value) = output_value(value) {
384 _ = organization
385 .additional_fields
386 .insert(field.to_owned(), value);
387 }
388 }
389 organization
390 }
391}
392impl From<&Row<member::Model>> for alibi_core::Member {
393 fn from(row: &Row<member::Model>) -> Self {
394 Self::from(&row.value)
395 }
396}
397impl From<&Row<invitation::Model>> for alibi_core::Invitation {
398 fn from(row: &Row<invitation::Model>) -> Self {
399 let mut invitation = Self::from(&row.value);
400 if row
401 .active
402 .get("role")
403 .and_then(crate::ActiveValue::value)
404 .is_some_and(SqlValue::is_null)
405 {
406 invitation.role = None;
407 }
408 invitation
409 }
410}
411
412fn output_value(value: &SqlValue) -> Option<serde_json::Value> {
413 if value.is_null() {
414 return Some(serde_json::Value::Null);
415 }
416 match value {
417 SqlValue::Text(Some(value)) | SqlValue::BpChar(Some(value)) => Some(value.clone().into()),
418 SqlValue::Bool(Some(value)) => Some((*value).into()),
419 SqlValue::Int(Some(value)) => Some((*value).into()),
420 SqlValue::BigInt(Some(value)) => Some((*value).into()),
421 SqlValue::Float(Some(value)) => {
422 serde_json::Number::from_f64(f64::from(*value)).map(Into::into)
423 }
424 SqlValue::Double(Some(value)) => serde_json::Number::from_f64(*value).map(Into::into),
425 SqlValue::Json(Some(value)) => Some((**value).clone()),
426 _ => None,
427 }
428}