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