107 lines
3.1 KiB
Rust
107 lines
3.1 KiB
Rust
|
|
use derive_builder::Builder;
|
||
|
|
use sqlx::{query_as, types::Json};
|
||
|
|
use uuid::Uuid;
|
||
|
|
|
||
|
|
use crate::{client::AppDbClient, expression::PgExpressionAny};
|
||
|
|
|
||
|
|
/// A form transition directionally connects two portals within the same
|
||
|
|
/// workspace, representing a potential navigation of a user between two forms.
|
||
|
|
/// If the user submits a form, form transitions with `source_id` corresponding
|
||
|
|
/// to that portal will be evaluated one by one (in order by ID---that is, by
|
||
|
|
/// creation time), and the first with a condition evaluating to true will be
|
||
|
|
/// used to direct the user to the form corresponding to portal `dest_id`.
|
||
|
|
#[derive(Clone, Debug)]
|
||
|
|
pub struct FormTransition {
|
||
|
|
/// Primary key (defaults to UUIDv7).
|
||
|
|
pub id: Uuid,
|
||
|
|
|
||
|
|
/// When a user is filling out a sequence of forms, this is the ID of the
|
||
|
|
/// portal for which they have just submitted a form for.
|
||
|
|
///
|
||
|
|
/// **Source portal is expected to belong to the same workspace as the
|
||
|
|
/// destination portal.**
|
||
|
|
pub source_id: Uuid,
|
||
|
|
|
||
|
|
/// When a user is filling out a sequence of forms, this is the ID of the
|
||
|
|
/// portal for which they will be directed to if the condition evaluates to
|
||
|
|
/// true.
|
||
|
|
///
|
||
|
|
/// **Destination portal is expected to belong to the same workspace as the
|
||
|
|
/// source portal.**
|
||
|
|
pub dest_id: Uuid,
|
||
|
|
|
||
|
|
/// Represents a semi-arbitrary Postgres expression which will permit this
|
||
|
|
/// transition to be followed, only if the expression evaluates to true at
|
||
|
|
/// the time of the source form's submission.
|
||
|
|
pub condition: Json<Option<PgExpressionAny>>,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl FormTransition {
|
||
|
|
/// Build an insert statement to create a new transtition.
|
||
|
|
pub fn insert() -> InsertableBuilder {
|
||
|
|
InsertableBuilder::default()
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Build a single-field query by source portal ID.
|
||
|
|
pub fn with_source(id: Uuid) -> WithSourceQuery {
|
||
|
|
WithSourceQuery { id }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Clone, Copy, Debug)]
|
||
|
|
pub struct WithSourceQuery {
|
||
|
|
id: Uuid,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl WithSourceQuery {
|
||
|
|
pub async fn fetch_all(
|
||
|
|
self,
|
||
|
|
app_db: &mut AppDbClient,
|
||
|
|
) -> Result<Vec<FormTransition>, sqlx::Error> {
|
||
|
|
query_as!(
|
||
|
|
FormTransition,
|
||
|
|
r#"
|
||
|
|
select
|
||
|
|
id,
|
||
|
|
source_id,
|
||
|
|
dest_id,
|
||
|
|
condition as "condition: Json<Option<PgExpressionAny>>"
|
||
|
|
from form_transitions
|
||
|
|
where source_id = $1
|
||
|
|
"#,
|
||
|
|
self.id,
|
||
|
|
)
|
||
|
|
.fetch_all(app_db.get_conn())
|
||
|
|
.await
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Builder, Clone, Debug)]
|
||
|
|
pub struct Insertable {
|
||
|
|
source_id: Uuid,
|
||
|
|
dest_id: Uuid,
|
||
|
|
condition: Option<PgExpressionAny>,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl Insertable {
|
||
|
|
pub async fn execute(self, app_db: &mut AppDbClient) -> Result<FormTransition, sqlx::Error> {
|
||
|
|
query_as!(
|
||
|
|
FormTransition,
|
||
|
|
r#"
|
||
|
|
insert into form_transitions (source_id, dest_id, condition)
|
||
|
|
values ($1, $2, $3)
|
||
|
|
returning
|
||
|
|
id,
|
||
|
|
source_id,
|
||
|
|
dest_id,
|
||
|
|
condition as "condition: Json<Option<PgExpressionAny>>"
|
||
|
|
"#,
|
||
|
|
self.source_id,
|
||
|
|
self.dest_id,
|
||
|
|
Json(self.condition) as Json<Option<PgExpressionAny>>,
|
||
|
|
)
|
||
|
|
.fetch_one(app_db.get_conn())
|
||
|
|
.await
|
||
|
|
}
|
||
|
|
}
|