phonograph/interim-models/src/portal.rs

199 lines
4.6 KiB
Rust
Raw Normal View History

2025-07-08 14:37:03 -07:00
use derive_builder::Builder;
use serde::Serialize;
2025-08-24 23:24:01 -07:00
use sqlx::{postgres::types::Oid, query, query_as, types::Json};
2025-07-08 14:37:03 -07:00
use uuid::Uuid;
2025-08-24 23:24:01 -07:00
use crate::{client::AppDbClient, expression::PgExpressionAny};
2025-07-08 14:37:03 -07:00
/// A portal is a derivative representation of a Postgres relation.
2025-07-08 14:37:03 -07:00
#[derive(Clone, Debug, Serialize)]
pub struct Portal {
/// Primary key (defaults to UUIDv7).
2025-07-08 14:37:03 -07:00
pub id: Uuid,
/// Human friendly name for portal.
2025-07-08 14:37:03 -07:00
pub name: String,
/// Workspace to which this portal belongs.
pub workspace_id: Uuid,
/// OID of the underlying Postgres relation. Currently, this is expected
/// to be a normal table, not a view, etc.
2025-07-08 14:37:03 -07:00
pub class_oid: Oid,
/// JSONB-encoded expression to use for filtering rows in the web-based
/// table view.
pub table_filter: Json<Option<PgExpressionAny>>,
2025-07-08 14:37:03 -07:00
}
impl Portal {
/// Build an insert statement to create a new portal.
pub fn insert() -> InsertablePortalBuilder {
InsertablePortalBuilder::default()
2025-07-08 14:37:03 -07:00
}
/// Build an update statement to alter an existing portal.
pub fn update() -> PortalUpdateBuilder {
PortalUpdateBuilder::default()
2025-08-24 23:24:01 -07:00
}
/// Build a single-field query by portal ID.
2025-08-04 13:59:42 -07:00
pub fn with_id(id: Uuid) -> WithIdQuery {
WithIdQuery { id }
}
/// Build a query by workspace ID and relation OID.
pub fn belonging_to_workspace(workspace_id: Uuid) -> BelongingToWorkspaceQuery {
BelongingToWorkspaceQuery { workspace_id }
2025-08-04 13:59:42 -07:00
}
}
#[derive(Clone, Debug)]
pub struct WithIdQuery {
id: Uuid,
}
impl WithIdQuery {
pub async fn fetch_optional(
self,
app_db: &mut AppDbClient,
) -> Result<Option<Portal>, sqlx::Error> {
2025-07-08 14:37:03 -07:00
query_as!(
Portal,
2025-07-08 14:37:03 -07:00
r#"
select
id,
name,
workspace_id,
2025-07-08 14:37:03 -07:00
class_oid,
table_filter as "table_filter: Json<Option<PgExpressionAny>>"
from portals
2025-07-08 14:37:03 -07:00
where id = $1
"#,
2025-08-04 13:59:42 -07:00
self.id
2025-07-08 14:37:03 -07:00
)
2025-08-04 13:59:42 -07:00
.fetch_optional(&mut *app_db.conn)
2025-07-08 14:37:03 -07:00
.await
}
pub async fn fetch_one(self, app_db: &mut AppDbClient) -> Result<Portal, sqlx::Error> {
2025-07-08 14:37:03 -07:00
query_as!(
Portal,
2025-07-08 14:37:03 -07:00
r#"
select
id,
name,
workspace_id,
2025-07-08 14:37:03 -07:00
class_oid,
table_filter as "table_filter: Json<Option<PgExpressionAny>>"
from portals
2025-08-04 13:59:42 -07:00
where id = $1
2025-07-08 14:37:03 -07:00
"#,
2025-08-04 13:59:42 -07:00
self.id
2025-07-08 14:37:03 -07:00
)
2025-08-04 13:59:42 -07:00
.fetch_one(&mut *app_db.conn)
2025-07-08 14:37:03 -07:00
.await
}
2025-08-04 13:59:42 -07:00
}
2025-07-08 14:37:03 -07:00
2025-08-04 13:59:42 -07:00
#[derive(Clone, Debug)]
pub struct BelongingToWorkspaceQuery {
workspace_id: Uuid,
2025-08-04 13:59:42 -07:00
}
impl BelongingToWorkspaceQuery {
2025-08-04 13:59:42 -07:00
pub fn belonging_to_rel(self, rel_oid: Oid) -> BelongingToRelQuery {
BelongingToRelQuery {
workspace_id: self.workspace_id,
2025-08-04 13:59:42 -07:00
rel_oid,
}
}
}
#[derive(Clone, Debug)]
pub struct BelongingToRelQuery {
workspace_id: Uuid,
2025-08-04 13:59:42 -07:00
rel_oid: Oid,
}
impl BelongingToRelQuery {
pub async fn fetch_all(self, app_db: &mut AppDbClient) -> Result<Vec<Portal>, sqlx::Error> {
2025-07-08 14:37:03 -07:00
query_as!(
Portal,
2025-07-08 14:37:03 -07:00
r#"
select
id,
2025-07-18 16:20:03 -07:00
name,
workspace_id,
2025-08-04 13:59:42 -07:00
class_oid,
table_filter as "table_filter: Json<Option<PgExpressionAny>>"
from portals
where workspace_id = $1 and class_oid = $2
2025-07-08 14:37:03 -07:00
"#,
self.workspace_id,
2025-08-04 13:59:42 -07:00
self.rel_oid
2025-07-08 14:37:03 -07:00
)
2025-08-04 13:59:42 -07:00
.fetch_all(&mut *app_db.conn)
2025-07-08 14:37:03 -07:00
.await
}
}
#[derive(Clone, Debug, Serialize, sqlx::Type)]
#[sqlx(type_name = "lens_display_type", rename_all = "lowercase")]
pub enum LensDisplayType {
Table,
}
#[derive(Builder, Clone, Debug)]
pub struct InsertablePortal {
2025-07-08 14:37:03 -07:00
name: String,
workspace_id: Uuid,
2025-07-08 14:37:03 -07:00
class_oid: Oid,
}
impl InsertablePortal {
pub async fn execute(self, app_db: &mut AppDbClient) -> Result<Portal, sqlx::Error> {
2025-07-08 14:37:03 -07:00
query_as!(
Portal,
2025-07-08 14:37:03 -07:00
r#"
insert into portals
(workspace_id, class_oid, name)
values ($1, $2, $3)
2025-07-08 14:37:03 -07:00
returning
id,
name,
workspace_id,
2025-07-08 14:37:03 -07:00
class_oid,
table_filter as "table_filter: Json<Option<PgExpressionAny>>"
2025-07-08 14:37:03 -07:00
"#,
self.workspace_id,
2025-07-08 14:37:03 -07:00
self.class_oid,
self.name,
)
2025-08-04 13:59:42 -07:00
.fetch_one(&mut *app_db.conn)
2025-07-08 14:37:03 -07:00
.await
}
}
2025-08-24 23:24:01 -07:00
#[derive(Builder, Clone, Debug)]
pub struct PortalUpdate {
2025-08-24 23:24:01 -07:00
id: Uuid,
#[builder(setter(strip_option = true))]
filter: Option<Option<PgExpressionAny>>,
}
impl PortalUpdate {
2025-08-24 23:24:01 -07:00
pub async fn execute(self, app_db: &mut AppDbClient) -> Result<(), sqlx::Error> {
if let Some(filter) = self.filter {
query!(
"update portals set table_filter = $1 where id = $2",
2025-08-24 23:24:01 -07:00
Json(filter) as Json<Option<PgExpressionAny>>,
self.id
)
.execute(&mut *app_db.conn)
.await?;
}
Ok(())
}
}