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 && self.physical("team_id").is_none() {
256 canonical.unchanged("team_id", None::<String>);
257 }
258 let mut model = ActiveRow::new();
259 for (logical, physical) in T::FIELD_COLUMNS {
260 if let Some(value) = canonical.get(logical).and_then(crate::ActiveValue::value) {
261 model.unchanged(physical, value.clone());
262 }
263 }
264 Ok(Row {
265 value: T::from_active(model)?,
266 active,
267 })
268 }
269 pub(crate) fn select(&self, exec: Exec<'_>) -> Sql {
270 crate::sql::select(exec.engine(), self.table(), self.backend.columns())
271 }
272 pub(crate) fn by_id(&self, exec: Exec<'_>, id: impl Into<SqlValue>) -> AuthResult<Sql> {
273 let mut sql = self.select(exec);
274 sql.push(" WHERE ");
275 self.compare(&mut sql, "id", " = ", id)?;
276 Ok(sql)
277 }
278 pub(crate) fn delete_for_organization(&self, exec: Exec<'_>, id: &str) -> AuthResult<Sql> {
279 let mut sql = Sql::with(exec.engine(), "DELETE FROM ");
280 sql.ident(self.table());
281 sql.push(" WHERE ");
282 self.compare(&mut sql, "organization_id", " = ", id)?;
283 Ok(sql)
284 }
285 pub(crate) fn delete_by_id(&self, exec: Exec<'_>, id: impl Into<SqlValue>) -> AuthResult<Sql> {
286 let mut sql = Sql::with(exec.engine(), "DELETE FROM ");
287 sql.ident(self.table());
288 sql.push(" WHERE ");
289 self.compare(&mut sql, "id", " = ", id)?;
290 Ok(sql)
291 }
292 pub(crate) fn column(&self, sql: &mut Sql, field: &str) -> AuthResult<()> {
293 let column = self.physical(field).ok_or_else(|| {
294 AuthError::internal(format!("organization model has no {field} column"))
295 })?;
296 sql.column(self.table(), column);
297 Ok(())
298 }
299 pub(crate) fn compare(
300 &self,
301 sql: &mut Sql,
302 field: &str,
303 operator: &str,
304 value: impl Into<SqlValue>,
305 ) -> AuthResult<()> {
306 self.column(sql, field)?;
307 sql.push(operator);
308 sql.bind(self.value(field, value.into())?);
309 Ok(())
310 }
311 pub(crate) fn assign(
312 &self,
313 sql: &mut Sql,
314 field: &str,
315 value: impl Into<SqlValue>,
316 ) -> AuthResult<()> {
317 let column = self.physical(field).ok_or_else(|| {
318 AuthError::internal(format!("organization model has no {field} column"))
319 })?;
320 sql.assign(column, self.value(field, value.into())?);
321 Ok(())
322 }
323 pub(crate) fn bind_list<I: IntoIterator<Item = String>>(
324 &self,
325 sql: &mut Sql,
326 field: &str,
327 values: I,
328 ) -> AuthResult<()> {
329 sql.bind_list(
330 values
331 .into_iter()
332 .map(|value| self.value(field, value.into()))
333 .collect::<AuthResult<Vec<_>>>()?,
334 );
335 Ok(())
336 }
337 pub(crate) fn returning(&self, sql: &mut Sql) {
338 sql.push(" RETURNING ");
339 sql.column_list(self.backend.columns());
340 }
341 pub(crate) async fn fetch_all(&self, exec: Exec<'_>, sql: Sql) -> AuthResult<Vec<Row<T>>> {
342 self.backend
343 .fetch(exec, sql)
344 .await?
345 .into_iter()
346 .map(|active| self.row(&active))
347 .collect()
348 }
349 pub(crate) async fn fetch_optional(
350 &self,
351 exec: Exec<'_>,
352 sql: Sql,
353 ) -> AuthResult<Option<Row<T>>> {
354 Ok(self.fetch_all(exec, sql).await?.into_iter().next())
355 }
356 pub(crate) async fn insert(&self, exec: Exec<'_>, active: &ActiveRow) -> AuthResult<Row<T>> {
357 self.row(&self.backend.insert(exec, &self.stage(active)?).await?)
358 }
359 pub(crate) async fn update(
360 &self,
361 exec: Exec<'_>,
362 active: &ActiveRow,
363 ) -> AuthResult<Option<Row<T>>> {
364 self.backend
365 .update(exec, &self.stage(active)?)
366 .await?
367 .map(|active| self.row(&active))
368 .transpose()
369 }
370}
371
372impl From<&Row<organization::Model>> for Organization {
373 fn from(row: &Row<organization::Model>) -> Self {
374 let mut organization = Self::from(&row.value);
375 for (field, value) in row.active.present() {
376 if organization::Model::COLUMN_NAMES.contains(&field) {
377 continue;
378 }
379 if let Some(value) = output_value(value) {
380 _ = organization
381 .additional_fields
382 .insert(field.to_owned(), value);
383 }
384 }
385 organization
386 }
387}
388impl From<&Row<member::Model>> for Member {
389 fn from(row: &Row<member::Model>) -> Self {
390 Self::from(&row.value)
391 }
392}
393impl From<&Row<invitation::Model>> for Invitation {
394 fn from(row: &Row<invitation::Model>) -> Self {
395 let mut invitation = Self::from(&row.value);
396 if row
397 .active
398 .get("role")
399 .and_then(crate::ActiveValue::value)
400 .is_some_and(SqlValue::is_null)
401 {
402 invitation.role = None;
403 }
404 invitation
405 }
406}
407
408fn output_value(value: &SqlValue) -> Option<serde_json::Value> {
409 if value.is_null() {
410 return Some(serde_json::Value::Null);
411 }
412 match value {
413 SqlValue::Text(Some(value)) | SqlValue::BpChar(Some(value)) => Some(value.clone().into()),
414 SqlValue::Bool(Some(value)) => Some((*value).into()),
415 SqlValue::Int(Some(value)) => Some((*value).into()),
416 SqlValue::BigInt(Some(value)) => Some((*value).into()),
417 SqlValue::Float(Some(value)) => {
418 serde_json::Number::from_f64(f64::from(*value)).map(Into::into)
419 }
420 SqlValue::Double(Some(value)) => serde_json::Number::from_f64(*value).map(Into::into),
421 SqlValue::Json(Some(value)) => Some((**value).clone()),
422 _ => None,
423 }
424}